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/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: boolean[];
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: Set<Feature>;
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 createSerializationContext(options: Partial<Options> = {}): SerializationContext {
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: parseTargets(result.target),
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[current] = true;
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[index]) {
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 createObjectIdentifierAssign(
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
- createAssignment(ctx, `${getRefParam(ctx, ref)}.${key}`, value);
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.Date, value: Date, id: number]
382
- | [type: SerovalNodeType.RegExp, value: RegExp, id: number]
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: TypedArrayValue],
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: SerializationContext,
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: SerializationContext,
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.has(Feature.TypedArray), 'Unsupported value type "Int8Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Int16Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Int32Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Uint8Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Uint16Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Uint32Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Uint8ClampedArray"');
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.has(Feature.TypedArray), 'Unsupported value type "Float32Array"');
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.has(Feature.TypedArray), 'Unsupported value type "Float64Array"');
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.has(Feature.TypedArray)
523
- && ctx.features.has(Feature.BigInt),
541
+ ctx.features & (Feature.BigIntTypedArray),
524
542
  'Unsupported value type "BigInt64Array"',
525
543
  );
526
- return [SerovalNodeType.TypedArray, ['BigInt64Array', current], id];
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.has(Feature.TypedArray)
531
- && ctx.features.has(Feature.BigInt),
558
+ ctx.features & (Feature.BigIntTypedArray),
532
559
  'Unsupported value type "BigUint64Array"',
533
560
  );
534
- return [SerovalNodeType.TypedArray, ['BigUint64Array', current], id];
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: SerializationContext,
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.push(key);
552
- deferredValues.push(properties[key] as ServerValue);
590
+ deferredKeys[deferredSize] = key;
591
+ deferredValues[deferredSize] = properties[key] as ServerValue;
553
592
  deferredSize++;
554
593
  } else {
555
- keyNodes.push(key);
556
- valueNodes.push(generateTreeSync(ctx, properties[key] as ServerValue));
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.push(deferredKeys[i]);
561
- valueNodes.push(generateTreeSync(ctx, deferredValues[i]));
600
+ keyNodes[nodesSize + i] = deferredKeys[i];
601
+ valueNodes[nodesSize + i] = generateTreeSync(ctx, deferredValues[i]);
562
602
  }
563
- return [keyNodes, valueNodes, keys.length];
603
+ return [keyNodes, valueNodes, size];
564
604
  }
565
605
 
566
- export function generateTreeSync(
567
- ctx: SerializationContext,
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.has(Feature.Set), 'Unsupported type "Set"');
585
- const nodes: SerovalNode[] = [];
586
- const deferred: ServerValue[] = [];
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.push(item);
633
+ deferred[deferredSize++] = item;
591
634
  } else {
592
- nodes.push(generateTreeSync(ctx, item));
635
+ nodes[nodeSize++] = generateTreeSync(ctx, item);
593
636
  }
594
637
  }
595
638
  // Parse deferred items
596
- for (let i = 0, len = deferred.length; i < len; i++) {
597
- nodes.push(generateTreeSync(ctx, deferred[i]));
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.has(Feature.Map), 'Unsupported type "Map"');
603
- const keyNodes: SerovalNode[] = [];
604
- const valueNodes: SerovalNode[] = [];
605
- const deferredKey: ServerValue[] = [];
606
- const deferredValue: ServerValue[] = [];
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.push(key);
612
- deferredValue.push(value);
656
+ deferredKey[deferredSize] = key;
657
+ deferredValue[deferredSize] = value;
613
658
  deferredSize++;
614
659
  } else {
615
- keyNodes.push(generateTreeSync(ctx, key));
616
- valueNodes.push(generateTreeSync(ctx, value));
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.push(generateTreeSync(ctx, deferredKey[i]));
621
- valueNodes.push(generateTreeSync(ctx, deferredValue[i]));
666
+ keyNodes[nodeSize + i] = generateTreeSync(ctx, deferredKey[i]);
667
+ valueNodes[nodeSize + i] = generateTreeSync(ctx, deferredValue[i]);
622
668
  }
623
- return [SerovalNodeType.Map, [keyNodes, valueNodes, current.size], id];
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.has(Feature.AggregateError)) {
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.has(Feature.SymbolIterator), 'Unsupported type "Iterable"');
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: SerializationContext,
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.push(key);
697
- deferredValues.push(properties[key] as ServerValue);
752
+ deferredKeys[deferredSize] = key;
753
+ deferredValues[deferredSize] = properties[key] as ServerValue;
698
754
  deferredSize++;
