mingo
Version:
MongoDB query language for in-memory objects
385 lines (384 loc) • 11.4 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 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["CLONE_ALL"] = "CLONE_ALL";
ProcessingMode2["CLONE_INPUT"] = "CLONE_INPUT";
ProcessingMode2["CLONE_OUTPUT"] = "CLONE_OUTPUT";
ProcessingMode2["CLONE_OFF"] = "CLONE_OFF";
return ProcessingMode2;
})(ProcessingMode || {});
class ComputeOptions {
constructor(_opts, _root, _local, timestamp = Date.now()) {
this._opts = _opts;
this._root = _root;
this._local = _local;
this.timestamp = timestamp;
this.update(_root, _local);
}
/**
* Initialize new ComputeOptions.
*
* @param options
* @param root
* @param local
* @returns {ComputeOptions}
*/
static init(options, root, local) {
return options instanceof ComputeOptions ? new ComputeOptions(
options._opts,
(0, import_util.isNil)(options.root) ? root : options.root,
Object.assign({}, options.local, local)
) : new ComputeOptions(options, root, local);
}
/** Updates the internal mutable state. */
update(root, local) {
this._root = root;
this._local = local ? Object.assign({}, local, {
variables: Object.assign({}, this._local?.variables, local?.variables)
}) : local;
return this;
}
getOptions() {
return Object.freeze({
...this._opts,
context: Context.from(this._opts.context)
});
}
get root() {
return this._root;
}
get local() {
return this._local;
}
get idKey() {
return this._opts.idKey;
}
get collation() {
return this._opts?.collation;
}
get processingMode() {
return this._opts?.processingMode || "CLONE_OFF" /* CLONE_OFF */;
}
get useStrictMode() {
return this._opts?.useStrictMode;
}
get scriptEnabled() {
return this._opts?.scriptEnabled;
}
get useGlobalContext() {
return this._opts?.useGlobalContext;
}
get hashFunction() {
return this._opts?.hashFunction;
}
get collectionResolver() {
return this._opts?.collectionResolver;
}
get jsonSchemaValidator() {
return this._opts?.jsonSchemaValidator;
}
get variables() {
return this._opts?.variables;
}
get context() {
return this._opts?.context;
}
}
function initOptions(options) {
return options instanceof ComputeOptions ? options.getOptions() : Object.freeze({
idKey: "_id",
scriptEnabled: true,
useStrictMode: true,
useGlobalContext: true,
processingMode: "CLONE_OFF" /* 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 {
constructor(ops) {
this.operators = {
["accumulator" /* ACCUMULATOR */]: {},
["expression" /* EXPRESSION */]: {},
["pipeline" /* PIPELINE */]: {},
["projection" /* PROJECTION */]: {},
["query" /* QUERY */]: {},
["window" /* WINDOW */]: {}
};
for (const [type, operators] of Object.entries(ops)) {
this.addOperators(type, operators);
}
}
static init(ops = {}) {
return new Context(ops);
}
static from(ctx) {
return new Context(ctx.operators);
}
addOperators(type, ops) {
for (const [name, fn] of Object.entries(ops)) {
if (!this.getOperator(type, name)) {
this.operators[type][name] = fn;
}
}
return this;
}
// register
addAccumulatorOps(ops) {
return this.addOperators("accumulator" /* ACCUMULATOR */, ops);
}
addExpressionOps(ops) {
return this.addOperators("expression" /* EXPRESSION */, ops);
}
addQueryOps(ops) {
return this.addOperators("query" /* QUERY */, ops);
}
addPipelineOps(ops) {
return this.addOperators("pipeline" /* PIPELINE */, ops);
}
addProjectionOps(ops) {
return this.addOperators("projection" /* PROJECTION */, ops);
}
addWindowOps(ops) {
return this.addOperators("window" /* WINDOW */, ops);
}
// getters
getOperator(type, name) {
return type in this.operators ? this.operators[type][name] || null : null;
}
}
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" /* ACCUMULATOR */:
GLOBAL_CONTEXT.addAccumulatorOps(operators);
break;
case "expression" /* EXPRESSION */:
GLOBAL_CONTEXT.addExpressionOps(operators);
break;
case "pipeline" /* PIPELINE */:
GLOBAL_CONTEXT.addPipelineOps(operators);
break;
case "projection" /* PROJECTION */:
GLOBAL_CONTEXT.addProjectionOps(operators);
break;
case "query" /* QUERY */:
GLOBAL_CONTEXT.addQueryOps(operators);
break;
case "window" /* WINDOW */:
GLOBAL_CONTEXT.addWindowOps(operators);
break;
}
}
function getOperator(type, operator, options) {
const { context: ctx, useGlobalContext: fallback } = options || {};
const fn = ctx ? ctx.getOperator(type, operator) : null;
return !fn && fallback ? GLOBAL_CONTEXT.getOperator(type, operator) : fn;
}
const systemVariables = {
$$ROOT(_obj, _expr, options) {
return options.root;
},
$$CURRENT(obj, _expr, _options) {
return obj;
},
$$REMOVE(_obj, _expr, _options) {
return void 0;
},
$$NOW(_obj, _expr, options) {
return new Date(options.timestamp);
}
};
const redactVariables = {
$$KEEP(obj, _expr, _options) {
return obj;
},
$$PRUNE(_obj, _expr, _options) {
return void 0;
},
$$DESCEND(obj, expr, options) {
if (!(0, import_util.has)(expr, "$cond"))
return obj;
let result;
for (const [key, current] of Object.entries(obj)) {
if ((0, import_util.isObjectLike)(current)) {
if (current instanceof Array) {
const array = [];
for (let elem of current) {
if ((0, import_util.isObject)(elem)) {
elem = redact(elem, expr, options.update(elem));
}
if (!(0, import_util.isNil)(elem)) {
array.push(elem);
}
}
result = array;
} else {
result = redact(
current,
expr,
options.update(current)
);
}
if ((0, import_util.isNil)(result)) {
delete obj[key];
} else {
obj[key] = result;
}
}
}
return obj;
}
};
function computeValue(obj, expr, operator, options) {
const copts = ComputeOptions.init(options, obj);
operator = operator || "";
if ((0, import_util.isOperator)(operator)) {
const callExpression = getOperator(
"expression" /* EXPRESSION */,
operator,
options
);
if (callExpression)
return callExpression(obj, expr, copts);
const callAccumulator = getOperator(
"accumulator" /* ACCUMULATOR */,
operator,
options
);
if (callAccumulator) {
if (!(obj instanceof Array)) {
obj = computeValue(obj, expr, null, copts);
expr = null;
}
(0, import_util.assert)(obj instanceof Array, `'${operator}' target must be an array.`);
return callAccumulator(
obj,
expr,
// reset the root object for accumulators.
copts.update(null, copts.local)
);
}
throw new import_util.MingoError(`operator '${operator}' is not registered`);
}
if ((0, import_util.isString)(expr) && expr.length > 0 && expr[0] === "$") {
if ((0, import_util.has)(redactVariables, expr)) {
return expr;
}
let context = copts.root;
const arr = expr.split(".");
if ((0, import_util.has)(systemVariables, arr[0])) {
context = systemVariables[arr[0]](
obj,
null,
copts
);
expr = expr.slice(arr[0].length + 1);
} else if (arr[0].slice(0, 2) === "$$") {
context = Object.assign(
{},
copts.variables,
// global vars
// current item is added before local variables because the binding may be changed.
{ this: obj },
copts.local?.variables
// local vars
);
const prefix = arr[0].slice(2);
(0, import_util.assert)(
(0, import_util.has)(context, prefix),
`Use of undefined variable: ${prefix}`
);
expr = expr.slice(2);
} else {
expr = expr.slice(1);
}
if (expr === "")
return context;
return (0, import_util.resolve)(context, expr);
}
if ((0, import_util.isArray)(expr)) {
return expr.map((item) => computeValue(obj, item, null, copts));
} else if ((0, import_util.isObject)(expr)) {
const result = {};
for (const [key, val] of Object.entries(expr)) {
result[key] = computeValue(obj, val, key, copts);
if (["expression" /* EXPRESSION */, "accumulator" /* ACCUMULATOR */].some(
(t) => !!getOperator(t, key, options)
)) {
(0, import_util.assert)(
Object.keys(expr).length === 1,
"Invalid aggregation expression '" + JSON.stringify(expr) + "'"
);
return result[key];
}
}
return result;
}
return expr;
}
function redact(obj, expr, options) {
const result = computeValue(obj, expr, null, options);
return (0, import_util.has)(redactVariables, result) ? redactVariables[result](obj, expr, options) : result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ComputeOptions,
Context,
OperatorType,
ProcessingMode,
computeValue,
getOperator,
initOptions,
redact,
useOperators
});