total.js
Version:
MVC framework for Node.js
2,009 lines (1,640 loc) • 161 kB
JavaScript
// 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