UNPKG

total.js

Version:

MVC framework for Node.js

2,009 lines (1,640 loc) 161 kB
// Copyright 2012-2020 (c) Peter Širka <petersirka@gmail.com> // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. /** * @module FrameworkBuilders * @version 3.4.4 */ 'use strict'; const REQUIRED = 'The field "@" is invalid.'; const DEFAULT_SCHEMA = 'default'; const SKIP = { $$schema: 1, $$async: 1, $$repository: 1, $$controller: 1, $$workflow: 1, $$parent: 1, $$keys: 1 }; const REGEXP_CLEAN_EMAIL = /\s/g; const REGEXP_CLEAN_PHONE = /\s|\.|-|\(|\)/g; const REGEXP_NEWOPERATION = /^(async\s)?function(\s)?\([a-zA-Z$\s]+\)|^function anonymous\(\$|^\([a-zA-Z$\s]+\)|^function\*\(\$|^\([a-zA-Z$\s]+\)/; const hasOwnProperty = Object.prototype.hasOwnProperty; const Qs = require('querystring'); const MSG_OBSOLETE_NEW = 'You used older declaration of this delegate and you must rewrite it. Read more in docs.'; const BOOL = { true: 1, on: 1, '1': 1 }; const REGEXP_FILTER = /[a-z0-9-_]+:(\s)?(\[)?(String|Number|Boolean|Date)(\])?/i; var schemas = {}; var schemasall = {}; var schemacache = {}; var operations = {}; var tasks = {}; var transforms = { pagination: {}, error: {}, restbuilder: {} }; var restbuilderupgrades = []; function SchemaBuilder(name) { this.name = name; this.collection = {}; } const SchemaBuilderProto = SchemaBuilder.prototype; function SchemaOptions(error, model, options, callback, controller, name, schema) { this.error = error; this.value = this.model = model; this.options = options || EMPTYOBJECT; this.callback = this.next = callback; this.controller = (controller instanceof SchemaOptions || controller instanceof OperationOptions) ? controller.controller : controller; this.name = name; this.schema = schema; } function TaskBuilder($) { var t = this; t.value = {}; t.tasks = {}; if ($ instanceof SchemaOptions || $ instanceof OperationOptions) { t.error = $.error; t.controller = $.controller; } else { if ($ instanceof Controller || $ instanceof WebSocketClient) t.controller = $; else if ($ instanceof ErrorBuilder) t.error = $; } } TaskBuilder.prototype = { get user() { return this.controller ? this.controller.user : null; }, get session() { return this.controller ? this.controller.session : null; }, get sessionid() { return this.controller && this.controller ? this.controller.req.sessionid : null; }, get language() { return (this.controller ? this.controller.language : '') || ''; }, get ip() { return this.controller ? this.controller.ip : null; }, get id() { return this.controller ? this.controller.id : null; }, get req() { return this.controller ? this.controller.req : null; }, get res() { return this.controller ? this.controller.res : null; }, get params() { return this.controller ? this.controller.params : null; }, get files() { return this.controller ? this.controller.files : null; }, get body() { return this.controller ? this.controller.body : null; }, get query() { return this.controller ? this.controller.query : null; }, get model() { return this.value; }, set model(val) { this.value = val; }, get headers() { return this.controller && this.controller.req ? this.controller.req.headers : null; }, get ua() { return this.controller && this.controller.req ? this.controller.req.ua : null; }, get filter() { var ctrl = this.controller; if (ctrl && !ctrl.$filter) ctrl.$filter = ctrl.$filterschema ? CONVERT(ctrl.query, ctrl.$filterschema) : ctrl.query; return ctrl ? ctrl.$filter : EMPTYOBJECT; } }; const TaskBuilderProto = TaskBuilder.prototype; SchemaOptions.prototype = { get user() { return this.controller ? this.controller.user : null; }, get session() { return this.controller ? this.controller.session : null; }, get keys() { return this.model.$$keys; }, get parent() { return this.model.$$parent; }, get repo() { if (this.controller) return this.controller.repository; if (!this.model.$$repository) this.model.$$repository = {}; return this.model.$$repository; }, get sessionid() { return this.controller && this.controller ? this.controller.req.sessionid : null; }, get language() { return (this.controller ? this.controller.language : '') || ''; }, get ip() { return this.controller ? this.controller.ip : null; }, get id() { return this.controller ? this.controller.id : null; }, get req() { return this.controller ? this.controller.req : null; }, get res() { return this.controller ? this.controller.res : null; }, get params() { return this.controller ? this.controller.params : null; }, get files() { return this.controller ? this.controller.files : null; }, get body() { return this.controller ? this.controller.body : null; }, get query() { return this.controller ? this.controller.query : null; }, get headers() { return this.controller && this.controller.req ? this.controller.req.headers : null; }, get ua() { return this.controller && this.controller.req ? this.controller.req.ua : null; }, get filter() { var ctrl = this.controller; if (ctrl && !ctrl.$filter) ctrl.$filter = ctrl.$filterschema ? CONVERT(ctrl.query, ctrl.$filterschema) : ctrl.query; return ctrl ? ctrl.$filter : EMPTYOBJECT; } }; var SchemaOptionsProto = SchemaOptions.prototype; SchemaOptionsProto.cancel = function() { var self = this; self.callback = self.next = null; self.error = null; self.controller = null; self.model = null; self.options = null; return self; }; SchemaOptionsProto.extend = function(data) { var self = this; var ext = self.schema.extensions[self.name]; if (ext) { for (var i = 0; i < ext.length; i++) ext[i](self, data); return true; } }; SchemaOptionsProto.redirect = function(url) { this.callback(new F.callback_redirect(url)); return this; }; SchemaOptionsProto.clean = function() { return this.model.$clean(); }; SchemaOptionsProto.$async = function(callback, index) { return this.model.$async(callback, index); }; SchemaOptionsProto.$workflow = function(name, helper, callback, async) { return this.model.$workflow(name, helper, callback, async); }; SchemaOptionsProto.$transform = function(name, helper, callback, async) { return this.model.$transform(name, helper, callback, async); }; SchemaOptionsProto.$operation = function(name, helper, callback, async) { return this.model.$operation(name, helper, callback, async); }; SchemaOptionsProto.$hook = function(name, helper, callback, async) { return this.model.$hook(name, helper, callback, async); }; SchemaOptionsProto.$save = function(helper, callback, async) { return this.model.$save(helper, callback, async); }; SchemaOptionsProto.$insert = function(helper, callback, async) { return this.model.$insert(helper, callback, async); }; SchemaOptionsProto.$update = function(helper, callback, async) { return this.model.$update(helper, callback, async); }; SchemaOptionsProto.$patch = function(helper, callback, async) { return this.model.$patch(helper, callback, async); }; SchemaOptionsProto.$query = function(helper, callback, async) { return this.model.$query(helper, callback, async); }; SchemaOptionsProto.$delete = SchemaOptionsProto.$remove = function(helper, callback, async) { return this.model.$remove(helper, callback, async); }; SchemaOptionsProto.$get = SchemaOptionsProto.$read = function(helper, callback, async) { return this.model.$get(helper, callback, async); }; SchemaOptionsProto.push = function(type, name, helper, first) { return this.model.$push(type, name, helper, first); }; SchemaOptionsProto.next = function(type, name, helper) { return this.model.$next(type, name, helper); }; SchemaOptionsProto.output = function() { return this.model.$output(); }; SchemaOptionsProto.stop = function() { return this.model.$stop(); }; SchemaOptionsProto.response = function(index) { return this.model.$response(index); }; SchemaOptionsProto.DB = function() { return F.database(this.error); }; SchemaOptionsProto.successful = function(callback) { var self = this; return function(err, a, b, c) { if (err) self.invalid(err); else callback.call(self, a, b, c); }; }; SchemaOptionsProto.success = function(a, b) { if (a && b === undefined && typeof(a) !== 'boolean') { b = a; a = true; } this.callback(SUCCESS(a === undefined ? true : a, b)); return this; }; SchemaOptionsProto.done = function(arg) { var self = this; return function(err, response) { if (err) { if (self.error !== err) self.error.push(err); self.callback(); } else if (arg) self.callback(SUCCESS(err == null, arg === true ? response : arg)); else self.callback(SUCCESS(err == null)); }; }; SchemaOptionsProto.invalid = function(name, error, path, index) { var self = this; if (arguments.length) { self.error.push(name, error, path, index); self.callback(); return self; } return function(err) { self.error.push(err); self.callback(); }; }; SchemaOptionsProto.repository = function(name, value) { return this.model && this.model.$repository ? this.model.$repository(name, value) : value; }; SchemaOptionsProto.noop = function() { this.callback(NoOp); return this; }; /** * * Get a schema * @param {String} name * @return {Object} */ SchemaBuilderProto.get = function(name) { return this.collection[name]; }; /** * Create a new schema * @alias * @return {SchemaBuilderEntity} */ SchemaBuilderProto.create = function(name) { this.collection[name] = new SchemaBuilderEntity(this, name); return this.collection[name]; }; /** * Removes an existing schema or group of schemas * @param {String} name Schema name, optional. * @return {SchemaBuilder} */ SchemaBuilderProto.remove = function(name) { if (name) { var schema = this.collection[name]; schema && schema.destroy(); schema = null; delete schemasall[name.toLowerCase()]; delete this.collection[name]; } else { exports.remove(this.name); this.collection = null; } }; SchemaBuilderProto.destroy = function(name) { return this.remove(name); }; function SchemaBuilderEntity(parent, name) { this.parent = parent; this.name = name; this.primary; this.trim = true; this.schema = {}; this.meta = {}; this.properties = []; this.inherits = []; this.verifications = null; this.resourcePrefix; this.extensions = {}; this.resourceName; this.transforms; this.workflows; this.hooks; this.operations; this.constants; this.onPrepare; this.$onPrepare; // Array of functions for inherits this.onDefault; this.$onDefault; // Array of functions for inherits this.onValidate = F.onValidate; this.onSave; this.onInsert; this.onUpdate; this.onGet; this.onRemove; this.onQuery; this.onError; this.gcache = {}; this.dependencies; this.fields; this.fields_allow; this.CurrentSchemaInstance = function(){}; this.CurrentSchemaInstance.prototype = new SchemaInstance(); this.CurrentSchemaInstance.prototype.$$schema = this; } const SchemaBuilderEntityProto = SchemaBuilderEntity.prototype; SchemaBuilderEntityProto.allow = function() { var self = this; if (!self.fields_allow) self.fields_allow = []; var arr = arguments; if (arr.length === 1) arr = arr[0].split(',').trim(); for (var i = 0, length = arr.length; i < length; i++) { if (arr[i] instanceof Array) arr[i].forEach(item => self.fields_allow.push(item)); else self.fields_allow.push(arr[i]); } return self; }; SchemaBuilderEntityProto.before = function(name, fn) { var self = this; if (!self.preparation) self.preparation = {}; self.preparation[name] = fn; return self; }; SchemaBuilderEntityProto.required = function(name, fn) { var self = this; if (!name) return self; if (name.indexOf(',') !== -1) { var arr = name.split(','); for (var i = 0; i < arr.length; i++) self.required(arr[i].trim(), fn); return self; } if (fn === false) { self.properties && (self.properties = self.properties.remove(name)); return self; } var prop = self.schema[name]; if (!prop) throw new Error('Property "{0}" doesn\'t exist in "{1}" schema.'.format(name, self.name)); prop.can = typeof(fn) === 'function' ? fn : null; if (!prop.required) { prop.required = true; if (self.properties) { self.properties.indexOf(name) === -1 && self.properties.push(name); } else self.properties = [name]; } return self; }; SchemaBuilderEntityProto.clear = function() { var self = this; self.schema = {}; self.properties = []; self.fields = []; self.verifications = null; if (self.preparation) self.preparation = null; if (self.dependencies) self.dependencies = null; if (self.fields_allow) self.fields_allow = null; return self; }; SchemaBuilderEntityProto.middleware = function(fn) { var self = this; if (!self.middlewares) self.middlewares = []; self.middlewares.push(fn); return self; }; function runmiddleware(opt, schema, callback, index, processor) { if (!index) index = 0; var fn = schema.middlewares[index]; if (fn == null) { callback.call(schema, opt); return; } if (processor) { fn(opt, processor); return; } processor = function(stop) { if (!stop) runmiddleware(opt, schema, callback, index + 1, processor); }; fn(opt, processor); } /** * Define type in schema * @param {String|String[]} name * @param {Object/String} type * @param {Boolean} [required=false] Is required? Default: false. * @param {Number|String} [custom] Custom tag for search. * @return {SchemaBuilder} */ SchemaBuilderEntityProto.define = function(name, type, required, custom) { if (name instanceof Array) { for (var i = 0, length = name.length; i < length; i++) this.define(name[i], type, required, custom); return this; } var rt = typeof(required); if (required !== undefined && rt === 'string') { custom = required; required = false; } if (type == null) { // remove delete this.schema[name]; this.properties = this.properties.remove(name); if (this.dependencies) this.dependencies = this.dependencies.remove(name); this.fields = Object.keys(this.schema); return this; } if (type instanceof SchemaBuilderEntity) type = type.name; var a = this.schema[name] = this.$parse(name, type, required, custom); switch (this.schema[name].type) { case 7: if (this.dependencies) this.dependencies.push(name); else this.dependencies = [name]; break; } this.fields = Object.keys(this.schema); if (a.type === 7) required = true; if (required) this.properties.indexOf(name) === -1 && this.properties.push(name); else this.properties = this.properties.remove(name); return function(val) { a.def = val; return this; }; }; SchemaBuilderEntityProto.verify = function(name, fn, cache) { var self = this; if (!self.verifications) self.verifications = []; var cachekey; if (cache) cachekey = self.name + '_verify_' + name + '_'; self.verifications.push({ name: name, fn: fn, cache: cache, cachekey: cachekey }); return self; }; SchemaBuilderEntityProto.inherit = function(group, name) { if (!name) { name = group; group = DEFAULT_SCHEMA; } var self = this; exports.getschema(group, name, function(err, schema) { if (err) throw err; self.primary = schema.primary; self.inherits.push(schema); if (!self.resourceName && schema.resourceName) self.resourceName = schema.resourceName; if (!self.resourcePrefix && schema.resourcePrefix) self.resourcePrefix = schema.resourcePrefix; copy_inherit(self, 'schema', schema.schema); copy_inherit(self, 'meta', schema.meta); copy_inherit(self, 'transforms', schema.transforms); copy_inherit(self, 'workflows', schema.workflows); copy_inherit(self, 'hooks', schema.hooks); copy_inherit(self, 'operations', schema.operations); copy_inherit(self, 'constants', schema.constants); if (schema.middlewares) { self.middlewares = []; for (var i = 0; i < schema.middlewares.length; i++) self.middlewares.push(schema.middlewares[i]); } if (schema.verifications) { self.verifications = []; for (var i = 0; i < schema.verifications.length; i++) self.verifications.push(schema.verifications[i]); } schema.properties.forEach(function(item) { if (self.properties.indexOf(item) === -1) self.properties.push(item); }); if (schema.preparation) { self.preparation = {}; Object.keys(schema.preparation).forEach(function(key) { self.preparation[key] = schema.preparation[key]; }); } if (schema.onPrepare) { if (!self.$onPrepare) self.$onPrepare = []; self.$onPrepare.push(schema.onPrepare); } if (schema.onDefault) { if (!self.$onDefault) self.$onDefault = []; self.$onDefault.push(schema.onDefault); } if (self.onValidate === F.onValidate && self.onValidate !== schema.onValidate) self.onValidate = schema.onValidate; if (!self.onSave && schema.onSave) self.onSave = schema.onSave; if (!self.onInsert && schema.onInsert) self.onInsert = schema.onInsert; if (!self.onUpdate && schema.onUpdate) self.onUpdate = schema.onUpdate; if (!self.onGet && schema.onGet) self.onGet = schema.onGet; if (!self.onRemove && schema.onRemove) self.onRemove = schema.onRemove; if (!self.onQuery && schema.onQuery) self.onQuery = schema.onQuery; if (!self.onError && schema.onError) self.onError = schema.onError; self.fields = Object.keys(self.schema); }); return self; }; function copy_inherit(schema, field, value) { if (!value) return; if (value && !schema[field]) { schema[field] = framework_utils.clone(value); return; } Object.keys(value).forEach(function(key) { if (schema[field][key] === undefined) schema[field][key] = framework_utils.clone(value[key]); }); } /** * Set primary key * @param {String} name */ SchemaBuilderEntityProto.setPrimary = function(name) { this.primary = name; return this; }; /** * Filters current names of the schema via custom attribute * @param {Number/String} custom * @param {Object} model Optional * @param {Boolean} reverse Reverse results. * @return {Array|Object} Returns Array (with property names) if the model is undefined otherwise returns Object Name/Value. */ SchemaBuilderEntityProto.filter = function(custom, model, reverse) { if (typeof(model) === 'boolean') { var tmp = reverse; reverse = model; model = tmp; } var output = model === undefined ? [] : {}; var type = typeof(custom); var isSearch = type === 'string' ? custom[0] === '*' || custom[0] === '%' : false; var isReg = false; if (isSearch) custom = custom.substring(1); else if (type === 'object') isReg = framework_utils.isRegExp(custom); for (var prop in this.schema) { var schema = this.schema[prop]; if (!schema) continue; var tv = typeof(schema.custom); if (isSearch) { if (tv === 'string') { if (schema.custom.indexOf(custom) === -1) { if (!reverse) continue; } else if (reverse) continue; } else continue; } else if (isReg) { if (tv === 'string') { if (!custom.test(schema.current)) { if (!reverse) continue; } else if (reverse) continue; } else continue; } else if (schema.custom !== custom) { if (!reverse) continue; } else if (reverse) continue; if (model === undefined) output.push(prop); else output[prop] = model[prop]; } return output; }; function parseLength(lower, result) { result.raw = 'string'; var beg = lower.indexOf('('); if (beg !== -1) { result.length = lower.substring(beg + 1, lower.length - 1).parseInt(); result.raw = lower.substring(0, beg); } return result; } SchemaBuilderEntityProto.$parse = function(name, value, required, custom) { var type = typeof(value); var result = {}; result.raw = value; result.type = 0; result.length = 0; result.required = required ? true : false; result.validate = typeof(required) === 'function' ? required : null; result.can = null; result.isArray = false; result.custom = custom || ''; // 0 = undefined // 1 = integer // 2 = float // 3 = string // 4 = boolean // 5 = date // 6 = object // 7 = custom object // 8 = enum // 9 = keyvalue // 10 = custom object type // 11 = number2 // 12 = object as filter if (value === null) return result; if (value === '[]') { result.isArray = true; return result; } if (type === 'function') { if (value === UID) { result.type = 3; result.length = 20; result.raw = 'string'; result.subtype = 'uid'; return result; } if (value === Number) { result.type = 2; return result; } if (value === String) { result.type = 3; return result; } if (value === Boolean) { result.type = 4; return result; } if (value === Date) { result.type = 5; return result; } if (value === Array) { result.isArray = true; return result; } if (value === Object) { result.type = 6; return result; } if (value instanceof SchemaBuilderEntity) result.type = 7; else { result.type = 10; if (!this.asyncfields) this.asyncfields = []; this.asyncfields.push(name); } return result; } if (type === 'object') { if (value instanceof Array) { result.type = 8; // enum result.subtype = typeof(value[0]); } else result.type = 9; // keyvalue return result; } if (value[0] === '[') { value = value.substring(1, value.length - 1); result.isArray = true; result.raw = value; } var lower = value.toLowerCase(); if (lower === 'object') { result.type = 6; return result; } if (lower === 'array') { result.isArray = true; return result; } if (value.indexOf(',') !== -1) { // multiple result.type = 12; return result; } if ((/^(string|text)+(\(\d+\))?$/).test(lower)) { result.type = 3; return parseLength(lower, result); } if ((/^(capitalize2)+(\(\d+\))?$/).test(lower)) { result.type = 3; result.subtype = 'capitalize2'; return parseLength(lower, result); } if ((/^(capitalize|camelcase|camelize)+(\(\d+\))?$/).test(lower)) { result.type = 3; result.subtype = 'capitalize'; return parseLength(lower, result); } if ((/^(lower|lowercase)+(\(\d+\))?$/).test(lower)) { result.subtype = 'lowercase'; result.type = 3; return parseLength(lower, result); } if (lower.indexOf('base64') !== -1) { result.type = 3; result.raw = 'string'; result.subtype = 'base64'; return result; } if ((/^(upper|uppercase)+(\(\d+\))?$/).test(lower)) { result.subtype = 'uppercase'; result.type = 3; return parseLength(lower, result); } if (lower === 'uid') { result.type = 3; result.length = 20; result.raw = 'string'; result.subtype = 'uid'; return result; } if (lower === 'email') { result.type = 3; result.length = 120; result.raw = 'string'; result.subtype = 'email'; return result; } if (lower === 'json') { result.type = 3; result.raw = 'string'; result.subtype = 'json'; return result; } if (lower === 'url') { result.type = 3; result.length = 500; result.raw = 'string'; result.subtype = 'url'; return result; } if (lower === 'zip') { result.type = 3; result.length = 10; result.raw = 'string'; result.subtype = 'zip'; return result; } if (lower === 'phone') { result.type = 3; result.length = 20; result.raw = 'string'; result.subtype = 'phone'; return result; } if (lower === 'number2') { result.type = 11; return result; } if (['int', 'integer', 'byte'].indexOf(lower) !== -1) { result.type = 1; return result; } if (['decimal', 'number', 'float', 'double'].indexOf(lower) !== -1) { result.type = 2; return result; } if (['bool', 'boolean'].indexOf(lower) !== -1) { result.type = 4; return result; } if (['date', 'time', 'datetime'].indexOf(lower) !== -1) { result.type = 5; return result; } result.type = 7; return result; }; SchemaBuilderEntityProto.getDependencies = function() { var dependencies = []; for (var name in this.schema) { var type = this.schema[name]; if (typeof(type) !== 'string') continue; var isArray = type[0] === ']'; if (isArray) type = type.substring(1, type.length - 1); var m = this.parent.get(type); m && dependencies.push({ name: name, isArray: isArray, schema: m }); } return dependencies; }; /** * Set schema validation * @param {String|Array} properties Properties to validate, optional. * @param {Function(propertyName, value, path, entityName, model)} fn A validation function. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setValidate = function(properties, fn) { if (fn === undefined && properties instanceof Array) { this.properties = properties; return this; } if (typeof(properties) !== 'function') { this.properties = properties; this.onValidate = fn; } else this.onValidate = properties; return this; }; SchemaBuilderEntityProto.setPrefix = function(prefix) { this.resourcePrefix = prefix; return this; }; SchemaBuilderEntityProto.setResource = function(name) { this.resourceName = name; return this; }; /** * Set the default values for schema * @param {Function(propertyName, isntPreparing, entityName)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setDefault = function(fn) { this.onDefault = fn; return this; }; /** * Set the prepare * @param {Function(name, value)} fn Must return a new value. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setPrepare = function(fn) { this.onPrepare = fn; return this; }; /** * Set save handler * @param {Function(error, model, helper, next(value), controller)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setSave = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onSave = fn; this.meta.save = description || null; this.meta.savefilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setSave()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setSaveExtension = function(fn) { var key = 'save'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Set insert handler * @param {Function(error, model, helper, next(value), controller)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setInsert = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onInsert = fn; this.meta.insert = description || null; this.meta.insertfilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setInsert()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setInsertExtension = function(fn) { var key = 'insert'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Set update handler * @param {Function(error, model, helper, next(value), controller)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setUpdate = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onUpdate = fn; this.meta.update = description || null; this.meta.updatefilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setUpdate()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setUpdateExtension = function(fn) { var key = 'update'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Set patch handler * @param {Function(error, model, helper, next(value), controller)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setPatch = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onPatch = fn; this.meta.patch = description || null; this.meta.patchfilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setPatch()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setPatchExtension = function(fn) { var key = 'patch'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Set error handler * @param {Function(error)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setError = function(fn) { this.onError = fn; return this; }; /** * Set getter handler * @param {Function(error, model, helper, next(value), controller)} fn * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setGet = SchemaBuilderEntityProto.setRead = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onGet = fn; this.meta.get = this.meta.read = description || null; this.meta.getfilter = this.meta.readfilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setGet()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setGetExtension = SchemaBuilderEntityProto.setReadExtension = function(fn) { var key = 'read'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Set query handler * @param {Function(error, helper, next(value), controller)} fn * @param {String} description Optional. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setQuery = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onQuery = fn; this.meta.query = description || null; this.meta.queryfilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setQuery()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setQueryExtension = function(fn) { var key = 'query'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Set remove handler * @param {Function(error, helper, next(value), controller)} fn * @param {String} description Optional. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.setRemove = function(fn, description, filter) { if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); this.onRemove = fn; this.meta.remove = description || null; this.meta.removefilter = filter; !fn.$newversion && OBSOLETE('Schema("{0}").setRemove()'.format(this.name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.setRemoveExtension = function(fn) { var key = 'remove'; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Add a new constant for the schema * @param {String} name Constant name, optional. * @param {Object} value * @param {String} description Optional. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.constant = function(name, value, description) { OBSOLETE('Constants will be removed from schemas.'); if (value === undefined) return this.constants ? this.constants[name] : undefined; !this.constants && (this.constants = {}); this.constants[name] = value; this.meta['constant#' + name] = description || null; return this; }; /** * Add a new transformation for the entity * @param {String} name Transform name, optional. * @param {Function(errorBuilder, model, helper, next([output]), controller)} fn * @param {String} description Optional. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.addTransform = function(name, fn, description, filter) { if (typeof(name) === 'function') { fn = name; name = 'default'; } if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); !this.transforms && (this.transforms = {}); this.transforms[name] = fn; this.meta['transform#' + name] = description || null; this.meta['transformfilter#' + name] = filter; !fn.$newversion && OBSOLETE('Schema("{0}").addTransform("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.addTransformExtension = function(name, fn) { var key = 'transform.' + name; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Add a new operation for the entity * @param {String} name Operation name, optional. * @param {Function(errorBuilder, [model], helper, next([output]), controller)} fn * @param {String} description Optional. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.addOperation = function(name, fn, description, filter) { if (typeof(name) === 'function') { fn = name; name = 'default'; } if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); !this.operations && (this.operations = {}); this.operations[name] = fn; this.meta['operation#' + name] = description || null; this.meta['operationfilter#' + name] = filter; !fn.$newversion && OBSOLETE('Schema("{0}").addOperation("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.addOperationExtension = function(name, fn) { var key = 'operation.' + name; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Add a new workflow for the entity * @param {String} name Workflow name, optional. * @param {Function(errorBuilder, model, helper, next([output]), controller)} fn * @param {String} description Optional. * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.addWorkflow = function(name, fn, description, filter) { if (typeof(name) === 'function') { fn = name; name = 'default'; } if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); !this.workflows && (this.workflows = {}); this.workflows[name] = fn; this.meta['workflow#' + name] = description || null; this.meta['workflowfilter#' + name] = filter; !fn.$newversion && OBSOLETE('Schema("{0}").addWorkflow("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.addWorkflowExtension = function(name, fn) { var key = 'workflow.' + name; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; SchemaBuilderEntityProto.addHook = function(name, fn, description, filter) { if (!this.hooks) this.hooks = {}; if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) { filter = description; description = null; } fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString()); !this.hooks[name] && (this.hooks[name] = []); this.hooks[name].push({ owner: F.$owner(), fn: fn }); this.meta['hook#' + name] = description || null; this.meta['hookfilter#' + name] = filter; !fn.$newversion && OBSOLETE('Schema("{0}").addHook("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW); return this; }; SchemaBuilderEntityProto.addHookExtension = function(name, fn) { var key = 'hook.' + name; if (this.extensions[key]) this.extensions[key].push(fn); else this.extensions[key] = [fn]; return this; }; /** * Find an entity in current group * @param {String} name * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.find = function(name) { return this.parent.get(name); }; /** * Destroys current entity */ SchemaBuilderEntityProto.destroy = function() { delete this.parent.collection[this.name]; delete this.properties; delete this.schema; delete this.onDefault; delete this.$onDefault; delete this.onValidate; delete this.onSave; delete this.onInsert; delete this.onUpdate; delete this.onRead; delete this.onGet; delete this.onRemove; delete this.onQuery; delete this.workflows; delete this.operations; delete this.transforms; delete this.meta; delete this.newversion; delete this.properties; delete this.hooks; delete this.constants; delete this.onPrepare; delete this.$onPrepare; delete this.onError; delete this.gcache; delete this.dependencies; delete this.fields; delete this.fields_allow; }; /** * Execute onSave delegate * @param {Object} model * @param {Object} options Custom options object, optional * @param {Function(err, result)} callback * @param {Controller} controller * @param {Boolean} skip Skips preparing and validation, optional * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.save = function(model, options, callback, controller, skip) { return this.execute('onSave', model, options, callback, controller, skip); }; /** * Execute onInsert delegate * @param {Object} model * @param {Object} options Custom options object, optional * @param {Function(err, result)} callback * @param {Controller} controller * @param {Boolean} skip Skips preparing and validation, optional * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.insert = function(model, options, callback, controller, skip) { return this.execute('onInsert', model, options, callback, controller, skip); }; /** * Execute onUpdate delegate * @param {Object} model * @param {Object} options Custom options object, optional * @param {Function(err, result)} callback * @param {Controller} controller * @param {Boolean} skip Skips preparing and validation, optional * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.update = function(model, options, callback, controller, skip) { return this.execute('onUpdate', model, options, callback, controller, skip); }; SchemaBuilderEntityProto.patch = function(model, options, callback, controller, skip) { return this.execute('onPatch', model, options, callback, controller, skip); }; SchemaBuilderEntityProto.execute = function(TYPE, model, options, callback, controller, skip) { if (typeof(callback) === 'boolean') { skip = callback; callback = options; options = undefined; } else if (typeof(options) === 'function') { callback = options; options = undefined; } if (typeof(controller) === 'boolean') { var tmp = skip; skip = controller; controller = tmp; } if (typeof(callback) !== 'function') callback = function(){}; var self = this; var $type; switch (TYPE) { case 'onInsert': $type = 'insert'; break; case 'onUpdate': $type = 'update'; break; case 'onPatch': $type = 'patch'; break; default: $type = 'save'; break; } if (!self[TYPE]) return callback(new Error('Operation "{0}/{1}" not found'.format(self.name, $type))); self.$prepare(model, function(err, model) { if (err) { callback(err, model); return; } if (controller instanceof SchemaOptions || controller instanceof OperationOptions) controller = controller.controller; if (model && !controller && model.$$controller) controller = model.$$controller; var builder = new ErrorBuilder(); var $now; if (CONF.logger) $now = Date.now(); self.resourceName && builder.setResource(self.resourceName); self.resourcePrefix && builder.setPrefix(self.resourcePrefix); if (!isGenerator(self, $type, self[TYPE])) { if (self[TYPE].$newversion) { var opt = new SchemaOptions(builder, model, options, function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); self.$process(arguments, model, $type, undefined, builder, res, callback, controller); }, controller, $type, self); if (self.middlewares && self.middlewares.length) runmiddleware(opt, self, self[TYPE]); else self[TYPE](opt); } else self[TYPE](builder, model, options, function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); self.$process(arguments, model, $type, undefined, builder, res, callback, controller); }, controller, skip !== true); return self; } callback.success = false; var onError = function(err) { if (!err || callback.success) return; callback.success = true; if (builder !== err) builder.push(err); self.onError && self.onError(builder, model, $type); callback(builder); }; var onCallback = function(res) { CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now); if (callback.success) return; if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) { if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res) builder.push(res); res = arguments[1]; } var has = builder.is; has && self.onError && self.onError(builder, model, $type); callback.success = true; callback(has ? builder : null, res === undefined ? model : res); }; if (self[TYPE].$newversion) { var opt = new SchemaOptions(builder, model, options, onCallback, controller, $type, self); if (self.middlewares && self.middlewares.length) runmiddleware(opt, self, () => async.call(self, self[TYPE])(onError, opt)); else async.call(self, self[TYPE])(onError, opt); } else async.call(self, self[TYPE])(onError, builder, model, options, onCallback, controller, skip !== true); }, controller ? controller.req : null); return self; }; function isGenerator(obj, name, fn) { return obj.gcache[name] ? obj.gcache[name] : obj.gcache[name] = fn.toString().substring(0, 9) === 'function*'; } /** * Execute onGet delegate * @param {Object} options Custom options object, optional * @param {Function(err, result)} callback * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.get = SchemaBuilderEntityProto.read = function(options, callback, controller) { if (typeof(options) === 'function') { callback = options; options = undefined; } if (typeof(callback) !== 'function') callback = function(){}; var self = this; var builder = new ErrorBuilder(); var $now; self.resourceName && builder.setResource(self.resourceName); self.resourcePrefix && builder.setPrefix(self.resourcePrefix); if (controller instanceof SchemaOptions || controller instanceof OperationOptions) controller = controller.controller; if (self.meta.getfilter && controller) { controller.$filterschema = self.meta.getfilter; controller.$filter = null; } if (CONF.logger) $now = Date.now(); var output = self.default(); var $type = 'get'; if (!isGenerator(self, $type, self.onGet)) { if (self.onGet.$newversion) { var opt = new SchemaOptions(builder, output, options, function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); self.$process(arguments, output, $type, undefined, builder, res, callback, controller); }, controller, $type, self); if (self.middlewares && self.middlewares.length) runmiddleware(opt, self, self.onGet); else self.onGet(opt); } else self.onGet(builder, output, options, function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); self.$process(arguments, output, $type, undefined, builder, res, callback, controller); }, controller); return self; } callback.success = false; var onError = function(err) { if (!err || callback.success) return; callback.success = true; if (builder !== err) builder.push(err); self.onError && self.onError(builder, output, $type); callback(builder); }; var onCallback = function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); if (callback.success) return; if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) { if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res) builder.push(res); res = arguments[1]; } callback.success = true; var has = builder.is; has && self.onError && self.onError(builder, output, $type); callback(has ? builder : null, res === undefined ? output : res); }; if (self.onGet.$newversion) { var opt = new SchemaOptions(builder, output, options, onCallback, controller, $type, self); if (self.middlewares && self.middlewares.length) runmiddleware(opt, self, () => async.call(self, self.onGet)(onError, opt)); else async.call(self, self.onGet)(onError, opt); } else async.call(self, self.onGet)(onError, builder, output, options, onCallback, controller); return self; }; /** * Execute onRemove delegate * @param {Object} options Custom options object, optional * @param {Function(err, result)} callback * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.remove = function(options, callback, controller) { if (typeof(options) === 'function') { callback = options; options = undefined; } var self = this; var builder = new ErrorBuilder(); var $type = 'remove'; var $now; if (!self.onRemove) return callback(new Error('Operation "{0}/{1}" not found'.format(self.name, $type))); if (controller instanceof SchemaOptions || controller instanceof OperationOptions) controller = controller.controller; if (self.meta.removefilter && controller) { controller.$filterschema = self.meta.removefilter; controller.$filter = null; } if (CONF.logger) $now = Date.now(); self.resourceName && builder.setResource(self.resourceName); self.resourcePrefix && builder.setPrefix(self.resourcePrefix); if (!isGenerator(self, $type, self.onRemove)) { if (self.onRemove.$newversion) { var opt = new SchemaOptions(builder, controller ? controller.body : undefined, options, function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller); }, controller, $type, self); if (self.middlewares && self.middlewares.length) runmiddleware(opt, self, self.onRemove); else self.onRemove(opt); } else self.onRemove(builder, options, function(res) { CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now); self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller); }, controller); return self; } callback.success = false; var onError = function(err) { if (!err || callback.success) return; callback.success = true; if (builder !== err) builder.push(err); self.onError && self.onError(builder, EMPTYOBJECT, $type); callback(builder); }; var onCallback = function(res) { CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now); if (callback.success) return; if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) { if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res) builder.push(res); res = arguments[1]; } var has = builder.is; has && self.onError && self.onError(builder, EMPTYOBJECT, $type); callback.success = true; callback(has ? builder : null, res === undefined ? options : res); }; if (self.onRemove.$newversion) { var opt = new SchemaOptions(builder, undefined, options, onCallback, controller, $type, self); if (self.middlewares && self.middlewares.length) runmiddleware(opt, self, () => async.call(self, self.onRemove)(onError, opt)); else async.call(self, self.onRemove)(onError, opt); } else async.call(self, self.onRemove)(onError, builder, options, onCallback, controller); return self; }; /** * Execute onQuery delegate * @param {Object} options Custom options object, optional * @param {Function(err, result)} callback * @return {SchemaBuilderEntity} */ SchemaBuilderEntityProto.query = function(options, callback, controller) { if (typeof(options) === 'function') { callback = options; options = undefined; } if (controller instanceof SchemaOptions || controller instanceof OperationOptions) controller = controller.controller; var self = this; var builder = new ErrorBuilder(); var $type = 'query'; var $now; if (self.meta.queryfilter && controller) { controller.$filterschema = self.meta.queryfilter; controller.$filter = null; } self.resourceName && builder.setResource(self.resourceName); self.resourcePrefix && builder.setPrefix(self.resourcePrefix); if (CONF.logg