Modeling library for javascript. Define schemas that represent your data. Create instances from schemas that can be observed, validated and composed.
npm install supermodels.js --save
For use in Node.js, the browser (3Kb gzipped) or any other JavaScript environment.
- Isomorphic
- JSON serializable/deserializable (no pollution)
- Model instances are observable, validatable and composable.
supermodels can be used for all sorts of purposes including:
- On the front end as model components in a unidirectional data flow or MVC
- Defining your Business Objects (Customer, Address, Order etc.), their relationships and any validation rules.
- Validating HTTP payloads
- Building blocks like Services, Singletons
Returns a model constructor function for the given schema
value.
// Define a basic `Comment` schema
var commentSchema = {
body: String
createdDate: Date
}
var Comment = supermodels(commentSchema)
// Define a `Customer` with some personal details,
// an address, a list of orders and a list of comments
var customerSchema = {
name: String,
age: Number,
address: {
line1: String,
line2: String,
postcode: String,
get fullAddress() {
return this.line1 + ', ' + this.line2;
}
},
orders: [{
productCode: String,
price: Number,
quantity: Number,
get total () {
return '$' + price * quantity
}
}],
comments: [Comment]
}
// Call supermodels to get our Customer constructor
var Customer = supermodels(customerSchema)
// Call constructor like you would
// We can pass is some initial data
var customer = new Customer({
name: 'Jane Doe',
age: 42,
address: {
line1: 'Buckingham Palace',
line2: 'London',
postcode: 'L1 1XY'
},
orders: [{ productCode: 'ABC001', price: 99.99, quantity: 2 }]
})
console.log(customer.fullAddress)
//=> 'Buckingham Palace, London'
console.log(customer.orders.length)
//=> 1
console.log(customer.order[0].total)
//=> $199.98
console.log(customer.comments.length)
//=> 0
customer.comments.push(new Comment({ text: 'Hello world', date: Date.now() }))
console.log(customer.comments.length)
//=> 1
See the examples folder
The prop
function provides a fluent interface for building properties.
It allows us to add metadata and validation rules to our schema.
Metadata methods available in the fluent interface:
type
String
,Number
,Date
,Boolean
,Array
orObject
- Values will be cast on supported types
String
,Number
,Date
andBoolean
. - Can also be a reference another model
name
- A display name for the property
value
- The default value of the property. If a function is passed, it will be called and the return value used e.g.
Date.now
can be used to timestamp the model.
- The default value of the property. If a function is passed, it will be called and the return value used e.g.
validate
(fn)- Register a validator. This can be used as an alternative to registering on the
prop
's fluent interface
- Register a validator. This can be used as an alternative to registering on the
get
(fn)- A getter function.
set
(fn)- A setter function.
enumerable
,writable
,configurable
There are no built in validators but they can easily defined using the register
function.
var prop = supermodels.prop()
// Let's register 3 simple validators. Registering validators
// makes them part of the fluent interface when using `prop`.
prop.register('required', function () {
return function (val, name) {
if (!val) {
return name + ' is required'
}
}
})
prop.register('min', function (min) {
return function (val, name) {
if (val < min) {
return name + ' is less than ' + min
}
}
})
prop.register('max', function (max) {
return function (val, name) {
if (val > max) {
return name + ' is greater than ' + max
}
}
})
// Using the previous customer example
// we can start to build some validation.
var customerSchema = {
name: prop(String).name('Name').required(),
age: prop(Number).name('Age').required().min(0).max(150),
address: {
line1: prop(String).name('Line 1').required(),
line2: prop(String).name('Line 2').required(),
postcode: prop(String).name('Postcode'),
get fullAddress () {
return this.line1 + ', ' + this.line2
}
},
orders: [{
productCode: String,
price: Number,
quantity: prop(Number).name('Quantity').required().value(1).min(1).max(10),
get total () {
return '$' + (this.price * this.quantity)
}
}],
comments: [Comment]
}
var Customer = supermodels(customerSchema)
var customer = new Customer()
// All model objects have a special `errors` property.
// Accessing this property will run the validators and
// return an array of `ValidationError`s
console.log(customer.address.errors)
// => ['Line 1 is required', 'Line 2 is required']
console.log(customer.orders.errors)
// => Returns all `orders` errors
console.log(customer.comments.errors)
// => Returns all `comments` errors
console.log(customer.errors)
// => Returns all errors
Events are propagated up through models is an similar fashion to how DOM events do. Event handlers are added and removed using the on
and off
methods.
A change
event occurs for all modifications to the model. Other more specific events are also available:
set
- Occurs when a value is updated. Event detail will include the new value and the old value.
set
events also occur when array items are updated. See the note on updating array values.
- Occurs when a value is updated. Event detail will include the new value and the old value.
push
pop
unshift
shift
sort
reverse
splice
customer.on('change', function(e) {
console.log(e.path, e.detail.newValue, e.detail.oldValue);
})
customer.address.line1 = 'Line 1'
customer.address.line1 = 'Line 2'
// Listeners can be added to any level of the model
customer.address.on('change', function(e) {})
customer.orders.on('push', function(e) {
console.log('Order count: ', e.detail.value)
})
$ npm test
MIT