






Native Models provides a way to map objects in a clean and typed way. The main goal is to ensure runtime type checking and consistent models for APIs.
Getting Started
const { createModel } = require('nativemodels');
const { array, boolean, computed, date, int, object, string } = require('nativemodels/datatypes');
const photoSchema = {
ext: string(),
url: string().required(),
};
const contactSchema = {
email: string(),
phone: string(),
url: string(),
};
const userSchema = {
accountID: int().nullable(),
contact: object(contactSchema),
created: date(),
firstName: string().required(),
fullName: computed((record) => `${record.firstName} ${record.lastName}`),
isAdmin: boolean().nullable(),
lastName: string().required(),
photos: array(object(photoSchema)),
typeID: int().default(2),
};
const userModel = createModel(userSchema);
const johnSmith = userModel({
contact: {
email: 'j.smith@example.com',
},
firstName: 'John',
lastName: 'Smith',
photos: [
{
ext: '.jpg',
url: 'https://example.com/img.jpg',
},
],
});
const userRecords = [
{
firstName: 'John',
lastName: 'Smith',
},
{
firstName: 'Jane',
lastName: 'Doe',
},
];
const users = userRecords.map(userModel);
const janeDoe = userModel({
...johnSmith,
firstName: 'Jane',
lastName: 'Doe',
});
Datatype API
Datatype methods that can be chained when defining schema.
datatypes.default(defaultValue)
Sets a default value if no value is set
datatypes.nullable()
Allows the value set to be null (useful for database models)
datatypes.required()
Forces the value to be required. Is ignored if default value is set
datatypes.strict()
Requires the value that is passed in to be the correct datatype instead of coerced
Datatypes
- array
- boolean
- computed
- date
- float
- int
- object
- string
Extending Datatypes
const { base } = require('nativemodels/datatypes');
const myCustomDataType = () => ({
...base,
parse: (key, value) => `${key}:${value}`,
requiredCheck(key, value) {
if (key && value) {
return true;
}
throw new Error(`Property: '${key}' is required`);
},
strictCheck: (key, value) => {
if (typeof value === 'string') {
return true;
}
throw new Error(`Property ${key} is not a customDataType`);
},
validate: (key, value) => {
if (`${key}:${value}` !== ':') {
return true;
}
throw new Error(`Property ${key} is not a customDataType`);
},
});
module.exports = int;
base.parse(key, value)
Parses the value being set
base.validCheck(key, value)
Returns true if passes your valid check else should throw an erorr
base.requiredCheck(key, value)
Returns true if passes your required check else should throw an error
base.strictCheck(key, value)
Returns true is passes your strict check else should throw an error
Customtypes
Custom types are types that are useful to have and common enough for use to include them in our library. They currently include
- email
- enumberable
- guid
- phone
- url
Examples
const { email, enumberable, guid, phone, url } = require('nativemodels/customtypes');
const model = createModel({
email: email(),
enumberable: enumberable(['FOO', 'BAR']),
guid: guid(),
phone: phone(),
url: url(),
});
Async / Promise Computed Functions
Sometimes computed values aren't syncronous. To help you deal with that, we have provided the resolver method which will allow you to resolve all computed functions that are promises or async functions.
NOTE: You must return an async function, Promise or syncronous result. Generators will not work with this
WARNING: This is an N+1 unoptimized resolver meaning that for each nested array / object will require an extra iteration.
const { createModel, resolver } = require('nativemodels');
const { boolean, computed } = require('nativemodels/datatypes');
const schema = {
async: computed(
(record) =>
new Promise((succeed, reject) => (record.succeed ? succeed(1) : reject(new Error('Failed to resolve')))),
),
succeed: boolean().default(false),
};
const model = createModel(schema);
const data = model({ succeed: true });
const resolvedData = await resolver(data);
Schema Parsing of resolved data
You can provide a second option to resolver() that will allow you to receive back an object that has had the schema applied to it.
const { createModel, resolver } = require('nativemodels');
const { boolean, computed, int } = require('nativemodels/datatypes');
const schema = {
async: computed(
(record) =>
new Promise((succeed, reject) => (record.succeed ? succeed(1) : reject(new Error('Failed to resolve')))),
),
succeed: boolean().default(false),
};
const resolvedSchema = {
async: int(),
succeed: boolean(),
};
const model = createModel(schema);
const data = model({ succeed: true });
const resolvedData = await resolver(data, resolvedSchema);
Options for createModel
defaultOptions
const defaultOptions = {
caseSensitive: true,
strict: false,
};
caseSensitive
The caseSensitive option default(true) allows you to turn off caseSensitive matching. This is useful for ignoring and parsing user submitted data into a nice clean format while still maintaining model integrity
const { createModel } = require('nativemodels');
const { string } = require('nativemodels/datatypes');
const options = {
caseSensitive: false,
};
const schema = {
foo: string(),
};
const model = createModel(schema, options);
const data = model({ FOO: 'bar' });
Options are shallow by default, so if you have a deeply nested object, you will need to pass down options by hand.
const { createModel } = require('nativemodels');
const { object, string } = require('nativemodels/datatypes');
const options = {
caseSensitive: false,
};
const schema = {
foo: string(),
};
const deepSchema = {
nested: object(schema, options),
};
const model = createModel(deepSchema, options);
const data = model({ Nested: { FOO: 'bar' } });
strict
The strict option default: false allows you to throw an error if the inital object you are assigning has extra keys. This is useful for validating data structure when coming from an unknown source
const { createModel } = require('nativemodels');
const { string } = require('nativemodels/datatypes');
const options = {
strict: true,
};
const schema = {
foo: string(),
};
const model = createModel(schema, options);
const data = model({ faa: 'bar' });
Options are shallow by default, so if you have a deeply nested object, you will need to pass down options by hand.
const { createModel } = require('nativemodels');
const { object, string } = require('nativemodels/datatypes');
const options = {
strict: true,
};
const schema = {
foo: string(),
};
const deepSchema = {
nested: object(schema, options),
};
const model = createModel(deepSchema, options);
const data = model({ nested: { faa: 'bar' } });