699
755
  } else {
700
- keyNodes.push(key);
701
- valueNodes.push(await generateTreeAsync(ctx, properties[key] as ServerValue));
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.push(deferredKeys[i]);
706
- valueNodes.push(await generateTreeAsync(ctx, deferredValues[i]));
762
+ keyNodes[nodesSize + i] = deferredKeys[i];
763
+ valueNodes[nodesSize + i] = await generateTreeAsync(ctx, deferredValues[i]);
707
764
  }
708
- return [keyNodes, valueNodes, keys.length];
765
+ return [keyNodes, valueNodes, size];
709
766
  }
710
767
 
711
- export async function generateTreeAsync(
712
- ctx: SerializationContext,
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.has(Feature.Promise), 'Unsupported type "Promise"');
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.has(Feature.Set), 'Unsupported type "Set"');
736
- const nodes: SerovalNode[] = [];
737
- const deferred: AsyncServerValue[] = [];
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.push(item);
801
+ deferred[deferredSize++] = item;
741
802
  } else {
742
- nodes.push(await generateTreeAsync(ctx, item));
803
+ nodes[nodeSize++] = await generateTreeAsync(ctx, item);
743
804
  }
744
805
  }
745
- for (let i = 0, len = deferred.length; i < len; i++) {
746
- nodes.push(await generateTreeAsync(ctx, deferred[i]));
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.has(Feature.Map), 'Unsupported type "Map"');
752
- const keyNodes: SerovalNode[] = [];
753
- const valueNodes: SerovalNode[] = [];
754
- const deferredKey: AsyncServerValue[] = [];
755
- const deferredValue: AsyncServerValue[] = [];
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.push(key);
760
- deferredValue.push(value);
824
+ deferredKey[deferredSize] = key;
825
+ deferredValue[deferredSize] = value;
761
826
  deferredSize++;
762
827
  } else {
763
- keyNodes.push(await generateTreeAsync(ctx, key));
764
- valueNodes.push(await generateTreeAsync(ctx, value));
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.push(await generateTreeAsync(ctx, deferredKey[i]));
769
- valueNodes.push(await generateTreeAsync(ctx, deferredValue[i]));
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, current.size], id];
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.has(Feature.SymbolIterator), 'Unsupported type "Iterable"');
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 'null';
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 String(value);
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
- check >= 0
893
- || (Number.isNaN(check) && IDENTIFIER_CHECK.test(key))
894
- ) {
895
- if (!Number.isNaN(check)) {
896
- createObjectComputedAssign(ctx, targetRef, key, refParam);
897
- } else {
898
- createObjectIdentifierAssign(ctx, targetRef, key, refParam);
899
- }
900
- } else {
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
- if (
923
- check >= 0
924
- || (Number.isNaN(check) && IDENTIFIER_CHECK.test(key))
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
- createObjectComputedAssign(ctx, sourceID, quote(key), refParam);
991
+ createObjectAssign(
992
+ ctx,
993
+ sourceID,
994
+ validKey,
995
+ refParam,
996
+ !(isIdentifier && Number.isNaN(check)),
997
+ );
939
998
  } else {
940
- result += `${quote(key)}:${serializeTree(ctx, val)},`;
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 serializePrimitive(ctx, value);
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.toISOString()}")`);
1036
+ return assignRef(ctx, id, `new Date("${value}")`);
976
1037
  case SerovalNodeType.RegExp:
977
- return assignRef(ctx, id, String(value));
1038
+ return assignRef(ctx, id, value);
1039
+ case SerovalNodeType.BigIntTypedArray:
978
1040
  case SerovalNodeType.TypedArray: {
979
- // BigInt typed arrays are broken for toString()
980
- let values = '';
981
- for (let i = 0, len = value[1].length; i < len; i++) {
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 (i in value) {
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.has(Feature.ObjectAssign)) {
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.has(Feature.ObjectAssign)) {
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.has(Feature.ArrayPrototypeValues)) {
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.has(Feature.ArrowFunction)) {
1202
+ if (ctx.features & Feature.ArrowFunction) {
1146
1203
  serialized = `{[Symbol.iterator]:()=>${serialized}}`;
1147
- } else if (ctx.features.has(Feature.MethodShorthand)) {
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.has(Feature.ObjectAssign)) {
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.has(Feature.ObjectAssign)) {
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})`;