Muesli
Simple, ORM-agnostic domain driven model management
Installation
npm install muesli
or
yarn add muesli
Optional packages(in TODO)
npm install muesli-filters
npm install muesli-validators
npm install muesli-constraints
Usage
Import Model
class from muesli
package.
import Model from 'muesli';
const Model = require('muesli');
Define your model's props
class Book extends Model {
}
Book.props = {
ISBN: {
filter: 'string',
value: '',
constraints: [],
validate: (value) => {},
json: {},
},
title: {
filter: 'string',
},
author: {
filter: (value) => Author.fromJSON(value),
},
reference: {
filter: (ref) => CustomModelStore.findByRef(ref),
},
};
Add settings for your model
class Book extends Model {
}
Book.propsSettings = {
nameStrategy: 'camel',
strict: false,
throwOnStrictError: false,
useGetters: true,
useSetters: true,
validators: [
MuesliValidators.equalProps(['password', 'password-confirmation'], ['validation-group1']),
MuesliValidators.validDates(['birthdate'], 'validation-group2')
]
};
Setting name | Default value | Possible values | Description |
---|
nameStrategy | <empty string> | (camel OR pascal OR title OR snake OR lower OR upper OR constant ) | Props names' serialization strategy. Look inside package https://www.npmjs.com/package/change-case |
strict | false | true OR false | if true is set, then model won't accept non schema props. |
throwOnStrictError | false | true OR false | if true is set and strict === true , model will throw an error when model tries to set non schema prop |
useGetters | true | true OR false | gives direct access to props values via model.values object |
useSetters | true | true OR false | |
validators | [] | | array of model validators that can validate through whole model |
Model validation
const book = Book.fromJSON({ ISBN: '1232412-123' });
book.validate()
.then((validationErrors) => {
if (validationErrors.length) {
} else {
}
})
.catch((error) => {
console.error(error);
});
There is static method to make the same operation quicker
Book.validate({ ISBN: '12345123-123' })
.then((validationErrors) => {
if (validationErrors.length) {
} else {
}
})
.catch((error) => {
console.error(error);
});
You can also validate only part of the model using validation group
book.validate(['group1'])
.then((validationErrors) => {
if (validationErrors.length) {
} else {
}
})
.catch((error) => {
console.error(error);
});
Custom constraints and validators
Constraints and validators are asynchronous by default, but you not required to use Promises
if you don't need to.
const customConstraint = (groups = []) => {
return (propValue, propName, currentGroup) => {
return new Promise((resolve, reject) => {
if (!groups.includes(currentGroup)) {
resolve();
return;
}
if (propValue !== 'custom value') {
reject(new Error(`${propName} must be equals 'custom value'`));
return;
}
resolve();
});
};
};
const customValidator = (groups = []) => {
return (values, currentGroup) => {
if (!groups.includes(currentGroup)) {
return;
}
if (values.password !== values.password_confirmation) {
throw new Error('Password and password confirmation must be equal');
}
};
};
Computed props
class Author extends Model {}
Author.props = {
firstName: {
filter: String,
value: 'Dmytro',
},
lastName: {
filter: String,
},
fullName: {
filter: function (deps = []) {
return deps.filter((v) => v).join(' ');
},
computed: ['firstName', 'lastName'],
},
};
const author = new Author();
console.log(author.get('fullName'));
author.set('lastName', 'Zelenetskyi');
console.log(author.get('fullName'));
Deserializing and serializing model(fromJSON/toJSON)
class Author extends Model {}
Author.props = {
name: {
filter: 'string',
constraints: [MusliConstraints.required()],
},
lastName: {
filter: 'string',
},
};
const horror = Author.fromJSON({
name: 'Stephen',
lastName: 'King',
});
console.log(horror instanceof Author);
console.log(horror.toJSON());
Using useGetters
and useSetters
options
if useGetters
or/and useSetters
options are set to true
,
then you can use props' values directly via model.values
object.
Example:
class Author extends Model {}
Author.props = {
name: {
filter: 'string',
value: 'Default value',
},
age: {
filter: 'number',
value: 0,
},
};
Author.propsSettings = {
useGetters: true,
useSetters: true,
};
const model = new Author();
console.log(model.values.name);
console.log(model.values.age);
model.values.age = 30;
console.log(model.values.age);
model.values
object can't be extended. Only props from schema are available. Setters won't be provided for
computed properties.
Model version
With each model mutation model increases it's version, so you can track it.
const author = Author.fromJSON({ name: 'Stephen' });
console.log(model.version);
model.set('name', 'Dmytro');
console.log(model.version);
Creating ORM-like entities
const pg = require('pg');
const SQL = require('sql-template-strings')
class Entity extends Model {
static get source() {
throw new Error('Implement me');
}
static async findById(id) {
const rows = await pool.query(SQL`SELECT * FROM "`.append(this.source).append(SQL`" WHERE id=${id}`));
return this.fromJSON(rows[0]);
}
}
class UserEntity extends Entity {
static get source() {
return 'users';
}
static get props() {
return {
first_name: {
filter: 'string',
},
last_name: {
filter: 'string',
},
password: {
filter: 'string',
},
};
}
}
const user = UserEntity.findById(1);
console.log(user.get('first_name'));
console.log(user.values);
Events
Model inherits event system from rx-emitter
(github link) package that uses rxjs
and handles all events as observables.
const book = new Book();
Rx.Observable.combineLatest(
book.subject('chapter1'),
book.subject('chapter2'),
)
.subscribe(() => {
console.log('Two first chapters are ready');
});
book.publish('chapter1');
book.publish('chapter2');
License
Muesli is released under the MIT license.
Donate