easen-models
Version:
ES6 models & validation
117 lines (97 loc) • 2.94 kB
JavaScript
const ModelValidationError = require('./model-validation-error')
const symbols = require('./symbols')
const types = require('./types')
/**
* Build message from errors array
*
* @param {{ key: string, error: Error }[]} errors
* @returns string
*/
function buildMessage (errors) {
return `Model thrown validation error: ${buildDetails(errors)}`
}
/**
* Build details information from errors array
*
* @param {{ key: string, error: Error }[]} errors
* @returns string
*/
function buildDetails (errors) {
return `${errors.map(error => error.key).join(', ')}`
}
/**
* Build function which will set up getters & setters for model
*
* @param {object} definition
* @param {string[]} keys
* @returns {function}
*/
function buildSetup (definition, keys) {
// Prepare list of functions which will set up getters & setters
const setupFunctions = keys.map(key => (instance, data) => {
Object.defineProperty(instance, key, {
enumerable: true,
configurable: true,
get: () => data[key],
set: value => {
data[key] = definition[key](value)
return data[key]
}
})
})
// Make single function to set up all in instance
function setup (instance, data) {
for (let i = 0; i < setupFunctions.length; i++) {
setupFunctions[i](instance, data)
}
}
return setup
}
/**
* Create new model constructor
* TODO: check if defineProperties is faster than defineProperty many times
*
* @param {object} definition
* @returns {function}
*/
function createModel (definition) {
// Copy object to be sure that nothing will reassign definition meanwhile
definition = Object.assign({}, definition)
// Memoize object keys for performance reasons
const keys = Object.keys(definition)
// Memoize property setup functions
const setup = buildSetup(definition, keys)
function Model (initialData) {
// Throw validation error when initial data are not an object
types.object(initialData)
// Prepare basic object for storing data
const data = {}
// Set up getters and setters
setup(this, data)
// Set single value and return it
const set = (key, value) => {
try {
this[key] = value
} catch (error) {
return { key, error }
}
}
// Set up property to get copy of raw data
Object.defineProperty(this, symbols.Raw, {
enumerable: false,
get: () => Object.assign({}, data)
})
// Set up initial values & check for validation errors
const errors = keys.map(key => set(key, initialData[key])).filter(Boolean)
// Throw validation errors
if (errors.length) {
throw new ModelValidationError(buildMessage(errors), buildDetails(errors), errors)
}
}
// Prepare factory to use it for initialization
Model.create = initialData => new Model(initialData)
Model.Definition = definition
return Model
}
module.exports = createModel