diff options
author | Florian Dold <florian@dold.me> | 2023-07-11 15:41:48 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-08-22 08:01:13 +0200 |
commit | b2d0ad57ddf251a109d536cdc49fb6505dbdc50c (patch) | |
tree | 7eaeca3ad8ec97a9c1970c1004feda2d61c3441b /packages/idb-bridge/src/util/structuredClone.ts | |
parent | 58fdf9dc091b076787a9746c405fe6a9366f5da6 (diff) | |
download | wallet-core-b2d0ad57ddf251a109d536cdc49fb6505dbdc50c.tar.xz |
sqlite3 backend for idb-bridge / wallet-core
Diffstat (limited to 'packages/idb-bridge/src/util/structuredClone.ts')
-rw-r--r-- | packages/idb-bridge/src/util/structuredClone.ts | 231 |
1 files changed, 104 insertions, 127 deletions
diff --git a/packages/idb-bridge/src/util/structuredClone.ts b/packages/idb-bridge/src/util/structuredClone.ts index 2170118d5..2f857c6c5 100644 --- a/packages/idb-bridge/src/util/structuredClone.ts +++ b/packages/idb-bridge/src/util/structuredClone.ts @@ -16,22 +16,21 @@ /** * Encoding (new, compositional version): - * + * * Encapsulate object that itself might contain a "$" field: - * { $: { E... } } + * { $: "obj", val: ... } + * (Outer level only:) Wrap other values into object + * { $: "lit", val: ... } * Circular reference: - * { $: ["ref", uplevel, field...] } + * { $: "ref" l: uplevel, p: path } * Date: - * { $: ["data"], val: datestr } + * { $: "date", val: datestr } * Bigint: - * { $: ["bigint"], val: bigintstr } + * { $: "bigint", val: bigintstr } * Array with special (non-number) attributes: - * { $: ["array"], val: arrayobj } + * { $: "array", val: arrayobj } * Undefined field * { $: "undef" } - * - * Legacy (top-level only), for backwards compatibility: - * { $types: [...] } */ /** @@ -261,22 +260,18 @@ export function mkDeepCloneCheckOnly() { function internalEncapsulate( val: any, - outRoot: any, path: string[], memo: Map<any, string[]>, - types: Array<[string[], string]>, ): any { const memoPath = memo.get(val); if (memoPath) { - types.push([path, "ref"]); - return memoPath; + return { $: "ref", d: path.length, p: memoPath }; } if (val === null) { return null; } if (val === undefined) { - types.push([path, "undef"]); - return 0; + return { $: "undef" }; } if (Array.isArray(val)) { memo.set(val, path); @@ -289,31 +284,33 @@ function internalEncapsulate( break; } } - if (special) { - types.push([path, "array"]); - } for (const x in val) { const p = [...path, x]; - outArr[x] = internalEncapsulate(val[x], outRoot, p, memo, types); + outArr[x] = internalEncapsulate(val[x], p, memo); + } + if (special) { + return { $: "array", val: outArr }; + } else { + return outArr; } - return outArr; } if (val instanceof Date) { - types.push([path, "date"]); - return val.getTime(); + return { $: "date", val: val.getTime() }; } if (isUserObject(val) || isPlainObject(val)) { memo.set(val, path); const outObj: any = {}; for (const x in val) { const p = [...path, x]; - outObj[x] = internalEncapsulate(val[x], outRoot, p, memo, types); + outObj[x] = internalEncapsulate(val[x], p, memo); + } + if ("$" in outObj) { + return { $: "obj", val: outObj }; } return outObj; } if (typeof val === "bigint") { - types.push([path, "bigint"]); - return val.toString(); + return { $: "bigint", val: val.toString() }; } if (typeof val === "boolean") { return val; @@ -327,123 +324,103 @@ function internalEncapsulate( throw Error(); } -/** - * Encapsulate a cloneable value into a plain JSON object. - */ -export function structuredEncapsulate(val: any): any { - const outRoot = {}; - const types: Array<[string[], string]> = []; - let res; - res = internalEncapsulate(val, outRoot, [], new Map(), types); - if (res === null) { - return res; - } - // We need to further encapsulate the outer layer - if ( - Array.isArray(res) || - typeof res !== "object" || - "$" in res || - "$types" in res - ) { - res = { $: res }; - } - if (types.length > 0) { - res["$types"] = types; - } - return res; +function derefPath( + root: any, + p1: Array<string | number>, + n: number, + p2: Array<string | number>, +): any { + let v = root; + for (let i = 0; i < n; i++) { + v = v[p1[i]]; + } + for (let i = 0; i < p2.length; i++) { + v = v[p2[i]]; + } + return v; } -export function applyLegacyTypeAnnotations(val: any): any { - if (val === null) { - return null; +function internalReviveArray(sval: any, root: any, path: string[]): any { + const newArr: any[] = []; + if (root === undefined) { + root = newArr; } - if (typeof val === "number") { - return val; + for (let i = 0; i < sval.length; i++) { + const p = [...path, String(i)]; + newArr.push(internalStructuredRevive(sval[i], root, p)); } - if (typeof val === "string") { - return val; + return newArr; +} + +function internalReviveObject(sval: any, root: any, path: string[]): any { + const newObj = {} as any; + if (root === undefined) { + root = newObj; } - if (typeof val === "boolean") { - return val; + for (const key of Object.keys(sval)) { + const p = [...path, key]; + newObj[key] = internalStructuredRevive(sval[key], root, p); } - if (!isPlainObject(val)) { - throw Error(); - } - let types = val.$types ?? []; - delete val.$types; - let outRoot: any; - if ("$" in val) { - outRoot = val.$; - } else { - outRoot = val; - } - function mutatePath(path: string[], f: (x: any) => any): void { - if (path.length == 0) { - outRoot = f(outRoot); - return; - } - let obj = outRoot; - for (let i = 0; i < path.length - 1; i++) { - const n = path[i]; - if (!(n in obj)) { - obj[n] = {}; - } - obj = obj[n]; - } - const last = path[path.length - 1]; - obj[last] = f(obj[last]); + return newObj; +} + +function internalStructuredRevive(sval: any, root: any, path: string[]): any { + if (typeof sval === "string") { + return sval; } - function lookupPath(path: string[]): any { - let obj = outRoot; - for (const n of path) { - obj = obj[n]; - } - return obj; + if (typeof sval === "number") { + return sval; } - for (const [path, type] of types) { - switch (type) { - case "bigint": { - mutatePath(path, (x) => BigInt(x)); - break; - } - case "array": { - mutatePath(path, (x) => { - const newArr: any = []; - for (const k in x) { - newArr[k] = x[k]; - } - return newArr; - }); - break; - } - case "date": { - mutatePath(path, (x) => new Date(x)); - break; - } - case "undef": { - mutatePath(path, (x) => undefined); - break; - } - case "ref": { - mutatePath(path, (x) => lookupPath(x)); - break; + if (typeof sval === "boolean") { + return sval; + } + if (sval === null) { + return null; + } + if (Array.isArray(sval)) { + return internalReviveArray(sval, root, path); + } + + if (isUserObject(sval) || isPlainObject(sval)) { + if ("$" in sval) { + const dollar = sval.$; + switch (dollar) { + case "undef": + return undefined; + case "bigint": + return BigInt((sval as any).val); + case "date": + return new Date((sval as any).val); + case "obj": { + return internalReviveObject((sval as any).val, root, path); + } + case "array": + return internalReviveArray((sval as any).val, root, path); + case "ref": { + const level = (sval as any).l; + const p2 = (sval as any).p; + return derefPath(root, path, path.length - level, p2); + } + default: + throw Error(); } - default: - throw Error(`type '${type}' not implemented`); + } else { + return internalReviveObject(sval, root, path); } } - return outRoot; + + throw Error(); } -export function internalStructuredRevive(val: any): any { - // FIXME: Do the newly specified, compositional encoding here. - val = JSON.parse(JSON.stringify(val)); - return val; +/** + * Encapsulate a cloneable value into a plain JSON value. + */ +export function structuredEncapsulate(val: any): any { + return internalEncapsulate(val, [], new Map()); } -export function structuredRevive(val: any): any { - const r = internalStructuredRevive(val); - return applyLegacyTypeAnnotations(r); +export function structuredRevive(sval: any): any { + return internalStructuredRevive(sval, undefined, []); } /** |