mongoku
Version:
[](https://github.com/huggingface/Mongoku/actions/workflows/ci.yml)
208 lines (181 loc) • 5.37 kB
text/typescript
import { parseScript } from "esprima";
import type { Expression, Node } from "estree";
function buildObject(node: Node | Expression): unknown {
switch (node.type) {
case "ObjectExpression": {
const obj: Record<string, unknown> = {};
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 === undefined
) {
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: unknown[] = [];
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;
}
// const arg = buildObject(node.argument);
// const exp = node.prefix ? `${node.operator}${arg}` : `${arg}${node.operator}`;
// return eval(exp);
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") {
// BinData(subType, base64String)
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 undefined;
}
if (node.name === "Infinity") {
return Infinity;
}
throw `Unknown identifier: ${node.name}`;
}
default:
throw new Error(`Sorry but ${node.type} are not authorized`);
}
}
export function parseJSON(text: string, opts?: { allowArray?: boolean }): unknown {
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);
}
/**
* Serializes an object to a string format that can be parsed back with parseJSON.
* Handles MongoDB special types like ObjectId, Date, and RegExp.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function serializeForEditing(obj: any, depth = 0): string {
const indent = "\t".repeat(depth);
const nextIndent = "\t".repeat(depth + 1);
if (obj === null) {
return "null";
}
if (obj === undefined) {
return "undefined";
}
if (typeof obj === "string") {
return JSON.stringify(obj);
}
if (typeof obj === "number") {
return obj.toString();
}
if (typeof obj === "boolean") {
return obj.toString();
}
if (Array.isArray(obj)) {
if (obj.length === 0) {
return "[]";
}
const items = obj.map((item) => `${nextIndent}${serializeForEditing(item, depth + 1)}`).join(",\n");
return `[\n${items}\n${indent}]`;
}
if (typeof obj === "object") {
// Handle special MongoDB types
if (obj.$type === "ObjectId") {
return `new ObjectId("${obj.$value}")`;
}
if (obj.$type === "Date") {
return `new Date("${obj.$value}")`;
}
if (obj.$type === "RegExp") {
return `new RegExp("${obj.$value.$pattern}", "${obj.$value.$flags}")`;
}
// Handle regular objects
const keys = Object.keys(obj);
if (keys.length === 0) {
return "{}";
}
const pairs = keys
.map((key) => {
const value = serializeForEditing(obj[key], depth + 1);
return `${nextIndent}${JSON.stringify(key)}: ${value}`;
})
.join(",\n");
return `{\n${pairs}\n${indent}}`;
}
return JSON.stringify(obj);
}