import { BSON } from "https://esm.sh/bson@6.2.0"; // BSON doesn't support top-level primitives, so we need to wrap them in an object const topLevelValueKey = "$_tl"; // BSON doesn't support undefined, so we need to encode it as a "magic" string const undefinedPlaceHolder = "$_undefined_$"; /** * BSON encoder, but also supporting "edge cases" like encoding strings, numbers, etc. * @param obj * @returns */ export function encodeBSON(obj: any): Uint8Array { if ( obj === undefined || obj === null || !(typeof obj === "object" && obj.constructor === Object) ) { obj = { [topLevelValueKey]: obj }; } obj = traverseAndRewriteJSON(obj, (val) => { if (val === undefined) { return undefinedPlaceHolder; } return val; }); return BSON.serialize(obj); } export function decodeBSON(data: Uint8Array): any { let result = BSON.deserialize(data); // For whatever reason the BSON library doesn't unwrap binary blobs automatically result = traverseAndRewriteJSON(result, (val) => { if (typeof val?.value === "function") { return val.value(); } else if (val === undefinedPlaceHolder) { return undefined; } return val; }); if (Object.hasOwn(result, topLevelValueKey)) { return result[topLevelValueKey]; } else { return result; } } /** * Traverses and rewrites an object recursively. * * @param obj - The object to traverse and rewrite. * @param rewrite - The function to apply for rewriting each value. * @returns The rewritten object. */ export function traverseAndRewriteJSON( obj: any, rewrite: (val: any) => any, ): any { // Apply rewrite to object as a whole obj = rewrite(obj); // Recurse down if this is an array or a "plain object" if ( obj && (Array.isArray(obj) || (typeof obj === "object" && obj.constructor === Object)) ) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { const key = keys[i]; obj[key] = traverseAndRewriteJSON(obj[key], rewrite); } } return obj; }