seroval 0.11.6 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +29 -16
  2. package/dist/cjs/development/index.cjs +3088 -4876
  3. package/dist/cjs/development/index.cjs.map +4 -4
  4. package/dist/cjs/production/index.cjs +3 -3
  5. package/dist/esm/development/index.mjs +3085 -4875
  6. package/dist/esm/development/index.mjs.map +4 -4
  7. package/dist/esm/production/index.mjs +3 -3
  8. package/dist/types/core/Serializer.d.ts +6 -0
  9. package/dist/types/core/Serializer.d.ts.map +1 -0
  10. package/dist/types/core/UnsupportedTypeError.d.ts +1 -0
  11. package/dist/types/core/UnsupportedTypeError.d.ts.map +1 -0
  12. package/dist/types/core/assert.d.ts +1 -0
  13. package/dist/types/core/assert.d.ts.map +1 -0
  14. package/dist/types/core/base/async.d.ts +30 -0
  15. package/dist/types/core/base/async.d.ts.map +1 -0
  16. package/dist/types/core/base/stream.d.ts +40 -0
  17. package/dist/types/core/base/stream.d.ts.map +1 -0
  18. package/dist/types/core/base/sync.d.ts +32 -0
  19. package/dist/types/core/base/sync.d.ts.map +1 -0
  20. package/dist/types/core/base-primitives.d.ts +3 -1
  21. package/dist/types/core/base-primitives.d.ts.map +1 -0
  22. package/dist/types/core/compat.d.ts +1 -0
  23. package/dist/types/core/compat.d.ts.map +1 -0
  24. package/dist/types/core/constants.d.ts +19 -4
  25. package/dist/types/core/constants.d.ts.map +1 -0
  26. package/dist/types/core/constructors.d.ts +1 -0
  27. package/dist/types/core/constructors.d.ts.map +1 -0
  28. package/dist/types/core/cross/async.d.ts +8 -3
  29. package/dist/types/core/cross/async.d.ts.map +1 -0
  30. package/dist/types/core/cross/cross-parser.d.ts +8 -0
  31. package/dist/types/core/cross/cross-parser.d.ts.map +1 -0
  32. package/dist/types/core/cross/index.d.ts +12 -5
  33. package/dist/types/core/cross/index.d.ts.map +1 -0
  34. package/dist/types/core/cross/serialize.d.ts +23 -5
  35. package/dist/types/core/cross/serialize.d.ts.map +1 -0
  36. package/dist/types/core/cross/stream.d.ts +8 -3
  37. package/dist/types/core/cross/stream.d.ts.map +1 -0
  38. package/dist/types/core/cross/sync.d.ts +8 -3
  39. package/dist/types/core/cross/sync.d.ts.map +1 -0
  40. package/dist/types/core/get-identifier.d.ts +1 -0
  41. package/dist/types/core/get-identifier.d.ts.map +1 -0
  42. package/dist/types/core/keys.d.ts +1 -0
  43. package/dist/types/core/keys.d.ts.map +1 -0
  44. package/dist/types/core/literals.d.ts +1 -0
  45. package/dist/types/core/literals.d.ts.map +1 -0
  46. package/dist/types/core/parser-context.d.ts +23 -0
  47. package/dist/types/core/parser-context.d.ts.map +1 -0
  48. package/dist/types/core/plugin.d.ts +26 -0
  49. package/dist/types/core/plugin.d.ts.map +1 -0
  50. package/dist/types/core/promise-to-result.d.ts +1 -0
  51. package/dist/types/core/promise-to-result.d.ts.map +1 -0
  52. package/dist/types/core/reference.d.ts +1 -0
  53. package/dist/types/core/reference.d.ts.map +1 -0
  54. package/dist/types/core/serializer-context.old.d.ts +165 -0
  55. package/dist/types/core/serializer-context.old.d.ts.map +1 -0
  56. package/dist/types/core/shared.d.ts +4 -7
  57. package/dist/types/core/shared.d.ts.map +1 -0
  58. package/dist/types/core/string.d.ts +1 -0
  59. package/dist/types/core/string.d.ts.map +1 -0
  60. package/dist/types/core/tree/async.d.ts +8 -3
  61. package/dist/types/core/tree/async.d.ts.map +1 -0
  62. package/dist/types/core/tree/deserialize.d.ts +49 -2
  63. package/dist/types/core/tree/deserialize.d.ts.map +1 -0
  64. package/dist/types/core/tree/index.d.ts +10 -7
  65. package/dist/types/core/tree/index.d.ts.map +1 -0
  66. package/dist/types/core/tree/serialize.d.ts +37 -4
  67. package/dist/types/core/tree/serialize.d.ts.map +1 -0
  68. package/dist/types/core/tree/sync.d.ts +8 -3
  69. package/dist/types/core/tree/sync.d.ts.map +1 -0
  70. package/dist/types/core/types.d.ts +10 -3
  71. package/dist/types/core/types.d.ts.map +1 -0
  72. package/dist/types/core/web-api.d.ts +1 -0
  73. package/dist/types/core/web-api.d.ts.map +1 -0
  74. package/dist/types/index.d.ts +3 -0
  75. package/dist/types/index.d.ts.map +1 -0
  76. package/dist/types/types.d.ts +1 -0
  77. package/dist/types/types.d.ts.map +1 -0
  78. package/package.json +2 -2
  79. package/src/core/Serializer.ts +20 -0
  80. package/src/core/base/async.ts +804 -0
  81. package/src/core/base/stream.ts +496 -0
  82. package/src/core/base/sync.ts +675 -0
  83. package/src/core/base-primitives.ts +22 -0
  84. package/src/core/constants.ts +61 -45
  85. package/src/core/cross/async.ts +6 -751
  86. package/src/core/cross/cross-parser.ts +9 -0
  87. package/src/core/cross/index.ts +62 -84
  88. package/src/core/cross/serialize.ts +71 -862
  89. package/src/core/cross/stream.ts +6 -873
  90. package/src/core/cross/sync.ts +6 -624
  91. package/src/core/is-serializable.new.ts +208 -0
  92. package/src/core/keys.ts +5 -3
  93. package/src/core/parser-context.ts +131 -0
  94. package/src/core/plugin.ts +55 -0
  95. package/src/core/serializer-context.new.ts +1168 -0
  96. package/src/core/serializer-context.old.ts +1126 -0
  97. package/src/core/shared.ts +46 -123
  98. package/src/core/tree/async.ts +6 -750
  99. package/src/core/tree/deserialize.ts +423 -420
  100. package/src/core/tree/index.ts +49 -91
  101. package/src/core/tree/parser.ts +45 -0
  102. package/src/core/tree/serialize.ts +122 -828
  103. package/src/core/tree/sync.ts +6 -624
  104. package/src/core/types.ts +13 -2
  105. package/src/example.ts +28 -0
  106. package/src/index.ts +3 -0
  107. package/dist/types/core/assignments.d.ts +0 -33
  108. package/dist/types/core/context.d.ts +0 -9
  109. package/dist/types/core/cross/context.d.ts +0 -37
  110. package/dist/types/core/cross/primitives.d.ts +0 -9
  111. package/dist/types/core/cross/web-api.d.ts +0 -4
  112. package/dist/types/core/tree/context.d.ts +0 -44
  113. package/dist/types/core/tree/primitives.d.ts +0 -9
  114. package/dist/types/core/tree/web-api.d.ts +0 -4
  115. package/src/core/assignments.ts +0 -159
  116. package/src/core/context.ts +0 -15
  117. package/src/core/cross/context.ts +0 -102
  118. package/src/core/cross/primitives.ts +0 -128
  119. package/src/core/cross/web-api.ts +0 -50
  120. package/src/core/tree/context.ts +0 -125
  121. package/src/core/tree/primitives.ts +0 -123
  122. package/src/core/tree/web-api.ts +0 -50
