2023-10-03 11:14:36 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const SchemaType = require('./schematype');
|
|
|
|
const Types = require('./types');
|
|
|
|
const Promise = require('bluebird');
|
|
|
|
const { getProp, setProp, delProp } = require('./util');
|
|
|
|
const PopulationError = require('./error/population');
|
|
|
|
const { isPlainObject } = require('is-plain-object');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @callback queryFilterCallback
|
|
|
|
* @param {*} data
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @callback queryCallback
|
|
|
|
* @param {*} data
|
|
|
|
* @return {void}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @callback queryParseCallback
|
|
|
|
* @param {*} a
|
|
|
|
* @param {*} b
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef PopulateResult
|
|
|
|
* @property {string} path
|
|
|
|
* @property {*} model
|
|
|
|
*/
|
|
|
|
|
|
|
|
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 Promise.promisify(fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.method(fn);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Function[]} stack
|
|
|
|
*/
|
|
|
|
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(key);
|
|
|
|
const descending = sort === 'desc' || sort === -1;
|
|
|
|
|
|
|
|
return (a, b) => {
|
|
|
|
const result = path.compare(getProp(a, key), getProp(b, key));
|
|
|
|
return descending && result ? result * -1 : result;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
class UpdateParser {
|
|
|
|
static updateStackNormal(key, update) {
|
|
|
|
return data => { setProp(data, key, update); };
|
|
|
|
}
|
|
|
|
|
|
|
|
static updateStackOperator(path_, ukey, key, update) {
|
|
|
|
const path = path_ || new SchemaType(key);
|
|
|
|
|
|
|
|
return data => {
|
|
|
|
const result = path[ukey](getProp(data, key), update, data);
|
|
|
|
setProp(data, key, result);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (isPlainObject(update)) {
|
|
|
|
this.parseUpdate(update, `${name}.`, stack);
|
|
|
|
} else {
|
|
|
|
stack.push(UpdateParser.updateStackNormal(name, update));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return stack;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
class QueryParser {
|
|
|
|
constructor(paths) {
|
|
|
|
this.paths = paths;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* @param {*} query
|
|
|
|
* @return {queryFilterCallback}
|
|
|
|
*/
|
|
|
|
queryStackNormal(name, query) {
|
|
|
|
const path = this.paths[name] || new SchemaType(name);
|
|
|
|
|
|
|
|
return data => path.match(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(name);
|
|
|
|
|
|
|
|
return data => path[qkey](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 (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 (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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets/Sets a path.
|
|
|
|
*
|
|
|
|
* @param {String} name
|
|
|
|
* @param {*} obj
|
|
|
|
* @return {SchemaType | undefined}
|
|
|
|
*/
|
|
|
|
path(name, obj) {
|
|
|
|
if (obj == null) {
|
|
|
|
return this.paths[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
let type;
|
|
|
|
let nested = false;
|
|
|
|
|
|
|
|
if (obj instanceof SchemaType) {
|
|
|
|
type = obj;
|
|
|
|
} else {
|
|
|
|
switch (typeof obj) {
|
|
|
|
case 'function':
|
|
|
|
type = getSchemaType(name, {type: obj});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'object':
|
|
|
|
if (obj.type) {
|
|
|
|
type = getSchemaType(name, obj);
|
|
|
|
} else if (Array.isArray(obj)) {
|
|
|
|
type = new Types.Array(name, {
|
|
|
|
child: obj.length ? getSchemaType(name, obj[0]) : new SchemaType(name)
|
|
|
|
});
|
|
|
|
} 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 = getProp(data, name);
|
|
|
|
const result = type.cast(value, data);
|
|
|
|
|
|
|
|
if (result !== undefined) {
|
|
|
|
setProp(data, name, result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
stacks.setter.push(data => {
|
|
|
|
const value = getProp(data, name);
|
|
|
|
const result = type.validate(value, data);
|
|
|
|
|
|
|
|
if (result !== undefined) {
|
|
|
|
setProp(data, name, result);
|
|
|
|
} else {
|
|
|
|
delProp(data, name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
stacks.import.push(data => {
|
|
|
|
const value = getProp(data, name);
|
|
|
|
const result = type.parse(value, data);
|
|
|
|
|
|
|
|
if (result !== undefined) {
|
|
|
|
setProp(data, name, result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
stacks.export.push(data => {
|
|
|
|
const value = getProp(data, name);
|
|
|
|
const result = type.value(value, data);
|
|
|
|
|
|
|
|
if (result !== undefined) {
|
|
|
|
setProp(data, name, result);
|
|
|
|
} else {
|
|
|
|
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 PopulationError('path is required');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!item.model) {
|
|
|
|
const path = paths[key];
|
|
|
|
const ref = path.child ? path.child.options.ref : path.options.ref;
|
|
|
|
|
|
|
|
if (!ref) {
|
|
|
|
throw new PopulationError('model is required');
|
|
|
|
}
|
|
|
|
|
|
|
|
item.model = ref;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Schema.prototype.Types = Types;
|
|
|
|
Schema.Types = Schema.prototype.Types;
|
|
|
|
|
|
|
|
module.exports = Schema;
|