mingo
Version:
MongoDB query language for in-memory objects
264 lines (263 loc) • 8.8 kB
JavaScript
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 internal_exports = {};
__export(internal_exports, {
ComputeOptions: () => ComputeOptions,
Context: () => Context,
OpType: () => OpType,
ProcessingMode: () => ProcessingMode,
computeValue: () => computeValue,
evalExpr: () => evalExpr
});
module.exports = __toCommonJS(internal_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 {
constructor(options, locals) {
this.options = options;
this.#locals = locals ? { ...locals } : {};
}
#locals;
/**
* Initializes a new instance of the `ComputeOptions` class with the provided options.
*
* @param options - A partial set of options to configure the `ComputeOptions` instance.
* If an instance of `ComputeOptions` is provided, its internal options and locals are used.
* @returns A new `ComputeOptions` instance configured with the provided options and root.
*/
static init(options) {
return options instanceof ComputeOptions ? new ComputeOptions(options.options, options.#locals) : new ComputeOptions({
idKey: "_id",
scriptEnabled: true,
useStrictMode: true,
failOnError: true,
processingMode: 0 /* CLONE_OFF */,
...options,
context: options?.context ? Context.from(options?.context) : Context.init()
});
}
update(locals) {
Object.assign(this.#locals, locals, {
// DO NOT override timestamp
timestamp: this.#locals.timestamp,
// merge variables.
variables: { ...this.#locals?.variables, ...locals?.variables }
});
return this;
}
get local() {
return this.#locals;
}
get now() {
let timestamp = this.#locals.timestamp ?? 0;
if (!timestamp) {
timestamp = Date.now();
Object.assign(this.#locals, { timestamp });
}
return new Date(timestamp);
}
get idKey() {
return this.options.idKey;
}
get collation() {
return this.options?.collation;
}
get processingMode() {
return this.options?.processingMode;
}
get useStrictMode() {
return this.options?.useStrictMode;
}
get scriptEnabled() {
return this.options?.scriptEnabled;
}
get failOnError() {
return this.options?.failOnError;
}
get collectionResolver() {
return this.options?.collectionResolver;
}
get jsonSchemaValidator() {
return this.options?.jsonSchemaValidator;
}
get variables() {
return this.options?.variables;
}
get context() {
return this.options?.context;
}
}
var OpType = /* @__PURE__ */ ((OpType2) => {
OpType2["ACCUMULATOR"] = "accumulator";
OpType2["EXPRESSION"] = "expression";
OpType2["PIPELINE"] = "pipeline";
OpType2["PROJECTION"] = "projection";
OpType2["QUERY"] = "query";
OpType2["WINDOW"] = "window";
return OpType2;
})(OpType || {});
class Context {
#operators;
constructor() {
this.#operators = {
["accumulator" /* ACCUMULATOR */]: {},
["expression" /* EXPRESSION */]: {},
["pipeline" /* PIPELINE */]: {},
["projection" /* PROJECTION */]: {},
["query" /* QUERY */]: {},
["window" /* WINDOW */]: {}
};
}
static init(ops = {}) {
const ctx = new Context();
for (const type of Object.keys(ops)) {
ctx.#operators[type] = { ...ops[type] };
}
return ctx;
}
/** Returns a new context with the operators from the provided contexts merged left to right. */
static from(...ctx) {
if (ctx.length === 1) return Context.init(ctx[0].#operators);
const newCtx = new Context();
for (const context of ctx) {
for (const type of Object.values(OpType)) {
newCtx.addOps(type, context.#operators[type]);
}
}
return newCtx;
}
addOps(type, operators) {
this.#operators[type] = Object.assign({}, operators, this.#operators[type]);
return this;
}
getOperator(type, name) {
return this.#operators[type][name] ?? null;
}
addAccumulatorOps(ops) {
return this.addOps("accumulator" /* ACCUMULATOR */, ops);
}
addExpressionOps(ops) {
return this.addOps("expression" /* EXPRESSION */, ops);
}
addQueryOps(ops) {
return this.addOps("query" /* QUERY */, ops);
}
addPipelineOps(ops) {
return this.addOps("pipeline" /* PIPELINE */, ops);
}
addProjectionOps(ops) {
return this.addOps("projection" /* PROJECTION */, ops);
}
addWindowOps(ops) {
return this.addOps("window" /* WINDOW */, ops);
}
}
function computeValue(obj, expr, operator, options) {
return evalExpr(obj, { [operator]: expr }, options);
}
function evalExpr(obj, expr, options) {
const copts = !(options instanceof ComputeOptions) || (0, import_util.isNil)(options.local.root) ? ComputeOptions.init(options).update({ root: obj }) : options;
return computeExpression(obj, expr, copts);
}
const SYSTEM_VARS = /* @__PURE__ */ new Set(["$$ROOT", "$$CURRENT", "$$REMOVE", "$$NOW"]);
function computeExpression(obj, expr, options) {
if ((0, import_util.isString)(expr) && expr.length > 0 && expr[0] === "$") {
if (expr === "$$KEEP" || expr === "$$PRUNE" || expr === "$$DESCEND")
return expr;
let ctx = options.local.root;
const arr = expr.split(".");
const dotIdx = expr.indexOf(".");
const prefix = dotIdx === -1 ? expr : expr.substring(0, dotIdx);
if (SYSTEM_VARS.has(arr[0])) {
switch (prefix) {
case "$$ROOT":
break;
case "$$CURRENT":
ctx = obj;
break;
case "$$REMOVE":
ctx = void 0;
break;
case "$$NOW":
ctx = new Date(options.now);
break;
}
expr = dotIdx === -1 ? "" : expr.substring(dotIdx + 1);
} else if (prefix.length >= 2 && prefix[1] === "$") {
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 = prefix.substring(2);
(0, import_util.assert)((0, import_util.has)(ctx, name), `Use of undefined variable: ${name}`);
expr = expr.substring(2);
} else {
expr = expr.substring(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 keys = Object.keys(expr);
if ((0, import_util.isOperator)(keys[0])) {
(0, import_util.assert)(keys.length === 1, "Expression must contain a single operator.");
return computeOperator(obj, expr[keys[0]], keys[0], options);
}
const result = {};
for (let i = 0; i < keys.length; i++) {
result[keys[i]] = computeExpression(obj, expr[keys[i]], options);
}
return result;
}
return expr;
}
function computeOperator(obj, expr, operator, options) {
const context = options.context;
const fn = context.getOperator("expression" /* EXPRESSION */, operator);
if (fn) return fn(obj, expr, options);
const accFn = context.getOperator("accumulator" /* ACCUMULATOR */, operator);
(0, import_util.assert)(!!accFn, `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 accFn(obj, expr, options);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ComputeOptions,
Context,
OpType,
ProcessingMode,
computeValue,
evalExpr
});