@webjsdev/core 0.7.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 +55 -0
- package/index.d.ts +28 -0
- package/index.js +39 -0
- package/package.json +46 -0
- package/src/component.d.ts +120 -0
- package/src/component.js +1061 -0
- package/src/context.js +327 -0
- package/src/css.js +54 -0
- package/src/directives.js +454 -0
- package/src/escape.js +15 -0
- package/src/expose.js +90 -0
- package/src/html.js +30 -0
- package/src/lazy-loader.js +107 -0
- package/src/nav.js +45 -0
- package/src/registry.js +190 -0
- package/src/render-client.js +1717 -0
- package/src/render-server.js +1128 -0
- package/src/repeat.js +37 -0
- package/src/rich-fetch.js +70 -0
- package/src/router-client.js +1434 -0
- package/src/serialize.js +452 -0
- package/src/signal.js +684 -0
- package/src/slot.js +860 -0
- package/src/suspense.js +46 -0
- package/src/task.js +386 -0
- package/src/testing.js +157 -0
- package/src/webjs-frame.js +65 -0
- package/src/websocket-client.js +111 -0
package/src/serialize.js
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* webjs serializer: JSON-friendly encoding for rich JS types.
|
|
3
|
+
*
|
|
4
|
+
* Replaces superjson with a pure-ESM, dependency-free implementation
|
|
5
|
+
* tuned to the wire surface that React Server Actions support, plus a
|
|
6
|
+
* couple of webjs-specific niceties.
|
|
7
|
+
*
|
|
8
|
+
* Wire format: tagged-inline. Non-JSON values are wrapped in a small
|
|
9
|
+
* object whose tag key is `_$wj`:
|
|
10
|
+
*
|
|
11
|
+
* { _$wj: "Date", v: "2026-04-28T12:00:00.000Z" }
|
|
12
|
+
* { _$wj: "BigInt", v: "12345678901234567890" }
|
|
13
|
+
* { _$wj: "Map", v: [[k, v], ...] } // recursively encoded
|
|
14
|
+
* { _$wj: "Set", v: [...] }
|
|
15
|
+
* { _$wj: "Error", v: { name, message, stack } }
|
|
16
|
+
* { _$wj: "Sym", v: <Symbol.for key> }
|
|
17
|
+
* { _$wj: "u8", v: "<base64>" } // and i8/u16/i16/u32/i32/u8c/f32/f64/buf/dv
|
|
18
|
+
* { _$wj: "Blob", v: "<base64>", t: "<mime>" }
|
|
19
|
+
* { _$wj: "File", v: "<base64>", n: "<name>", t: "<mime>", m: <lastModified> }
|
|
20
|
+
* { _$wj: "FD", v: [[name, encoded], ...] } // FormData
|
|
21
|
+
* { _$wj: "undef" } | "NaN" | "Inf" | "-Inf" | "-0"
|
|
22
|
+
* { _$wj: "Ref", v: <id> } // back-reference for cycles / shared refs
|
|
23
|
+
*
|
|
24
|
+
* A container value (object/array/Map/Set) that participates in a
|
|
25
|
+
* cycle or appears more than once gets an `_id` field so refs can
|
|
26
|
+
* back-point to it.
|
|
27
|
+
*
|
|
28
|
+
* Plain-object keys that look like reserved markers (`_$wj`, `__$wj`,
|
|
29
|
+
* `_id`, `__id`, …) are escaped on encode by adding a leading
|
|
30
|
+
* underscore; decode strips one. This makes the format collision-safe
|
|
31
|
+
* for arbitrary user data.
|
|
32
|
+
*
|
|
33
|
+
* ## Public API
|
|
34
|
+
*
|
|
35
|
+
* stringify(v) async: JSON.stringify(serialize(v))
|
|
36
|
+
* serialize(v) async: produces JSON-safe value
|
|
37
|
+
*
|
|
38
|
+
* parse(s) sync: deserialize(JSON.parse(s))
|
|
39
|
+
* deserialize(v) sync: inverse of serialize
|
|
40
|
+
*
|
|
41
|
+
* The serializer is async because Blob / File / FormData require
|
|
42
|
+
* `await blob.arrayBuffer()` to read their bytes. For payloads that
|
|
43
|
+
* don't include any binary types the cost is just a single Promise
|
|
44
|
+
* tick (microseconds). Single async function keeps the API obvious -
|
|
45
|
+
* AI agents can't pick the wrong variant.
|
|
46
|
+
*
|
|
47
|
+
* (Built-in `JSON.stringify` / `JSON.parse` are unaffected and remain
|
|
48
|
+
* available for plain-JSON use cases.)
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const TAG = '_$wj';
|
|
52
|
+
const ID_KEY = '_id';
|
|
53
|
+
|
|
54
|
+
/* ----------------------------------------------------------------- *
|
|
55
|
+
* Public API *
|
|
56
|
+
* ----------------------------------------------------------------- */
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Serialize `value` to a JSON string. Async to support Blob / File /
|
|
60
|
+
* FormData (which require `await arrayBuffer()` to read their bytes).
|
|
61
|
+
*
|
|
62
|
+
* @param {unknown} value
|
|
63
|
+
* @returns {Promise<string>}
|
|
64
|
+
*/
|
|
65
|
+
export async function stringify(value) {
|
|
66
|
+
return JSON.stringify(await serialize(value));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Inverse of `stringify`. Synchronous: no async types need awaiting
|
|
71
|
+
* during decode (binary is already inlined as base64).
|
|
72
|
+
*
|
|
73
|
+
* @param {string} text
|
|
74
|
+
* @returns {unknown}
|
|
75
|
+
*/
|
|
76
|
+
export function parse(text) {
|
|
77
|
+
return deserialize(JSON.parse(text));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Serialize `value` to a JSON-safe shape (plain JSON values + tagged
|
|
82
|
+
* marker objects for rich types). Use `stringify` if you also need
|
|
83
|
+
* the JSON string.
|
|
84
|
+
*
|
|
85
|
+
* @param {unknown} value
|
|
86
|
+
* @returns {Promise<unknown>}
|
|
87
|
+
*/
|
|
88
|
+
export async function serialize(value) {
|
|
89
|
+
const ctx = newEncodeCtx();
|
|
90
|
+
await countRefs(value, ctx);
|
|
91
|
+
return encodeOne(value, ctx);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Inverse of `serialize`. Synchronous.
|
|
96
|
+
*
|
|
97
|
+
* @param {unknown} value
|
|
98
|
+
* @returns {unknown}
|
|
99
|
+
*/
|
|
100
|
+
export function deserialize(value) {
|
|
101
|
+
return decode(value, newDecodeCtx());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ----------------------------------------------------------------- *
|
|
105
|
+
* Encode *
|
|
106
|
+
* ----------------------------------------------------------------- */
|
|
107
|
+
|
|
108
|
+
function newEncodeCtx() {
|
|
109
|
+
return {
|
|
110
|
+
refCount: new Map(), // obj → number of references
|
|
111
|
+
idOf: new Map(), // obj → assigned id (only for multi-ref)
|
|
112
|
+
emitted: new Set(), // ids already emitted as the canonical encoding
|
|
113
|
+
nextId: 0,
|
|
114
|
+
blobBytes: new Map(), // Blob → Uint8Array (precomputed during countRefs)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Pass 1: walk the value, count references, pre-read Blob/File bytes.
|
|
120
|
+
* Async because `Blob.arrayBuffer()` is async. Iterative (uses an
|
|
121
|
+
* explicit stack) to avoid recursion limits on deeply nested values.
|
|
122
|
+
*/
|
|
123
|
+
async function countRefs(value, ctx) {
|
|
124
|
+
const stack = [value];
|
|
125
|
+
while (stack.length) {
|
|
126
|
+
const v = stack.pop();
|
|
127
|
+
if (!isCountableObject(v)) continue;
|
|
128
|
+
const cur = ctx.refCount.get(v) || 0;
|
|
129
|
+
ctx.refCount.set(v, cur + 1);
|
|
130
|
+
if (cur > 0) continue; // already traversed children
|
|
131
|
+
if (typeof Blob !== 'undefined' && v instanceof Blob) {
|
|
132
|
+
const buf = new Uint8Array(await v.arrayBuffer());
|
|
133
|
+
ctx.blobBytes.set(v, buf);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (typeof FormData !== 'undefined' && v instanceof FormData) {
|
|
137
|
+
for (const [, val] of v.entries()) {
|
|
138
|
+
if (typeof val !== 'string') stack.push(val);
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(v)) {
|
|
143
|
+
for (let i = 0; i < v.length; i++) stack.push(v[i]);
|
|
144
|
+
} else if (v instanceof Map) {
|
|
145
|
+
for (const [k, val] of v) { stack.push(k); stack.push(val); }
|
|
146
|
+
} else if (v instanceof Set) {
|
|
147
|
+
for (const item of v) stack.push(item);
|
|
148
|
+
} else if (isPlainObject(v)) {
|
|
149
|
+
for (const k in v) {
|
|
150
|
+
if (Object.prototype.hasOwnProperty.call(v, k)) stack.push(v[k]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Assign ids only to objects with multiple refs (cycles or shared refs).
|
|
155
|
+
for (const [obj, count] of ctx.refCount) {
|
|
156
|
+
if (count > 1) ctx.idOf.set(obj, ctx.nextId++);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function encodeOne(v, ctx) {
|
|
161
|
+
// Primitives + sentinel-tagged values
|
|
162
|
+
if (v === null) return null;
|
|
163
|
+
if (v === undefined) return { [TAG]: 'undef' };
|
|
164
|
+
const t = typeof v;
|
|
165
|
+
if (t === 'string' || t === 'boolean') return v;
|
|
166
|
+
if (t === 'number') {
|
|
167
|
+
if (Number.isNaN(v)) return { [TAG]: 'NaN' };
|
|
168
|
+
if (v === Infinity) return { [TAG]: 'Inf' };
|
|
169
|
+
if (v === -Infinity) return { [TAG]: '-Inf' };
|
|
170
|
+
if (Object.is(v, -0)) return { [TAG]: '-0' };
|
|
171
|
+
return v;
|
|
172
|
+
}
|
|
173
|
+
if (t === 'bigint') return { [TAG]: 'BigInt', v: v.toString() };
|
|
174
|
+
if (t === 'symbol') {
|
|
175
|
+
const k = Symbol.keyFor(v);
|
|
176
|
+
if (k === undefined) {
|
|
177
|
+
throw new TypeError('Cannot serialize a local Symbol; only Symbol.for() registered symbols are supported.');
|
|
178
|
+
}
|
|
179
|
+
return { [TAG]: 'Sym', v: k };
|
|
180
|
+
}
|
|
181
|
+
if (t === 'function') {
|
|
182
|
+
throw new TypeError('Cannot serialize a function.');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Object types: first check for repeat reference
|
|
186
|
+
const id = ctx.idOf.get(v);
|
|
187
|
+
if (id !== undefined && ctx.emitted.has(id)) {
|
|
188
|
+
return { [TAG]: 'Ref', v: id };
|
|
189
|
+
}
|
|
190
|
+
if (id !== undefined) ctx.emitted.add(id);
|
|
191
|
+
|
|
192
|
+
// Containers + tagged leaf values
|
|
193
|
+
let out;
|
|
194
|
+
if (v instanceof Date) {
|
|
195
|
+
out = { [TAG]: 'Date', v: Number.isNaN(v.getTime()) ? null : v.toISOString() };
|
|
196
|
+
} else if (v instanceof Error) {
|
|
197
|
+
out = { [TAG]: 'Error', v: { name: v.name, message: v.message, stack: v.stack || null } };
|
|
198
|
+
} else if (typeof Blob !== 'undefined' && v instanceof Blob) {
|
|
199
|
+
const bytes = ctx.blobBytes.get(v);
|
|
200
|
+
if (typeof File !== 'undefined' && v instanceof File) {
|
|
201
|
+
out = { [TAG]: 'File', v: bytesToB64(bytes), n: v.name, t: v.type || '', m: v.lastModified };
|
|
202
|
+
} else {
|
|
203
|
+
out = { [TAG]: 'Blob', v: bytesToB64(bytes), t: v.type || '' };
|
|
204
|
+
}
|
|
205
|
+
} else if (typeof FormData !== 'undefined' && v instanceof FormData) {
|
|
206
|
+
const entries = [];
|
|
207
|
+
for (const [k, val] of v.entries()) entries.push([k, encodeOne(val, ctx)]);
|
|
208
|
+
out = { [TAG]: 'FD', v: entries };
|
|
209
|
+
} else if (ArrayBuffer.isView(v) || v instanceof ArrayBuffer) {
|
|
210
|
+
out = encodeBinary(v);
|
|
211
|
+
} else if (v instanceof Map) {
|
|
212
|
+
const entries = [];
|
|
213
|
+
for (const [k, val] of v) entries.push([encodeOne(k, ctx), encodeOne(val, ctx)]);
|
|
214
|
+
out = { [TAG]: 'Map', v: entries };
|
|
215
|
+
} else if (v instanceof Set) {
|
|
216
|
+
const items = [];
|
|
217
|
+
for (const item of v) items.push(encodeOne(item, ctx));
|
|
218
|
+
out = { [TAG]: 'Set', v: items };
|
|
219
|
+
} else if (Array.isArray(v)) {
|
|
220
|
+
out = v.map((item) => encodeOne(item, ctx));
|
|
221
|
+
} else {
|
|
222
|
+
// Plain object (or class instance: degrades to plain object,
|
|
223
|
+
// matching React's behavior).
|
|
224
|
+
out = {};
|
|
225
|
+
for (const k in v) {
|
|
226
|
+
if (!Object.prototype.hasOwnProperty.call(v, k)) continue;
|
|
227
|
+
const escaped = isReservedKey(k) ? '_' + k : k;
|
|
228
|
+
out[escaped] = encodeOne(v[k], ctx);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (id !== undefined) {
|
|
233
|
+
if (Array.isArray(out)) {
|
|
234
|
+
// Arrays can't carry ids inline; wrap in a tagged container.
|
|
235
|
+
return { [TAG]: 'Arr', [ID_KEY]: id, v: out };
|
|
236
|
+
}
|
|
237
|
+
out[ID_KEY] = id;
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Encode a typed array / ArrayBuffer / DataView as a tagged base64 value. */
|
|
243
|
+
function encodeBinary(v) {
|
|
244
|
+
if (v instanceof ArrayBuffer) {
|
|
245
|
+
return { [TAG]: 'buf', v: bytesToB64(new Uint8Array(v)) };
|
|
246
|
+
}
|
|
247
|
+
if (v instanceof DataView) {
|
|
248
|
+
return { [TAG]: 'dv', v: bytesToB64(new Uint8Array(v.buffer, v.byteOffset, v.byteLength)) };
|
|
249
|
+
}
|
|
250
|
+
// Typed array
|
|
251
|
+
const ctor = v.constructor.name;
|
|
252
|
+
const tag = TYPED_ARRAY_TAG[ctor];
|
|
253
|
+
if (!tag) {
|
|
254
|
+
throw new TypeError(`Unsupported typed array: ${ctor}`);
|
|
255
|
+
}
|
|
256
|
+
const u8 = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
|
|
257
|
+
return { [TAG]: tag, v: bytesToB64(u8) };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const TYPED_ARRAY_TAG = {
|
|
261
|
+
Int8Array: 'i8',
|
|
262
|
+
Uint8Array: 'u8',
|
|
263
|
+
Uint8ClampedArray: 'u8c',
|
|
264
|
+
Int16Array: 'i16',
|
|
265
|
+
Uint16Array: 'u16',
|
|
266
|
+
Int32Array: 'i32',
|
|
267
|
+
Uint32Array: 'u32',
|
|
268
|
+
Float32Array: 'f32',
|
|
269
|
+
Float64Array: 'f64',
|
|
270
|
+
BigInt64Array: 'bi64',
|
|
271
|
+
BigUint64Array: 'bu64',
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const TAG_TO_TYPED_ARRAY = {
|
|
275
|
+
i8: Int8Array,
|
|
276
|
+
u8: Uint8Array,
|
|
277
|
+
u8c: Uint8ClampedArray,
|
|
278
|
+
i16: Int16Array,
|
|
279
|
+
u16: Uint16Array,
|
|
280
|
+
i32: Int32Array,
|
|
281
|
+
u32: Uint32Array,
|
|
282
|
+
f32: Float32Array,
|
|
283
|
+
f64: Float64Array,
|
|
284
|
+
bi64: typeof BigInt64Array !== 'undefined' ? BigInt64Array : null,
|
|
285
|
+
bu64: typeof BigUint64Array !== 'undefined' ? BigUint64Array : null,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/* ----------------------------------------------------------------- *
|
|
289
|
+
* Decode *
|
|
290
|
+
* ----------------------------------------------------------------- */
|
|
291
|
+
|
|
292
|
+
function newDecodeCtx() {
|
|
293
|
+
return {
|
|
294
|
+
refs: new Map(), // id → object (mutable; populated as we decode)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function decode(v, ctx) {
|
|
299
|
+
if (v === null || typeof v !== 'object') return v;
|
|
300
|
+
if (Array.isArray(v)) {
|
|
301
|
+
const out = [];
|
|
302
|
+
for (let i = 0; i < v.length; i++) out.push(decode(v[i], ctx));
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
const tag = v[TAG];
|
|
306
|
+
if (typeof tag === 'string') {
|
|
307
|
+
return decodeTagged(v, tag, ctx);
|
|
308
|
+
}
|
|
309
|
+
// Plain object
|
|
310
|
+
const out = {};
|
|
311
|
+
const id = v[ID_KEY];
|
|
312
|
+
if (typeof id === 'number') ctx.refs.set(id, out);
|
|
313
|
+
for (const k in v) {
|
|
314
|
+
if (!Object.prototype.hasOwnProperty.call(v, k)) continue;
|
|
315
|
+
if (k === ID_KEY) continue;
|
|
316
|
+
const realKey = isEscapedReservedKey(k) ? k.slice(1) : k;
|
|
317
|
+
out[realKey] = decode(v[k], ctx);
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function decodeTagged(v, tag, ctx) {
|
|
323
|
+
const id = v[ID_KEY];
|
|
324
|
+
switch (tag) {
|
|
325
|
+
case 'undef': return undefined;
|
|
326
|
+
case 'NaN': return NaN;
|
|
327
|
+
case 'Inf': return Infinity;
|
|
328
|
+
case '-Inf': return -Infinity;
|
|
329
|
+
case '-0': return -0;
|
|
330
|
+
case 'BigInt': return BigInt(v.v);
|
|
331
|
+
case 'Sym': return Symbol.for(v.v);
|
|
332
|
+
case 'Date': return v.v == null ? new Date(NaN) : new Date(v.v);
|
|
333
|
+
case 'Error': {
|
|
334
|
+
const e = new Error(v.v.message);
|
|
335
|
+
e.name = v.v.name;
|
|
336
|
+
if (v.v.stack) e.stack = v.v.stack;
|
|
337
|
+
if (typeof id === 'number') ctx.refs.set(id, e);
|
|
338
|
+
return e;
|
|
339
|
+
}
|
|
340
|
+
case 'Map': {
|
|
341
|
+
const m = new Map();
|
|
342
|
+
if (typeof id === 'number') ctx.refs.set(id, m);
|
|
343
|
+
for (const [k, val] of v.v) m.set(decode(k, ctx), decode(val, ctx));
|
|
344
|
+
return m;
|
|
345
|
+
}
|
|
346
|
+
case 'Set': {
|
|
347
|
+
const s = new Set();
|
|
348
|
+
if (typeof id === 'number') ctx.refs.set(id, s);
|
|
349
|
+
for (const item of v.v) s.add(decode(item, ctx));
|
|
350
|
+
return s;
|
|
351
|
+
}
|
|
352
|
+
case 'Arr': {
|
|
353
|
+
const arr = [];
|
|
354
|
+
if (typeof id === 'number') ctx.refs.set(id, arr);
|
|
355
|
+
for (const item of v.v) arr.push(decode(item, ctx));
|
|
356
|
+
return arr;
|
|
357
|
+
}
|
|
358
|
+
case 'Ref': {
|
|
359
|
+
const ref = ctx.refs.get(v.v);
|
|
360
|
+
if (ref === undefined) {
|
|
361
|
+
throw new TypeError(`Dangling reference: id=${v.v}`);
|
|
362
|
+
}
|
|
363
|
+
return ref;
|
|
364
|
+
}
|
|
365
|
+
case 'Blob': {
|
|
366
|
+
if (typeof Blob === 'undefined') throw new TypeError('Blob is not available in this environment.');
|
|
367
|
+
const bytes = b64ToBytes(v.v);
|
|
368
|
+
return new Blob([bytes], { type: v.t || '' });
|
|
369
|
+
}
|
|
370
|
+
case 'File': {
|
|
371
|
+
if (typeof File === 'undefined') throw new TypeError('File is not available in this environment.');
|
|
372
|
+
const bytes = b64ToBytes(v.v);
|
|
373
|
+
return new File([bytes], v.n, { type: v.t || '', lastModified: v.m });
|
|
374
|
+
}
|
|
375
|
+
case 'FD': {
|
|
376
|
+
if (typeof FormData === 'undefined') throw new TypeError('FormData is not available in this environment.');
|
|
377
|
+
const fd = new FormData();
|
|
378
|
+
for (const [k, val] of v.v) {
|
|
379
|
+
const decoded = decode(val, ctx);
|
|
380
|
+
fd.append(k, decoded);
|
|
381
|
+
}
|
|
382
|
+
return fd;
|
|
383
|
+
}
|
|
384
|
+
default: {
|
|
385
|
+
// Typed arrays + binary buffers
|
|
386
|
+
if (tag === 'buf') {
|
|
387
|
+
const bytes = b64ToBytes(v.v);
|
|
388
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
389
|
+
}
|
|
390
|
+
if (tag === 'dv') {
|
|
391
|
+
const bytes = b64ToBytes(v.v);
|
|
392
|
+
return new DataView(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
|
|
393
|
+
}
|
|
394
|
+
const Ctor = TAG_TO_TYPED_ARRAY[tag];
|
|
395
|
+
if (Ctor) {
|
|
396
|
+
const bytes = b64ToBytes(v.v);
|
|
397
|
+
const sliced = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
398
|
+
return new Ctor(sliced);
|
|
399
|
+
}
|
|
400
|
+
throw new TypeError(`Unknown serialization tag: ${tag}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* ----------------------------------------------------------------- *
|
|
406
|
+
* Helpers *
|
|
407
|
+
* ----------------------------------------------------------------- */
|
|
408
|
+
|
|
409
|
+
function isCountableObject(v) {
|
|
410
|
+
// Returns true for any object that countRefs should visit. Includes
|
|
411
|
+
// Blob/FormData (so countRefs can pre-read their bytes via async
|
|
412
|
+
// arrayBuffer) and the leaf typed values (Date/Error/typed arrays /
|
|
413
|
+
// ArrayBuffer / DataView), which countRefs records but doesn't
|
|
414
|
+
// traverse into.
|
|
415
|
+
return v !== null && typeof v === 'object';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function isPlainObject(v) {
|
|
419
|
+
if (v === null || typeof v !== 'object') return false;
|
|
420
|
+
const p = Object.getPrototypeOf(v);
|
|
421
|
+
return p === Object.prototype || p === null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** A user-supplied object key that would collide with our marker syntax. */
|
|
425
|
+
function isReservedKey(k) {
|
|
426
|
+
return /^_+\$wj$/.test(k) || /^_+id$/.test(k);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** A key on the wire that was escaped because it would have been reserved. */
|
|
430
|
+
function isEscapedReservedKey(k) {
|
|
431
|
+
return /^__+\$wj$/.test(k) || /^__+id$/.test(k);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const _hasBuffer = typeof Buffer !== 'undefined' && typeof Buffer.from === 'function';
|
|
435
|
+
|
|
436
|
+
function bytesToB64(u8) {
|
|
437
|
+
if (_hasBuffer) return Buffer.from(u8.buffer, u8.byteOffset, u8.byteLength).toString('base64');
|
|
438
|
+
let s = '';
|
|
439
|
+
for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]);
|
|
440
|
+
return btoa(s);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function b64ToBytes(b64) {
|
|
444
|
+
if (_hasBuffer) {
|
|
445
|
+
const buf = Buffer.from(b64, 'base64');
|
|
446
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
447
|
+
}
|
|
448
|
+
const bin = atob(b64);
|
|
449
|
+
const out = new Uint8Array(bin.length);
|
|
450
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
451
|
+
return out;
|
|
452
|
+
}
|