idb: custom structured clone, don't rely on typeson anymore
This commit is contained in:
parent
627ae6958e
commit
3a336799a0
@ -28,9 +28,7 @@
|
|||||||
"typescript": "^4.1.3"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0",
|
"tslib": "^2.1.0"
|
||||||
"typeson": "^5.18.2",
|
|
||||||
"typeson-registry": "^1.0.0-alpha.38"
|
|
||||||
},
|
},
|
||||||
"ava": {
|
"ava": {
|
||||||
"require": [
|
"require": [
|
||||||
|
@ -115,3 +115,5 @@ export function shimIndexedDB(factory: BridgeIDBFactory): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from "./idbtypes";
|
export * from "./idbtypes";
|
||||||
|
|
||||||
|
export * from "./util/structuredClone";
|
@ -18,15 +18,238 @@
|
|||||||
import Typeson from "typeson";
|
import Typeson from "typeson";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import structuredCloningThrowing from "typeson-registry/dist/presets/structured-cloning-throwing";
|
import structuredCloningThrowing from "typeson-registry/dist/presets/structured-cloning-throwing";
|
||||||
|
import { DataCloneError } from "./errors";
|
||||||
|
|
||||||
const TSON = new Typeson().register(structuredCloningThrowing);
|
const TSON = new Typeson().register(structuredCloningThrowing);
|
||||||
|
|
||||||
|
const { toString: toStr } = {};
|
||||||
|
const hasOwn = {}.hasOwnProperty;
|
||||||
|
const getProto = Object.getPrototypeOf;
|
||||||
|
const fnToString = hasOwn.toString;
|
||||||
|
|
||||||
|
function toStringTag(val: any) {
|
||||||
|
return toStr.call(val).slice(8, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasConstructorOf(a: any, b: any) {
|
||||||
|
if (!a || typeof a !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const proto = getProto(a);
|
||||||
|
if (!proto) {
|
||||||
|
return b === null;
|
||||||
|
}
|
||||||
|
const Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
|
||||||
|
if (typeof Ctor !== "function") {
|
||||||
|
return b === null;
|
||||||
|
}
|
||||||
|
if (b === Ctor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (b !== null && fnToString.call(Ctor) === fnToString.call(b)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} val
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isPlainObject(val: any): boolean {
|
||||||
|
if (!val || toStringTag(val) !== "Object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proto = getProto(val);
|
||||||
|
if (!proto) {
|
||||||
|
// `Object.create(null)`
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasConstructorOf(val, Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUserObject(val: any): boolean {
|
||||||
|
if (!val || toStringTag(val) !== "Object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proto = getProto(val);
|
||||||
|
if (!proto) {
|
||||||
|
// `Object.create(null)`
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return hasConstructorOf(val, Object) || isUserObject(proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRegExp(val: any): boolean {
|
||||||
|
return toStringTag(val) === "RegExp";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (val === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (val === undefined) {
|
||||||
|
types.push([path, "undef"]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
memo.set(val, path);
|
||||||
|
const outArr: any[] = [];
|
||||||
|
let special = false;
|
||||||
|
for (const x in val) {
|
||||||
|
const n = Number(x);
|
||||||
|
if (n < 0 || n >= val.length || Number.isNaN(n)) {
|
||||||
|
special = true;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return outArr;
|
||||||
|
}
|
||||||
|
if (val instanceof Date) {
|
||||||
|
types.push([path, "date"]);
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
return outObj;
|
||||||
|
}
|
||||||
|
if (typeof val === "bigint") {
|
||||||
|
types.push([path, "bigint"]);
|
||||||
|
return val.toString();
|
||||||
|
}
|
||||||
|
if (typeof val === "boolean") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (typeof val === "number") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (typeof val === "string") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulate a cloneable value into a plain JSON object.
|
||||||
|
*/
|
||||||
export function structuredEncapsulate(val: any): any {
|
export function structuredEncapsulate(val: any): any {
|
||||||
return TSON.encapsulate(val);
|
const outRoot = {};
|
||||||
|
const types: Array<[string[], string]> = [];
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = internalEncapsulate(val, outRoot, [], new Map(), types);
|
||||||
|
} catch (e) {
|
||||||
|
throw new DataCloneError();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function internalStructuredRevive(val: any): any {
|
||||||
|
val = JSON.parse(JSON.stringify(val));
|
||||||
|
if (val === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof val === "number") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (typeof val === "string") {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
for (const [path, type] of types) {
|
||||||
|
if (type === "bigint") {
|
||||||
|
mutatePath(path, (x) => BigInt(x));
|
||||||
|
} else if (type === "array") {
|
||||||
|
mutatePath(path, (x) => {
|
||||||
|
const newArr: any = [];
|
||||||
|
for (const k in x) {
|
||||||
|
newArr[k] = x[k];
|
||||||
|
}
|
||||||
|
return newArr;
|
||||||
|
});
|
||||||
|
} else if (type === "date") {
|
||||||
|
mutatePath(path, (x) => new Date(x));
|
||||||
|
} else {
|
||||||
|
throw Error("type not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function structuredRevive(val: any): any {
|
export function structuredRevive(val: any): any {
|
||||||
return TSON.revive(val);
|
try {
|
||||||
|
return internalStructuredRevive(val);
|
||||||
|
} catch (e) {
|
||||||
|
throw new DataCloneError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user