UNPKG

@drip_sync/drip

Version:

Scalable incremental sync for MongoDB aggregation pipelines

110 lines (109 loc) 4.25 kB
"use strict"; // The "query" here refers to the MQL. // See https://www.mongodb.com/docs/manual/reference/operator/query/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.scopeQueryClause = scopeQueryClause; exports.scopeQueryPredicate = scopeQueryPredicate; const zod_1 = __importDefault(require("zod")); const atoms_1 = require("./atoms"); const invalid_expression_1 = require("./invalid_expression"); const scope_expression_1 = require("./scope_expression"); function scopeQueryClause(c, root) { const res = {}; for (const [k, v] of Object.entries(c)) { if (!k.startsWith("$")) { // field name - scope res[`${root}.${k}`] = scopeQueryPredicate(v, root); } else { // a query selector switch (k) { case "$and": case "$nor": case "$or": { if (!Array.isArray(v)) { throw new invalid_expression_1.InvalidExpression(`${k} argument must be an array`); } const vParsed = zod_1.default .array(zod_1.default.record(zod_1.default.string(), zod_1.default.unknown())) .safeParse(v); if (!vParsed.success) { throw new invalid_expression_1.InvalidExpression(`${k} argument's entries must be objects`); } res[k] = vParsed.data.map((c) => scopeQueryClause(c, root)); break; } case "$expr": res[k] = (0, scope_expression_1.scopeExpression)(v, root, {}); break; default: throw new invalid_expression_1.InvalidExpression(`unknown top level operator: ${k}. If you have a field name that starts with a '$' symbol, consider using $getField or $setField.`); } } } return res; } // TODO: how about mongo-recognized classes like // regex, uuid, timestamp, ... // they'll appear as objects, but must not be recursed into function scopeQueryPredicate(p, root) { if (!(0, atoms_1.isComposite)(p) || Array.isArray(p)) { // shorthand notation for equality/regex match // can pass as-is return p; } // a query selector/object equality // We don't really need to change them, but // we do need to validate them. const res = {}; for (const [k, v] of Object.entries(p)) { if (!k.startsWith("$")) { // No need to scope the key - scopeQueryClause already did it res[k] = scopeQueryPredicate(v, root); } else { // an operator - validate switch (k) { case "$eq": case "$gt": case "$gte": case "$in": case "$lt": case "$lte": case "$ne": case "$nin": case "$not": case "$exists": case "$type": case "$mod": case "$regex": case "$geoIntersects": case "$geoWithin": case "$near": case "$nearSphere": case "$all": case "$size": case "$bitsAllClear": case "$bitsAllSet": case "$bitsAnyClear": case "$bitsAnySet": // can pass as-is res[k] = v; break; case "$elemMatch": if (!(0, scope_expression_1.isObjectExpression)(v)) { throw new invalid_expression_1.InvalidExpression("$elemMatch needs an Object"); } // must recurse res[k] = scopeQueryPredicate(v, root); break; default: throw new invalid_expression_1.InvalidExpression(`unknown operator: ${k}`); } } } return res; }