UNPKG

mingo

Version:

MongoDB query language for in-memory objects

369 lines (368 loc) 11.1 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var core_exports = {}; __export(core_exports, { ComputeOptions: () => ComputeOptions, Context: () => Context, OperatorType: () => OperatorType, ProcessingMode: () => ProcessingMode, computeValue: () => computeValue, getOperator: () => getOperator, initOptions: () => initOptions, redact: () => redact, useOperators: () => useOperators }); module.exports = __toCommonJS(core_exports); var import_util = require("./util"); var ProcessingMode = /* @__PURE__ */ ((ProcessingMode2) => { ProcessingMode2[ProcessingMode2["CLONE_OFF"] = 0] = "CLONE_OFF"; ProcessingMode2[ProcessingMode2["CLONE_INPUT"] = 1] = "CLONE_INPUT"; ProcessingMode2[ProcessingMode2["CLONE_OUTPUT"] = 2] = "CLONE_OUTPUT"; ProcessingMode2[ProcessingMode2["CLONE_ALL"] = 3] = "CLONE_ALL"; return ProcessingMode2; })(ProcessingMode || {}); class ComputeOptions { #options; /** Reference to the root object when processing subgraphs of the object. */ #root; #local; constructor(options, root, local) { this.#options = options; this.update(root, local); } /** * Initialize new ComputeOptions. * @returns {ComputeOptions} */ static init(options, root, local) { return !(options instanceof ComputeOptions) ? new ComputeOptions(options, root, local) : new ComputeOptions(options.#options, options.root ?? root, { ...options.#local, ...local, variables: Object.assign( {}, options.#local?.variables, local?.variables ) }); } /** * Updates the internal state. * * @param root The new root context for this object. * @param local The new local state to merge into current if it exists. * @returns */ update(root, local) { this.#root = root; const variables = Object.assign( {}, this.#local?.variables, local?.variables ); if (Object.keys(variables).length) { this.#local = { ...local, variables }; } else { this.#local = local ?? {}; } return this; } getOptions() { return Object.freeze({ ...this.#options, context: Context.from(this.#options.context) }); } get root() { return this.#root; } get local() { return this.#local; } get idKey() { return this.#options.idKey; } get collation() { return this.#options?.collation; } get processingMode() { return this.#options?.processingMode || 0 /* CLONE_OFF */; } get useStrictMode() { return this.#options?.useStrictMode; } get scriptEnabled() { return this.#options?.scriptEnabled; } get useGlobalContext() { return this.#options?.useGlobalContext; } get hashFunction() { return this.#options?.hashFunction; } get collectionResolver() { return this.#options?.collectionResolver; } get jsonSchemaValidator() { return this.#options?.jsonSchemaValidator; } get variables() { return this.#options?.variables; } get context() { return this.#options?.context; } } function initOptions(options) { return options instanceof ComputeOptions ? options.getOptions() : Object.freeze({ idKey: "_id", scriptEnabled: true, useStrictMode: true, useGlobalContext: true, processingMode: 0 /* CLONE_OFF */, ...options, context: options?.context ? Context.from(options?.context) : Context.init() }); } var OperatorType = /* @__PURE__ */ ((OperatorType2) => { OperatorType2["ACCUMULATOR"] = "accumulator"; OperatorType2["EXPRESSION"] = "expression"; OperatorType2["PIPELINE"] = "pipeline"; OperatorType2["PROJECTION"] = "projection"; OperatorType2["QUERY"] = "query"; OperatorType2["WINDOW"] = "window"; return OperatorType2; })(OperatorType || {}); class Context { #operators = /* @__PURE__ */ new Map(); constructor() { } static init() { return new Context(); } static from(ctx) { const instance = Context.init(); if ((0, import_util.isNil)(ctx)) return instance; ctx.#operators.forEach((v, k) => instance.addOperators(k, v)); return instance; } addOperators(type, operators) { if (!this.#operators.has(type)) this.#operators.set(type, {}); for (const [name, fn] of Object.entries(operators)) { if (!this.getOperator(type, name)) { this.#operators.get(type)[name] = fn; } } return this; } getOperator(type, name) { const ops = this.#operators.get(type) ?? {}; return ops[name] ?? null; } addAccumulatorOps(ops) { return this.addOperators("accumulator", ops); } addExpressionOps(ops) { return this.addOperators("expression", ops); } addQueryOps(ops) { return this.addOperators("query", ops); } addPipelineOps(ops) { return this.addOperators("pipeline", ops); } addProjectionOps(ops) { return this.addOperators("projection", ops); } addWindowOps(ops) { return this.addOperators("window", ops); } } const GLOBAL_CONTEXT = Context.init(); function useOperators(type, operators) { for (const [name, fn] of Object.entries(operators)) { (0, import_util.assert)( (0, import_util.isFunction)(fn) && (0, import_util.isOperator)(name), `'${name}' is not a valid operator` ); const currentFn = getOperator(type, name, null); (0, import_util.assert)( !currentFn || fn === currentFn, `${name} already exists for '${type}' operators. Cannot change operator function once registered.` ); } switch (type) { case "accumulator": GLOBAL_CONTEXT.addAccumulatorOps(operators); break; case "expression": GLOBAL_CONTEXT.addExpressionOps(operators); break; case "pipeline": GLOBAL_CONTEXT.addPipelineOps(operators); break; case "projection": GLOBAL_CONTEXT.addProjectionOps(operators); break; case "query": GLOBAL_CONTEXT.addQueryOps(operators); break; case "window": GLOBAL_CONTEXT.addWindowOps(operators); break; } } function getOperator(type, name, options) { const { context: ctx, useGlobalContext: fallback } = options || {}; const fn = ctx ? ctx.getOperator(type, name) : null; return !fn && fallback ? GLOBAL_CONTEXT.getOperator(type, name) : fn; } function computeValue(obj, expr, operator, options) { const copts = ComputeOptions.init(options, obj); return !!operator && (0, import_util.isOperator)(operator) ? computeOperator(obj, expr, operator, copts) : computeExpression(obj, expr, copts); } const SYSTEM_VARS = ["$$ROOT", "$$CURRENT", "$$REMOVE", "$$NOW"]; function computeExpression(obj, expr, options) { if ((0, import_util.isString)(expr) && expr.length > 0 && expr[0] === "$") { if (REDACT_ACTIONS.includes(expr)) return expr; let ctx = options.root; const arr = expr.split("."); if (SYSTEM_VARS.includes(arr[0])) { switch (arr[0]) { case "$$ROOT": break; case "$$CURRENT": ctx = obj; break; case "$$REMOVE": ctx = void 0; break; case "$$NOW": ctx = /* @__PURE__ */ new Date(); break; } expr = expr.slice(arr[0].length + 1); } else if (arr[0].slice(0, 2) === "$$") { ctx = Object.assign( {}, // global vars options.variables, // current item is added before local variables because the binding may be changed. { this: obj }, // local vars options?.local?.variables ); const name = arr[0].slice(2); (0, import_util.assert)((0, import_util.has)(ctx, name), `Use of undefined variable: ${name}`); expr = expr.slice(2); } else { expr = expr.slice(1); } return expr === "" ? ctx : (0, import_util.resolve)(ctx, expr); } if ((0, import_util.isArray)(expr)) { return expr.map((item) => computeExpression(obj, item, options)); } if ((0, import_util.isObject)(expr)) { const result = {}; const elems = Object.entries(expr); for (const [key, val] of elems) { if ((0, import_util.isOperator)(key)) { (0, import_util.assert)(elems.length == 1, "expression must have single operator."); return computeOperator(obj, val, key, options); } result[key] = computeExpression(obj, val, options); } return result; } return expr; } function computeOperator(obj, expr, operator, options) { const callExpression = getOperator( "expression", operator, options ); if (callExpression) return callExpression(obj, expr, options); const callAccumulator = getOperator( "accumulator", operator, options ); (0, import_util.assert)(!!callAccumulator, `accumulator '${operator}' is not registered.`); if (!(0, import_util.isArray)(obj)) { obj = computeExpression(obj, expr, options); expr = null; } (0, import_util.assert)((0, import_util.isArray)(obj), `arguments must resolve to array for ${operator}.`); return callAccumulator( obj, expr, options.update(null, options.local) // reset the root object. ); } const REDACT_ACTIONS = ["$$KEEP", "$$PRUNE", "$$DESCEND"]; function redact(obj, expr, options) { const action = computeValue(obj, expr, null, options); switch (action) { case "$$KEEP": return obj; case "$$PRUNE": return void 0; case "$$DESCEND": { if (!(0, import_util.has)(expr, "$cond")) return obj; const output = {}; for (const [key, value] of Object.entries(obj)) { if ((0, import_util.isArray)(value)) { const res = new Array(); for (let elem of value) { if ((0, import_util.isObject)(elem)) { elem = redact(elem, expr, options.update(elem)); } if (!(0, import_util.isNil)(elem)) res.push(elem); } output[key] = res; } else if ((0, import_util.isObject)(value)) { const res = redact( value, expr, options.update(value) ); if (!(0, import_util.isNil)(res)) output[key] = res; } else { output[key] = value; } } return output; } default: return action; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ComputeOptions, Context, OperatorType, ProcessingMode, computeValue, getOperator, initOptions, redact, useOperators });