seroval 0.4.0-alpha.4 → 0.4.0-alpha.6

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 DELETED
@@ -1,1246 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-use-before-define */
2
- /* eslint-disable guard-for-in */
3
- /* eslint-disable @typescript-eslint/no-for-in-array */
4
- /* eslint-disable no-await-in-loop */
5
- import {
6
- isIterable,
7
- isPrimitive,
8
- isPromise,
9
- constructorCheck,
10
- } from './checks';
11
- import quote from './quote';
12
- import {
13
- AsyncServerValue,
14
- ErrorValue,
15
- PrimitiveValue,
16
- ServerValue,
17
- } from './types';
18
- import getIdentifier from './get-identifier';
19
- import { Feature, parseTargets } from './compat';
20
- import assert from './assert';
21
-
22
- interface IndexAssignment {
23
- type: 'index';
24
- source: string;
25
- value: string;
26
- }
27
-
28
- interface MapAssignment {
29
- type: 'map';
30
- source: string;
31
- key: string;
32
- value: string;
33
- }
34
-
35
- interface SetAssignment {
36
- type: 'set';
37
- source: string;
38
- value: string;
39
- }
40
-
41
- // Array of assignments to be done (used for recursion)
42
- export type Assignment =
43
- | IndexAssignment
44
- | MapAssignment
45
- | SetAssignment;
46
-
47
- export interface ParserContext {
48
- refs: Map<unknown, number>;
49
- markedRefs: Set<number>;
50
- features: number;
51
- }
52
-
53
- export interface SerializationContext {
54
- stack: number[];
55
- // Map tree refs to actual refs
56
- validRefs: Map<number, number>;
57
- // Refs that are...referenced
58
- markedRefs: Set<number>;
59
- // Variables
60
- vars: string[];
61
- // Array of assignments to be done (used for recursion)
62
- assignments: Assignment[];
63
- // Supported features
64
- features: number;
65
- }
66
-
67
- export interface Options {
68
- target: string | string[];
69
- }
70
-
71
- const DEFAULT_OPTIONS: Options = {
72
- target: 'es2023',
73
- };
74
-
75
- export function createParserContext(options: Partial<Options> = {}): ParserContext {
76
- // eslint-disable-next-line prefer-object-spread
77
- const result = Object.assign({}, DEFAULT_OPTIONS, options || {});
78
- return {
79
- markedRefs: new Set(),
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: [],
93
- vars: [],
94
- assignments: [],
95
- validRefs: new Map(),
96
- features: options.features,
97
- markedRefs: new Set(options.markedRefs),
98
- };
99
- }
100
-
101
- function getAssignmentExpression(assignment: Assignment): string {
102
- switch (assignment.type) {
103
- case 'index':
104
- return `${assignment.source}=${assignment.value}`;
105
- case 'map':
106
- return `${assignment.source}.set(${assignment.key},${assignment.value})`;
107
- case 'set':
108
- return `${assignment.source}.add(${assignment.value})`;
109
- default:
110
- return '';
111
- }
112
- }
113
-
114
- function mergeAssignments(assignments: Assignment[]) {
115
- const newAssignments = [];
116
- let current = assignments[0];
117
- let prev = current;
118
- for (let i = 1, len = assignments.length; i < len; i++) {
119
- const item = assignments[i];
120
- if (item.type === prev.type) {
121
- if (item.type === 'index' && item.value === prev.value) {
122
- // Merge if the right-hand value is the same
123
- // saves at least 2 chars
124
- current = {
125
- type: 'index',
126
- source: item.source,
127
- value: getAssignmentExpression(current),
128
- };
129
- } else if (item.type === 'map' && item.source === prev.source) {
130
- // Maps has chaining methods, merge if source is the same
131
- current = {
132
- type: 'map',
133
- source: getAssignmentExpression(current),
134
- key: item.key,
135
- value: item.value,
136
- };
137
- } else if (item.type === 'set' && item.source === prev.source) {
138
- // Sets has chaining methods too
139
- current = {
140
- type: 'set',
141
- source: getAssignmentExpression(current),
142
- value: item.value,
143
- };
144
- } else {
145
- // Different assignment, push current
146
- newAssignments.push(current);
147
- current = item;
148
- }
149
- } else {
150
- newAssignments.push(current);
151
- current = item;
152
- }
153
- prev = item;
154
- }
155
-
156
- newAssignments.push(current);
157
-
158
- return newAssignments;
159
- }
160
-
161
- function resolveAssignments(assignments: Assignment[]) {
162
- if (assignments.length) {
163
- let result = '';
164
- const merged = mergeAssignments(assignments);
165
- for (let i = 0, len = merged.length; i < len; i++) {
166
- result += `${getAssignmentExpression(merged[i])},`;
167
- }
168
- return result;
169
- }
170
- return undefined;
171
- }
172
-
173
- export function resolvePatches(ctx: SerializationContext) {
174
- return resolveAssignments(ctx.assignments);
175
- }
176
-
177
- /**
178
- * Increments the number of references the referenced value has
179
- */
180
- function markRef(ctx: ParserContext | SerializationContext, current: number) {
181
- ctx.markedRefs.add(current);
182
- }
183
-
184
- /**
185
- * Creates a new reference ID from a given reference ID
186
- * This new reference ID means that the reference itself
187
- * has been referenced at least once, and is used to generate
188
- * the variables
189
- */
190
- function createValidRef(
191
- ctx: SerializationContext,
192
- index: number,
193
- ) {
194
- const current = ctx.validRefs.get(index);
195
- if (current != null) {
196
- return current;
197
- }
198
- const id = ctx.validRefs.size;
199
- ctx.validRefs.set(index, id);
200
- return id;
201
- }
202
-
203
- /**
204
- * Creates the reference param (identifier) from the given reference ID
205
- * Calling this function means the value has been referenced somewhere
206
- */
207
- export function getRefParam(ctx: SerializationContext, index: number) {
208
- const actualIndex = createValidRef(ctx, index);
209
- if (ctx.vars[actualIndex]) {
210
- return ctx.vars[actualIndex];
211
- }
212
- const result = getIdentifier(actualIndex);
213
- ctx.vars[actualIndex] = result;
214
- return result;
215
- }
216
-
217
- /**
218
- * Generates the inlined assignment for the reference
219
- * This is different from the assignments array as this one
220
- * signifies creation rather than mutation
221
- */
222
- function assignRef(
223
- ctx: SerializationContext,
224
- index: number,
225
- value: string,
226
- ) {
227
- if (ctx.markedRefs.has(index)) {
228
- return `${getRefParam(ctx, index)}=${value}`;
229
- }
230
- return value;
231
- }
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
-
249
- function createAssignment(
250
- ctx: SerializationContext,
251
- source: string,
252
- value: string,
253
- ) {
254
- ctx.assignments.push({
255
- type: 'index',
256
- source,
257
- value,
258
- });
259
- }
260
-
261
- function createSetAdd(
262
- ctx: SerializationContext,
263
- ref: number,
264
- value: string,
265
- ) {
266
- markRef(ctx, ref);
267
- ctx.assignments.push({
268
- type: 'set',
269
- source: getRefParam(ctx, ref),
270
- value,
271
- });
272
- }
273
-
274
- function createMapSet(
275
- ctx: SerializationContext,
276
- ref: number,
277
- key: string,
278
- value: string,
279
- ) {
280
- markRef(ctx, ref);
281
- ctx.assignments.push({
282
- type: 'map',
283
- source: getRefParam(ctx, ref),
284
- key,
285
- value,
286
- });
287
- }
288
-
289
- function createArrayAssign(
290
- ctx: SerializationContext,
291
- ref: number,
292
- index: number,
293
- value: string,
294
- ) {
295
- markRef(ctx, ref);
296
- createAssignment(ctx, `${getRefParam(ctx, ref)}[${index}]`, value);
297
- }
298
-
299
- function createObjectAssign(
300
- ctx: SerializationContext,
301
- ref: number,
302
- key: string,
303
- value: string,
304
- computed: boolean,
305
- ) {
306
- markRef(ctx, ref);
307
- const member = computed ? `[${key}]` : `.${key}`;
308
- createAssignment(ctx, `${getRefParam(ctx, ref)}${member}`, value);
309
- }
310
-
311
- function getErrorConstructor(error: ErrorValue) {
312
- if (error instanceof EvalError) {
313
- return 'EvalError';
314
- }
315
- if (error instanceof RangeError) {
316
- return 'RangeError';
317
- }
318
- if (error instanceof ReferenceError) {
319
- return 'ReferenceError';
320
- }
321
- if (error instanceof SyntaxError) {
322
- return 'SyntaxError';
323
- }
324
- if (error instanceof TypeError) {
325
- return 'TypeError';
326
- }
327
- if (error instanceof URIError) {
328
- return 'URIError';
329
- }
330
- return 'Error';
331
- }
332
-
333
- function getErrorOptions(
334
- error: Error,
335
- ) {
336
- let options: Record<string, any> | undefined;
337
- const constructor = getErrorConstructor(error);
338
- // Name has been modified
339
- if (error.name !== constructor) {
340
- options = { name: error.name };
341
- } else if (error.constructor.name !== constructor) {
342
- // Otherwise, name is overriden because
343
- // the Error class is extended
344
- options = { name: error.constructor.name };
345
- }
346
- const names = Object.getOwnPropertyNames(error);
347
- for (const name of names) {
348
- if (name !== 'name' && name !== 'message') {
349
- options = options || {};
350
- options[name] = error[name as keyof Error];
351
- }
352
- }
353
- return options;
354
- }
355
-
356
- function getIterableOptions(obj: Iterable<any>) {
357
- const names = Object.getOwnPropertyNames(obj);
358
- if (names.length) {
359
- const options: Record<string, unknown> = {};
360
- for (const name of names) {
361
- options[name] = obj[name as unknown as keyof typeof obj];
362
- }
363
- return options;
364
- }
365
- return undefined;
366
- }
367
-
368
- const enum SerovalNodeType {
369
- Primitive,
370
- BigInt,
371
- Reference,
372
- Date,
373
- RegExp,
374
- Set,
375
- Map,
376
- Array,
377
- Object,
378
- NullConstructor,
379
- Promise,
380
- Error,
381
- AggregateError,
382
- Iterable,
383
- TypedArray,
384
- BigIntTypedArray,
385
- }
386
-
387
- type SerovalPrimitiveNode = [type: SerovalNodeType.Primitive, value: PrimitiveValue];
388
- type SerovalReferenceNode = [type: SerovalNodeType.Reference, value: number];
389
- type SerovalSemiPrimitiveNode =
390
- | [type: SerovalNodeType.BigInt, value: string]
391
- | [type: SerovalNodeType.Date, value: string, id: number]
392
- | [type: SerovalNodeType.RegExp, value: string, id: number]
393
- | [
394
- type: SerovalNodeType.TypedArray,
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],
401
- id: number
402
- ];
403
-
404
- type SerovalDictionaryNode = [key: string[], value: SerovalNode[], size: number];
405
- type SerovalSetNode = [type: SerovalNodeType.Set, value: SerovalNode[], id: number];
406
- type SerovalMapNode = [
407
- type: SerovalNodeType.Map,
408
- value: [key: SerovalNode[], value: SerovalNode[], size: number],
409
- id: number
410
- ];
411
- type SerovalArrayNode = [type: SerovalNodeType.Array, value: SerovalNode[], id: number];
412
- type SerovalObjectNode = [type: SerovalNodeType.Object, value: SerovalDictionaryNode, id: number];
413
- type SerovalNullConstructorNode = [
414
- type: SerovalNodeType.NullConstructor,
415
- value: SerovalDictionaryNode,
416
- id: number,
417
- ];
418
- type SerovalPromiseNode = [type: SerovalNodeType.Promise, value: SerovalNode, id: number];
419
- type SerovalErrorNode = [
420
- type: SerovalNodeType.Error,
421
- value: [
422
- constructor: string,
423
- message: string,
424
- options?: SerovalDictionaryNode,
425
- ],
426
- id: number
427
- ];
428
- type SerovalAggregateErrorNode = [
429
- type: SerovalNodeType.AggregateError,
430
- value: [
431
- message: string,
432
- options: SerovalDictionaryNode | undefined,
433
- errors: SerovalNode,
434
- ],
435
- id: number
436
- ];
437
-
438
- type SerovalIterableNode = [
439
- type: SerovalNodeType.Iterable,
440
- value: [
441
- options: SerovalDictionaryNode | undefined,
442
- items: SerovalNode,
443
- ],
444
- id: number,
445
- ];
446
-
447
- export type SerovalNode =
448
- | SerovalPrimitiveNode
449
- | SerovalReferenceNode
450
- | SerovalSemiPrimitiveNode
451
- | SerovalSetNode
452
- | SerovalMapNode
453
- | SerovalArrayNode
454
- | SerovalObjectNode
455
- | SerovalNullConstructorNode
456
- | SerovalPromiseNode
457
- | SerovalErrorNode
458
- | SerovalAggregateErrorNode
459
- | SerovalIterableNode;
460
-
461
- function isReferenceInStack(
462
- ctx: SerializationContext,
463
- node: SerovalNode,
464
- ): node is SerovalReferenceNode {
465
- return node[0] === SerovalNodeType.Reference && ctx.stack.includes(node[1]);
466
- }
467
-
468
- function generateRef(
469
- ctx: ParserContext,
470
- current: unknown,
471
- ): number | SerovalReferenceNode {
472
- // Check if reference number already exists
473
- const ref = ctx.refs.has(current);
474
- if (ref) {
475
- // Exists, means this value is currently
476
- // being referenced
477
- const refID = ctx.refs.get(current) || 0;
478
- // Mark reference
479
- markRef(ctx, refID);
480
- return [SerovalNodeType.Reference, refID];
481
- }
482
- // Create a new reference ID
483
- const id = ctx.refs.size;
484
- ctx.refs.set(current, id);
485
- return id;
486
- }
487
-
488
- function generateSemiPrimitiveValue(
489
- ctx: ParserContext,
490
- current: unknown,
491
- id: number,
492
- ): SerovalNode | undefined {
493
- if (typeof current === 'bigint') {
494
- assert(ctx.features & Feature.BigInt, 'Unsupported type "BigInt"');
495
- return [SerovalNodeType.BigInt, `${current}n`];
496
- }
497
- if (constructorCheck<Date>(current, Date)) {
498
- return [SerovalNodeType.Date, current.toISOString(), id];
499
- }
500
- if (constructorCheck<RegExp>(current, RegExp)) {
501
- return [SerovalNodeType.RegExp, String(current), id];
502
- }
503
- if (constructorCheck<Int8Array>(current, Int8Array)) {
504
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Int8Array"');
505
- return [SerovalNodeType.TypedArray, ['Int8Array', current.toString(), current.byteOffset], id];
506
- }
507
- if (constructorCheck<Int16Array>(current, Int16Array)) {
508
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Int16Array"');
509
- return [SerovalNodeType.TypedArray, ['Int16Array', current.toString(), current.byteOffset], id];
510
- }
511
- if (constructorCheck<Int32Array>(current, Int32Array)) {
512
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Int32Array"');
513
- return [SerovalNodeType.TypedArray, ['Int32Array', current.toString(), current.byteOffset], id];
514
- }
515
- if (constructorCheck<Uint8Array>(current, Uint8Array)) {
516
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint8Array"');
517
- return [SerovalNodeType.TypedArray, ['Uint8Array', current.toString(), current.byteOffset], id];
518
- }
519
- if (constructorCheck<Uint16Array>(current, Uint16Array)) {
520
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint16Array"');
521
- return [SerovalNodeType.TypedArray, ['Uint16Array', current.toString(), current.byteOffset], id];
522
- }
523
- if (constructorCheck<Uint32Array>(current, Uint32Array)) {
524
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint32Array"');
525
- return [SerovalNodeType.TypedArray, ['Uint32Array', current.toString(), current.byteOffset], id];
526
- }
527
- if (constructorCheck<Uint8ClampedArray>(current, Uint8ClampedArray)) {
528
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Uint8ClampedArray"');
529
- return [SerovalNodeType.TypedArray, ['Uint8ClampedArray', current.toString(), current.byteOffset], id];
530
- }
531
- if (constructorCheck<Float32Array>(current, Float32Array)) {
532
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Float32Array"');
533
- return [SerovalNodeType.TypedArray, ['Float32Array', current.toString(), current.byteOffset], id];
534
- }
535
- if (constructorCheck<Float64Array>(current, Float64Array)) {
536
- assert(ctx.features & Feature.TypedArray, 'Unsupported value type "Float64Array"');
537
- return [SerovalNodeType.TypedArray, ['Float64Array', current.toString(), current.byteOffset], id];
538
- }
539
- if (constructorCheck<BigInt64Array>(current, BigInt64Array)) {
540
- assert(
541
- ctx.features & (Feature.BigIntTypedArray),
542
- 'Unsupported value type "BigInt64Array"',
543
- );
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
- ];
555
- }
556
- if (constructorCheck<BigUint64Array>(current, BigUint64Array)) {
557
- assert(
558
- ctx.features & (Feature.BigIntTypedArray),
559
- 'Unsupported value type "BigUint64Array"',
560
- );
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
- ];
572
- }
573
- return undefined;
574
- }
575
-
576
- function serializePropertiesSync(
577
- ctx: ParserContext,
578
- properties: Record<string, unknown>,
579
- ): SerovalDictionaryNode {
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);
586
- let deferredSize = 0;
587
- let nodesSize = 0;
588
- for (const key of keys) {
589
- if (isIterable(properties[key])) {
590
- deferredKeys[deferredSize] = key;
591
- deferredValues[deferredSize] = properties[key] as ServerValue;
592
- deferredSize++;
593
- } else {
594
- keyNodes[nodesSize] = key;
595
- valueNodes[nodesSize] = generateTreeSync(ctx, properties[key] as ServerValue);
596
- nodesSize++;
597
- }
598
- }
599
- for (let i = 0; i < deferredSize; i++) {
600
- keyNodes[nodesSize + i] = deferredKeys[i];
601
- valueNodes[nodesSize + i] = generateTreeSync(ctx, deferredValues[i]);
602
- }
603
- return [keyNodes, valueNodes, size];
604
- }
605
-
606
- function generateTreeSync(
607
- ctx: ParserContext,
608
- current: ServerValue,
609
- ): SerovalNode {
610
- if (isPrimitive(current)) {
611
- return [SerovalNodeType.Primitive, serializePrimitive(current)];
612
- }
613
- // Non-primitive values needs a reference ID
614
- // mostly because the values themselves are stateful
615
- const id = generateRef(ctx, current);
616
- if (Array.isArray(id)) {
617
- return id;
618
- }
619
- const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
620
- if (semiPrimitive) {
621
- return semiPrimitive;
622
- }
623
- if (constructorCheck<Set<ServerValue>>(current, Set)) {
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;
630
- for (const item of current.keys()) {
631
- // Iterables are lazy, so the evaluation must be deferred
632
- if (isIterable(item)) {
633
- deferred[deferredSize++] = item;
634
- } else {
635
- nodes[nodeSize++] = generateTreeSync(ctx, item);
636
- }
637
- }
638
- // Parse deferred items
639
- for (let i = 0; i < deferredSize; i++) {
640
- nodes[nodeSize + i] = generateTreeSync(ctx, deferred[i]);
641
- }
642
- return [SerovalNodeType.Set, nodes, id];
643
- }
644
- if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) {
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);
651
- let deferredSize = 0;
652
- let nodeSize = 0;
653
- for (const [key, value] of current.entries()) {
654
- // Either key or value might be an iterable
655
- if (isIterable(key) || isIterable(value)) {
656
- deferredKey[deferredSize] = key;
657
- deferredValue[deferredSize] = value;
658
- deferredSize++;
659
- } else {
660
- keyNodes[nodeSize] = generateTreeSync(ctx, key);
661
- valueNodes[nodeSize] = generateTreeSync(ctx, value);
662
- nodeSize++;
663
- }
664
- }
665
- for (let i = 0; i < deferredSize; i++) {
666
- keyNodes[nodeSize + i] = generateTreeSync(ctx, deferredKey[i]);
667
- valueNodes[nodeSize + i] = generateTreeSync(ctx, deferredValue[i]);
668
- }
669
- return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
670
- }
671
- if (Array.isArray(current)) {
672
- const size = current.length;
673
- const nodes = new Array<SerovalNode>(size);
674
- const deferred = new Array<ServerValue>(size);
675
- for (let i = 0; i < size; i++) {
676
- if (i in current) {
677
- if (isIterable(current[i])) {
678
- deferred[i] = current[i];
679
- } else {
680
- nodes[i] = generateTreeSync(ctx, current[i]);
681
- }
682
- }
683
- }
684
- for (let i = 0; i < size; i++) {
685
- if (i in deferred) {
686
- nodes[i] = generateTreeSync(ctx, deferred[i]);
687
- }
688
- }
689
- return [SerovalNodeType.Array, nodes, id];
690
- }
691
- if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
692
- const options = getErrorOptions(current);
693
- const optionsNode = options
694
- ? serializePropertiesSync(ctx, options)
695
- : undefined;
696
- const errorsNode = generateTreeSync(ctx, current.errors as ServerValue);
697
- return [SerovalNodeType.AggregateError, [current.message, optionsNode, errorsNode], id];
698
- }
699
- if (current instanceof Error) {
700
- const options = getErrorOptions(current);
701
- const optionsNode = options
702
- ? serializePropertiesSync(ctx, options)
703
- : undefined;
704
- return [
705
- SerovalNodeType.Error,
706
- [getErrorConstructor(current), current.message, optionsNode],
707
- id,
708
- ];
709
- }
710
- if (isIterable(current)) {
711
- assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
712
- const options = getIterableOptions(current);
713
- return [SerovalNodeType.Iterable, [
714
- // Parse options first before the items
715
- options ? serializePropertiesSync(ctx, options) : undefined,
716
- generateTreeSync(ctx, Array.from(current)),
717
- ], id];
718
- }
719
- const empty = current.constructor == null;
720
- if (current.constructor === Object || empty) {
721
- return [
722
- empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
723
- serializePropertiesSync(ctx, current as Record<string, unknown>),
724
- id,
725
- ];
726
- }
727
- throw new Error('Unsupported value');
728
- }
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
-
738
- async function serializePropertiesAsync(
739
- ctx: ParserContext,
740
- properties: Record<string, unknown>,
741
- ): Promise<SerovalDictionaryNode> {
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);
748
- let deferredSize = 0;
749
- let nodesSize = 0;
750
- for (const key of keys) {
751
- if (isIterable(properties[key])) {
752
- deferredKeys[deferredSize] = key;
753
- deferredValues[deferredSize] = properties[key] as ServerValue;
754
- deferredSize++;
755
- } else {
756
- keyNodes[nodesSize] = key;
757
- valueNodes[nodesSize] = await generateTreeAsync(ctx, properties[key] as ServerValue);
758
- nodesSize++;
759
- }
760
- }
761
- for (let i = 0; i < deferredSize; i++) {
762
- keyNodes[nodesSize + i] = deferredKeys[i];
763
- valueNodes[nodesSize + i] = await generateTreeAsync(ctx, deferredValues[i]);
764
- }
765
- return [keyNodes, valueNodes, size];
766
- }
767
-
768
- async function generateTreeAsync(
769
- ctx: ParserContext,
770
- current: AsyncServerValue,
771
- ): Promise<SerovalNode> {
772
- if (isPrimitive(current)) {
773
- return [SerovalNodeType.Primitive, serializePrimitive(current)];
774
- }
775
- const id = generateRef(ctx, current);
776
- if (Array.isArray(id)) {
777
- return id;
778
- }
779
- const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
780
- if (semiPrimitive) {
781
- return semiPrimitive;
782
- }
783
- if (isPromise(current)) {
784
- assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
785
- return current.then(async (value) => [
786
- SerovalNodeType.Promise,
787
- await generateTreeAsync(ctx, value),
788
- id,
789
- ]);
790
- }
791
- if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
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;
798
- for (const item of current.keys()) {
799
- // Iterables are lazy, so the evaluation must be deferred
800
- if (isIterable(item)) {
801
- deferred[deferredSize++] = item;
802
- } else {
803
- nodes[nodeSize++] = await generateTreeAsync(ctx, item);
804
- }
805
- }
806
- // Parse deferred items
807
- for (let i = 0; i < deferredSize; i++) {
808
- nodes[nodeSize + i] = await generateTreeAsync(ctx, deferred[i]);
809
- }
810
- return [SerovalNodeType.Set, nodes, id];
811
- }
812
- if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
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);
819
- let deferredSize = 0;
820
- let nodeSize = 0;
821
- for (const [key, value] of current.entries()) {
822
- // Either key or value might be an iterable
823
- if (isIterable(key) || isIterable(value)) {
824
- deferredKey[deferredSize] = key;
825
- deferredValue[deferredSize] = value;
826
- deferredSize++;
827
- } else {
828
- keyNodes[nodeSize] = await generateTreeAsync(ctx, key);
829
- valueNodes[nodeSize] = await generateTreeAsync(ctx, value);
830
- nodeSize++;
831
- }
832
- }
833
- for (let i = 0; i < deferredSize; i++) {
834
- keyNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredKey[i]);
835
- valueNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredValue[i]);
836
- }
837
- return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
838
- }
839
- if (Array.isArray(current)) {
840
- const size = current.length;
841
- const nodes = new Array<SerovalNode>(size);
842
- const deferred = new Array<AsyncServerValue>(size);
843
- for (let i = 0; i < size; i++) {
844
- const item = current[i];
845
- if (i in current) {
846
- if (isIterable(item)) {
847
- deferred[i] = item;
848
- } else {
849
- nodes[i] = await generateTreeAsync(ctx, item);
850
- }
851
- }
852
- }
853
- for (let i = 0; i < size; i++) {
854
- if (i in deferred) {
855
- nodes[i] = await generateTreeAsync(ctx, deferred[i]);
856
- }
857
- }
858
- return [SerovalNodeType.Array, nodes, id];
859
- }
860
- if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
861
- const options = getErrorOptions(current);
862
- const optionsNode = options
863
- ? await serializePropertiesAsync(ctx, options)
864
- : undefined;
865
- const errorsNode = await generateTreeAsync(ctx, current.errors as ServerValue);
866
- return [SerovalNodeType.AggregateError, [current.message, optionsNode, errorsNode], id];
867
- }
868
- if (current instanceof Error) {
869
- const options = getErrorOptions(current);
870
- const optionsNode = options
871
- ? await serializePropertiesAsync(ctx, options)
872
- : undefined;
873
- return [
874
- SerovalNodeType.Error,
875
- [getErrorConstructor(current), current.message, optionsNode],
876
- id,
877
- ];
878
- }
879
- if (isIterable(current)) {
880
- assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
881
- const options = getIterableOptions(current);
882
- return [SerovalNodeType.Iterable, [
883
- options
884
- ? await serializePropertiesAsync(ctx, options)
885
- : undefined,
886
- await generateTreeAsync(ctx, Array.from(current)),
887
- ], id];
888
- }
889
- const empty = current.constructor == null;
890
- if (current.constructor === Object || empty) {
891
- return [
892
- empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
893
- await serializePropertiesAsync(ctx, current as Record<string, unknown>),
894
- id,
895
- ];
896
- }
897
- throw new Error('Unsupported value');
898
- }
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
-
908
- export function serializePrimitive(
909
- value: PrimitiveValue,
910
- ): string | number | null {
911
- // Shortened forms
912
- if (value === true) {
913
- return '!0';
914
- }
915
- if (value === false) {
916
- return '!1';
917
- }
918
- if (value === undefined) {
919
- return 'void 0';
920
- }
921
- if (value === null) {
922
- return null;
923
- }
924
- if (typeof value === 'string') {
925
- return quote(value);
926
- }
927
- // negative 0 isn't the same as 0
928
- if (Object.is(value, -0)) {
929
- return '-0';
930
- }
931
- if (Object.is(value, Infinity)) {
932
- return '1/0';
933
- }
934
- if (Object.is(value, -Infinity)) {
935
- return '-1/0';
936
- }
937
- return value;
938
- }
939
-
940
- const IDENTIFIER_CHECK = /^([$A-Z_][0-9A-Z_$]*)$/i;
941
-
942
- function serializeAssignments(
943
- ctx: SerializationContext,
944
- targetRef: number,
945
- [keys, values, size]: SerovalDictionaryNode,
946
- ) {
947
- ctx.stack.push(targetRef);
948
- const mainAssignments: Assignment[] = [];
949
- for (let i = 0; i < size; i++) {
950
- const parentStack = ctx.stack;
951
- ctx.stack = [];
952
- const refParam = serializeTree(ctx, values[i]);
953
- ctx.stack = parentStack;
954
- const key = keys[i];
955
- const check = Number(key);
956
- const parentAssignment = ctx.assignments;
957
- ctx.assignments = mainAssignments;
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
- );
968
- ctx.assignments = parentAssignment;
969
- }
970
- ctx.stack.pop();
971
- return resolveAssignments(mainAssignments);
972
- }
973
-
974
- function serializeObject(
975
- ctx: SerializationContext,
976
- sourceID: number,
977
- [keys, values, size]: SerovalDictionaryNode,
978
- ) {
979
- let result = '';
980
- ctx.stack.push(sourceID);
981
- for (let i = 0; i < size; i++) {
982
- const key = keys[i];
983
- const val = values[i];
984
- const check = Number(key);
985
- // Test if key is a valid number or JS identifier
986
- // so that we don't have to serialize the key and wrap with brackets
987
- const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
988
- const validKey = isIdentifier ? key : quote(key);
989
- if (isReferenceInStack(ctx, val)) {
990
- const refParam = getRefParam(ctx, val[1]);
991
- createObjectAssign(
992
- ctx,
993
- sourceID,
994
- validKey,
995
- refParam,
996
- !(isIdentifier && Number.isNaN(check)),
997
- );
998
- } else {
999
- result += `${validKey}:${serializeTree(ctx, val)},`;
1000
- }
1001
- }
1002
- ctx.stack.pop();
1003
- return `{${result.substring(0, result.length - 1)}}`;
1004
- }
1005
-
1006
- export function serializeTree(
1007
- ctx: SerializationContext,
1008
- [type, value, id]: SerovalNode,
1009
- ): string {
1010
- switch (type) {
1011
- case SerovalNodeType.Primitive:
1012
- return String(value);
1013
- case SerovalNodeType.Reference:
1014
- return getRefParam(ctx, value);
1015
- case SerovalNodeType.Promise: {
1016
- let serialized: string;
1017
- // Check if resolved value is a parent expression
1018
- if (isReferenceInStack(ctx, value)) {
1019
- // A Promise trick, reference the value
1020
- // inside the `then` expression so that
1021
- // the Promise evaluates after the parent
1022
- // has initialized
1023
- serialized = `Promise.resolve().then(()=>${getRefParam(ctx, value[1])})`;
1024
- } else {
1025
- ctx.stack.push(id);
1026
- const result = serializeTree(ctx, value);
1027
- ctx.stack.pop();
1028
- // just inline the value/reference here
1029
- serialized = `Promise.resolve(${result})`;
1030
- }
1031
- return assignRef(ctx, id, serialized);
1032
- }
1033
- case SerovalNodeType.BigInt:
1034
- return value;
1035
- case SerovalNodeType.Date:
1036
- return assignRef(ctx, id, `new Date("${value}")`);
1037
- case SerovalNodeType.RegExp:
1038
- return assignRef(ctx, id, value);
1039
- case SerovalNodeType.BigIntTypedArray:
1040
- case SerovalNodeType.TypedArray: {
1041
- let args = `[${value[1]}]`;
1042
- if (value[2] !== 0) {
1043
- args += `,${value[2]}`;
1044
- }
1045
- return assignRef(ctx, id, `new ${value[0]}(${args})`);
1046
- }
1047
- case SerovalNodeType.Set: {
1048
- let serialized = 'new Set';
1049
- const size = value.length;
1050
- if (size) {
1051
- let result = '';
1052
- ctx.stack.push(id);
1053
- for (let i = 0; i < size; i++) {
1054
- const item = value[i];
1055
- if (isReferenceInStack(ctx, item)) {
1056
- createSetAdd(ctx, id, getRefParam(ctx, item[1]));
1057
- } else {
1058
- // Push directly
1059
- result += `${serializeTree(ctx, item)},`;
1060
- }
1061
- }
1062
- ctx.stack.pop();
1063
- if (result) {
1064
- serialized += `([${result.substring(0, result.length - 1)}])`;
1065
- }
1066
- }
1067
- return assignRef(ctx, id, serialized);
1068
- }
1069
- case SerovalNodeType.Map: {
1070
- let serialized = 'new Map';
1071
- const size = value[2];
1072
- if (size) {
1073
- let result = '';
1074
- ctx.stack.push(id);
1075
- for (let i = 0; i < size; i++) {
1076
- // Check if key is a parent
1077
- const key = value[0][i];
1078
- const val = value[1][i];
1079
- if (isReferenceInStack(ctx, key)) {
1080
- // Create reference for the map instance
1081
- const keyRef = getRefParam(ctx, key[1]);
1082
- // Check if value is a parent
1083
- if (isReferenceInStack(ctx, val)) {
1084
- const valueRef = getRefParam(ctx, val[1]);
1085
- // Register an assignment since
1086
- // both key and value are a parent of this
1087
- // Map instance
1088
- createMapSet(ctx, id, keyRef, valueRef);
1089
- } else {
1090
- // Reset the stack
1091
- // This is required because the serialized
1092
- // value is no longer part of the expression
1093
- // tree and has been moved to the deferred
1094
- // assignment
1095
- const parent = ctx.stack;
1096
- ctx.stack = [];
1097
- createMapSet(ctx, id, keyRef, serializeTree(ctx, val));
1098
- ctx.stack = parent;
1099
- }
1100
- } else if (isReferenceInStack(ctx, val)) {
1101
- // Create ref for the Map instance
1102
- const valueRef = getRefParam(ctx, val[1]);
1103
- // Reset stack for the key serialization
1104
- const parent = ctx.stack;
1105
- ctx.stack = [];
1106
- createMapSet(ctx, id, serializeTree(ctx, key), valueRef);
1107
- ctx.stack = parent;
1108
- } else {
1109
- result += `[${serializeTree(ctx, key)},${serializeTree(ctx, val)}],`;
1110
- }
1111
- }
1112
- ctx.stack.pop();
1113
- // Check if there are any values
1114
- // so that the empty Map constructor
1115
- // can be used instead
1116
- if (result) {
1117
- serialized += `([${result.substring(0, result.length - 1)}])`;
1118
- }
1119
- }
1120
- return assignRef(ctx, id, serialized);
1121
- }
1122
- case SerovalNodeType.Array: {
1123
- // This is different than Map and Set
1124
- // because we also need to serialize
1125
- // the holes of the Array
1126
- const size = value.length;
1127
- let values = '';
1128
- ctx.stack.push(id);
1129
- for (let i = 0; i < size; i++) {
1130
- const item = value[i];
1131
- // Check if index is a hole
1132
- if (item) {
1133
- // Check if item is a parent
1134
- if (isReferenceInStack(ctx, item)) {
1135
- createArrayAssign(ctx, id, i, getRefParam(ctx, item[1]));
1136
- values += ',';
1137
- } else {
1138
- values += serializeTree(ctx, item);
1139
- if (i < size - 1) {
1140
- values += ',';
1141
- }
1142
- }
1143
- } else {
1144
- // Add an empty item
1145
- values += ',';
1146
- }
1147
- }
1148
- ctx.stack.pop();
1149
- return assignRef(ctx, id, `[${values}]`);
1150
- }
1151
- case SerovalNodeType.AggregateError: {
1152
- // Serialize the required arguments
1153
- ctx.stack.push(id);
1154
- let serialized = `new AggregateError(${serializeTree(ctx, value[2])},${quote(value[0])})`;
1155
- ctx.stack.pop();
1156
- // `AggregateError` might've been extended
1157
- // either through class or custom properties
1158
- // Make sure to assign extra properties
1159
- if (value[1]) {
1160
- if (ctx.features & Feature.ObjectAssign) {
1161
- const options = serializeObject(ctx, id, value[1]);
1162
- serialized = `Object.assign(${serialized},${options})`;
1163
- } else {
1164
- markRef(ctx, id);
1165
- const assignments = serializeAssignments(ctx, id, value[1]);
1166
- if (assignments) {
1167
- const ref = getRefParam(ctx, id);
1168
- return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
1169
- }
1170
- }
1171
- }
1172
- return assignRef(ctx, id, serialized);
1173
- }
1174
- case SerovalNodeType.Error: {
1175
- let serialized = `new ${value[0]}(${quote(value[1])})`;
1176
- if (value[2]) {
1177
- if (ctx.features & Feature.ObjectAssign) {
1178
- const options = serializeObject(ctx, id, value[2]);
1179
- serialized = `Object.assign(${serialized},${options})`;
1180
- } else {
1181
- markRef(ctx, id);
1182
- const assignments = serializeAssignments(ctx, id, value[2]);
1183
- if (assignments) {
1184
- const ref = getRefParam(ctx, id);
1185
- return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
1186
- }
1187
- }
1188
- }
1189
- return assignRef(ctx, id, serialized);
1190
- }
1191
- case SerovalNodeType.Iterable: {
1192
- const parent = ctx.stack;
1193
- ctx.stack = [];
1194
- const values = serializeTree(ctx, value[1]);
1195
- ctx.stack = parent;
1196
- let serialized: string;
1197
- if (ctx.features & Feature.ArrayPrototypeValues) {
1198
- serialized = `${values}.values()`;
1199
- } else {
1200
- serialized = `${values}[Symbol.iterator]()`;
1201
- }
1202
- if (ctx.features & Feature.ArrowFunction) {
1203
- serialized = `{[Symbol.iterator]:()=>${serialized}}`;
1204
- } else if (ctx.features & Feature.MethodShorthand) {
1205
- serialized = `{[Symbol.iterator](){return ${serialized}}}`;
1206
- } else {
1207
- serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`;
1208
- }
1209
- if (value[0]) {
1210
- if (ctx.features & Feature.ObjectAssign) {
1211
- const options = serializeObject(ctx, id, value[0]);
1212
- serialized = `Object.assign(${serialized},${options})`;
1213
- } else {
1214
- markRef(ctx, id);
1215
- const assignments = serializeAssignments(ctx, id, value[0]);
1216
- if (assignments) {
1217
- const ref = getRefParam(ctx, id);
1218
- return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
1219
- }
1220
- }
1221
- }
1222
- return assignRef(ctx, id, serialized);
1223
- }
1224
- case SerovalNodeType.NullConstructor: {
1225
- let serialized = 'Object.create(null)';
1226
- if (ctx.features & Feature.ObjectAssign) {
1227
- const fields = serializeObject(ctx, id, value);
1228
- if (fields !== '{}') {
1229
- serialized = `Object.assign(${serialized},${fields})`;
1230
- }
1231
- } else {
1232
- markRef(ctx, id);
1233
- const assignments = serializeAssignments(ctx, id, value);
1234
- if (assignments) {
1235
- const ref = getRefParam(ctx, id);
1236
- return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
1237
- }
1238
- }
1239
- return assignRef(ctx, id, serialized);
1240
- }
1241
- case SerovalNodeType.Object:
1242
- return assignRef(ctx, id, serializeObject(ctx, id, value));
1243
- default:
1244
- throw new Error('Unsupported type');
1245
- }
1246
- }