warehouse
Version:
Simple JSON-based database
698 lines • 20.9 kB
JavaScript
"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