summaryrefslogtreecommitdiff
path: root/packages/idb-bridge/src/util/structuredClone.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/idb-bridge/src/util/structuredClone.ts')
-rw-r--r--packages/idb-bridge/src/util/structuredClone.ts251
1 files changed, 132 insertions, 119 deletions
diff --git a/packages/idb-bridge/src/util/structuredClone.ts b/packages/idb-bridge/src/util/structuredClone.ts
index c33dc5e36..2f857c6c5 100644
--- a/packages/idb-bridge/src/util/structuredClone.ts
+++ b/packages/idb-bridge/src/util/structuredClone.ts
@@ -14,6 +14,28 @@
permissions and limitations under the License.
*/
+/**
+ * Encoding (new, compositional version):
+ *
+ * Encapsulate object that itself might contain a "$" field:
+ * { $: "obj", val: ... }
+ * (Outer level only:) Wrap other values into object
+ * { $: "lit", val: ... }
+ * Circular reference:
+ * { $: "ref" l: uplevel, p: path }
+ * Date:
+ * { $: "date", val: datestr }
+ * Bigint:
+ * { $: "bigint", val: bigintstr }
+ * Array with special (non-number) attributes:
+ * { $: "array", val: arrayobj }
+ * Undefined field
+ * { $: "undef" }
+ */
+
+/**
+ * Imports.
+ */
import { DataCloneError } from "./errors.js";
const { toString: toStr } = {};
@@ -73,10 +95,6 @@ function isUserObject(val: any): boolean {
return hasConstructorOf(val, Object) || isUserObject(proto);
}
-function isRegExp(val: any): boolean {
- return toStringTag(val) === "RegExp";
-}
-
function copyBuffer(cur: any) {
if (cur instanceof Buffer) {
return Buffer.from(cur);
@@ -242,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);
@@ -270,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;
@@ -308,123 +324,114 @@ 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 internalStructuredRevive(val: any): any {
- val = JSON.parse(JSON.stringify(val));
- 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 structuredRevive(val: any): any {
- return internalStructuredRevive(val);
+/**
+ * Encapsulate a cloneable value into a plain JSON value.
+ */
+export function structuredEncapsulate(val: any): any {
+ return internalEncapsulate(val, [], new Map());
+}
+
+export function structuredRevive(sval: any): any {
+ return internalStructuredRevive(sval, undefined, []);
}
/**
* Structured clone for IndexedDB.
*/
export function structuredClone(val: any): any {
+ // @ts-ignore
+ if (globalThis._tart?.structuredClone) {
+ // @ts-ignore
+ return globalThis._tart?.structuredClone(val);
+ }
return mkDeepClone()(val);
}
@@ -432,5 +439,11 @@ export function structuredClone(val: any): any {
* Structured clone for IndexedDB.
*/
export function checkStructuredCloneOrThrow(val: any): void {
- return mkDeepCloneCheckOnly()(val);
+ // @ts-ignore
+ if (globalThis._tart?.structuredClone) {
+ // @ts-ignore
+ globalThis._tart?.structuredClone(val);
+ return;
+ }
+ mkDeepCloneCheckOnly()(val);
}