mongoku
Version:
[](https://github.com/huggingface/Mongoku/actions/workflows/ci.yml)
183 lines (180 loc) • 5.39 kB
JavaScript
import { parseScript } from 'esprima';
const ALLOWED_AGGREGATION_STAGES = /* @__PURE__ */ new Set([
"$addFields",
"$bucket",
"$bucketAuto",
"$collStats",
"$count",
"$densify",
"$documents",
"$facet",
"$fill",
"$geoNear",
"$graphLookup",
"$group",
"$indexStats",
"$limit",
"$listClusterCatalog",
"$listSampledQueries",
"$listSearchIndexes",
"$listSessions",
"$lookup",
"$match",
"$project",
"$querySettings",
"$queryStats",
"$rankFusion",
"$redact",
"$replaceRoot",
"$replaceWith",
"$sample",
"$search",
"$searchMeta",
"$set",
"$setWindowFields",
"$skip",
"$sort",
"$sortByCount",
"$unionWith",
"$unset",
"$unwind",
"$vectorSearch"
]);
function validateAggregationPipeline(pipeline) {
for (let i = 0; i < pipeline.length; i++) {
const stage = pipeline[i];
if (!stage || typeof stage !== "object") {
throw new Error(`Invalid stage at index ${i}: must be an object`);
}
const stageKeys = Object.keys(stage);
if (stageKeys.length !== 1) {
throw new Error(`Invalid stage at index ${i}: must have exactly one key, found ${stageKeys.length}`);
}
const stageOperator = stageKeys[0];
if (!ALLOWED_AGGREGATION_STAGES.has(stageOperator)) {
throw new Error(
`Disallowed stage operator at index ${i}: "${stageOperator}". Only allowed stages are permitted, to avoid future write stages like '$merge' or '$out'.`
);
}
}
}
function isEmptyObject(obj) {
for (const key in obj) {
return false;
}
return true;
}
function buildObject(node) {
switch (node.type) {
case "ObjectExpression": {
const obj = {};
for (const prop of node.properties) {
let name;
if (prop.type === "SpreadElement") {
throw new Error(`Expected "Property" but received: ${prop.type}`);
}
if (prop.key.type === "Identifier") {
name = prop.key.name;
} else if (prop.key.type === "Literal") {
if (prop.key.value instanceof RegExp || typeof prop.key.value === "bigint" || prop.key.value === false || prop.key.value === null || prop.key.value === true || prop.key.value === void 0) {
throw new Error(`Expected "Identifier" for object key but received: ${prop.key.type}`);
}
name = prop.key.value;
} else {
throw new Error(`Expected "Identifier" but received: ${prop.key.type}`);
}
obj[name] = buildObject(prop.value);
}
return obj;
}
case "ArrayExpression": {
const obj = [];
for (const prop of node.elements) {
if (prop === null) {
throw new Error(`Expected "Expression" but received: ${prop}`);
}
obj.push(buildObject(prop));
}
return obj;
}
case "Literal": {
if (node.value instanceof RegExp) {
return {
$type: "RegExp",
$value: {
$pattern: node.value.source,
$flags: node.value.flags
}
};
}
return node.value;
}
case "UnaryExpression": {
if (node.operator === "-" && node.argument.type === "Literal" && typeof node.argument.value === "number") {
return -node.argument.value;
}
throw new Error(`${node.type} are not authorized`);
}
case "NewExpression":
case "CallExpression": {
const authorizedCalls = ["ObjectId", "Date", "RegExp", "BinData"];
const callee = node.callee.type === "Identifier" ? node.callee.name : null;
if (callee && authorizedCalls.includes(callee)) {
if (callee === "RegExp") {
const [pattern, flags] = node.arguments.map((arg) => buildObject(arg));
return {
$type: "RegExp",
$value: {
$pattern: pattern,
$flags: flags
}
};
}
if (callee === "BinData") {
const [subType, base64] = node.arguments.map((arg) => buildObject(arg));
return {
$type: "Binary",
$value: base64,
$subType: subType
};
}
return {
$type: callee,
$value: buildObject(node.arguments[0])
};
} else {
throw new Error(`Unknown ${node.type}: ${callee}`);
}
}
case "Identifier": {
if (node.name === "undefined") {
return void 0;
}
if (node.name === "Infinity") {
return Infinity;
}
throw `Unknown identifier: ${node.name}`;
}
default:
throw new Error(`Sorry but ${node.type} are not authorized`);
}
}
function parseJSON(text, opts) {
const tree = parseScript(`var __JSON__ = ${text};`, {
tolerant: true
});
const varDeclaration = tree.body[0];
if (varDeclaration.type !== "VariableDeclaration") {
throw new Error("Expected VariableDeclaration but received: " + varDeclaration.type);
}
const objExpression = varDeclaration.declarations[0].init;
if (opts?.allowArray && objExpression?.type === "ArrayExpression") {
return buildObject(objExpression);
}
if (objExpression?.type !== "ObjectExpression") {
throw new Error("Expected ObjectExpression but received: " + objExpression?.type);
}
return buildObject(objExpression);
}
export { isEmptyObject as i, parseJSON as p, validateAggregationPipeline as v };
//# sourceMappingURL=jsonParser-C3QUcODD.js.map