@@ -0,0 +1,1126 @@
1
+ import { Feature } from './compat';
2
+ import {
3
+ CONSTANT_STRING,
4
+ ERROR_CONSTRUCTOR_STRING,
5
+ SYMBOL_STRING,
6
+ SerovalNodeType,
7
+ SerovalObjectFlags,
8
+ } from './constants';
9
+ import { REFERENCES_KEY } from './keys';
10
+ import type { Plugin, PluginAccessOptions, SerovalMode } from './plugin';
11
+ import { isValidIdentifier } from './shared';
12
+ import type {
13
+ SerovalArrayNode,
14
+ SerovalIndexedValueNode,
15
+ SerovalNode,
16
+ SerovalObjectRecordKey,
17
+ SerovalObjectRecordNode,
18
+ SerovalReferenceNode,
19
+ SerovalObjectNode,
20
+ SerovalNullConstructorNode,
21
+ SerovalRegExpNode,
22
+ SerovalDateNode,
23
+ SerovalSetNode,
24
+ SerovalMapNode,
25
+ SerovalArrayBufferNode,
26
+ SerovalTypedArrayNode,
27
+ SerovalBigIntTypedArrayNode,
28
+ SerovalDataViewNode,
29
+ SerovalAggregateErrorNode,
30
+ SerovalErrorNode,
31
+ SerovalPromiseNode,
32
+ SerovalWKSymbolNode,
33
+ SerovalURLNode,
34
+ SerovalURLSearchParamsNode,
35
+ SerovalBlobNode,
36
+ SerovalFileNode,
37
+ SerovalHeadersNode,
38
+ SerovalFormDataNode,
39
+ SerovalBoxedNode,
40
+ SerovalRequestNode,
41
+ SerovalResponseNode,
42
+ SerovalEventNode,
43
+ SerovalCustomEventNode,
44
+ SerovalDOMExceptionNode,
45
+ SerovalPluginNode,
46
+ SerovalPromiseConstructorNode,
47
+ SerovalPromiseResolveNode,
48
+ SerovalPromiseRejectNode,
49
+ SerovalReadableStreamConstructorNode,
50
+ SerovalReadableStreamEnqueueNode,
51
+ SerovalReadableStreamErrorNode,
52
+ SerovalReadableStreamCloseNode,
53
+ } from './types';
54
+ import {
55
+ SerovalObjectRecordSpecialKey,
56
+ } from './types';
57
+
58
+ interface IndexAssignment {
59
+ t: 'index';
60
+ s: string;
61
+ k: undefined;
62
+ v: string;
63
+ }
64
+
65
+ interface SetAssignment {
66
+ t: 'set';
67
+ s: string;
68
+ k: string;
69
+ v: string;
70
+ }
71
+
72
+ interface AddAssignment {
73
+ t: 'add';
74
+ s: string;
75
+ k: undefined;
76
+ v: string;
77
+ }
78
+
79
+ interface DeleteAssignment {
80
+ t: 'delete';
81
+ s: string;
82
+ k: string;
83
+ v: undefined;
84
+ }
85
+
86
+ // Array of assignments to be done (used for recursion)
87
+ type Assignment =
88
+ | IndexAssignment
89
+ | AddAssignment
90
+ | SetAssignment
91
+ | DeleteAssignment;
92
+
93
+ export interface FlaggedObject {
94
+ type: SerovalObjectFlags;
95
+ value: string;
96
+ }
97
+
98
+ function getAssignmentExpression(assignment: Assignment): string {
99
+ switch (assignment.t) {
100
+ case 'index':
101
+ return assignment.s + '=' + assignment.v;
102
+ case 'set':
103
+ return assignment.s + '.set(' + assignment.k + ',' + assignment.v + ')';
104
+ case 'add':
105
+ return assignment.s + '.add(' + assignment.v + ')';
106
+ case 'delete':
107
+ return assignment.s + '.delete(' + assignment.k + ')';
108
+ default:
109
+ return '';
110
+ }
111
+ }
112
+
113
+ function mergeAssignments(assignments: Assignment[]): Assignment[] {
114
+ const newAssignments: Assignment[] = [];
115
+ let current = assignments[0];
116
+ for (let i = 1, len = assignments.length, item: Assignment, prev = current; i < len; i++) {
117
+ item = assignments[i];
118
+ if (item.t === 'index' && item.v === prev.v) {
119
+ // Merge if the right-hand value is the same
120
+ // saves at least 2 chars
121
+ current = {
122
+ t: 'index',
123
+ s: item.s,
124
+ k: undefined,
125
+ v: getAssignmentExpression(current),
126
+ } as IndexAssignment;
127
+ } else if (item.t === 'set' && item.s === prev.s) {
128
+ // Maps has chaining methods, merge if source is the same
129
+ current = {
130
+ t: 'set',
131
+ s: getAssignmentExpression(current),
132
+ k: item.k,
133
+ v: item.v,
134
+ } as SetAssignment;
135
+ } else if (item.t === 'add' && item.s === prev.s) {
136
+ // Sets has chaining methods too
137
+ current = {
138
+ t: 'add',
139
+ s: getAssignmentExpression(current),
140
+ k: undefined,
141
+ v: item.v,
142
+ } as AddAssignment;
143
+ } else if (item.t === 'delete' && item.s === prev.s) {
144
+ // Maps has chaining methods, merge if source is the same
145
+ current = {
146
+ t: 'delete',
147
+ s: getAssignmentExpression(current),
148
+ k: item.k,
149
+ v: undefined,
150
+ } as DeleteAssignment;
151
+ } else {
152
+ // Different assignment, push current
153
+ newAssignments.push(current);
154
+ current = item;
155
+ }
156
+ prev = item;
157
+ }
158
+
159
+ newAssignments.push(current);
160
+
161
+ return newAssignments;
162
+ }
163
+
164
+ function resolveAssignments(assignments: Assignment[]): string | undefined {
165
+ if (assignments.length) {
166
+ let result = '';
167
+ const merged = mergeAssignments(assignments);
168
+ for (let i = 0, len = merged.length; i < len; i++) {
169
+ result += getAssignmentExpression(merged[i]) + ',';
170
+ }
171
+ return result;
172
+ }
173
+ return undefined;
174
+ }
175
+
176
+ const NULL_CONSTRUCTOR = 'Object.create(null)';
177
+ const SET_CONSTRUCTOR = 'new Set';
178
+ const MAP_CONSTRUCTOR = 'new Map';
179
+
180
+ const PROMISE_RESOLVE = 'Promise.resolve';
181
+ const PROMISE_REJECT = 'Promise.reject';
182
+
183
+ const SYMBOL_ITERATOR = 'Symbol.iterator';
184
+
185
+ const enum SpecialReference {
186
+ Sentinel = 0,
187
+ }
188
+
189
+ const SPECIAL_REFERENCE_VALUE: Record<SpecialReference, string> = {
190
+ [SpecialReference.Sentinel]: '[]',
191
+ };
192
+
193
+ const OBJECT_FLAG_CONSTRUCTOR: Record<SerovalObjectFlags, string | undefined> = {
194
+ [SerovalObjectFlags.Frozen]: 'Object.freeze',
195
+ [SerovalObjectFlags.Sealed]: 'Object.seal',
196
+ [SerovalObjectFlags.NonExtensible]: 'Object.preventExtensions',
197
+ [SerovalObjectFlags.None]: undefined,
198
+ };
199
+
200
+ export interface BaseSerializerContextOptions extends PluginAccessOptions {
201
+ features: number;
202
+ markedRefs: number[] | Set<number>;
203
+ }
204
+
205
+ export default abstract class BaseSerializerContext implements PluginAccessOptions {
206
+ /**
207
+ * @private
208
+ */
209
+ features: number;
210
+
211
+ /**
212
+ * To check if an object is synchronously referencing itself
213
+ * @private
214
+ */
215
+ stack: number[] = [];
216
+
217
+ /**
218
+ * Array of object mutations
219
+ * @private
220
+ */
221
+ flags: FlaggedObject[] = [];
222
+
223
+ /**
224
+ * Array of assignments to be done (used for recursion)
225
+ * @private
226
+ */
227
+ assignments: Assignment[] = [];
228
+
229
+ plugins?: Plugin<any, any>[] | undefined;
230
+
231
+ /**
232
+ * Refs that are...referenced
233
+ * @private
234
+ */
235
+ marked: Set<number>;
236
+
237
+ constructor(options: BaseSerializerContextOptions) {
238
+ this.plugins = options.plugins;
239
+ this.features = options.features;
240
+ this.marked = new Set(options.markedRefs);
241
+ }
242
+
243
+ abstract readonly mode: SerovalMode;
244
+
245
+ /**
246
+ * A tiny function that tells if a reference
247
+ * is to be accessed. This is a requirement for
248
+ * deciding whether or not we should generate
249
+ * an identifier for the object
250
+ */
251
+ protected markRef(id: number): void {
252
+ this.marked.add(id);
253
+ }
254
+
255
+ protected isMarked(id: number): boolean {
256
+ return this.marked.has(id);
257
+ }
258
+
259
+ /**
260
+ * Converts the ID of a reference into a identifier string
261
+ * that is used to refer to the object instance in the
262
+ * generated script.
263
+ */
264
+ abstract getRefParam(id: number | string): string;
265
+
266
+ private specials = new Set<SpecialReference>();
267
+
268
+ /**
269
+ * Generates special references that isn't provided by the user
270
+ * but by the script.
271
+ */
272
+ protected getSpecialReference(ref: SpecialReference): string {
273
+ const param = this.getRefParam('_' + ref);
274
+ if (this.specials.has(ref)) {
275
+ return param;
276
+ }
277
+ this.specials.add(ref);
278
+ return param + '=' + SPECIAL_REFERENCE_VALUE[ref];
279
+ }
280
+
281
+ protected pushObjectFlag(flag: SerovalObjectFlags, id: number): void {
282
+ if (flag !== SerovalObjectFlags.None) {
283
+ this.markRef(id);
284
+ this.flags.push({
285
+ type: flag,
286
+ value: this.getRefParam(id),
287
+ });
288
+ }
289
+ }
290
+
291
+ private resolveFlags(): string | undefined {
292
+ let result = '';
293
+ for (let i = 0, current = this.flags, len = current.length; i < len; i++) {
294
+ const flag = current[i];
295
+ result += OBJECT_FLAG_CONSTRUCTOR[flag.type] + '(' + flag.value + '),';
296
+ }
297
+ return result;
298
+ }
299
+
300
+ protected resolvePatches(): string | undefined {
301
+ const assignments = resolveAssignments(this.assignments);
302
+ const flags = this.resolveFlags();
303
+ if (assignments) {
304
+ if (flags) {
305
+ return assignments + flags;
306
+ }
307
+ return assignments;
308
+ }
309
+ return flags;
310
+ }
311
+
312
+ /**
313
+ * Generates the inlined assignment for the reference
314
+ * This is different from the assignments array as this one
315
+ * signifies creation rather than mutation
316
+ */
317
+ protected createAssignment(
318
+ source: string,
319
+ value: string,
320
+ ): void {
321
+ this.assignments.push({
322
+ t: 'index',
323
+ s: source,
324
+ k: undefined,
325
+ v: value,
326
+ });
327
+ }
328
+
329
+ protected createAddAssignment(
330
+ ref: number,
331
+ value: string,
332
+ ): void {
333
+ this.assignments.push({
334
+ t: 'add',
335
+ s: this.getRefParam(ref),
336
+ k: undefined,
337
+ v: value,
338
+ });
339
+ }
340
+
341
+ protected createSetAssignment(
342
+ ref: number,
343
+ key: string,
344
+ value: string,
345
+ ): void {
346
+ this.assignments.push({
347
+ t: 'set',
348
+ s: this.getRefParam(ref),
349
+ k: key,
350
+ v: value,
351
+ });
352
+ }
353
+
354
+ protected createDeleteAssignment(
355
+ ref: number,
356
+ key: string,
357
+ ): void {
358
+ this.assignments.push({
359
+ t: 'delete',
360
+ s: this.getRefParam(ref),
361
+ k: key,
362
+ v: undefined,
363
+ });
364
+ }
365
+
366
+ protected createArrayAssign(
367
+ ref: number,
368
+ index: number | string,
369
+ value: string,
370
+ ): void {
371
+ this.createAssignment(this.getRefParam(ref) + '[' + index + ']', value);
372
+ }
373
+
374
+ protected createObjectAssign(
375
+ ref: number,
376
+ key: string,
377
+ value: string,
378
+ ): void {
379
+ this.createAssignment(this.getRefParam(ref) + '.' + key, value);
380
+ }
381
+
382
+ /**
383
+ * Checks if the value is in the stack. Stack here is a reference
384
+ * structure to know if a object is to be accessed in a TDZ.
385
+ */
386
+ isIndexedValueInStack(
387
+ node: SerovalNode,
388
+ ): boolean {
389
+ return node.t === SerovalNodeType.IndexedValue && this.stack.includes(node.i);
390
+ }
391
+
392
+ /**
393
+ * Produces an assignment expression. `id` generates a reference
394
+ * parameter (through `getRefParam`) and has the option to
395
+ * return the reference parameter directly or assign a value to
396
+ * it.
397
+ */
398
+ protected abstract assignIndexedValue(
399
+ id: number,
400
+ value: string,
401
+ ): string;
402
+
403
+ protected serializeReference(
404
+ node: SerovalReferenceNode,
405
+ ): string {
406
+ return this.assignIndexedValue(node.i, REFERENCES_KEY + '.get("' + node.s + '")');
407
+ }
408
+
409
+ protected getIterableAccess(): string {
410
+ return this.features & Feature.ArrayPrototypeValues
411
+ ? '.values()'
412
+ : '[' + SYMBOL_ITERATOR + ']()';
413
+ }
414
+
415
+ protected serializeIterable(
416
+ node: SerovalNode,
417
+ ): string {
418
+ const key = '[' + SYMBOL_ITERATOR + ']';
419
+ const parent = this.stack;
420
+ this.stack = [];
421
+ let serialized = this.serialize(node) + this.getIterableAccess();
422
+ this.stack = parent;
423
+ if (this.features & Feature.ArrowFunction) {
424
+ serialized = ':()=>' + serialized;
425
+ } else if (this.features & Feature.MethodShorthand) {
426
+ serialized = '(){return ' + serialized + '}';
427
+ } else {
428
+ serialized = ':function(){return ' + serialized + '}';
429
+ }
430
+ return key + serialized;
431
+ }
432
+
433
+ protected serializeArrayItem(
434
+ id: number,
435
+ item: SerovalNode | undefined,
436
+ index: number,
437
+ ): string {
438
+ // Check if index is a hole
439
+ if (item) {
440
+ // Check if item is a parent
441
+ if (this.isIndexedValueInStack(item)) {
442
+ this.markRef(id);
443
+ this.createArrayAssign(id, index, this.getRefParam((item as SerovalIndexedValueNode).i));
444
+ return '';
445
+ }
446
+ return this.serialize(item);
447
+ }
448
+ return '';
449
+ }
450
+
451
+ protected serializeArray(
452
+ node: SerovalArrayNode,
453
+ ): string {
454
+ const id = node.i;
455
+ if (node.l) {
456
+ this.stack.push(id);
457
+ // This is different than Map and Set
458
+ // because we also need to serialize
459
+ // the holes of the Array
460
+ const list = node.a;
461
+ let values = this.serializeArrayItem(id, list[0], 0);
462
+ let isHoley = values === '';
463
+ for (let i = 1, len = node.l, item: string; i < len; i++) {
464
+ item = this.serializeArrayItem(id, list[i], i);
465
+ values += ',' + item;
466
+ isHoley = item === '';
467
+ }
468
+ this.stack.pop();
469
+ this.pushObjectFlag(node.o, node.i);
470
+ return this.assignIndexedValue(id, '[' + values + (isHoley ? ',]' : ']'));
471
+ }
472
+ return this.assignIndexedValue(id, '[]');
473
+ }
474
+
475
+ protected serializeProperty(
476
+ id: number,
477
+ key: SerovalObjectRecordKey,
478
+ val: SerovalNode,
479
+ ): string {
480
+ // Only reason this is a switch is so that
481
+ // in the future, maybe other Symbols are going
482
+ // to be introduced and/or has merit to be added
483
+ // E.g. Symbol.asyncIterator
484
+ switch (key) {
485
+ case SerovalObjectRecordSpecialKey.SymbolIterator:
486
+ return this.serializeIterable(val);
487
+ default: {
488
+ const check = Number(key);
489
+ // Test if key is a valid number or JS identifier
490
+ // so that we don't have to serialize the key and wrap with brackets
491
+ const isIdentifier = check >= 0 || isValidIdentifier(key);
492
+ if (this.isIndexedValueInStack(val)) {
493
+ const refParam = this.getRefParam((val as SerovalIndexedValueNode).i);
494
+ this.markRef(id);
495
+ // eslint-disable-next-line no-self-compare
496
+ if (isIdentifier && check !== check) {
497
+ this.createObjectAssign(id, key, refParam);
498
+ } else {
499
+ this.createArrayAssign(id, isIdentifier ? key : ('"' + key + '"'), refParam);
500
+ }
501
+ return '';
502
+ }
503
+ return (isIdentifier ? key : ('"' + key + '"')) + ':' + this.serialize(val);
504
+ }
505
+ }
506
+ }
507
+
508
+ protected serializeProperties(
509
+ sourceID: number,
510
+ node: SerovalObjectRecordNode,
511
+ ): string {
512
+ const len = node.s;
513
+ if (len) {
514
+ this.stack.push(sourceID);
515
+ const keys = node.k;
516
+ const values = node.v;
517
+ let result = this.serializeProperty(sourceID, keys[0], values[0]);
518
+ for (let i = 1, item = result; i < len; i++) {
519
+ item = this.serializeProperty(sourceID, keys[i], values[i]);
520
+ result += (item && result && ',') + item;
521
+ }
522
+ this.stack.pop();
523
+ return '{' + result + '}';
524
+ }
525
+ return '{}';
526
+ }
527
+
528
+ protected serializeObject(
529
+ node: SerovalObjectNode,
530
+ ): string {
531
+ this.pushObjectFlag(node.o, node.i);
532
+ return this.assignIndexedValue(node.i, this.serializeProperties(node.i, node.p));
533
+ }
534
+
535
+ protected serializeWithObjectAssign(
536
+ value: SerovalObjectRecordNode,
537
+ id: number,
538
+ serialized: string,
539
+ ): string {
540
+ const fields = this.serializeProperties(id, value);
541
+ if (fields !== '{}') {
542
+ return 'Object.assign(' + serialized + ',' + fields + ')';
543
+ }
544
+ return serialized;
545
+ }
546
+
547
+ protected serializeAssignment(
548
+ sourceID: number,
549
+ mainAssignments: Assignment[],
550
+ key: SerovalObjectRecordKey,
551
+ value: SerovalNode,
552
+ ): void {
553
+ switch (key) {
554
+ case SerovalObjectRecordSpecialKey.SymbolIterator: {
555
+ const parent = this.stack;
556
+ this.stack = [];
557
+ const serialized = this.serialize(value) + this.getIterableAccess();
558
+ this.stack = parent;
559
+ const parentAssignment = this.assignments;
560
+ this.assignments = mainAssignments;
561
+ this.createArrayAssign(
562
+ sourceID,
563
+ this.getSpecialReference(SpecialReference.Sentinel),
564
+ this.features & Feature.ArrowFunction
565
+ ? '()=>' + serialized
566
+ : 'function(){return ' + serialized + '}',
567
+ );
568
+ this.assignments = parentAssignment;
569
+ }
570
+ break;
571
+ default: {
572
+ const serialized = this.serialize(value);
573
+ const check = Number(key);
574
+ // Test if key is a valid number or JS identifier
575
+ // so that we don't have to serialize the key and wrap with brackets
576
+ const isIdentifier = check >= 0 || isValidIdentifier(key);
577
+ if (this.isIndexedValueInStack(value)) {
578
+ // eslint-disable-next-line no-self-compare
579
+ if (isIdentifier && check !== check) {
580
+ this.createObjectAssign(sourceID, key, serialized);
581
+ } else {
582
+ this.createArrayAssign(sourceID, isIdentifier ? key : ('"' + key + '"'), serialized);
583
+ }
584
+ } else {
585
+ const parentAssignment = this.assignments;
586
+ this.assignments = mainAssignments;
587
+ if (isIdentifier) {
588
+ this.createObjectAssign(sourceID, key, serialized);
589
+ } else {
590
+ this.createArrayAssign(sourceID, isIdentifier ? key : ('"' + key + '"'), serialized);
591
+ }
592
+ this.assignments = parentAssignment;
593
+ }
594
+ }
595
+ }
596
+ }
597
+
598
+ protected serializeAssignments(
599
+ sourceID: number,
600
+ node: SerovalObjectRecordNode,
601
+ ): string | undefined {
602
+ const len = node.s;
603
+ if (len) {
604
+ this.stack.push(sourceID);
605
+ const mainAssignments: Assignment[] = [];
606
+ const keys = node.k;
607
+ const values = node.v;
608
+ for (let i = 0; i < len; i++) {
609
+ this.serializeAssignment(sourceID, mainAssignments, keys[i], values[i]);
610
+ }
611
+ this.stack.pop();
612
+ return resolveAssignments(mainAssignments);
613
+ }
614
+ return undefined;
615
+ }
616
+
617
+ protected serializeDictionary(
618
+ i: number,
619
+ p: SerovalObjectRecordNode | undefined,
620
+ init: string,
621
+ ): string {
622
+ if (p) {
623
+ if (this.features & Feature.ObjectAssign) {
624
+ init = this.serializeWithObjectAssign(p, i, init);
625
+ } else {
626
+ this.markRef(i);
627
+ const assignments = this.serializeAssignments(i, p);
628
+ if (assignments) {
629
+ return '(' + this.assignIndexedValue(i, init) + ',' + assignments + this.getRefParam(i) + ')';
630
+ }
631
+ }
632
+ }
633
+ return this.assignIndexedValue(i, init);
634
+ }
635
+
636
+ protected serializeNullConstructor(
637
+ node: SerovalNullConstructorNode,
638
+ ): string {
639
+ this.pushObjectFlag(node.o, node.i);
640
+ return this.serializeDictionary(node.i, node.p, NULL_CONSTRUCTOR);
641
+ }
642
+
643
+ protected serializeDate(
644
+ node: SerovalDateNode,
645
+ ): string {
646
+ return this.assignIndexedValue(node.i, 'new Date("' + node.s + '")');
647
+ }
648
+
649
+ protected serializeRegExp(
650
+ node: SerovalRegExpNode,
651
+ ): string {
652
+ return this.assignIndexedValue(node.i, '/' + node.c + '/' + node.m);
653
+ }
654
+
655
+ protected serializeSetItem(
656
+ id: number,
657
+ item: SerovalNode,
658
+ ): string {
659
+ if (this.isIndexedValueInStack(item)) {
660
+ this.markRef(id);
661
+ this.createAddAssignment(id, this.getRefParam((item as SerovalIndexedValueNode).i));
662
+ return '';
663
+ }
664
+ return this.serialize(item);
665
+ }
666
+
667
+ protected serializeSet(
668
+ node: SerovalSetNode,
669
+ ): string {
670
+ let serialized = SET_CONSTRUCTOR;
671
+ const size = node.l;
672
+ const id = node.i;
673
+ if (size) {
674
+ const items = node.a;
675
+ this.stack.push(id);
676
+ let result = this.serializeSetItem(id, items[0]);
677
+ for (let i = 1, item = result; i < size; i++) {
678
+ item = this.serializeSetItem(id, items[i]);
679
+ result += (item && result && ',') + item;
680
+ }
681
+ this.stack.pop();
682
+ if (result) {
683
+ serialized += '([' + result + '])';
684
+ }
685
+ }
686
+ return this.assignIndexedValue(id, serialized);
687
+ }
688
+
689
+ protected serializeMapEntry(
690
+ id: number,
691
+ key: SerovalNode,
692
+ val: SerovalNode,
693
+ ): string {
694
+ if (this.isIndexedValueInStack(key)) {
695
+ // Create reference for the map instance
696
+ const keyRef = this.getRefParam((key as SerovalIndexedValueNode).i);
697
+ this.markRef(id);
698
+ // Check if value is a parent
699
+ if (this.isIndexedValueInStack(val)) {
700
+ const valueRef = this.getRefParam((val as SerovalIndexedValueNode).i);
701
+ // Register an assignment since
702
+ // both key and value are a parent of this
703
+ // Map instance
704
+ this.createSetAssignment(id, keyRef, valueRef);
705
+ return '';
706
+ }
707
+ // Reset the stack
708
+ // This is required because the serialized
709
+ // value is no longer part of the expression
710
+ // tree and has been moved to the deferred
711
+ // assignment
712
+ if (val.t !== SerovalNodeType.IndexedValue && val.i != null && this.isMarked(val.i)) {
713
+ // We use a trick here using sequence (or comma) expressions
714
+ // basically we serialize the intended object in place WITHOUT
715
+ // actually returning it, this is by returning a placeholder
716
+ // value that we will remove sometime after.
717
+ const serialized = '(' + this.serialize(val) + ',[' + this.getSpecialReference(SpecialReference.Sentinel) + ',' + this.getSpecialReference(SpecialReference.Sentinel) + '])';
718
+ this.createSetAssignment(id, keyRef, this.getRefParam(val.i));
719
+ this.createDeleteAssignment(id, this.getSpecialReference(SpecialReference.Sentinel));
720
+ return serialized;
721
+ }
722
+ const parent = this.stack;
723
+ this.stack = [];
724
+ this.createSetAssignment(id, keyRef, this.serialize(val));
725
+ this.stack = parent;
726
+ return '';
727
+ }
728
+ if (this.isIndexedValueInStack(val)) {
729
+ // Create ref for the Map instance
730
+ const valueRef = this.getRefParam((val as SerovalIndexedValueNode).i);
731
+ this.markRef(id);
732
+ if (key.t !== SerovalNodeType.IndexedValue && key.i != null && this.isMarked(key.i)) {
733
+ const serialized = '(' + this.serialize(key) + ',[' + this.getSpecialReference(SpecialReference.Sentinel) + ',' + this.getSpecialReference(SpecialReference.Sentinel) + '])';
734
+ this.createSetAssignment(id, this.getRefParam(key.i), valueRef);
735
+ this.createDeleteAssignment(id, this.getSpecialReference(SpecialReference.Sentinel));
736
+ return serialized;
737
+ }
738
+ // Reset stack for the key serialization
739
+ const parent = this.stack;
740
+ this.stack = [];
741
+ this.createSetAssignment(id, this.serialize(key), valueRef);
742
+ this.stack = parent;
743
+ return '';
744
+ }
745
+
746
+ return '[' + this.serialize(key) + ',' + this.serialize(val) + ']';
747
+ }
748
+
749
+ protected serializeMap(
750
+ node: SerovalMapNode,
751
+ ): string {
752
+ let serialized = MAP_CONSTRUCTOR;
753
+ const size = node.e.s;
754
+ const id = node.i;
755
+ if (size) {
756
+ const keys = node.e.k;
757
+ const vals = node.e.v;
758
+ this.stack.push(id);
759
+ let result = this.serializeMapEntry(id, keys[0], vals[0]);
760
+ for (let i = 1, item = result; i < size; i++) {
761
+ item = this.serializeMapEntry(id, keys[i], vals[i]);
762
+ result += (item && result && ',') + item;
763
+ }
764
+ this.stack.pop();
765
+ // Check if there are any values
766
+ // so that the empty Map constructor
767
+ // can be used instead
768
+ if (result) {
769
+ serialized += '([' + result + '])';
770
+ }
771
+ }
772
+ return this.assignIndexedValue(id, serialized);
773
+ }
774
+
775
+ protected serializeArrayBuffer(
776
+ node: SerovalArrayBufferNode,
777
+ ): string {
778
+ let result = 'new Uint8Array(';
779
+ const buffer = node.s;
780
+ const len = buffer.length;
781
+ if (len) {
782
+ result += '[' + buffer[0];
783
+ for (let i = 1; i < len; i++) {
784
+ result += ',' + buffer[i];
785
+ }
786
+ result += ']';
787
+ }
788
+ return this.assignIndexedValue(node.i, result + ').buffer');
789
+ }
790
+
791
+ protected serializeTypedArray(
792
+ node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
793
+ ): string {
794
+ return this.assignIndexedValue(
795
+ node.i,
796
+ 'new ' + node.c + '(' + this.serialize(node.f) + ',' + node.b + ',' + node.l + ')',
797
+ );
798
+ }
799
+
800
+ protected serializeDataView(
801
+ node: SerovalDataViewNode,
802
+ ): string {
803
+ return this.assignIndexedValue(
804
+ node.i,
805
+ 'new DataView(' + this.serialize(node.f) + ',' + node.b + ',' + node.l + ')',
806
+ );
807
+ }
808
+
809
+ protected serializeAggregateError(
810
+ node: SerovalAggregateErrorNode,
811
+ ): string {
812
+ // Serialize the required arguments
813
+ const id = node.i;
814
+ this.stack.push(id);
815
+ const serialized = 'new AggregateError([],"' + node.m + '")';
816
+ this.stack.pop();
817
+ // `AggregateError` might've been extended
818
+ // either through class or custom properties
819
+ // Make sure to assign extra properties
820
+ return this.serializeDictionary(id, node.p, serialized);
821
+ }
822
+
823
+ protected serializeError(
824
+ node: SerovalErrorNode,
825
+ ): string {
826
+ return this.serializeDictionary(node.i, node.p, 'new ' + ERROR_CONSTRUCTOR_STRING[node.s] + '("' + node.m + '")');
827
+ }
828
+
829
+ protected serializePromise(
830
+ node: SerovalPromiseNode,
831
+ ): string {
832
+ let serialized: string;
833
+ // Check if resolved value is a parent expression
834
+ const fulfilled = node.f;
835
+ const id = node.i;
836
+ const constructor = node.s ? PROMISE_RESOLVE : PROMISE_REJECT;
837
+ if (this.isIndexedValueInStack(fulfilled)) {
838
+ // A Promise trick, reference the value
839
+ // inside the `then` expression so that
840
+ // the Promise evaluates after the parent
841
+ // has initialized
842
+ const ref = this.getRefParam((fulfilled as SerovalIndexedValueNode).i);
843
+ if (this.features & Feature.ArrowFunction) {
844
+ if (node.s) {
845
+ serialized = constructor + '().then(()=>' + ref + ')';
846
+ } else {
847
+ serialized = constructor + '().catch(()=>{throw ' + ref + '})';
848
+ }
849
+ } else if (node.s) {
850
+ serialized = constructor + '().then(function(){return ' + ref + '})';
851
+ } else {
852
+ serialized = constructor + '().catch(function(){throw ' + ref + '})';
853
+ }
854
+ } else {
855
+ this.stack.push(id);
856
+ const result = this.serialize(fulfilled);
857
+ this.stack.pop();
858
+ // just inline the value/reference here
859
+ serialized = constructor + '(' + result + ')';
860
+ }
861
+ return this.assignIndexedValue(id, serialized);
862
+ }
863
+
864
+ protected serializeWKSymbol(
865
+ node: SerovalWKSymbolNode,
866
+ ): string {
867
+ return this.assignIndexedValue(node.i, SYMBOL_STRING[node.s]);
868
+ }
869
+
870
+ protected serializeURL(
871
+ node: SerovalURLNode,
872
+ ): string {
873
+ return this.assignIndexedValue(node.i, 'new URL("' + node.s + '")');
874
+ }
875
+
876
+ protected serializeURLSearchParams(
877
+ node: SerovalURLSearchParamsNode,
878
+ ): string {
879
+ return this.assignIndexedValue(
880
+ node.i,
881
+ node.s ? 'new URLSearchParams("' + node.s + '")' : 'new URLSearchParams',
882
+ );
883
+ }
884
+
885
+ protected serializeBlob(
886
+ node: SerovalBlobNode,
887
+ ): string {
888
+ return this.assignIndexedValue(
889
+ node.i,
890
+ 'new Blob([' + this.serialize(node.f) + '],{type:"' + node.c + '"})',
891
+ );
892
+ }
893
+
894
+ protected serializeFile(
895
+ node: SerovalFileNode,
896
+ ): string {
897
+ return this.assignIndexedValue(
898
+ node.i,
899
+ 'new File([' + this.serialize(node.f) + '],"' + node.m + '",{type:"' + node.c + '",lastModified:' + node.b + '})',
900
+ );
901
+ }
902
+
903
+ protected serializeHeaders(
904
+ node: SerovalHeadersNode,
905
+ ): string {
906
+ return this.assignIndexedValue(
907
+ node.i,
908
+ 'new Headers(' + this.serializeProperties(node.i, node.e) + ')',
909
+ );
910
+ }
911
+
912
+ protected serializeFormDataEntry(id: number, key: string, value: SerovalNode): string {
913
+ return this.getRefParam(id) + '.append("' + key + '",' + this.serialize(value) + ')';
914
+ }
915
+
916
+ protected serializeFormDataEntries(
917
+ node: SerovalFormDataNode,
918
+ size: number,
919
+ ): string {
920
+ const keys = node.e.k;
921
+ const vals = node.e.v;
922
+ const id = node.i;
923
+ let result = this.serializeFormDataEntry(id, keys[0], vals[0]);
924
+ for (let i = 1; i < size; i++) {
925
+ result += ',' + this.serializeFormDataEntry(id, keys[i], vals[i]);
926
+ }
927
+ return result;
928
+ }
929
+
930
+ protected serializeFormData(
931
+ node: SerovalFormDataNode,
932
+ ): string {
933
+ const size = node.e.s;
934
+ const id = node.i;
935
+ if (size) {
936
+ this.markRef(id);
937
+ }
938
+ const result = this.assignIndexedValue(id, 'new FormData()');
939
+ if (size) {
940
+ const entries = this.serializeFormDataEntries(node, size);
941
+ return '(' + result + ',' + (entries ? entries + ',' : '') + this.getRefParam(id) + ')';
942
+ }
943
+ return result;
944
+ }
945
+
946
+ protected serializeBoxed(
947
+ node: SerovalBoxedNode,
948
+ ): string {
949
+ return this.assignIndexedValue(node.i, 'Object(' + this.serialize(node.f) + ')');
950
+ }
951
+
952
+ protected serializeRequest(
953
+ node: SerovalRequestNode,
954
+ ): string {
955
+ return this.assignIndexedValue(node.i, 'new Request("' + node.s + '",' + this.serialize(node.f) + ')');
956
+ }
957
+
958
+ protected serializeResponse(
959
+ node: SerovalResponseNode,
960
+ ): string {
961
+ return this.assignIndexedValue(
962
+ node.i,
963
+ 'new Response(' + this.serialize(node.a[0]) + ',' + this.serialize(node.a[1]) + ')',
964
+ );
965
+ }
966
+
967
+ protected serializeEvent(
968
+ node: SerovalEventNode,
969
+ ): string {
970
+ return this.assignIndexedValue(
971
+ node.i,
972
+ 'new Event("' + node.s + '",' + this.serialize(node.f) + ')',
973
+ );
974
+ }
975
+
976
+ protected serializeCustomEvent(
977
+ node: SerovalCustomEventNode,
978
+ ): string {
979
+ return this.assignIndexedValue(
980
+ node.i,
981
+ 'new CustomEvent("' + node.s + '",' + this.serialize(node.f) + ')',
982
+ );
983
+ }
984
+
985
+ protected serializeDOMException(
986
+ node: SerovalDOMExceptionNode,
987
+ ): string {
988
+ return this.assignIndexedValue(
989
+ node.i,
990
+ 'new DOMException("' + node.s + '","' + node.c + '")',
991
+ );
992
+ }
993
+
994
+ protected serializePlugin(
995
+ node: SerovalPluginNode,
996
+ ): string {
997
+ const currentPlugins = this.plugins;
998
+ if (currentPlugins) {
999
+ for (let i = 0, len = currentPlugins.length; i < len; i++) {
1000
+ const plugin = currentPlugins[i];
1001
+ if (plugin.tag === node.c) {
1002
+ return plugin.serialize(node.s, this, {
1003
+ id: node.i,
1004
+ });
1005
+ }
1006
+ }
1007
+ }
1008
+ throw new Error('Missing plugin for tag "' + node.c + '".');
1009
+ }
1010
+
1011
+ protected abstract serializePromiseConstructor(
1012
+ node: SerovalPromiseConstructorNode,
1013
+ ): string;
1014
+
1015
+ protected abstract serializePromiseResolve(
1016
+ node: SerovalPromiseResolveNode,
1017
+ ): string;
1018
+
1019
+ protected abstract serializePromiseReject(
1020
+ node: SerovalPromiseRejectNode,
1021
+ ): string;
1022
+
1023
+ protected abstract serializeReadableStreamConstructor(
1024
+ node: SerovalReadableStreamConstructorNode,
1025
+ ): string;
1026
+
1027
+ protected abstract serializeReadableStreamEnqueue(
1028
+ node: SerovalReadableStreamEnqueueNode,
1029
+ ): string;
1030
+
1031
+ protected abstract serializeReadableStreamError(
1032
+ node: SerovalReadableStreamErrorNode,
1033
+ ): string;
1034
+
1035
+ protected abstract serializeReadableStreamClose(
1036
+ node: SerovalReadableStreamCloseNode,
1037
+ ): string;
1038
+
1039
+ serialize(node: SerovalNode): string {
1040
+ switch (node.t) {
1041
+ case SerovalNodeType.Constant:
1042
+ return CONSTANT_STRING[node.s];
1043
+ case SerovalNodeType.Number:
1044
+ return '' + node.s;
1045
+ case SerovalNodeType.String:
1046
+ return '"' + node.s + '"';
1047
+ case SerovalNodeType.BigInt:
1048
+ return node.s + 'n';
1049
+ case SerovalNodeType.IndexedValue:
1050
+ return this.getRefParam(node.i);
1051
+ case SerovalNodeType.Reference:
1052
+ return this.serializeReference(node);
1053
+ case SerovalNodeType.Array:
1054
+ return this.serializeArray(node);
1055
+ case SerovalNodeType.Object:
1056
+ return this.serializeObject(node);
1057
+ case SerovalNodeType.NullConstructor:
1058
+ return this.serializeNullConstructor(node);
1059
+ case SerovalNodeType.Date:
1060
+ return this.serializeDate(node);
1061
+ case SerovalNodeType.RegExp:
1062
+ return this.serializeRegExp(node);
1063
+ case SerovalNodeType.Set:
1064
+ return this.serializeSet(node);
1065
+ case SerovalNodeType.Map:
1066
+ return this.serializeMap(node);
1067
+ case SerovalNodeType.ArrayBuffer:
1068
+ return this.serializeArrayBuffer(node);
1069
+ case SerovalNodeType.BigIntTypedArray:
1070
+ case SerovalNodeType.TypedArray:
1071
+ return this.serializeTypedArray(node);
1072
+ case SerovalNodeType.DataView:
1073
+ return this.serializeDataView(node);
1074
+ case SerovalNodeType.AggregateError:
1075
+ return this.serializeAggregateError(node);
1076
+ case SerovalNodeType.Error:
1077
+ return this.serializeError(node);
1078
+ case SerovalNodeType.Promise:
1079
+ return this.serializePromise(node);
1080
+ case SerovalNodeType.WKSymbol:
1081
+ return this.serializeWKSymbol(node);
1082
+ case SerovalNodeType.URL:
1083
+ return this.serializeURL(node);
1084
+ case SerovalNodeType.URLSearchParams:
1085
+ return this.serializeURLSearchParams(node);
1086
+ case SerovalNodeType.Blob:
1087
+ return this.serializeBlob(node);
1088
+ case SerovalNodeType.File:
1089
+ return this.serializeFile(node);
1090
+ case SerovalNodeType.Headers:
1091
+ return this.serializeHeaders(node);
1092
+ case SerovalNodeType.FormData:
1093
+ return this.serializeFormData(node);
1094
+ case SerovalNodeType.Boxed:
1095
+ return this.serializeBoxed(node);
1096
+ case SerovalNodeType.PromiseConstructor:
1097
+ return this.serializePromiseConstructor(node);
1098
+ case SerovalNodeType.PromiseResolve:
1099
+ return this.serializePromiseResolve(node);
1100
+ case SerovalNodeType.PromiseReject:
1101
+ return this.serializePromiseReject(node);
1102
+ case SerovalNodeType.ReadableStreamConstructor:
1103
+ return this.serializeReadableStreamConstructor(node);
1104
+ case SerovalNodeType.ReadableStreamEnqueue:
1105
+ return this.serializeReadableStreamEnqueue(node);
1106
+ case SerovalNodeType.ReadableStreamError:
1107
+ return this.serializeReadableStreamError(node);
1108
+ case SerovalNodeType.ReadableStreamClose:
1109
+ return this.serializeReadableStreamClose(node);
1110
+ case SerovalNodeType.Request:
1111
+ return this.serializeRequest(node);
1112
+ case SerovalNodeType.Response:
1113
+ return this.serializeResponse(node);
1114
+ case SerovalNodeType.Event:
1115
+ return this.serializeEvent(node);
1116
+ case SerovalNodeType.CustomEvent:
1117
+ return this.serializeCustomEvent(node);
1118
+ case SerovalNodeType.DOMException:
1119
+ return this.serializeDOMException(node);
1120
+ case SerovalNodeType.Plugin:
1121
+ return this.serializePlugin(node);
1122
+ default:
1123
+ throw new Error('invariant');
1124
+ }
1125
+ }
1126
+ }