UNPKG

warehouse

Version:
698 lines 20.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const schematype_1 = __importDefault(require("./schematype")); const Types = __importStar(require("./types/index")); const bluebird_1 = __importDefault(require("bluebird")); const util_1 = require("./util"); const population_1 = __importDefault(require("./error/population")); const is_plain_object_1 = require("is-plain-object"); const builtinTypes = new Set(['String', 'Number', 'Boolean', 'Array', 'Object', 'Date', 'Buffer']); const getSchemaType = (name, options) => { const Type = options.type || options; const typeName = Type.name; if (builtinTypes.has(typeName)) { return new Types[typeName](name, options); } return new Type(name, options); }; const checkHookType = (type) => { if (type !== 'save' && type !== 'remove') { throw new TypeError('Hook type must be `save` or `remove`!'); } }; const hookWrapper = (fn) => { if (fn.length > 1) { return bluebird_1.default.promisify(fn); } return bluebird_1.default.method(fn); }; const execSortStack = (stack) => { const len = stack.length; return (a, b) => { let result; for (let i = 0; i < len; i++) { result = stack[i](a, b); if (result) break; } return result; }; }; const sortStack = (path_, key, sort) => { const path = path_ || new schematype_1.default(key); const descending = sort === 'desc' || sort === -1; return (a, b) => { const result = path.compare((0, util_1.getProp)(a, key), (0, util_1.getProp)(b, key)); return descending && result ? result * -1 : result; }; }; class UpdateParser { static updateStackNormal(key, update) { return (data) => { (0, util_1.setProp)(data, key, update); }; } static updateStackOperator(path_, ukey, key, update) { const path = path_ || new schematype_1.default(key); return (data) => { const result = path[ukey]((0, util_1.getProp)(data, key), update, data); (0, util_1.setProp)(data, key, result); }; } // eslint-disable-next-line no-useless-constructor constructor(paths) { this.paths = paths; } /** * Parses updating expressions and returns a stack. * * @param {Object} updates * @param {queryCallback[]} [stack] * @private */ parseUpdate(updates, prefix = '', stack = []) { const { paths } = this; const { updateStackOperator } = UpdateParser; const keys = Object.keys(updates); let path, prefixNoDot; if (prefix) { prefixNoDot = prefix.substring(0, prefix.length - 1); path = paths[prefixNoDot]; } for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; const update = updates[key]; const name = prefix + key; // Update operators if (key[0] === '$') { const ukey = `u${key}`; // First-class update operators if (prefix) { stack.push(updateStackOperator(path, ukey, prefixNoDot, update)); } else { // Inline update operators const fields = Object.keys(update); const fieldLen = fields.length; for (let j = 0; j < fieldLen; j++) { const field = fields[i]; stack.push(updateStackOperator(paths[field], ukey, field, update[field])); } } } else if ((0, is_plain_object_1.isPlainObject)(update)) { this.parseUpdate(update, `${name}.`, stack); } else { stack.push(UpdateParser.updateStackNormal(name, update)); } } return stack; } } /** * @private */ class QueryParser { // eslint-disable-next-line no-useless-constructor constructor(paths) { this.paths = paths; } /** * * @param {string} name * @param {*} query * @return {queryFilterCallback} */ queryStackNormal(name, query) { const path = this.paths[name] || new schematype_1.default(name); return (data) => path.match((0, util_1.getProp)(data, name), query, data); } /** * * @param {string} qkey * @param {string} name * @param {*} query * @return {queryFilterCallback} */ queryStackOperator(qkey, name, query) { const path = this.paths[name] || new schematype_1.default(name); return (data) => path[qkey]((0, util_1.getProp)(data, name), query, data); } /** * @param {Array} arr * @param {queryFilterCallback[]} stack The function generated by query is added to the stack. * @return {void} * @private */ $and(arr, stack) { for (let i = 0, len = arr.length; i < len; i++) { stack.push(this.execQuery(arr[i])); } } /** * @param {Array} query * @return {queryFilterCallback} * @private */ $or(query) { const stack = this.parseQueryArray(query); const len = stack.length; return data => { for (let i = 0; i < len; i++) { if (stack[i](data)) return true; } return false; }; } /** * @param {Array} query * @return {queryFilterCallback} * @private */ $nor(query) { const stack = this.parseQueryArray(query); const len = stack.length; return data => { for (let i = 0; i < len; i++) { if (stack[i](data)) return false; } return true; }; } /** * @param {*} query * @return {queryFilterCallback} * @private */ $not(query) { const stack = this.parseQuery(query); const len = stack.length; return data => { for (let i = 0; i < len; i++) { if (!stack[i](data)) return true; } return false; }; } /** * @callback queryWherecallback * @return {boolean} * @this {QueryPerser} */ /** * @param {queryWherecallback} fn * @return {queryFilterCallback} * @private */ $where(fn) { return data => Reflect.apply(fn, data, []); } /** * Parses array of query expressions and returns a stack. * * @param {Array} arr * @return {queryFilterCallback[]} * @private */ parseQueryArray(arr) { const stack = []; this.$and(arr, stack); return stack; } /** * Parses normal query expressions and returns a stack. * * @param {Object} queries * @param {String} prefix * @param {queryFilterCallback[]} [stack] The function generated by query is added to the stack passed in this argument. If not passed, a new stack will be created. * @return {void} * @private */ parseNormalQuery(queries, prefix, stack = []) { const keys = Object.keys(queries); for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; const query = queries[key]; if (key[0] === '$') { stack.push(this.queryStackOperator(`q${key}`, prefix, query)); continue; } const name = `${prefix}.${key}`; if ((0, is_plain_object_1.isPlainObject)(query)) { this.parseNormalQuery(query, name, stack); } else { stack.push(this.queryStackNormal(name, query)); } } } /** * Parses query expressions and returns a stack. * * @param {Object} queries * @return {queryFilterCallback[]} * @private */ parseQuery(queries) { /** @type {queryFilterCallback[]} */ const stack = []; const keys = Object.keys(queries); for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; const query = queries[key]; switch (key) { case '$and': this.$and(query, stack); break; case '$or': stack.push(this.$or(query)); break; case '$nor': stack.push(this.$nor(query)); break; case '$not': stack.push(this.$not(query)); break; case '$where': stack.push(this.$where(query)); break; default: if ((0, is_plain_object_1.isPlainObject)(query)) { this.parseNormalQuery(query, key, stack); } else { stack.push(this.queryStackNormal(key, query)); } } } return stack; } /** * Returns a function for querying. * * @param {Object} query * @return {queryFilterCallback} * @private */ execQuery(query) { const stack = this.parseQuery(query); const len = stack.length; return data => { for (let i = 0; i < len; i++) { if (!stack[i](data)) return false; } return true; }; } } class Schema { /** * Schema constructor. * * @param {Object} [schema] */ constructor(schema) { this.paths = {}; this.statics = {}; this.methods = {}; this.hooks = { pre: { save: [], remove: [] }, post: { save: [], remove: [] } }; this.stacks = { getter: [], setter: [], import: [], export: [] }; if (schema) { this.add(schema); } } /** * Adds paths. * * @param {Object} schema * @param {String} prefix */ add(schema, prefix = '') { const keys = Object.keys(schema); const len = keys.length; if (!len) return; for (let i = 0; i < len; i++) { const key = keys[i]; const value = schema[key]; this.path(prefix + key, value); } } path(name, obj) { if (obj == null) { return this.paths[name]; } let type; let nested = false; if (obj instanceof schematype_1.default) { type = obj; } else { switch (typeof obj) { case 'function': type = getSchemaType(name, { type: obj }); break; case 'object': if (Array.isArray(obj)) { type = new Types.Array(name, { child: obj.length ? getSchemaType(name, obj[0]) : new schematype_1.default(name) }); } else if (obj.type) { type = getSchemaType(name, obj); } else { type = new Types.Object(); nested = Object.keys(obj).length > 0; } break; default: throw new TypeError(`Invalid value for schema path \`${name}\``); } } this.paths[name] = type; this._updateStack(name, type); if (nested) this.add(obj, `${name}.`); } /** * Updates cache stacks. * * @param {String} name * @param {SchemaType} type * @private */ _updateStack(name, type) { const { stacks } = this; stacks.getter.push(data => { const value = (0, util_1.getProp)(data, name); const result = type.cast(value, data); if (result !== undefined) { (0, util_1.setProp)(data, name, result); } }); stacks.setter.push(data => { const value = (0, util_1.getProp)(data, name); const result = type.validate(value, data); if (result !== undefined) { (0, util_1.setProp)(data, name, result); } else { (0, util_1.delProp)(data, name); } }); stacks.import.push(data => { const value = (0, util_1.getProp)(data, name); const result = type.parse(value); if (result !== undefined) { (0, util_1.setProp)(data, name, result); } }); stacks.export.push(data => { const value = (0, util_1.getProp)(data, name); const result = type.value(value, data); if (result !== undefined) { (0, util_1.setProp)(data, name, result); } else { (0, util_1.delProp)(data, name); } }); } /** * Adds a virtual path. * * @param {String} name * @param {Function} [getter] * @return {SchemaType.Virtual} */ virtual(name, getter) { const virtual = new Types.Virtual(name, {}); if (getter) virtual.get(getter); this.path(name, virtual); return virtual; } /** * Adds a pre-hook. * * @param {String} type Hook type. One of `save` or `remove`. * @param {Function} fn */ pre(type, fn) { checkHookType(type); if (typeof fn !== 'function') throw new TypeError('Hook must be a function!'); this.hooks.pre[type].push(hookWrapper(fn)); } /** * Adds a post-hook. * * @param {String} type Hook type. One of `save` or `remove`. * @param {Function} fn */ post(type, fn) { checkHookType(type); if (typeof fn !== 'function') throw new TypeError('Hook must be a function!'); this.hooks.post[type].push(hookWrapper(fn)); } /** * Adds a instance method. * * @param {String} name * @param {Function} fn */ method(name, fn) { if (!name) throw new TypeError('Method name is required!'); if (typeof fn !== 'function') { throw new TypeError('Instance method must be a function!'); } this.methods[name] = fn; } /** * Adds a static method. * * @param {String} name * @param {Function} fn */ static(name, fn) { if (!name) throw new TypeError('Method name is required!'); if (typeof fn !== 'function') { throw new TypeError('Static method must be a function!'); } this.statics[name] = fn; } /** * Apply getters. * * @param {Object} data * @return {void} * @private */ _applyGetters(data) { const stack = this.stacks.getter; for (let i = 0, len = stack.length; i < len; i++) { stack[i](data); } } /** * Apply setters. * * @param {Object} data * @return {void} * @private */ _applySetters(data) { const stack = this.stacks.setter; for (let i = 0, len = stack.length; i < len; i++) { stack[i](data); } } /** * Parses database. * * @param {Object} data * @return {Object} * @private */ _parseDatabase(data) { const stack = this.stacks.import; for (let i = 0, len = stack.length; i < len; i++) { stack[i](data); } return data; } /** * Exports database. * * @param {Object} data * @return {Object} * @private */ _exportDatabase(data) { const stack = this.stacks.export; for (let i = 0, len = stack.length; i < len; i++) { stack[i](data); } return data; } /** * Parses updating expressions and returns a stack. * * @param {Object} updates * @return {queryCallback[]} * @private */ _parseUpdate(updates) { return new UpdateParser(this.paths).parseUpdate(updates); } /** * Returns a function for querying. * * @param {Object} query * @return {queryFilterCallback} * @private */ _execQuery(query) { return new QueryParser(this.paths).execQuery(query); } /** * Parses sorting expressions and returns a stack. * * @param {Object} sorts * @param {string} [prefix] * @param {queryParseCallback[]} [stack] * @return {queryParseCallback[]} * @private */ _parseSort(sorts, prefix = '', stack = []) { const { paths } = this; const keys = Object.keys(sorts); for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; const sort = sorts[key]; const name = prefix + key; if (typeof sort === 'object') { this._parseSort(sort, `${name}.`, stack); } else { stack.push(sortStack(paths[name], name, sort)); } } return stack; } /** * Returns a function for sorting. * * @param {Object} sorts * @return {queryParseCallback} * @private */ _execSort(sorts) { const stack = this._parseSort(sorts); return execSortStack(stack); } /** * Parses population expression and returns a stack. * * @param {String|Object} expr * @return {PopulateResult[]} * @private */ _parsePopulate(expr) { const { paths } = this; const arr = []; if (typeof expr === 'string') { const split = expr.split(' '); for (let i = 0, len = split.length; i < len; i++) { arr[i] = { path: split[i] }; } } else if (Array.isArray(expr)) { for (let i = 0, len = expr.length; i < len; i++) { const item = expr[i]; arr[i] = typeof item === 'string' ? { path: item } : item; } } else { arr[0] = expr; } for (let i = 0, len = arr.length; i < len; i++) { const item = arr[i]; const key = item.path; if (!key) { throw new population_1.default('path is required'); } if (!item.model) { const path = paths[key]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ref = path.child ? path.child.options.ref : path.options.ref; if (!ref) { throw new population_1.default('model is required'); } item.model = ref; } } return arr; } } Schema.Types = Types; Schema.prototype.Types = Types; exports.default = Schema; //# sourceMappingURL=schema.js.map