seroval 0.4.0-alpha.2 → 0.4.0-alpha.4
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/dist/cjs/development/index.cjs +314 -217
- package/dist/cjs/development/index.cjs.map +2 -2
- package/dist/cjs/production/index.cjs +2 -2
- package/dist/esm/development/index.mjs +314 -217
- package/dist/esm/development/index.mjs.map +2 -2
- package/dist/esm/production/index.mjs +2 -2
- package/dist/types/compat.d.ts +13 -12
- package/dist/types/index.d.ts +4 -0
- package/dist/types/tree.d.ts +39 -29
- package/dist/types/types.d.ts +2 -2
- package/package.json +2 -2
- package/src/checks.ts +1 -2
- package/src/compat.ts +28 -14
- package/src/index.ts +74 -19
- package/src/tree.ts +260 -203
- package/src/types.ts +3 -3
package/src/tree.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
ErrorValue,
|
|
15
15
|
PrimitiveValue,
|
|
16
16
|
ServerValue,
|
|
17
|
-
TypedArrayValue,
|
|
18
17
|
} from './types';
|
|
19
18
|
import getIdentifier from './get-identifier';
|
|
20
19
|
import { Feature, parseTargets } from './compat';
|
|
@@ -45,20 +44,24 @@ export type Assignment =
|
|
|
45
44
|
| MapAssignment
|
|
46
45
|
| SetAssignment;
|
|
47
46
|
|
|
47
|
+
export interface ParserContext {
|
|
48
|
+
refs: Map<unknown, number>;
|
|
49
|
+
markedRefs: Set<number>;
|
|
50
|
+
features: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
export interface SerializationContext {
|
|
49
54
|
stack: number[];
|
|
50
|
-
// Value-to-ref map
|
|
51
|
-
refs: Map<unknown, number>;
|
|
52
55
|
// Map tree refs to actual refs
|
|
53
56
|
validRefs: Map<number, number>;
|
|
54
57
|
// Refs that are...referenced
|
|
55
|
-
markedRefs:
|
|
58
|
+
markedRefs: Set<number>;
|
|
56
59
|
// Variables
|
|
57
60
|
vars: string[];
|
|
58
61
|
// Array of assignments to be done (used for recursion)
|
|
59
62
|
assignments: Assignment[];
|
|
60
63
|
// Supported features
|
|
61
|
-
features:
|
|
64
|
+
features: number;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export interface Options {
|
|
@@ -69,17 +72,29 @@ const DEFAULT_OPTIONS: Options = {
|
|
|
69
72
|
target: 'es2023',
|
|
70
73
|
};
|
|
71
74
|
|
|
72
|
-
export function
|
|
75
|
+
export function createParserContext(options: Partial<Options> = {}): ParserContext {
|
|
73
76
|
// eslint-disable-next-line prefer-object-spread
|
|
74
77
|
const result = Object.assign({}, DEFAULT_OPTIONS, options || {});
|
|
75
78
|
return {
|
|
76
|
-
markedRefs:
|
|
77
|
-
stack: [],
|
|
79
|
+
markedRefs: new Set(),
|
|
78
80
|
refs: new Map(),
|
|
81
|
+
features: parseTargets(result.target),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SerializationOptions {
|
|
86
|
+
markedRefs: number[] | Set<number>;
|
|
87
|
+
features: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createSerializationContext(options: SerializationOptions): SerializationContext {
|
|
91
|
+
return {
|
|
92
|
+
stack: [],
|
|
79
93
|
vars: [],
|
|
80
94
|
assignments: [],
|
|
81
95
|
validRefs: new Map(),
|
|
82
|
-
features:
|
|
96
|
+
features: options.features,
|
|
97
|
+
markedRefs: new Set(options.markedRefs),
|
|
83
98
|
};
|
|
84
99
|
}
|
|
85
100
|
|
|
@@ -162,24 +177,8 @@ export function resolvePatches(ctx: SerializationContext) {
|
|
|
162
177
|
/**
|
|
163
178
|
* Increments the number of references the referenced value has
|
|
164
179
|
*/
|
|
165
|
-
function markRef(ctx: SerializationContext, current: number) {
|
|
166
|
-
ctx.markedRefs
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Creates a reference ID from the given values
|
|
171
|
-
*/
|
|
172
|
-
export function createRef(
|
|
173
|
-
ctx: SerializationContext,
|
|
174
|
-
index: unknown,
|
|
175
|
-
) {
|
|
176
|
-
const current = ctx.refs.get(index);
|
|
177
|
-
if (current != null) {
|
|
178
|
-
return current;
|
|
179
|
-
}
|
|
180
|
-
const id = ctx.refs.size;
|
|
181
|
-
ctx.refs.set(index, id);
|
|
182
|
-
return id;
|
|
180
|
+
function markRef(ctx: ParserContext | SerializationContext, current: number) {
|
|
181
|
+
ctx.markedRefs.add(current);
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
/**
|
|
@@ -225,12 +224,28 @@ function assignRef(
|
|
|
225
224
|
index: number,
|
|
226
225
|
value: string,
|
|
227
226
|
) {
|
|
228
|
-
if (ctx.markedRefs
|
|
227
|
+
if (ctx.markedRefs.has(index)) {
|
|
229
228
|
return `${getRefParam(ctx, index)}=${value}`;
|
|
230
229
|
}
|
|
231
230
|
return value;
|
|
232
231
|
}
|
|
233
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Creates a reference ID from the given values
|
|
235
|
+
*/
|
|
236
|
+
function createRef(
|
|
237
|
+
ctx: ParserContext,
|
|
238
|
+
index: unknown,
|
|
239
|
+
) {
|
|
240
|
+
const current = ctx.refs.get(index);
|
|
241
|
+
if (current != null) {
|
|
242
|
+
return current;
|
|
243
|
+
}
|
|
244
|
+
const id = ctx.refs.size;
|
|
245
|
+
ctx.refs.set(index, id);
|
|
246
|
+
return id;
|
|
247
|
+
}
|
|
248
|
+
|
|
234
249
|
function createAssignment(
|
|
235
250
|
ctx: SerializationContext,
|
|
236
251
|
source: string,
|
|
@@ -281,24 +296,16 @@ function createArrayAssign(
|
|
|
281
296
|
createAssignment(ctx, `${getRefParam(ctx, ref)}[${index}]`, value);
|
|
282
297
|
}
|
|
283
298
|
|
|
284
|
-
function
|
|
299
|
+
function createObjectAssign(
|
|
285
300
|
ctx: SerializationContext,
|
|
286
301
|
ref: number,
|
|
287
302
|
key: string,
|
|
288
303
|
value: string,
|
|
304
|
+
computed: boolean,
|
|
289
305
|
) {
|
|
290
306
|
markRef(ctx, ref);
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function createObjectComputedAssign(
|
|
295
|
-
ctx: SerializationContext,
|
|
296
|
-
ref: number,
|
|
297
|
-
key: string,
|
|
298
|
-
value: string,
|
|
299
|
-
) {
|
|
300
|
-
markRef(ctx, ref);
|
|
301
|
-
createAssignment(ctx, `${getRefParam(ctx, ref)}[${key}]`, value);
|
|
307
|
+
const member = computed ? `[${key}]` : `.${key}`;
|
|
308
|
+
createAssignment(ctx, `${getRefParam(ctx, ref)}${member}`, value);
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
function getErrorConstructor(error: ErrorValue) {
|
|
@@ -360,6 +367,7 @@ function getIterableOptions(obj: Iterable<any>) {
|
|
|
360
367
|
|
|
361
368
|
const enum SerovalNodeType {
|
|
362
369
|
Primitive,
|
|
370
|
+
BigInt,
|
|
363
371
|
Reference,
|
|
364
372
|
Date,
|
|
365
373
|
RegExp,
|
|
@@ -373,16 +381,23 @@ const enum SerovalNodeType {
|
|
|
373
381
|
AggregateError,
|
|
374
382
|
Iterable,
|
|
375
383
|
TypedArray,
|
|
384
|
+
BigIntTypedArray,
|
|
376
385
|
}
|
|
377
386
|
|
|
378
387
|
type SerovalPrimitiveNode = [type: SerovalNodeType.Primitive, value: PrimitiveValue];
|
|
379
388
|
type SerovalReferenceNode = [type: SerovalNodeType.Reference, value: number];
|
|
380
389
|
type SerovalSemiPrimitiveNode =
|
|
381
|
-
| [type: SerovalNodeType.
|
|
382
|
-
| [type: SerovalNodeType.
|
|
390
|
+
| [type: SerovalNodeType.BigInt, value: string]
|
|
391
|
+
| [type: SerovalNodeType.Date, value: string, id: number]
|
|
392
|
+
| [type: SerovalNodeType.RegExp, value: string, id: number]
|
|
383
393
|
| [
|
|
384
394
|
type: SerovalNodeType.TypedArray,
|
|
385
|
-
value: [constructor: string, array:
|
|
395
|
+
value: [constructor: string, array: string, byteOffset: number],
|
|
396
|
+
id: number
|
|
397
|
+
]
|
|
398
|
+
| [
|
|
399
|
+
type: SerovalNodeType.BigIntTypedArray,
|
|
400
|
+
value: [constructor: string, array: string, byteOffset: number],
|
|
386
401
|
id: number
|
|
387
402
|
];
|
|
388
403
|
|
|
@@ -429,7 +444,7 @@ type SerovalIterableNode = [
|
|
|
429
444
|
id: number,
|
|
430
445
|
];
|
|
431
446
|
|
|
432
|
-
type SerovalNode =
|
|
447
|
+
export type SerovalNode =
|
|
433
448
|
| SerovalPrimitiveNode
|
|
434
449
|
| SerovalReferenceNode
|
|
435
450
|
| SerovalSemiPrimitiveNode
|
|
@@ -451,7 +466,7 @@ function isReferenceInStack(
|
|
|
451
466
|
}
|
|
452
467
|
|
|
453
468
|
function generateRef(
|
|
454
|
-
ctx:
|
|
469
|
+
ctx: ParserContext,
|
|
455
470
|
current: unknown,
|
|
456
471
|
): number | SerovalReferenceNode {
|
|
457
472
|
// Check if reference number already exists
|
|
@@ -471,104 +486,129 @@ function generateRef(
|
|
|
471
486
|
}
|
|
472
487
|
|
|
473
488
|
function generateSemiPrimitiveValue(
|
|
474
|
-
ctx:
|
|
489
|
+
ctx: ParserContext,
|
|
475
490
|
current: unknown,
|
|
476
491
|
id: number,
|
|
477
492
|
): SerovalNode | undefined {
|
|
493
|
+
if (typeof current === 'bigint') {
|
|
494
|
+
assert(ctx.features & Feature.BigInt, 'Unsupported type "BigInt"');
|
|
495
|
+
return [SerovalNodeType.BigInt, `${current}n`];
|
|
496
|
+
}
|
|
478
497
|
if (constructorCheck<Date>(current, Date)) {
|
|
479
|
-
return [SerovalNodeType.Date, current, id];
|
|
498
|
+
return [SerovalNodeType.Date, current.toISOString(), id];
|
|
480
499
|
}
|
|
481
500
|
if (constructorCheck<RegExp>(current, RegExp)) {
|
|
482
|
-
return [SerovalNodeType.RegExp, current, id];
|
|
501
|
+
return [SerovalNodeType.RegExp, String(current), id];
|
|
483
502
|
}
|
|
484
503
|
if (constructorCheck<Int8Array>(current, Int8Array)) {
|
|
485
|
-
assert(ctx.features
|
|
486
|
-
return [SerovalNodeType.TypedArray, ['Int8Array', current], id];
|
|
504
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Int8Array"');
|
|
505
|
+
return [SerovalNodeType.TypedArray, ['Int8Array', current.toString(), current.byteOffset], id];
|
|
487
506
|
}
|
|
488
507
|
if (constructorCheck<Int16Array>(current, Int16Array)) {
|
|
489
|
-
assert(ctx.features
|
|
490
|
-
return [SerovalNodeType.TypedArray, ['Int16Array', current], id];
|
|
508
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Int16Array"');
|
|
509
|
+
return [SerovalNodeType.TypedArray, ['Int16Array', current.toString(), current.byteOffset], id];
|
|
491
510
|
}
|
|
492
511
|
if (constructorCheck<Int32Array>(current, Int32Array)) {
|
|
493
|
-
assert(ctx.features
|
|
494
|
-
return [SerovalNodeType.TypedArray, ['Int32Array', current], id];
|
|
512
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Int32Array"');
|
|
513
|
+
return [SerovalNodeType.TypedArray, ['Int32Array', current.toString(), current.byteOffset], id];
|
|
495
514
|
}
|
|
496
515
|
if (constructorCheck<Uint8Array>(current, Uint8Array)) {
|
|
497
|
-
assert(ctx.features
|
|
498
|
-
return [SerovalNodeType.TypedArray, ['Uint8Array', current], id];
|
|
516
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint8Array"');
|
|
517
|
+
return [SerovalNodeType.TypedArray, ['Uint8Array', current.toString(), current.byteOffset], id];
|
|
499
518
|
}
|
|
500
519
|
if (constructorCheck<Uint16Array>(current, Uint16Array)) {
|
|
501
|
-
assert(ctx.features
|
|
502
|
-
return [SerovalNodeType.TypedArray, ['Uint16Array', current], id];
|
|
520
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint16Array"');
|
|
521
|
+
return [SerovalNodeType.TypedArray, ['Uint16Array', current.toString(), current.byteOffset], id];
|
|
503
522
|
}
|
|
504
523
|
if (constructorCheck<Uint32Array>(current, Uint32Array)) {
|
|
505
|
-
assert(ctx.features
|
|
506
|
-
return [SerovalNodeType.TypedArray, ['Uint32Array', current], id];
|
|
524
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint32Array"');
|
|
525
|
+
return [SerovalNodeType.TypedArray, ['Uint32Array', current.toString(), current.byteOffset], id];
|
|
507
526
|
}
|
|
508
527
|
if (constructorCheck<Uint8ClampedArray>(current, Uint8ClampedArray)) {
|
|
509
|
-
assert(ctx.features
|
|
510
|
-
return [SerovalNodeType.TypedArray, ['Uint8ClampedArray', current], id];
|
|
528
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint8ClampedArray"');
|
|
529
|
+
return [SerovalNodeType.TypedArray, ['Uint8ClampedArray', current.toString(), current.byteOffset], id];
|
|
511
530
|
}
|
|
512
531
|
if (constructorCheck<Float32Array>(current, Float32Array)) {
|
|
513
|
-
assert(ctx.features
|
|
514
|
-
return [SerovalNodeType.TypedArray, ['Float32Array', current], id];
|
|
532
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Float32Array"');
|
|
533
|
+
return [SerovalNodeType.TypedArray, ['Float32Array', current.toString(), current.byteOffset], id];
|
|
515
534
|
}
|
|
516
535
|
if (constructorCheck<Float64Array>(current, Float64Array)) {
|
|
517
|
-
assert(ctx.features
|
|
518
|
-
return [SerovalNodeType.TypedArray, ['Float64Array', current], id];
|
|
536
|
+
assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Float64Array"');
|
|
537
|
+
return [SerovalNodeType.TypedArray, ['Float64Array', current.toString(), current.byteOffset], id];
|
|
519
538
|
}
|
|
520
539
|
if (constructorCheck<BigInt64Array>(current, BigInt64Array)) {
|
|
521
540
|
assert(
|
|
522
|
-
ctx.features
|
|
523
|
-
&& ctx.features.has(Feature.BigInt),
|
|
541
|
+
ctx.features & (Feature.BigIntTypedArray),
|
|
524
542
|
'Unsupported value type "BigInt64Array"',
|
|
525
543
|
);
|
|
526
|
-
|
|
544
|
+
let result = '';
|
|
545
|
+
const cap = current.length - 1;
|
|
546
|
+
for (let i = 0; i < cap; i++) {
|
|
547
|
+
result += `${current[i]}n,`;
|
|
548
|
+
}
|
|
549
|
+
result += `"${current[cap]}"`;
|
|
550
|
+
return [
|
|
551
|
+
SerovalNodeType.BigIntTypedArray,
|
|
552
|
+
['BigInt64Array', result, current.byteOffset],
|
|
553
|
+
id,
|
|
554
|
+
];
|
|
527
555
|
}
|
|
528
556
|
if (constructorCheck<BigUint64Array>(current, BigUint64Array)) {
|
|
529
557
|
assert(
|
|
530
|
-
ctx.features
|
|
531
|
-
&& ctx.features.has(Feature.BigInt),
|
|
558
|
+
ctx.features & (Feature.BigIntTypedArray),
|
|
532
559
|
'Unsupported value type "BigUint64Array"',
|
|
533
560
|
);
|
|
534
|
-
|
|
561
|
+
let result = '';
|
|
562
|
+
const cap = current.length - 1;
|
|
563
|
+
for (let i = 0; i < cap; i++) {
|
|
564
|
+
result += `"${current[i]}n",`;
|
|
565
|
+
}
|
|
566
|
+
result += `"${current[cap]}"`;
|
|
567
|
+
return [
|
|
568
|
+
SerovalNodeType.BigIntTypedArray,
|
|
569
|
+
['BigInt64Array', result, current.byteOffset],
|
|
570
|
+
id,
|
|
571
|
+
];
|
|
535
572
|
}
|
|
536
573
|
return undefined;
|
|
537
574
|
}
|
|
538
575
|
|
|
539
576
|
function serializePropertiesSync(
|
|
540
|
-
ctx:
|
|
577
|
+
ctx: ParserContext,
|
|
541
578
|
properties: Record<string, unknown>,
|
|
542
579
|
): SerovalDictionaryNode {
|
|
543
|
-
const keyNodes: string[] = [];
|
|
544
|
-
const valueNodes: SerovalNode[] = [];
|
|
545
|
-
const deferredKeys: string[] = [];
|
|
546
|
-
const deferredValues: ServerValue[] = [];
|
|
547
580
|
const keys = Object.keys(properties);
|
|
581
|
+
const size = keys.length;
|
|
582
|
+
const keyNodes = new Array<string>(size);
|
|
583
|
+
const valueNodes = new Array<SerovalNode>(size);
|
|
584
|
+
const deferredKeys = new Array<string>(size);
|
|
585
|
+
const deferredValues = new Array<ServerValue>(size);
|
|
548
586
|
let deferredSize = 0;
|
|
587
|
+
let nodesSize = 0;
|
|
549
588
|
for (const key of keys) {
|
|
550
589
|
if (isIterable(properties[key])) {
|
|
551
|
-
deferredKeys
|
|
552
|
-
deferredValues
|
|
590
|
+
deferredKeys[deferredSize] = key;
|
|
591
|
+
deferredValues[deferredSize] = properties[key] as ServerValue;
|
|
553
592
|
deferredSize++;
|
|
554
593
|
} else {
|
|
555
|
-
keyNodes
|
|
556
|
-
valueNodes
|
|
594
|
+
keyNodes[nodesSize] = key;
|
|
595
|
+
valueNodes[nodesSize] = generateTreeSync(ctx, properties[key] as ServerValue);
|
|
596
|
+
nodesSize++;
|
|
557
597
|
}
|
|
558
598
|
}
|
|
559
599
|
for (let i = 0; i < deferredSize; i++) {
|
|
560
|
-
keyNodes
|
|
561
|
-
valueNodes
|
|
600
|
+
keyNodes[nodesSize + i] = deferredKeys[i];
|
|
601
|
+
valueNodes[nodesSize + i] = generateTreeSync(ctx, deferredValues[i]);
|
|
562
602
|
}
|
|
563
|
-
return [keyNodes, valueNodes,
|
|
603
|
+
return [keyNodes, valueNodes, size];
|
|
564
604
|
}
|
|
565
605
|
|
|
566
|
-
|
|
567
|
-
ctx:
|
|
606
|
+
function generateTreeSync(
|
|
607
|
+
ctx: ParserContext,
|
|
568
608
|
current: ServerValue,
|
|
569
609
|
): SerovalNode {
|
|
570
610
|
if (isPrimitive(current)) {
|
|
571
|
-
return [SerovalNodeType.Primitive, current];
|
|
611
|
+
return [SerovalNodeType.Primitive, serializePrimitive(current)];
|
|
572
612
|
}
|
|
573
613
|
// Non-primitive values needs a reference ID
|
|
574
614
|
// mostly because the values themselves are stateful
|
|
@@ -581,46 +621,52 @@ export function generateTreeSync(
|
|
|
581
621
|
return semiPrimitive;
|
|
582
622
|
}
|
|
583
623
|
if (constructorCheck<Set<ServerValue>>(current, Set)) {
|
|
584
|
-
assert(ctx.features
|
|
585
|
-
const
|
|
586
|
-
const
|
|
624
|
+
assert(ctx.features & Feature.Set, 'Unsupported type "Set"');
|
|
625
|
+
const len = current.size;
|
|
626
|
+
const nodes = new Array<SerovalNode>(len);
|
|
627
|
+
const deferred = new Array<ServerValue>(len);
|
|
628
|
+
let deferredSize = 0;
|
|
629
|
+
let nodeSize = 0;
|
|
587
630
|
for (const item of current.keys()) {
|
|
588
631
|
// Iterables are lazy, so the evaluation must be deferred
|
|
589
632
|
if (isIterable(item)) {
|
|
590
|
-
deferred
|
|
633
|
+
deferred[deferredSize++] = item;
|
|
591
634
|
} else {
|
|
592
|
-
nodes
|
|
635
|
+
nodes[nodeSize++] = generateTreeSync(ctx, item);
|
|
593
636
|
}
|
|
594
637
|
}
|
|
595
638
|
// Parse deferred items
|
|
596
|
-
for (let i = 0
|
|
597
|
-
nodes
|
|
639
|
+
for (let i = 0; i < deferredSize; i++) {
|
|
640
|
+
nodes[nodeSize + i] = generateTreeSync(ctx, deferred[i]);
|
|
598
641
|
}
|
|
599
642
|
return [SerovalNodeType.Set, nodes, id];
|
|
600
643
|
}
|
|
601
644
|
if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) {
|
|
602
|
-
assert(ctx.features
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const
|
|
645
|
+
assert(ctx.features & Feature.Map, 'Unsupported type "Map"');
|
|
646
|
+
const len = current.size;
|
|
647
|
+
const keyNodes = new Array<SerovalNode>(len);
|
|
648
|
+
const valueNodes = new Array<SerovalNode>(len);
|
|
649
|
+
const deferredKey = new Array<ServerValue>(len);
|
|
650
|
+
const deferredValue = new Array<ServerValue>(len);
|
|
607
651
|
let deferredSize = 0;
|
|
652
|
+
let nodeSize = 0;
|
|
608
653
|
for (const [key, value] of current.entries()) {
|
|
609
654
|
// Either key or value might be an iterable
|
|
610
655
|
if (isIterable(key) || isIterable(value)) {
|
|
611
|
-
deferredKey
|
|
612
|
-
deferredValue
|
|
656
|
+
deferredKey[deferredSize] = key;
|
|
657
|
+
deferredValue[deferredSize] = value;
|
|
613
658
|
deferredSize++;
|
|
614
659
|
} else {
|
|
615
|
-
keyNodes
|
|
616
|
-
valueNodes
|
|
660
|
+
keyNodes[nodeSize] = generateTreeSync(ctx, key);
|
|
661
|
+
valueNodes[nodeSize] = generateTreeSync(ctx, value);
|
|
662
|
+
nodeSize++;
|
|
617
663
|
}
|
|
618
664
|
}
|
|
619
665
|
for (let i = 0; i < deferredSize; i++) {
|
|
620
|
-
keyNodes
|
|
621
|
-
valueNodes
|
|
666
|
+
keyNodes[nodeSize + i] = generateTreeSync(ctx, deferredKey[i]);
|
|
667
|
+
valueNodes[nodeSize + i] = generateTreeSync(ctx, deferredValue[i]);
|
|
622
668
|
}
|
|
623
|
-
return [SerovalNodeType.Map, [keyNodes, valueNodes,
|
|
669
|
+
return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
|
|
624
670
|
}
|
|
625
671
|
if (Array.isArray(current)) {
|
|
626
672
|
const size = current.length;
|
|
@@ -642,7 +688,7 @@ export function generateTreeSync(
|
|
|
642
688
|
}
|
|
643
689
|
return [SerovalNodeType.Array, nodes, id];
|
|
644
690
|
}
|
|
645
|
-
if (current instanceof AggregateError && ctx.features
|
|
691
|
+
if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
|
|
646
692
|
const options = getErrorOptions(current);
|
|
647
693
|
const optionsNode = options
|
|
648
694
|
? serializePropertiesSync(ctx, options)
|
|
@@ -662,7 +708,7 @@ export function generateTreeSync(
|
|
|
662
708
|
];
|
|
663
709
|
}
|
|
664
710
|
if (isIterable(current)) {
|
|
665
|
-
assert(ctx.features
|
|
711
|
+
assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
|
|
666
712
|
const options = getIterableOptions(current);
|
|
667
713
|
return [SerovalNodeType.Iterable, [
|
|
668
714
|
// Parse options first before the items
|
|
@@ -681,39 +727,50 @@ export function generateTreeSync(
|
|
|
681
727
|
throw new Error('Unsupported value');
|
|
682
728
|
}
|
|
683
729
|
|
|
730
|
+
export function parseSync(
|
|
731
|
+
ctx: ParserContext,
|
|
732
|
+
current: ServerValue,
|
|
733
|
+
) {
|
|
734
|
+
const result = generateTreeSync(ctx, current);
|
|
735
|
+
return [result, createRef(ctx, current), result[0] === SerovalNodeType.Object] as const;
|
|
736
|
+
}
|
|
737
|
+
|
|
684
738
|
async function serializePropertiesAsync(
|
|
685
|
-
ctx:
|
|
739
|
+
ctx: ParserContext,
|
|
686
740
|
properties: Record<string, unknown>,
|
|
687
741
|
): Promise<SerovalDictionaryNode> {
|
|
688
|
-
const keyNodes: string[] = [];
|
|
689
|
-
const valueNodes: SerovalNode[] = [];
|
|
690
|
-
const deferredKeys: string[] = [];
|
|
691
|
-
const deferredValues: ServerValue[] = [];
|
|
692
742
|
const keys = Object.keys(properties);
|
|
743
|
+
const size = keys.length;
|
|
744
|
+
const keyNodes = new Array<string>(size);
|
|
745
|
+
const valueNodes = new Array<SerovalNode>(size);
|
|
746
|
+
const deferredKeys = new Array<string>(size);
|
|
747
|
+
const deferredValues = new Array<ServerValue>(size);
|
|
693
748
|
let deferredSize = 0;
|
|
749
|
+
let nodesSize = 0;
|
|
694
750
|
for (const key of keys) {
|
|
695
751
|
if (isIterable(properties[key])) {
|
|
696
|
-
deferredKeys
|
|
697
|
-
deferredValues
|
|
752
|
+
deferredKeys[deferredSize] = key;
|
|
753
|
+
deferredValues[deferredSize] = properties[key] as ServerValue;
|
|
698
754
|
deferredSize++;
|
|
699
755
|
} else {
|
|
700
|
-
keyNodes
|
|
701
|
-
valueNodes
|
|
756
|
+
keyNodes[nodesSize] = key;
|
|
757
|
+
valueNodes[nodesSize] = await generateTreeAsync(ctx, properties[key] as ServerValue);
|
|
758
|
+
nodesSize++;
|
|
702
759
|
}
|
|
703
760
|
}
|
|
704
761
|
for (let i = 0; i < deferredSize; i++) {
|
|
705
|
-
keyNodes
|
|
706
|
-
valueNodes
|
|
762
|
+
keyNodes[nodesSize + i] = deferredKeys[i];
|
|
763
|
+
valueNodes[nodesSize + i] = await generateTreeAsync(ctx, deferredValues[i]);
|
|
707
764
|
}
|
|
708
|
-
return [keyNodes, valueNodes,
|
|
765
|
+
return [keyNodes, valueNodes, size];
|
|
709
766
|
}
|
|
710
767
|
|
|
711
|
-
|
|
712
|
-
ctx:
|
|
768
|
+
async function generateTreeAsync(
|
|
769
|
+
ctx: ParserContext,
|
|
713
770
|
current: AsyncServerValue,
|
|
714
771
|
): Promise<SerovalNode> {
|
|
715
772
|
if (isPrimitive(current)) {
|
|
716
|
-
return [SerovalNodeType.Primitive, current];
|
|
773
|
+
return [SerovalNodeType.Primitive, serializePrimitive(current)];
|
|
717
774
|
}
|
|
718
775
|
const id = generateRef(ctx, current);
|
|
719
776
|
if (Array.isArray(id)) {
|
|
@@ -724,7 +781,7 @@ export async function generateTreeAsync(
|
|
|
724
781
|
return semiPrimitive;
|
|
725
782
|
}
|
|
726
783
|
if (isPromise(current)) {
|
|
727
|
-
assert(ctx.features
|
|
784
|
+
assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
|
|
728
785
|
return current.then(async (value) => [
|
|
729
786
|
SerovalNodeType.Promise,
|
|
730
787
|
await generateTreeAsync(ctx, value),
|
|
@@ -732,43 +789,52 @@ export async function generateTreeAsync(
|
|
|
732
789
|
]);
|
|
733
790
|
}
|
|
734
791
|
if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
|
|
735
|
-
assert(ctx.features
|
|
736
|
-
const
|
|
737
|
-
const
|
|
792
|
+
assert(ctx.features & Feature.Set, 'Unsupported type "Set"');
|
|
793
|
+
const len = current.size;
|
|
794
|
+
const nodes = new Array<SerovalNode>(len);
|
|
795
|
+
const deferred = new Array<AsyncServerValue>(len);
|
|
796
|
+
let deferredSize = 0;
|
|
797
|
+
let nodeSize = 0;
|
|
738
798
|
for (const item of current.keys()) {
|
|
799
|
+
// Iterables are lazy, so the evaluation must be deferred
|
|
739
800
|
if (isIterable(item)) {
|
|
740
|
-
deferred
|
|
801
|
+
deferred[deferredSize++] = item;
|
|
741
802
|
} else {
|
|
742
|
-
nodes
|
|
803
|
+
nodes[nodeSize++] = await generateTreeAsync(ctx, item);
|
|
743
804
|
}
|
|
744
805
|
}
|
|
745
|
-
|
|
746
|
-
|
|
806
|
+
// Parse deferred items
|
|
807
|
+
for (let i = 0; i < deferredSize; i++) {
|
|
808
|
+
nodes[nodeSize + i] = await generateTreeAsync(ctx, deferred[i]);
|
|
747
809
|
}
|
|
748
810
|
return [SerovalNodeType.Set, nodes, id];
|
|
749
811
|
}
|
|
750
812
|
if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
|
|
751
|
-
assert(ctx.features
|
|
752
|
-
const
|
|
753
|
-
const
|
|
754
|
-
const
|
|
755
|
-
const
|
|
813
|
+
assert(ctx.features & Feature.Map, 'Unsupported type "Map"');
|
|
814
|
+
const len = current.size;
|
|
815
|
+
const keyNodes = new Array<SerovalNode>(len);
|
|
816
|
+
const valueNodes = new Array<SerovalNode>(len);
|
|
817
|
+
const deferredKey = new Array<AsyncServerValue>(len);
|
|
818
|
+
const deferredValue = new Array<AsyncServerValue>(len);
|
|
756
819
|
let deferredSize = 0;
|
|
820
|
+
let nodeSize = 0;
|
|
757
821
|
for (const [key, value] of current.entries()) {
|
|
822
|
+
// Either key or value might be an iterable
|
|
758
823
|
if (isIterable(key) || isIterable(value)) {
|
|
759
|
-
deferredKey
|
|
760
|
-
deferredValue
|
|
824
|
+
deferredKey[deferredSize] = key;
|
|
825
|
+
deferredValue[deferredSize] = value;
|
|
761
826
|
deferredSize++;
|
|
762
827
|
} else {
|
|
763
|
-
keyNodes
|
|
764
|
-
valueNodes
|
|
828
|
+
keyNodes[nodeSize] = await generateTreeAsync(ctx, key);
|
|
829
|
+
valueNodes[nodeSize] = await generateTreeAsync(ctx, value);
|
|
830
|
+
nodeSize++;
|
|
765
831
|
}
|
|
766
832
|
}
|
|
767
833
|
for (let i = 0; i < deferredSize; i++) {
|
|
768
|
-
keyNodes
|
|
769
|
-
valueNodes
|
|
834
|
+
keyNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredKey[i]);
|
|
835
|
+
valueNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredValue[i]);
|
|
770
836
|
}
|
|
771
|
-
return [SerovalNodeType.Map, [keyNodes, valueNodes,
|
|
837
|
+
return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
|
|
772
838
|
}
|
|
773
839
|
if (Array.isArray(current)) {
|
|
774
840
|
const size = current.length;
|
|
@@ -791,7 +857,7 @@ export async function generateTreeAsync(
|
|
|
791
857
|
}
|
|
792
858
|
return [SerovalNodeType.Array, nodes, id];
|
|
793
859
|
}
|
|
794
|
-
if (current instanceof AggregateError) {
|
|
860
|
+
if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
|
|
795
861
|
const options = getErrorOptions(current);
|
|
796
862
|
const optionsNode = options
|
|
797
863
|
? await serializePropertiesAsync(ctx, options)
|
|
@@ -811,7 +877,7 @@ export async function generateTreeAsync(
|
|
|
811
877
|
];
|
|
812
878
|
}
|
|
813
879
|
if (isIterable(current)) {
|
|
814
|
-
assert(ctx.features
|
|
880
|
+
assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
|
|
815
881
|
const options = getIterableOptions(current);
|
|
816
882
|
return [SerovalNodeType.Iterable, [
|
|
817
883
|
options
|
|
@@ -831,10 +897,17 @@ export async function generateTreeAsync(
|
|
|
831
897
|
throw new Error('Unsupported value');
|
|
832
898
|
}
|
|
833
899
|
|
|
900
|
+
export async function parseAsync(
|
|
901
|
+
ctx: ParserContext,
|
|
902
|
+
current: AsyncServerValue,
|
|
903
|
+
) {
|
|
904
|
+
const result = await generateTreeAsync(ctx, current);
|
|
905
|
+
return [result, createRef(ctx, current), result[0] === SerovalNodeType.Object] as const;
|
|
906
|
+
}
|
|
907
|
+
|
|
834
908
|
export function serializePrimitive(
|
|
835
|
-
ctx: SerializationContext,
|
|
836
909
|
value: PrimitiveValue,
|
|
837
|
-
): string {
|
|
910
|
+
): string | number | null {
|
|
838
911
|
// Shortened forms
|
|
839
912
|
if (value === true) {
|
|
840
913
|
return '!0';
|
|
@@ -846,11 +919,7 @@ export function serializePrimitive(
|
|
|
846
919
|
return 'void 0';
|
|
847
920
|
}
|
|
848
921
|
if (value === null) {
|
|
849
|
-
return
|
|
850
|
-
}
|
|
851
|
-
if (typeof value === 'bigint') {
|
|
852
|
-
assert(ctx.features.has(Feature.BigInt), 'Unsupported type "BigInt"');
|
|
853
|
-
return `${value}n`;
|
|
922
|
+
return null;
|
|
854
923
|
}
|
|
855
924
|
if (typeof value === 'string') {
|
|
856
925
|
return quote(value);
|
|
@@ -865,7 +934,7 @@ export function serializePrimitive(
|
|
|
865
934
|
if (Object.is(value, -Infinity)) {
|
|
866
935
|
return '-1/0';
|
|
867
936
|
}
|
|
868
|
-
return
|
|
937
|
+
return value;
|
|
869
938
|
}
|
|
870
939
|
|
|
871
940
|
const IDENTIFIER_CHECK = /^([$A-Z_][0-9A-Z_$]*)$/i;
|
|
@@ -884,22 +953,18 @@ function serializeAssignments(
|
|
|
884
953
|
ctx.stack = parentStack;
|
|
885
954
|
const key = keys[i];
|
|
886
955
|
const check = Number(key);
|
|
887
|
-
// Test if key is a valid number or JS identifier
|
|
888
|
-
// so that we don't have to serialize the key and wrap with brackets
|
|
889
956
|
const parentAssignment = ctx.assignments;
|
|
890
957
|
ctx.assignments = mainAssignments;
|
|
891
|
-
if
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
createObjectComputedAssign(ctx, targetRef, quote(key), refParam);
|
|
902
|
-
}
|
|
958
|
+
// Test if key is a valid number or JS identifier
|
|
959
|
+
// so that we don't have to serialize the key and wrap with brackets
|
|
960
|
+
const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
|
|
961
|
+
createObjectAssign(
|
|
962
|
+
ctx,
|
|
963
|
+
targetRef,
|
|
964
|
+
isIdentifier ? key : quote(key),
|
|
965
|
+
refParam,
|
|
966
|
+
!(isIdentifier && Number.isNaN(check)),
|
|
967
|
+
);
|
|
903
968
|
ctx.assignments = parentAssignment;
|
|
904
969
|
}
|
|
905
970
|
ctx.stack.pop();
|
|
@@ -919,25 +984,19 @@ function serializeObject(
|
|
|
919
984
|
const check = Number(key);
|
|
920
985
|
// Test if key is a valid number or JS identifier
|
|
921
986
|
// so that we don't have to serialize the key and wrap with brackets
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
) {
|
|
926
|
-
if (isReferenceInStack(ctx, val)) {
|
|
927
|
-
const refParam = getRefParam(ctx, val[1]);
|
|
928
|
-
if (!Number.isNaN(check)) {
|
|
929
|
-
createObjectComputedAssign(ctx, sourceID, key, refParam);
|
|
930
|
-
} else {
|
|
931
|
-
createObjectIdentifierAssign(ctx, sourceID, key, refParam);
|
|
932
|
-
}
|
|
933
|
-
} else {
|
|
934
|
-
result += `${key}:${serializeTree(ctx, val)},`;
|
|
935
|
-
}
|
|
936
|
-
} else if (isReferenceInStack(ctx, val)) {
|
|
987
|
+
const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
|
|
988
|
+
const validKey = isIdentifier ? key : quote(key);
|
|
989
|
+
if (isReferenceInStack(ctx, val)) {
|
|
937
990
|
const refParam = getRefParam(ctx, val[1]);
|
|
938
|
-
|
|
991
|
+
createObjectAssign(
|
|
992
|
+
ctx,
|
|
993
|
+
sourceID,
|
|
994
|
+
validKey,
|
|
995
|
+
refParam,
|
|
996
|
+
!(isIdentifier && Number.isNaN(check)),
|
|
997
|
+
);
|
|
939
998
|
} else {
|
|
940
|
-
result += `${
|
|
999
|
+
result += `${validKey}:${serializeTree(ctx, val)},`;
|
|
941
1000
|
}
|
|
942
1001
|
}
|
|
943
1002
|
ctx.stack.pop();
|
|
@@ -950,7 +1009,7 @@ export function serializeTree(
|
|
|
950
1009
|
): string {
|
|
951
1010
|
switch (type) {
|
|
952
1011
|
case SerovalNodeType.Primitive:
|
|
953
|
-
return
|
|
1012
|
+
return String(value);
|
|
954
1013
|
case SerovalNodeType.Reference:
|
|
955
1014
|
return getRefParam(ctx, value);
|
|
956
1015
|
case SerovalNodeType.Promise: {
|
|
@@ -971,19 +1030,17 @@ export function serializeTree(
|
|
|
971
1030
|
}
|
|
972
1031
|
return assignRef(ctx, id, serialized);
|
|
973
1032
|
}
|
|
1033
|
+
case SerovalNodeType.BigInt:
|
|
1034
|
+
return value;
|
|
974
1035
|
case SerovalNodeType.Date:
|
|
975
|
-
return assignRef(ctx, id, `new Date("${value
|
|
1036
|
+
return assignRef(ctx, id, `new Date("${value}")`);
|
|
976
1037
|
case SerovalNodeType.RegExp:
|
|
977
|
-
return assignRef(ctx, id,
|
|
1038
|
+
return assignRef(ctx, id, value);
|
|
1039
|
+
case SerovalNodeType.BigIntTypedArray:
|
|
978
1040
|
case SerovalNodeType.TypedArray: {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
values += `${serializePrimitive(ctx, value[1][i])},`;
|
|
983
|
-
}
|
|
984
|
-
let args = values ? `[${values.substring(0, values.length - 1)}]` : '[]';
|
|
985
|
-
if (value[1].byteOffset !== 0) {
|
|
986
|
-
args += `,${value[1].byteOffset}`;
|
|
1041
|
+
let args = `[${value[1]}]`;
|
|
1042
|
+
if (value[2] !== 0) {
|
|
1043
|
+
args += `,${value[2]}`;
|
|
987
1044
|
}
|
|
988
1045
|
return assignRef(ctx, id, `new ${value[0]}(${args})`);
|
|
989
1046
|
}
|
|
@@ -1072,7 +1129,7 @@ export function serializeTree(
|
|
|
1072
1129
|
for (let i = 0; i < size; i++) {
|
|
1073
1130
|
const item = value[i];
|
|
1074
1131
|
// Check if index is a hole
|
|
1075
|
-
if (
|
|
1132
|
+
if (item) {
|
|
1076
1133
|
// Check if item is a parent
|
|
1077
1134
|
if (isReferenceInStack(ctx, item)) {
|
|
1078
1135
|
createArrayAssign(ctx, id, i, getRefParam(ctx, item[1]));
|
|
@@ -1100,7 +1157,7 @@ export function serializeTree(
|
|
|
1100
1157
|
// either through class or custom properties
|
|
1101
1158
|
// Make sure to assign extra properties
|
|
1102
1159
|
if (value[1]) {
|
|
1103
|
-
if (ctx.features
|
|
1160
|
+
if (ctx.features & Feature.ObjectAssign) {
|
|
1104
1161
|
const options = serializeObject(ctx, id, value[1]);
|
|
1105
1162
|
serialized = `Object.assign(${serialized},${options})`;
|
|
1106
1163
|
} else {
|
|
@@ -1117,7 +1174,7 @@ export function serializeTree(
|
|
|
1117
1174
|
case SerovalNodeType.Error: {
|
|
1118
1175
|
let serialized = `new ${value[0]}(${quote(value[1])})`;
|
|
1119
1176
|
if (value[2]) {
|
|
1120
|
-
if (ctx.features
|
|
1177
|
+
if (ctx.features & Feature.ObjectAssign) {
|
|
1121
1178
|
const options = serializeObject(ctx, id, value[2]);
|
|
1122
1179
|
serialized = `Object.assign(${serialized},${options})`;
|
|
1123
1180
|
} else {
|
|
@@ -1137,20 +1194,20 @@ export function serializeTree(
|
|
|
1137
1194
|
const values = serializeTree(ctx, value[1]);
|
|
1138
1195
|
ctx.stack = parent;
|
|
1139
1196
|
let serialized: string;
|
|
1140
|
-
if (ctx.features
|
|
1197
|
+
if (ctx.features & Feature.ArrayPrototypeValues) {
|
|
1141
1198
|
serialized = `${values}.values()`;
|
|
1142
1199
|
} else {
|
|
1143
1200
|
serialized = `${values}[Symbol.iterator]()`;
|
|
1144
1201
|
}
|
|
1145
|
-
if (ctx.features
|
|
1202
|
+
if (ctx.features & Feature.ArrowFunction) {
|
|
1146
1203
|
serialized = `{[Symbol.iterator]:()=>${serialized}}`;
|
|
1147
|
-
} else if (ctx.features
|
|
1204
|
+
} else if (ctx.features & Feature.MethodShorthand) {
|
|
1148
1205
|
serialized = `{[Symbol.iterator](){return ${serialized}}}`;
|
|
1149
1206
|
} else {
|
|
1150
1207
|
serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`;
|
|
1151
1208
|
}
|
|
1152
1209
|
if (value[0]) {
|
|
1153
|
-
if (ctx.features
|
|
1210
|
+
if (ctx.features & Feature.ObjectAssign) {
|
|
1154
1211
|
const options = serializeObject(ctx, id, value[0]);
|
|
1155
1212
|
serialized = `Object.assign(${serialized},${options})`;
|
|
1156
1213
|
} else {
|
|
@@ -1166,7 +1223,7 @@ export function serializeTree(
|
|
|
1166
1223
|
}
|
|
1167
1224
|
case SerovalNodeType.NullConstructor: {
|
|
1168
1225
|
let serialized = 'Object.create(null)';
|
|
1169
|
-
if (ctx.features
|
|
1226
|
+
if (ctx.features & Feature.ObjectAssign) {
|
|
1170
1227
|
const fields = serializeObject(ctx, id, value);
|
|
1171
1228
|
if (fields !== '{}') {
|
|
1172
1229
|
serialized = `Object.assign(${serialized},${fields})`;
|