TrivialModels is a simple way to define a schema for a javascript objects. You define your model's schema, and Trivial Models handles reading/writing it to your backend of choice. It's extensible, supporting both custom types and custom backend drivers.
This is primarily designed for a small(ish) amount of data, and simple servers. It works great for electron applications as well as anything running a single server. With a more advanced database backing it, TrivialModels has no issue scaling to multiple workers, but there may better options once you reach that level of need.
One important thing to note: TrivialModels does not inherently have support for relationships or indexes. Frankly, more often than not your relationships are easier to define via how you use your objects then by some hard-coded model relationship. While it would be nice to support indexes eventually, the use case we are targeting doesn't need that sort of feature. (And, if you're using either the in-memory driver, or the trivialdb driver, it's blindingly fast, even for hundreds of thousands of documents.)
- Model: This is the generated class instance that holds your schema, and the Trivial Models API. You define these in your code.
- Driver: This holds all of the logic for reading/writing to the database of your choice. You can use either a built in driver, or load one from another module.
- Type: This holds all the special logic for validating and reading/writing a given field type to an intermediate value. These are generally built in, but could be loaded from another module.
Defining a model is intentionally very simple:
var tm = require('trivialmodels');
var types = tm.types;
// Define a simple model
var Author = tm.define({
name: 'Author',
driver: { ... },
schema: {
id: types.String({ pk: true }),
name: types.String(),
dob: types.Date()
}
});
The only required property is schema
. However, if you pass name
, we will generate a new class for you with the
correct name property. Additionally, if you wish to pick a driver (or pass options), you will want to specify the
driver
property.
- Built in:
'Simple'
,'TrivialDB'
When you need to specify a driver, it's simply by setting either a string, an object, or a driver instance as the
driver
property. If you need to pass options to one of the built in drivers, it must be an object of the form:
{
name: "TrivialDB",
options: { ... }
}
The options supported by each driver can currently only be found in the code:
When you define a schema, you need to specify what type each field is. TrivialModels supports the following basic types:
- String
- Number
- Boolean
- Date
- Object
- Array
- Enum
- Any
In addition, we support the following special types native to objects:
- Properties
- Functions
Any basic type can have options passed to it, and several have their own options for controlling validation and/or storage. These details are documented in each type's section below. However, all types support a few basic options:
- required - This field is not allowed to be
undefined
ornull
when validating/saving. - pk - This key is the primary key for the model. This may only be specified on one field per model.
- sanitize - A function of the form
function(value, modelInstance)
that takes in the value for this field and returns a 'sanitized' version to be stored instead. - validate - A function of the form
function(value, modelInstance)
that takes in the current value for this field and performs validation. It should returntrue
orfalse
. - default - A default value for this field.
This represents any valid javascript string. It can be of any length, any encoding.
- Options:
integer
:true
|false
This represents any valid javascript number. If integer
is true, we fail validation if the number is not a valid
integer.
Note: If you are using the TrivialDB
backend, or any other driver that stores as JSON, it should be noted that JSON
encodes Infinity
and NaN
as null
. This is a limitation of the JSON spec.
This represents a boolean, i.e. true
or false
. Any value that is not an instance of Boolean
will fail validation.
If you want to do any automatic type conversion, use a sanitize function:
var model = tm.define({
schema: {
boolField: types.Boolean({ sanitize: (val) => !!val })
}
});
The lambda (val) => !!val
will convert following javascripts truthy rules.
- Options:
auto
:true
|false
This represents a DateTime object. Under the hood, it is converted to a unix timestamp, but when retrieved, it is returned as a javascript date object. A unix timestamp or a valid date string can be set and it will be converted when you assign it.
If the auto
property is true, this will default to the current date/time.
- Options:
schema
:{ ... }
Supports a plain javascript object. If the schema
option is passed, it will perform type validation as if it were a
model. This is, effectively, an implementation of nested documents. Example:
var model = tm.define({
schema: {
nested: types.Object({
schema: {
name: types.String({ required: true }),
isFoo: types.Boolean({ default: false })
}
})
}
});
- Options:
type
: Any valid TrivialModels type
This represents an array of values. The values can be any valid javascript types. If the type
option is passed, then
it will perform validation that all the items stored inside the array are of the specified type.
Note: Individual driver may impose limitations on what chan be stored. For example, the TrivialDB
driver cannot save
functions or complex objects. It is limited to JSON types.
- Options:
values
: Any valid javascript type
This represents a field that can only be one of the specified possible values. These values are typically strings, but
can by any valid javascript type. The values are compared using the SameValueZero
algorithm (the same as ===
). (See
this document for details.)
This can store any valid javascript type, and performs no validation. If required
is true, it cannot be undefined
or
null
. That is the only limitation on it's value.
Note: Individual driver may impose limitations on what chan be stored. For example, the TrivialDB
driver cannot save
functions or complex objects. It is limited to JSON types.
Properties are supported via the standard getter/setter syntax on objects:
var tm = require('trivialmodels');
var types = tm.types;
// Define a simple model
var Foo = tm.define({
schema: {
id: types.String({ pk: true }),
firstName: types.String(),
lastName: types.String(),
// This is a standard javascript property with both a getter and a setter
get name()
{
return `${ this.firstName } ${ this.lastName }`;
},
set name(val)
{
var parts = val.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
}
});
// Create a new instance
var inst = new Foo({ firstName: 'John', lastName: 'Smith' });
// Returns 'John Smith'
console.log(inst.name);
// Set the name
inst.name = 'John Snow';
// Returns 'Snow'
console.log(inst.lastName);
It is important to note two things: First, the getter/setter objects have access to this
. Secondly, all getters will
appear in the output of JSON.stringify
. So please be aware that a serialized model will include all properties defined
on it's schema.
Functions are supported vial the standard function syntax on objects:
var tm = require('trivialmodels');
var types = tm.types;
// Define a simple model
var Foo = tm.define({
schema: {
id: types.String({ pk: true }),
firstName: types.String(),
lastName: types.String(),
// This is a standard javascript function
getName()
{
return `${ this.firstName } ${ this.lastName }`;
} // end getName
}
});
// Create a new instance
var inst = new Foo({ firstName: 'John', lastName: 'Smith' });
// Returns 'John Smith'
console.log(inst.getName());
Functions have access to this
, but they are not included in the output of JSON.stringify
.
It is rather easy to include logic in your models simply by including function and treating the model as if it were a class (it is, actually). This allows you to build smarter models without the need to wrap them in a more traditional class.
Primary key generation is handled by the underlying driver. Only one field on a model can be marked as the primary key,
and if one is not specified, an id
key (string) will be automatically added. If you are unsure what field is the
primary key, you can check the pk
property on the model class (class, not instance).
If you want the value of the primary key, you can use the $pk
property on a model instance (instance, not
class).
To mark a field as the primary key, simply set pk: true
in the options for the field. Example:
var tm = require('trivialmodels');
var types = tm.types;
// Define a simple model
var Foo = tm.define({
schema: {
fooID: types.Number({ pk: true }),
}
});
// Returns 'fooID'
console.log(Foo.pk);
// Create a new instance
var inst = new Foo({ fooID: 3 });
// Returns '3'
console.log(inst.$pk);
TBD.
TBD.