@peers-app/peers-sdk 0.26.6 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/bin/peers-package.js +13 -0
- package/define/package.json +4 -0
- package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
- package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
- package/dist/contracts/__tests__/also-implements.test.js +110 -0
- package/dist/contracts/__tests__/builder.test.js +45 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
- package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
- package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
- package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
- package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
- package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
- package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
- package/dist/contracts/__tests__/registry.test.js +68 -0
- package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
- package/dist/contracts/__tests__/target-dispatch.js +24 -0
- package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
- package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
- package/dist/contracts/__tests__/validate.test.js +177 -7
- package/dist/contracts/__tests__/workflow-context.test.js +0 -11
- package/dist/contracts/builder.d.ts +16 -2
- package/dist/contracts/builder.js +44 -3
- package/dist/contracts/contract-definition-schema.d.ts +251 -0
- package/dist/contracts/contract-definition-schema.js +48 -0
- package/dist/contracts/contract-provider-router.d.ts +2 -0
- package/dist/contracts/contract-provider-router.js +5 -2
- package/dist/contracts/contract-proxy.d.ts +52 -18
- package/dist/contracts/contract-proxy.js +144 -203
- package/dist/contracts/contract-resolvers.d.ts +6 -4
- package/dist/contracts/contract-resolvers.js +32 -9
- package/dist/contracts/frozen-contracts.d.ts +59 -0
- package/dist/contracts/frozen-contracts.js +86 -0
- package/dist/contracts/host-contract-client.d.ts +134 -0
- package/dist/contracts/host-contract-client.js +396 -0
- package/dist/contracts/host-contract-transport.d.ts +2 -0
- package/dist/contracts/host-contract-transport.js +8 -0
- package/dist/contracts/index.d.ts +6 -4
- package/dist/contracts/index.js +11 -5
- package/dist/contracts/registry.d.ts +31 -1
- package/dist/contracts/registry.js +100 -6
- package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
- package/dist/contracts/system/contract-catalog-contract.js +17 -5
- package/dist/contracts/tool-catalog-service.js +26 -6
- package/dist/contracts/tool-catalog.d.ts +1 -1
- package/dist/contracts/tool-catalog.js +4 -1
- package/dist/contracts/types.d.ts +32 -2
- package/dist/contracts/validate.d.ts +16 -1
- package/dist/contracts/validate.js +139 -25
- package/dist/data/change-tracking.d.ts +17 -5
- package/dist/data/change-tracking.js +22 -6
- package/dist/data/change-tracking.test.d.ts +1 -0
- package/dist/data/change-tracking.test.js +62 -0
- package/dist/data/create-group.js +1 -1
- package/dist/data/group-members.d.ts +14 -0
- package/dist/data/group-members.js +36 -0
- package/dist/data/group-members.test.js +49 -0
- package/dist/data/orm/sql.data-source.d.ts +9 -0
- package/dist/data/orm/sql.data-source.js +47 -2
- package/dist/data/orm/sql.data-source.test.js +142 -0
- package/dist/data/orm/table-container.js +9 -9
- package/dist/data/orm/table-container.test.js +44 -0
- package/dist/data/orm/types.d.ts +41 -1
- package/dist/data/orm/types.js +159 -28
- package/dist/data/orm/types.test.js +129 -0
- package/dist/data/peer-types.d.ts +8 -62
- package/dist/data/persistent-vars.d.ts +4 -1
- package/dist/data/persistent-vars.js +7 -2
- package/dist/data/tools.d.ts +29 -221
- package/dist/define/also-implements.test.d.ts +1 -0
- package/dist/define/also-implements.test.js +123 -0
- package/dist/define/context.d.ts +77 -0
- package/dist/define/context.js +2 -0
- package/dist/define/contract-ref.d.ts +177 -0
- package/dist/define/contract-ref.js +67 -0
- package/dist/define/declare-contract.d.ts +167 -0
- package/dist/define/declare-contract.js +220 -0
- package/dist/define/declare-contract.test.d.ts +1 -0
- package/dist/define/declare-contract.test.js +573 -0
- package/dist/define/define-package.d.ts +203 -0
- package/dist/define/define-package.js +289 -0
- package/dist/define/define.test.d.ts +1 -0
- package/dist/define/define.test.js +1010 -0
- package/dist/define/empty-fields.d.ts +15 -0
- package/dist/define/empty-fields.js +134 -0
- package/dist/define/empty-fields.test.d.ts +1 -0
- package/dist/define/empty-fields.test.js +55 -0
- package/dist/define/events-observables.test.d.ts +1 -0
- package/dist/define/events-observables.test.js +1013 -0
- package/dist/define/events.d.ts +81 -0
- package/dist/define/events.js +81 -0
- package/dist/define/generated-field.d.ts +20 -0
- package/dist/define/generated-field.js +25 -0
- package/dist/define/index.d.ts +28 -0
- package/dist/define/index.js +62 -0
- package/dist/define/lazy-members.d.ts +8 -0
- package/dist/define/lazy-members.js +38 -0
- package/dist/define/manifest.d.ts +63 -0
- package/dist/define/manifest.js +653 -0
- package/dist/define/observables.d.ts +146 -0
- package/dist/define/observables.js +187 -0
- package/dist/define/serve-members.d.ts +21 -0
- package/dist/define/serve-members.js +152 -0
- package/dist/define/serve-members.test.d.ts +1 -0
- package/dist/define/serve-members.test.js +630 -0
- package/dist/define/serve.d.ts +22 -0
- package/dist/define/serve.js +58 -0
- package/dist/define/system-contracts.d.ts +41 -0
- package/dist/define/system-contracts.js +21 -0
- package/dist/define/table-handle.d.ts +13 -0
- package/dist/define/table-handle.js +36 -0
- package/dist/define/table.d.ts +128 -0
- package/dist/define/table.js +122 -0
- package/dist/define/tool.d.ts +129 -0
- package/dist/define/tool.js +91 -0
- package/dist/define/vars.d.ts +82 -0
- package/dist/define/vars.js +256 -0
- package/dist/define/vars.test.d.ts +1 -0
- package/dist/define/vars.test.js +39 -0
- package/dist/define/wire-value.d.ts +12 -0
- package/dist/define/wire-value.js +76 -0
- package/dist/define/worker-state.d.ts +15 -0
- package/dist/define/worker-state.js +45 -0
- package/dist/device/tx-encoding.test.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/invites/group-approval.js +4 -2
- package/dist/invites/group-approval.test.js +2 -0
- package/dist/observable.d.ts +42 -2
- package/dist/observable.js +25 -7
- package/dist/package-build/build-package.d.ts +60 -0
- package/dist/package-build/build-package.js +312 -0
- package/dist/package-build/build-package.test.d.ts +1 -0
- package/dist/package-build/build-package.test.js +204 -0
- package/dist/package-build/cli.d.ts +7 -0
- package/dist/package-build/cli.js +218 -0
- package/dist/package-build/contract-add.d.ts +35 -0
- package/dist/package-build/contract-add.js +97 -0
- package/dist/package-build/contract-add.test.d.ts +1 -0
- package/dist/package-build/contract-add.test.js +467 -0
- package/dist/package-build/contract-source.d.ts +59 -0
- package/dist/package-build/contract-source.js +471 -0
- package/dist/package-build/freeze-contracts.d.ts +56 -0
- package/dist/package-build/freeze-contracts.js +198 -0
- package/dist/package-build/freeze-contracts.test.d.ts +1 -0
- package/dist/package-build/freeze-contracts.test.js +355 -0
- package/dist/package-installer/package-creator.d.ts +4 -0
- package/dist/package-installer/package-creator.js +57 -16
- package/dist/package-installer/package-creator.test.js +86 -0
- package/dist/package-loader/contract-members.d.ts +53 -0
- package/dist/package-loader/contract-members.js +31 -0
- package/dist/package-loader/contract-package-loader.js +5 -1
- package/dist/package-loader/contract-package-loader.test.js +11 -0
- package/dist/package-loader/host-authority-loading.test.js +9 -5
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-artifact.d.ts +80 -17
- package/dist/package-loader/isolated-artifact.js +257 -51
- package/dist/package-loader/isolated-artifact.test.js +294 -21
- package/dist/package-loader/isolated-runtime.d.ts +22 -7
- package/dist/package-loader/isolated-table-gateway.js +30 -11
- package/dist/package-loader/isolated-table-gateway.test.js +243 -0
- package/dist/package-loader/package-loader.d.ts +8 -1
- package/dist/package-loader/package-loader.js +39 -20
- package/dist/package-loader/package-loader.test.js +47 -0
- package/dist/peer-id.d.ts +25 -0
- package/dist/peer-id.js +99 -0
- package/dist/serial-json.compat.test.d.ts +1 -0
- package/dist/serial-json.compat.test.js +369 -0
- package/dist/serial-json.d.ts +41 -5
- package/dist/serial-json.js +216 -157
- package/dist/serial-json.oracle.d.ts +21 -0
- package/dist/serial-json.oracle.js +222 -0
- package/dist/serial-json.test.js +315 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/mock-host-contract.d.ts +123 -0
- package/dist/testing/mock-host-contract.js +434 -0
- package/dist/testing/mock-host-contract.test.d.ts +1 -0
- package/dist/testing/mock-host-contract.test.js +782 -0
- package/dist/types/field-type.d.ts +35 -18
- package/dist/types/field-type.js +4 -1
- package/dist/types/zod-types.d.ts +9 -0
- package/dist/types/zod-types.js +8 -2
- package/dist/utils.d.ts +22 -25
- package/dist/utils.js +45 -92
- package/dist/utils.test.js +36 -0
- package/package.json +9 -2
- package/testing/package.json +4 -0
package/dist/serial-json.js
CHANGED
|
@@ -6,193 +6,252 @@ exports.fromJSON = fromJSON;
|
|
|
6
6
|
exports.toJSONString = toJSONString;
|
|
7
7
|
exports.fromJSONString = fromJSONString;
|
|
8
8
|
const _ = require("lodash");
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* True for any non-null object or function that isn't an array or a Date. Despite the name, class
|
|
11
|
+
* instances, Maps and RegExps count.
|
|
12
|
+
*/
|
|
10
13
|
function isObject(x) {
|
|
11
14
|
return _.isObject(x) && !_.isArray(x) && !_.isDate(x) && x !== null;
|
|
12
15
|
}
|
|
16
|
+
/** Prefix of the string that stands in for a shared object (`obj_N`) or array (`ary_N`). */
|
|
17
|
+
const SHARED_REF_PREFIX = "__duplicate_ref_";
|
|
18
|
+
/** First element of an array that other places refer to; the text after the colon is its marker. */
|
|
19
|
+
const ARRAY_TAG_PREFIX = "__this_ref:";
|
|
13
20
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
21
|
+
* Encodes a value as a JSON-safe tree, using in-band sentinel strings for what JSON can't hold:
|
|
22
|
+
* - `undefined`, `NaN`, `Infinity` → `"__UNDEFINED"`, `"__NAN"`, `"__INFINITY"`
|
|
23
|
+
* - Date → `"__DATE <ISO>"`, RegExp → `"__REGEXP /src/flags"`, Error → `"__ERROR <stack>"`
|
|
24
|
+
* - Buffer → `"__BUFFER <base64>"`, Uint8Array → `"__UINT8ARRAY <base64>"`
|
|
25
|
+
* - functions → `"__FUNCTION <source>"` (one-way: {@link fromJSON} leaves the string)
|
|
26
|
+
* - keys starting with `$` → `__DOLLAR_…`
|
|
27
|
+
*
|
|
28
|
+
* A shared object or array (including a cycle) is copied once: the first copy is tagged with a
|
|
29
|
+
* `__this_ref` key (objects) or a `"__this_ref:<marker>"` first element (arrays), and every other
|
|
30
|
+
* occurrence becomes the `__duplicate_ref_obj_N` / `__duplicate_ref_ary_N` marker string. The
|
|
31
|
+
* markers only resolve when the whole tree is decoded together.
|
|
32
|
+
*
|
|
33
|
+
* Map, Set and class instances become plain objects, `-Infinity` and `-0` survive the tree but
|
|
34
|
+
* not `JSON.stringify`. BigInt and symbols remain in the tree; JSON text throws on BigInt and
|
|
35
|
+
* drops symbols (or writes null in arrays). Invalid Dates throw, and own `__proto__` keys aren't
|
|
36
|
+
* emitted. Strings that look like sentinels aren't escaped, so they decode as the value they spell.
|
|
37
|
+
*
|
|
38
|
+
* The output is signed (`canonicalJSONString` in keys.ts), so it must stay byte-identical across
|
|
39
|
+
* versions; `serial-json.compat.test.ts` checks it against a frozen copy.
|
|
40
|
+
* @returns A new tree; the input is never modified. Top-level primitives are returned as-is.
|
|
16
41
|
*/
|
|
17
42
|
function toJSON(obj) {
|
|
18
43
|
if (!_.isObject(obj)) {
|
|
19
44
|
return obj;
|
|
20
45
|
}
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (_.isDate(obj)) {
|
|
39
|
-
return `__DATE ${obj.toISOString()}`;
|
|
46
|
+
// Every object or array met so far, with its marker and the copy that gets the tag if it recurs.
|
|
47
|
+
const seen = new Map();
|
|
48
|
+
function recurse(value) {
|
|
49
|
+
switch (typeof value) {
|
|
50
|
+
case "string":
|
|
51
|
+
case "boolean":
|
|
52
|
+
case "bigint":
|
|
53
|
+
case "symbol":
|
|
54
|
+
return value;
|
|
55
|
+
case "number":
|
|
56
|
+
if (Number.isNaN(value))
|
|
57
|
+
return "__NAN";
|
|
58
|
+
if (value === Infinity)
|
|
59
|
+
return "__INFINITY";
|
|
60
|
+
return value;
|
|
61
|
+
case "undefined":
|
|
62
|
+
return "__UNDEFINED";
|
|
40
63
|
}
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
43
|
-
if (
|
|
44
|
-
return `
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
if (value === null)
|
|
65
|
+
return null;
|
|
66
|
+
if (value instanceof RegExp)
|
|
67
|
+
return `__REGEXP ${value.toString()}`;
|
|
68
|
+
if (_.isDate(value))
|
|
69
|
+
return `__DATE ${value.toISOString()}`;
|
|
70
|
+
if (_.isFunction(value))
|
|
71
|
+
return `__FUNCTION ${value.toString()}`;
|
|
72
|
+
if (_.isElement(value))
|
|
73
|
+
return `__HTML ${value.outerHTML}`;
|
|
74
|
+
if (typeof window !== "undefined" && window && value === window)
|
|
47
75
|
return "__WINDOW";
|
|
76
|
+
if (_.isError(value))
|
|
77
|
+
return `__ERROR ${value.stack}`;
|
|
78
|
+
if (Buffer.isBuffer(value))
|
|
79
|
+
return `__BUFFER ${value.toString("base64")}`;
|
|
80
|
+
if (value instanceof Uint8Array)
|
|
81
|
+
return `__UINT8ARRAY ${Buffer.from(value).toString("base64")}`;
|
|
82
|
+
const known = seen.get(value);
|
|
83
|
+
if (known) {
|
|
84
|
+
const target = known.copy;
|
|
85
|
+
if (Array.isArray(target)) {
|
|
86
|
+
if (typeof target[0] !== "string" || !target[0].startsWith(ARRAY_TAG_PREFIX)) {
|
|
87
|
+
target.unshift(`${ARRAY_TAG_PREFIX}${known.ref}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (isObject(target) && !target.__this_ref) {
|
|
91
|
+
target.__this_ref = known.ref;
|
|
92
|
+
}
|
|
93
|
+
return known.ref;
|
|
48
94
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return `__UINT8ARRAY ${Buffer.from(obj).toString("base64")}`;
|
|
59
|
-
}
|
|
60
|
-
// non-objects can just be returned at this point
|
|
61
|
-
if (!(isObject(obj) || _.isArray(obj))) {
|
|
62
|
-
return obj;
|
|
63
|
-
}
|
|
64
|
-
// if we've found a duplicate reference, deal with it
|
|
65
|
-
const iObj = knownObjs.indexOf(obj);
|
|
66
|
-
if (iObj >= 0) {
|
|
67
|
-
const ref = objRefs[iObj];
|
|
68
|
-
const duplicateTarget = newObjs[iObj];
|
|
69
|
-
if (_.isArray(duplicateTarget) &&
|
|
70
|
-
(!_.isString(duplicateTarget[0]) || !duplicateTarget[0].match(/^__this_ref:/)))
|
|
71
|
-
duplicateTarget.unshift(`__this_ref:${ref}`);
|
|
72
|
-
else if (isObject(duplicateTarget) && !duplicateTarget.__this_ref)
|
|
73
|
-
duplicateTarget.__this_ref = ref;
|
|
74
|
-
return ref;
|
|
75
|
-
}
|
|
76
|
-
// capture references in case we need them later
|
|
77
|
-
refCount++;
|
|
78
|
-
const newRef = `__duplicate_ref_${_.isArray(obj) ? "ary_" : "obj_"}${refCount}`;
|
|
79
|
-
const nObj = _.isArray(obj) ? [] : {};
|
|
80
|
-
knownObjs.push(obj);
|
|
81
|
-
objRefs.push(newRef);
|
|
82
|
-
newObjs.push(nObj);
|
|
83
|
-
// recurse on properties
|
|
84
|
-
if (_.isArray(obj)) {
|
|
85
|
-
for (let i = 0; i < obj.length; i++)
|
|
86
|
-
nObj.push(recurse(obj[i])); // use push so offset from reference capture doesn't mess things up
|
|
95
|
+
const isArray = Array.isArray(value);
|
|
96
|
+
const ref = `${SHARED_REF_PREFIX}${isArray ? "ary_" : "obj_"}${seen.size + 1}`;
|
|
97
|
+
const copy = isArray ? [] : {};
|
|
98
|
+
seen.set(value, { ref, copy });
|
|
99
|
+
// Each key is added only after its value is encoded, so a tag added meanwhile comes first.
|
|
100
|
+
// Signatures depend on that order.
|
|
101
|
+
if (isArray) {
|
|
102
|
+
for (let i = 0; i < value.length; i++)
|
|
103
|
+
copy.push(recurse(value[i]));
|
|
87
104
|
}
|
|
88
105
|
else {
|
|
89
|
-
for (const key of Object.keys(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (outKey[0] === "$")
|
|
93
|
-
// escape leading dollar signs
|
|
94
|
-
outKey = `__DOLLAR_${outKey.substring(1)}`;
|
|
95
|
-
nObj[outKey] = value;
|
|
106
|
+
for (const key of Object.keys(value)) {
|
|
107
|
+
const item = recurse(value[key]);
|
|
108
|
+
copy[key[0] === "$" ? `__DOLLAR_${key.substring(1)}` : key] = item;
|
|
96
109
|
}
|
|
97
110
|
}
|
|
98
|
-
return
|
|
111
|
+
return copy;
|
|
112
|
+
}
|
|
113
|
+
return recurse(obj);
|
|
114
|
+
}
|
|
115
|
+
// Orphaned markers are warned about once each per process, up to a cap, because a stored value
|
|
116
|
+
// can be decoded over and over.
|
|
117
|
+
const MAX_ORPHAN_WARNINGS = 100;
|
|
118
|
+
const warnedOrphans = new Set();
|
|
119
|
+
function warnOrphanedRefs(refs) {
|
|
120
|
+
for (const ref of refs) {
|
|
121
|
+
if (warnedOrphans.has(ref) || warnedOrphans.size >= MAX_ORPHAN_WARNINGS)
|
|
122
|
+
continue;
|
|
123
|
+
warnedOrphans.add(ref);
|
|
124
|
+
// One string argument, so the console log pipeline never stores the bare marker.
|
|
125
|
+
console.warn(`[serial-json] shared-reference marker ${ref} has no target in the decoded value, so it decodes empty`);
|
|
99
126
|
}
|
|
100
|
-
obj = recurse(obj);
|
|
101
|
-
return obj;
|
|
102
127
|
}
|
|
103
128
|
/**
|
|
104
|
-
* Reverses {@link toJSON}: restores
|
|
105
|
-
*
|
|
129
|
+
* Reverses {@link toJSON}: restores the sentinel values, shared objects and cycles, and keys
|
|
130
|
+
* escaped with `__DOLLAR_`.
|
|
131
|
+
*
|
|
132
|
+
* Input may come from another device. Shared-reference tags never resolve to a prototype or to a
|
|
133
|
+
* target of the other kind (object vs array), `__proto__` keys are dropped, and `__HTML` /
|
|
134
|
+
* `__WINDOW` stay plain strings. A marker with no accepted target in the same tree decodes to an
|
|
135
|
+
* empty object or array and warns once per distinct marker, up to 100 warnings per process.
|
|
136
|
+
*
|
|
137
|
+
* Only arrays and objects are walked. Values that are already decoded (Date, RegExp, Error, Buffer
|
|
138
|
+
* and any other typed array or DataView) are returned as they are, so decoding a partly decoded
|
|
139
|
+
* tree preserves these instances. Marker-like strings may decode again; live cycles aren't supported.
|
|
140
|
+
* @param obj Parsed JSON, a msgpack-decoded tree, or a mix with decoded values. Never modified.
|
|
141
|
+
* @param _externalReferences Unused. Callers pass `.map` indexes into it, so it must stay ignored.
|
|
142
|
+
* @returns A new tree for walked arrays and objects. Passed-through values retain their identity
|
|
143
|
+
* and properties.
|
|
144
|
+
* @throws SyntaxError for a `__REGEXP` sentinel that isn't a valid regular expression; RangeError
|
|
145
|
+
* when the tree is nested deeper than the call stack allows.
|
|
106
146
|
*/
|
|
107
147
|
function fromJSON(obj, _externalReferences) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const error = new Error();
|
|
144
|
-
error.stack = obj.substring(8);
|
|
145
|
-
return error;
|
|
148
|
+
// A Map, so no marker or `__this_ref` value (e.g. `"__proto__"`) can resolve to a prototype.
|
|
149
|
+
const dupRefs = new Map();
|
|
150
|
+
const referenced = new Set();
|
|
151
|
+
const tagged = new Set();
|
|
152
|
+
function decodeString(value) {
|
|
153
|
+
if (value === "__UNDEFINED")
|
|
154
|
+
return undefined;
|
|
155
|
+
if (value === "__NAN")
|
|
156
|
+
return NaN;
|
|
157
|
+
if (value === "__INFINITY")
|
|
158
|
+
return Infinity;
|
|
159
|
+
if (value.startsWith("__REGEXP ")) {
|
|
160
|
+
const match = value.split("__REGEXP ")[1].match(/\/(.*)\/(.*)?/);
|
|
161
|
+
if (!match)
|
|
162
|
+
return /(?:)/;
|
|
163
|
+
return new RegExp(match[1], match[2] || "");
|
|
164
|
+
}
|
|
165
|
+
if (value.startsWith("__DATE "))
|
|
166
|
+
return new Date(value.substring(7));
|
|
167
|
+
if (value.startsWith("__ERROR ")) {
|
|
168
|
+
const error = new Error();
|
|
169
|
+
error.stack = value.substring(8);
|
|
170
|
+
return error;
|
|
171
|
+
}
|
|
172
|
+
if (value.startsWith("__UINT8ARRAY ")) {
|
|
173
|
+
return new Uint8Array(Buffer.from(value.substring(13), "base64"));
|
|
174
|
+
}
|
|
175
|
+
if (value.startsWith("__BUFFER "))
|
|
176
|
+
return Buffer.from(value.substring(9), "base64");
|
|
177
|
+
if (value.startsWith(SHARED_REF_PREFIX)) {
|
|
178
|
+
referenced.add(value);
|
|
179
|
+
let target = dupRefs.get(value);
|
|
180
|
+
if (!target) {
|
|
181
|
+
target = value.includes("_obj_") ? {} : [];
|
|
182
|
+
dupRefs.set(value, target);
|
|
146
183
|
}
|
|
147
|
-
|
|
148
|
-
|
|
184
|
+
return target;
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
function recurse(value) {
|
|
189
|
+
if (typeof value === "string") {
|
|
190
|
+
// Every sentinel starts with "__" (95 is "_").
|
|
191
|
+
return value.charCodeAt(0) === 95 && value.charCodeAt(1) === 95 ? decodeString(value) : value;
|
|
192
|
+
}
|
|
193
|
+
if (value === null || typeof value !== "object")
|
|
194
|
+
return value;
|
|
195
|
+
const isArray = Array.isArray(value);
|
|
196
|
+
if (!isArray && Object.getPrototypeOf(value) !== Object.prototype) {
|
|
197
|
+
if (ArrayBuffer.isView(value) || _.isDate(value) || _.isRegExp(value) || _.isError(value)) {
|
|
198
|
+
return value;
|
|
149
199
|
}
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
200
|
+
if (_.isString(value) && value.startsWith("__")) {
|
|
201
|
+
const decoded = decodeString(value);
|
|
202
|
+
if (decoded !== value)
|
|
203
|
+
return decoded;
|
|
153
204
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
205
|
+
}
|
|
206
|
+
let ref;
|
|
207
|
+
let start = 0;
|
|
208
|
+
if (isArray) {
|
|
209
|
+
const first = value[0];
|
|
210
|
+
if (_.isString(first) && first.startsWith(ARRAY_TAG_PREFIX)) {
|
|
211
|
+
ref = first.split(":")[1];
|
|
212
|
+
start = 1;
|
|
157
213
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
ref = value.__this_ref;
|
|
217
|
+
}
|
|
218
|
+
let target = isArray ? [] : {};
|
|
219
|
+
// toJSON only writes string tags. A primitive keeps the old object-key coercion; an object tag
|
|
220
|
+
// names no target (coercing it can throw on crafted input).
|
|
221
|
+
if (ref && typeof ref !== "object") {
|
|
222
|
+
const refKey = String(ref);
|
|
223
|
+
const existing = dupRefs.get(refKey);
|
|
224
|
+
// toJSON never tags an object and an array with one marker. Crafted input can, and an
|
|
225
|
+
// object's keys written onto an array (`length`, a big index) make it huge or throw.
|
|
226
|
+
if (!existing || Array.isArray(existing) === isArray) {
|
|
227
|
+
if (existing)
|
|
228
|
+
target = existing;
|
|
229
|
+
else
|
|
230
|
+
dupRefs.set(refKey, target);
|
|
231
|
+
tagged.add(refKey);
|
|
163
232
|
}
|
|
164
233
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let dup_ref = null;
|
|
169
|
-
obj = _.clone(obj); // don't mess up the original JSON object
|
|
170
|
-
if (_.isArray(obj) && _.isString(obj[0]) && obj[0].match(/^__this_ref:/))
|
|
171
|
-
dup_ref = obj.shift().split(":")[1];
|
|
172
|
-
else if (obj.__this_ref) {
|
|
173
|
-
dup_ref = obj.__this_ref;
|
|
174
|
-
delete obj.__this_ref;
|
|
234
|
+
if (isArray) {
|
|
235
|
+
for (let i = start; i < value.length; i++)
|
|
236
|
+
target[i - start] = recurse(value[i]);
|
|
175
237
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
continue;
|
|
186
|
-
let outKey = key;
|
|
187
|
-
const value = recurse(obj[outKey]);
|
|
188
|
-
if (outKey.match(/^__DOLLAR_/))
|
|
189
|
-
outKey = `$${outKey.substring(9)}`;
|
|
190
|
-
mObj[outKey] = value;
|
|
238
|
+
else {
|
|
239
|
+
for (const key of Object.keys(value)) {
|
|
240
|
+
// A truthy `__this_ref` is the tag read above. Assigning `__proto__` would set the
|
|
241
|
+
// target's prototype, and toJSON never writes that key.
|
|
242
|
+
if (key === "__proto__" || (ref && key === "__this_ref"))
|
|
243
|
+
continue;
|
|
244
|
+
const item = recurse(value[key]);
|
|
245
|
+
target[key.startsWith("__DOLLAR_") ? `$${key.substring(9)}` : key] = item;
|
|
246
|
+
}
|
|
191
247
|
}
|
|
192
|
-
return
|
|
248
|
+
return target;
|
|
193
249
|
}
|
|
194
|
-
|
|
195
|
-
|
|
250
|
+
const result = recurse(obj);
|
|
251
|
+
// Checked only now: a marker can come before its target in the tree.
|
|
252
|
+
if (referenced.size)
|
|
253
|
+
warnOrphanedRefs([...referenced].filter((ref) => !tagged.has(ref)));
|
|
254
|
+
return result;
|
|
196
255
|
}
|
|
197
256
|
/**
|
|
198
257
|
* `JSON.stringify` of {@link toJSON}: extended types are encoded deterministically, but keys keep
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** True for plain object literals (excludes array, Date, and null). */
|
|
2
|
+
export declare function isObject(x: any): x is Record<string, any>;
|
|
3
|
+
/**
|
|
4
|
+
* Clones a value into a JSON-serializable tree: Dates, Buffers, cycles, `undefined`, `NaN`, RegExp, etc. use sentinel strings.
|
|
5
|
+
* Shared references are preserved with `__duplicate_ref_*` and `__this_ref` markers for {@link fromJSON}.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toJSON(obj: any): any;
|
|
8
|
+
/**
|
|
9
|
+
* Reverses {@link toJSON}: restores special values, shared references, and keys escaped with `__DOLLAR_`.
|
|
10
|
+
* Input may come from another device: shared-reference tags never resolve to a prototype, and
|
|
11
|
+
* `__proto__` keys are dropped.
|
|
12
|
+
* @param _externalReferences Reserved for future use (function revival, etc.)
|
|
13
|
+
*/
|
|
14
|
+
export declare function fromJSON(obj: any, _externalReferences?: any): any;
|
|
15
|
+
/**
|
|
16
|
+
* `JSON.stringify` of {@link toJSON}: extended types are encoded deterministically, but keys keep
|
|
17
|
+
* insertion order. Not canonical; for signatures use `canonicalJSONString` in `keys.ts`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function toJSONString(obj: any): string;
|
|
20
|
+
/** `JSON.parse` then {@link fromJSON}. */
|
|
21
|
+
export declare function fromJSONString(str: string): any;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isObject = isObject;
|
|
4
|
+
exports.toJSON = toJSON;
|
|
5
|
+
exports.fromJSON = fromJSON;
|
|
6
|
+
exports.toJSONString = toJSONString;
|
|
7
|
+
exports.fromJSONString = fromJSONString;
|
|
8
|
+
/**
|
|
9
|
+
* Test oracle: a frozen copy of `serial-json.ts` as it stood after the prototype-pollution fix
|
|
10
|
+
* (2026-10-19), before the speed and safety rewrite. `serial-json.compat.test.ts` checks that the
|
|
11
|
+
* live encoder still produces byte-identical output and that each decoder reads the other's output.
|
|
12
|
+
* Tests only: never import it from runtime code, and don't edit it (delete it once the old format
|
|
13
|
+
* no longer matters).
|
|
14
|
+
*/
|
|
15
|
+
const _ = require("lodash");
|
|
16
|
+
/** True for plain object literals (excludes array, Date, and null). */
|
|
17
|
+
function isObject(x) {
|
|
18
|
+
return _.isObject(x) && !_.isArray(x) && !_.isDate(x) && x !== null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Clones a value into a JSON-serializable tree: Dates, Buffers, cycles, `undefined`, `NaN`, RegExp, etc. use sentinel strings.
|
|
22
|
+
* Shared references are preserved with `__duplicate_ref_*` and `__this_ref` markers for {@link fromJSON}.
|
|
23
|
+
*/
|
|
24
|
+
function toJSON(obj) {
|
|
25
|
+
if (!_.isObject(obj)) {
|
|
26
|
+
return obj;
|
|
27
|
+
}
|
|
28
|
+
//console.log('toJSON');
|
|
29
|
+
const knownObjs = [];
|
|
30
|
+
const objRefs = [];
|
|
31
|
+
const newObjs = [];
|
|
32
|
+
let refCount = 0;
|
|
33
|
+
function recurse(obj) {
|
|
34
|
+
// stringify values
|
|
35
|
+
if (Number.isNaN(obj))
|
|
36
|
+
return "__NAN";
|
|
37
|
+
if (obj === undefined)
|
|
38
|
+
return "__UNDEFINED";
|
|
39
|
+
if (obj === Infinity)
|
|
40
|
+
return "__INFINITY";
|
|
41
|
+
if (obj instanceof RegExp)
|
|
42
|
+
return `__REGEXP ${obj.toString()}`;
|
|
43
|
+
// if(isDate(obj))
|
|
44
|
+
// return "__DATE " + obj.toISOString();
|
|
45
|
+
if (_.isDate(obj)) {
|
|
46
|
+
return `__DATE ${obj.toISOString()}`;
|
|
47
|
+
}
|
|
48
|
+
if (_.isFunction(obj))
|
|
49
|
+
return `__FUNCTION ${obj.toString()}`;
|
|
50
|
+
if (_.isElement(obj)) {
|
|
51
|
+
return `__HTML ${obj.outerHTML}`;
|
|
52
|
+
}
|
|
53
|
+
if (typeof window !== "undefined" && window && obj === window) {
|
|
54
|
+
return "__WINDOW";
|
|
55
|
+
}
|
|
56
|
+
if (_.isError(obj)) {
|
|
57
|
+
return `__ERROR ${obj.stack}`;
|
|
58
|
+
}
|
|
59
|
+
if (Buffer.isBuffer(obj)) {
|
|
60
|
+
// Convert Buffer to base64 string for JSON serialization (backward compatibility)
|
|
61
|
+
return `__BUFFER ${obj.toString("base64")}`;
|
|
62
|
+
}
|
|
63
|
+
if (obj instanceof Uint8Array) {
|
|
64
|
+
// Convert Uint8Array to base64 string for JSON serialization
|
|
65
|
+
return `__UINT8ARRAY ${Buffer.from(obj).toString("base64")}`;
|
|
66
|
+
}
|
|
67
|
+
// non-objects can just be returned at this point
|
|
68
|
+
if (!(isObject(obj) || _.isArray(obj))) {
|
|
69
|
+
return obj;
|
|
70
|
+
}
|
|
71
|
+
// if we've found a duplicate reference, deal with it
|
|
72
|
+
const iObj = knownObjs.indexOf(obj);
|
|
73
|
+
if (iObj >= 0) {
|
|
74
|
+
const ref = objRefs[iObj];
|
|
75
|
+
const duplicateTarget = newObjs[iObj];
|
|
76
|
+
if (_.isArray(duplicateTarget) &&
|
|
77
|
+
(!_.isString(duplicateTarget[0]) || !duplicateTarget[0].match(/^__this_ref:/)))
|
|
78
|
+
duplicateTarget.unshift(`__this_ref:${ref}`);
|
|
79
|
+
else if (isObject(duplicateTarget) && !duplicateTarget.__this_ref)
|
|
80
|
+
duplicateTarget.__this_ref = ref;
|
|
81
|
+
return ref;
|
|
82
|
+
}
|
|
83
|
+
// capture references in case we need them later
|
|
84
|
+
refCount++;
|
|
85
|
+
const newRef = `__duplicate_ref_${_.isArray(obj) ? "ary_" : "obj_"}${refCount}`;
|
|
86
|
+
const nObj = _.isArray(obj) ? [] : {};
|
|
87
|
+
knownObjs.push(obj);
|
|
88
|
+
objRefs.push(newRef);
|
|
89
|
+
newObjs.push(nObj);
|
|
90
|
+
// recurse on properties
|
|
91
|
+
if (_.isArray(obj)) {
|
|
92
|
+
for (let i = 0; i < obj.length; i++)
|
|
93
|
+
nObj.push(recurse(obj[i])); // use push so offset from reference capture doesn't mess things up
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
for (const key of Object.keys(obj)) {
|
|
97
|
+
let outKey = key;
|
|
98
|
+
const value = recurse(obj[key]);
|
|
99
|
+
if (outKey[0] === "$")
|
|
100
|
+
// escape leading dollar signs
|
|
101
|
+
outKey = `__DOLLAR_${outKey.substring(1)}`;
|
|
102
|
+
nObj[outKey] = value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return nObj;
|
|
106
|
+
}
|
|
107
|
+
obj = recurse(obj);
|
|
108
|
+
return obj;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Reverses {@link toJSON}: restores special values, shared references, and keys escaped with `__DOLLAR_`.
|
|
112
|
+
* Input may come from another device: shared-reference tags never resolve to a prototype, and
|
|
113
|
+
* `__proto__` keys are dropped.
|
|
114
|
+
* @param _externalReferences Reserved for future use (function revival, etc.)
|
|
115
|
+
*/
|
|
116
|
+
function fromJSON(obj, _externalReferences) {
|
|
117
|
+
// A Map, so no marker or `__this_ref` value (e.g. `"__proto__"`) can resolve to a prototype.
|
|
118
|
+
const dupRefs = new Map();
|
|
119
|
+
function recurse(obj) {
|
|
120
|
+
if (_.isString(obj)) {
|
|
121
|
+
// restore values
|
|
122
|
+
if (obj === "__UNDEFINED")
|
|
123
|
+
return undefined;
|
|
124
|
+
if (obj === "__NAN")
|
|
125
|
+
return NaN;
|
|
126
|
+
if (obj === "__INFINITY")
|
|
127
|
+
return Infinity;
|
|
128
|
+
if (obj.match(/^__REGEXP /)) {
|
|
129
|
+
const m = obj.split("__REGEXP ")[1].match(/\/(.*)\/(.*)?/);
|
|
130
|
+
if (!m) {
|
|
131
|
+
return /(?:)/;
|
|
132
|
+
}
|
|
133
|
+
return new RegExp(m[1], m[2] || "");
|
|
134
|
+
}
|
|
135
|
+
if (obj.startsWith("__DATE ")) {
|
|
136
|
+
return new Date(obj.substring(7));
|
|
137
|
+
}
|
|
138
|
+
// // assuming strings like this should be dates
|
|
139
|
+
// if (obj.match(/^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{3}Z$/)) {
|
|
140
|
+
// return new Date(obj)
|
|
141
|
+
// }
|
|
142
|
+
// if (obj.match(/^__FUNCTION /)) {
|
|
143
|
+
// return js(obj.substring(11), externalReferences);
|
|
144
|
+
// }
|
|
145
|
+
if (obj.match(/^__HTML /)) {
|
|
146
|
+
//@ts-expect-error
|
|
147
|
+
if (typeof $ !== "undefined")
|
|
148
|
+
return $(obj.substring(7))[0];
|
|
149
|
+
else
|
|
150
|
+
return obj;
|
|
151
|
+
}
|
|
152
|
+
if (obj.startsWith("__ERROR ")) {
|
|
153
|
+
const error = new Error();
|
|
154
|
+
error.stack = obj.substring(8);
|
|
155
|
+
return error;
|
|
156
|
+
}
|
|
157
|
+
if (obj === "__WINDOW") {
|
|
158
|
+
return window;
|
|
159
|
+
}
|
|
160
|
+
if (obj.startsWith("__UINT8ARRAY ")) {
|
|
161
|
+
// Convert base64 string back to Uint8Array
|
|
162
|
+
return new Uint8Array(Buffer.from(obj.substring(13), "base64"));
|
|
163
|
+
}
|
|
164
|
+
if (obj.startsWith("__BUFFER ")) {
|
|
165
|
+
// Convert base64 string back to Buffer (backward compatibility)
|
|
166
|
+
return Buffer.from(obj.substring(9), "base64");
|
|
167
|
+
}
|
|
168
|
+
// deal with duplicate refs
|
|
169
|
+
if (obj.match(/^__duplicate_ref_/)) {
|
|
170
|
+
if (!dupRefs.has(obj))
|
|
171
|
+
dupRefs.set(obj, obj.match(/_obj_/) ? {} : []);
|
|
172
|
+
return dupRefs.get(obj);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!(isObject(obj) || _.isArray(obj)))
|
|
176
|
+
return obj;
|
|
177
|
+
// deal with objects that have duplicate refs
|
|
178
|
+
let dup_ref = null;
|
|
179
|
+
obj = _.clone(obj); // don't mess up the original JSON object
|
|
180
|
+
if (_.isArray(obj) && _.isString(obj[0]) && obj[0].match(/^__this_ref:/))
|
|
181
|
+
dup_ref = obj.shift().split(":")[1];
|
|
182
|
+
else if (obj.__this_ref) {
|
|
183
|
+
dup_ref = obj.__this_ref;
|
|
184
|
+
delete obj.__this_ref;
|
|
185
|
+
}
|
|
186
|
+
let mObj = _.isArray(obj) ? [] : {};
|
|
187
|
+
// toJSON only writes string tags. A primitive keeps the old object-key coercion; an object tag
|
|
188
|
+
// names no target (coercing it can throw on crafted input).
|
|
189
|
+
if (dup_ref && typeof dup_ref !== "object") {
|
|
190
|
+
const refKey = String(dup_ref);
|
|
191
|
+
if (dupRefs.has(refKey))
|
|
192
|
+
mObj = dupRefs.get(refKey);
|
|
193
|
+
else
|
|
194
|
+
dupRefs.set(refKey, mObj);
|
|
195
|
+
}
|
|
196
|
+
// restore keys and recurse on objects
|
|
197
|
+
for (const key of Object.keys(obj)) {
|
|
198
|
+
// Assigning `__proto__` would set mObj's prototype; toJSON never emits the key either.
|
|
199
|
+
if (!Object.hasOwn(obj, key) || key === "__proto__")
|
|
200
|
+
continue;
|
|
201
|
+
let outKey = key;
|
|
202
|
+
const value = recurse(obj[outKey]);
|
|
203
|
+
if (outKey.match(/^__DOLLAR_/))
|
|
204
|
+
outKey = `$${outKey.substring(9)}`;
|
|
205
|
+
mObj[outKey] = value;
|
|
206
|
+
}
|
|
207
|
+
return mObj;
|
|
208
|
+
}
|
|
209
|
+
obj = recurse(obj);
|
|
210
|
+
return obj;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* `JSON.stringify` of {@link toJSON}: extended types are encoded deterministically, but keys keep
|
|
214
|
+
* insertion order. Not canonical; for signatures use `canonicalJSONString` in `keys.ts`.
|
|
215
|
+
*/
|
|
216
|
+
function toJSONString(obj) {
|
|
217
|
+
return JSON.stringify(toJSON(obj));
|
|
218
|
+
}
|
|
219
|
+
/** `JSON.parse` then {@link fromJSON}. */
|
|
220
|
+
function fromJSONString(str) {
|
|
221
|
+
return fromJSON(JSON.parse(str));
|
|
222
|
+
}
|