seroval 0.4.0-alpha.13 → 0.4.0-alpha.14

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.
@@ -7,7 +7,6 @@ import {
7
7
  markRef,
8
8
  } from '../context';
9
9
  import quote from '../quote';
10
- import { isReferenceInStack } from './shared';
11
10
  import {
12
11
  SerovalAggregateErrorNode,
13
12
  SerovalArrayNode,
@@ -20,18 +19,19 @@ import {
20
19
  SerovalNullConstructorNode,
21
20
  SerovalObjectRecordNode,
22
21
  SerovalPromiseNode,
22
+ SerovalReferenceNode,
23
23
  SerovalSetNode,
24
24
  SerovalTypedArrayNode,
25
25
  } from './types';
26
26
 
27
27
  function getAssignmentExpression(assignment: Assignment): string {
28
- switch (assignment.type) {
28
+ switch (assignment.t) {
29
29
  case 'index':
30
- return `${assignment.source}=${assignment.value}`;
30
+ return `${assignment.s}=${assignment.v}`;
31
31
  case 'map':
32
- return `${assignment.source}.set(${assignment.key},${assignment.value})`;
32
+ return `${assignment.s}.set(${assignment.k},${assignment.v})`;
33
33
  case 'set':
34
- return `${assignment.source}.add(${assignment.value})`;
34
+ return `${assignment.s}.add(${assignment.v})`;
35
35
  default:
36
36
  return '';
37
37
  }
@@ -43,29 +43,31 @@ function mergeAssignments(assignments: Assignment[]) {
43
43
  let prev = current;
44
44
  for (let i = 1, len = assignments.length; i < len; i++) {
45
45
  const item = assignments[i];
46
- if (item.type === prev.type) {
47
- if (item.type === 'index' && item.value === prev.value) {
46
+ if (item.t === prev.t) {
47
+ if (item.t === 'index' && item.v === prev.v) {
48
48
  // Merge if the right-hand value is the same
49
49
  // saves at least 2 chars
50
50
  current = {
51
- type: 'index',
52
- source: item.source,
53
- value: getAssignmentExpression(current),
51
+ t: 'index',
52
+ s: item.s,
53
+ k: undefined,
54
+ v: getAssignmentExpression(current),
54
55
  };
55
- } else if (item.type === 'map' && item.source === prev.source) {
56
+ } else if (item.t === 'map' && item.s === prev.s) {
56
57
  // Maps has chaining methods, merge if source is the same
57
58
  current = {
58
- type: 'map',
59
- source: getAssignmentExpression(current),
60
- key: item.key,
61
- value: item.value,
59
+ t: 'map',
60
+ s: getAssignmentExpression(current),
61
+ k: item.k,
62
+ v: item.v,
62
63
  };
63
- } else if (item.type === 'set' && item.source === prev.source) {
64
+ } else if (item.t === 'set' && item.s === prev.s) {
64
65
  // Sets has chaining methods too
65
66
  current = {
66
- type: 'set',
67
- source: getAssignmentExpression(current),
68
- value: item.value,
67
+ t: 'set',
68
+ s: getAssignmentExpression(current),
69
+ k: undefined,
70
+ v: item.v,
69
71
  };
70
72
  } else {
71
73
  // Different assignment, push current
@@ -105,16 +107,6 @@ export function resolvePatches(ctx: SerializationContext) {
105
107
  * This is different from the assignments array as this one
106
108
  * signifies creation rather than mutation
107
109
  */
108
- function assignRef(
109
- ctx: SerializationContext,
110
- index: number,
111
- value: string,
112
- ) {
113
- if (ctx.markedRefs[index]) {
114
- return `${getRefParam(ctx, index)}=${value}`;
115
- }
116
- return value;
117
- }
118
110
 
119
111
  function createAssignment(
120
112
  ctx: SerializationContext,
@@ -122,9 +114,10 @@ function createAssignment(
122
114
  value: string,
123
115
  ) {
124
116
  ctx.assignments.push({
125
- type: 'index',
126
- source,
127
- value,
117
+ t: 'index',
118
+ s: source,
119
+ k: undefined,
120
+ v: value,
128
121
  });
129
122
  }
130
123
 
@@ -135,9 +128,10 @@ function createSetAdd(
135
128
  ) {
136
129
  markRef(ctx, ref);
137
130
  ctx.assignments.push({
138
- type: 'set',
139
- source: getRefParam(ctx, ref),
140
- value,
131
+ t: 'set',
132
+ s: getRefParam(ctx, ref),
133
+ k: undefined,
134
+ v: value,
141
135
  });
142
136
  }
143
137
 
@@ -149,10 +143,10 @@ function createMapSet(
149
143
  ) {
150
144
  markRef(ctx, ref);
151
145
  ctx.assignments.push({
152
- type: 'map',
153
- source: getRefParam(ctx, ref),
154
- key,
155
- value,
146
+ t: 'map',
147
+ s: getRefParam(ctx, ref),
148
+ k: key,
149
+ v: value,
156
150
  });
157
151
  }
158
152
 
@@ -178,374 +172,379 @@ function createObjectAssign(
178
172
 
179
173
  const IDENTIFIER_CHECK = /^([$A-Z_][0-9A-Z_$]*)$/i;
180
174
 
181
- function serializeAssignments(
182
- ctx: SerializationContext,
183
- targetRef: number,
184
- node: SerovalObjectRecordNode,
185
- ) {
186
- ctx.stack.push(targetRef);
187
- const mainAssignments: Assignment[] = [];
188
- for (let i = 0; i < node.s; i++) {
189
- const parentStack = ctx.stack;
190
- ctx.stack = [];
191
- const refParam = serializeTree(ctx, node.v[i]);
192
- ctx.stack = parentStack;
193
- const key = node.k[i];
194
- const check = Number(key);
195
- const parentAssignment = ctx.assignments;
196
- ctx.assignments = mainAssignments;
197
- // Test if key is a valid number or JS identifier
198
- // so that we don't have to serialize the key and wrap with brackets
199
- const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
200
- if (isIdentifier && Number.isNaN(check)) {
201
- createObjectAssign(ctx, targetRef, key, refParam);
202
- } else {
203
- createArrayAssign(ctx, targetRef, isIdentifier ? key : quote(key), refParam);
204
- }
205
- ctx.assignments = parentAssignment;
206
- }
207
- ctx.stack.pop();
208
- return resolveAssignments(mainAssignments);
209
- }
175
+ export default class SerovalSerializer {
176
+ ctx: SerializationContext;
210
177
 
211
- function serializeObject(
212
- ctx: SerializationContext,
213
- sourceID: number,
214
- node: SerovalObjectRecordNode,
215
- ) {
216
- if (node.s === 0) {
217
- return '{}';
178
+ constructor(ctx: SerializationContext) {
179
+ this.ctx = ctx;
218
180
  }
219
- let result = '';
220
- ctx.stack.push(sourceID);
221
- for (let i = 0; i < node.s; i++) {
222
- const key = node.k[i];
223
- const val = node.v[i];
224
- const check = Number(key);
225
- // Test if key is a valid number or JS identifier
226
- // so that we don't have to serialize the key and wrap with brackets
227
- const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
228
- if (isReferenceInStack(ctx, val)) {
229
- const refParam = getRefParam(ctx, val.i);
230
- if (isIdentifier && Number.isNaN(check)) {
231
- createObjectAssign(ctx, sourceID, key, refParam);
232
- } else {
233
- createArrayAssign(ctx, sourceID, isIdentifier ? key : quote(key), refParam);
234
- }
235
- } else {
236
- result += `${isIdentifier ? key : quote(key)}:${serializeTree(ctx, val)},`;
237
- }
238
- }
239
- ctx.stack.pop();
240
- return `{${result.substring(0, result.length - 1)}}`;
241
- }
242
181
 
243
- function serializePromise(
244
- ctx: SerializationContext,
245
- node: SerovalPromiseNode,
246
- ) {
247
- let serialized: string;
248
- // Check if resolved value is a parent expression
249
- if (isReferenceInStack(ctx, node.n)) {
250
- // A Promise trick, reference the value
251
- // inside the `then` expression so that
252
- // the Promise evaluates after the parent
253
- // has initialized
254
- serialized = `Promise.resolve().then(()=>${getRefParam(ctx, node.n.i)})`;
255
- } else {
256
- ctx.stack.push(node.i);
257
- const result = serializeTree(ctx, node.n);
258
- ctx.stack.pop();
259
- // just inline the value/reference here
260
- serialized = `Promise.resolve(${result})`;
182
+ assignRef(index: number, value: string) {
183
+ if (this.ctx.markedRefs.has(index)) {
184
+ return `${getRefParam(this.ctx, index)}=${value}`;
185
+ }
186
+ return value;
261
187
  }
262
- return assignRef(ctx, node.i, serialized);
263
- }
264
188
 
265
- function serializeTypedArray(
266
- ctx: SerializationContext,
267
- node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
268
- ) {
269
- let args = `[${node.s}]`;
270
- if (node.l !== 0) {
271
- args += `,${node.l}`;
189
+ isReferenceInStack(
190
+ node: SerovalNode,
191
+ ): node is SerovalReferenceNode {
192
+ return node.t === SerovalNodeType.Reference && this.ctx.stack.includes(node.i);
272
193
  }
273
- return assignRef(ctx, node.i, `new ${node.c}(${args})`);
274
- }
275
194
 
276
- function serializeSet(
277
- ctx: SerializationContext,
278
- node: SerovalSetNode,
279
- ) {
280
- let serialized = 'new Set';
281
- const size = node.a.length;
282
- if (size) {
283
- let result = '';
284
- ctx.stack.push(node.i);
195
+ serializeNodeList(
196
+ node: SerovalArrayNode | SerovalIterableNode,
197
+ ) {
198
+ // This is different than Map and Set
199
+ // because we also need to serialize
200
+ // the holes of the Array
201
+ const size = node.a.length;
202
+ let values = '';
285
203
  for (let i = 0; i < size; i++) {
286
204
  const item = node.a[i];
287
- if (isReferenceInStack(ctx, item)) {
288
- createSetAdd(ctx, node.i, getRefParam(ctx, item.i));
205
+ // Check if index is a hole
206
+ if (item) {
207
+ // Check if item is a parent
208
+ if (this.isReferenceInStack(item)) {
209
+ createArrayAssign(this.ctx, node.i, i, getRefParam(this.ctx, item.i));
210
+ values += ',';
211
+ } else {
212
+ values += this.serialize(item);
213
+ if (i < size - 1) {
214
+ values += ',';
215
+ }
216
+ }
289
217
  } else {
290
- // Push directly
291
- result += `${serializeTree(ctx, item)},`;
218
+ // Add an empty item
219
+ values += ',';
292
220
  }
293
221
  }
294
- ctx.stack.pop();
295
- if (result) {
296
- serialized += `([${result.substring(0, result.length - 1)}])`;
297
- }
222
+ return `[${values}]`;
298
223
  }
299
- return assignRef(ctx, node.i, serialized);
300
- }
301
224
 
302
- function serializeMap(
303
- ctx: SerializationContext,
304
- node: SerovalMapNode,
305
- ) {
306
- let serialized = 'new Map';
307
- if (node.d.s) {
225
+ serializeArray(
226
+ node: SerovalArrayNode,
227
+ ) {
228
+ this.ctx.stack.push(node.i);
229
+ const result = this.serializeNodeList(node);
230
+ this.ctx.stack.pop();
231
+ return this.assignRef(node.i, result);
232
+ }
233
+
234
+ serializeObject(
235
+ sourceID: number,
236
+ node: SerovalObjectRecordNode,
237
+ ) {
238
+ if (node.s === 0) {
239
+ return '{}';
240
+ }
308
241
  let result = '';
309
- ctx.stack.push(node.i);
310
- for (let i = 0; i < node.d.s; i++) {
311
- // Check if key is a parent
312
- const key = node.d.k[i];
313
- const val = node.d.v[i];
314
- if (isReferenceInStack(ctx, key)) {
315
- // Create reference for the map instance
316
- const keyRef = getRefParam(ctx, key.i);
317
- // Check if value is a parent
318
- if (isReferenceInStack(ctx, val)) {
319
- const valueRef = getRefParam(ctx, val.i);
320
- // Register an assignment since
321
- // both key and value are a parent of this
322
- // Map instance
323
- createMapSet(ctx, node.i, keyRef, valueRef);
242
+ this.ctx.stack.push(sourceID);
243
+ for (let i = 0; i < node.s; i++) {
244
+ const key = node.k[i];
245
+ const val = node.v[i];
246
+ const check = Number(key);
247
+ // Test if key is a valid number or JS identifier
248
+ // so that we don't have to serialize the key and wrap with brackets
249
+ const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
250
+ if (this.isReferenceInStack(val)) {
251
+ const refParam = getRefParam(this.ctx, val.i);
252
+ if (isIdentifier && Number.isNaN(check)) {
253
+ createObjectAssign(this.ctx, sourceID, key, refParam);
324
254
  } else {
325
- // Reset the stack
326
- // This is required because the serialized
327
- // value is no longer part of the expression
328
- // tree and has been moved to the deferred
329
- // assignment
330
- const parent = ctx.stack;
331
- ctx.stack = [];
332
- createMapSet(ctx, node.i, keyRef, serializeTree(ctx, val));
333
- ctx.stack = parent;
255
+ createArrayAssign(this.ctx, sourceID, isIdentifier ? key : quote(key), refParam);
334
256
  }
335
- } else if (isReferenceInStack(ctx, val)) {
336
- // Create ref for the Map instance
337
- const valueRef = getRefParam(ctx, val.i);
338
- // Reset stack for the key serialization
339
- const parent = ctx.stack;
340
- ctx.stack = [];
341
- createMapSet(ctx, node.i, serializeTree(ctx, key), valueRef);
342
- ctx.stack = parent;
343
257
  } else {
344
- result += `[${serializeTree(ctx, key)},${serializeTree(ctx, val)}],`;
258
+ result += `${isIdentifier ? key : quote(key)}:${this.serialize(val)},`;
345
259
  }
346
260
  }
347
- ctx.stack.pop();
348
- // Check if there are any values
349
- // so that the empty Map constructor
350
- // can be used instead
351
- if (result) {
352
- serialized += `([${result.substring(0, result.length - 1)}])`;
261
+ this.ctx.stack.pop();
262
+ return `{${result.substring(0, result.length - 1)}}`;
263
+ }
264
+
265
+ serializeWithObjectAssign(
266
+ value: SerovalObjectRecordNode,
267
+ id: number,
268
+ serialized: string,
269
+ ) {
270
+ const fields = this.serializeObject(id, value);
271
+ if (fields !== '{}') {
272
+ return `Object.assign(${serialized},${fields})`;
353
273
  }
274
+ return serialized;
354
275
  }
355
- return assignRef(ctx, node.i, serialized);
356
- }
357
276
 
358
- function serializeNodeList(
359
- ctx: SerializationContext,
360
- node: SerovalArrayNode | SerovalIterableNode,
361
- ) {
362
- // This is different than Map and Set
363
- // because we also need to serialize
364
- // the holes of the Array
365
- const size = node.a.length;
366
- let values = '';
367
- for (let i = 0; i < size; i++) {
368
- const item = node.a[i];
369
- // Check if index is a hole
370
- if (item) {
371
- // Check if item is a parent
372
- if (isReferenceInStack(ctx, item)) {
373
- createArrayAssign(ctx, node.i, i, getRefParam(ctx, item.i));
374
- values += ',';
277
+ serializeAssignments(
278
+ sourceID: number,
279
+ node: SerovalObjectRecordNode,
280
+ ) {
281
+ this.ctx.stack.push(sourceID);
282
+ const mainAssignments: Assignment[] = [];
283
+ for (let i = 0; i < node.s; i++) {
284
+ const parentStack = this.ctx.stack;
285
+ this.ctx.stack = [];
286
+ const refParam = this.serialize(node.v[i]);
287
+ this.ctx.stack = parentStack;
288
+ const key = node.k[i];
289
+ const check = Number(key);
290
+ const parentAssignment = this.ctx.assignments;
291
+ this.ctx.assignments = mainAssignments;
292
+ // Test if key is a valid number or JS identifier
293
+ // so that we don't have to serialize the key and wrap with brackets
294
+ const isIdentifier = check >= 0 || IDENTIFIER_CHECK.test(key);
295
+ if (isIdentifier && Number.isNaN(check)) {
296
+ createObjectAssign(this.ctx, sourceID, key, refParam);
375
297
  } else {
376
- values += serializeTree(ctx, item);
377
- if (i < size - 1) {
378
- values += ',';
379
- }
298
+ createArrayAssign(this.ctx, sourceID, isIdentifier ? key : quote(key), refParam);
380
299
  }
300
+ this.ctx.assignments = parentAssignment;
301
+ }
302
+ this.ctx.stack.pop();
303
+ return resolveAssignments(mainAssignments);
304
+ }
305
+
306
+ serializeNullConstructor(
307
+ node: SerovalNullConstructorNode,
308
+ ) {
309
+ let serialized = 'Object.create(null)';
310
+ if (this.ctx.features & Feature.ObjectAssign) {
311
+ serialized = this.serializeWithObjectAssign(node.d, node.i, serialized);
381
312
  } else {
382
- // Add an empty item
383
- values += ',';
313
+ markRef(this.ctx, node.i);
314
+ const assignments = this.serializeAssignments(node.i, node.d);
315
+ if (assignments) {
316
+ const ref = getRefParam(this.ctx, node.i);
317
+ return `(${this.assignRef(node.i, serialized)},${assignments}${ref})`;
318
+ }
384
319
  }
320
+ return this.assignRef(node.i, serialized);
385
321
  }
386
- return `[${values}]`;
387
- }
388
322
 
389
- function serializeArray(
390
- ctx: SerializationContext,
391
- node: SerovalArrayNode,
392
- ) {
393
- ctx.stack.push(node.i);
394
- const result = serializeNodeList(ctx, node);
395
- ctx.stack.pop();
396
- return assignRef(ctx, node.i, result);
397
- }
323
+ serializeSet(
324
+ node: SerovalSetNode,
325
+ ) {
326
+ let serialized = 'new Set';
327
+ const size = node.a.length;
328
+ if (size) {
329
+ let result = '';
330
+ this.ctx.stack.push(node.i);
331
+ for (let i = 0; i < size; i++) {
332
+ const item = node.a[i];
333
+ if (this.isReferenceInStack(item)) {
334
+ createSetAdd(this.ctx, node.i, getRefParam(this.ctx, item.i));
335
+ } else {
336
+ // Push directly
337
+ result += `${this.serialize(item)},`;
338
+ }
339
+ }
340
+ this.ctx.stack.pop();
341
+ if (result) {
342
+ serialized += `([${result.substring(0, result.length - 1)}])`;
343
+ }
344
+ }
345
+ return this.assignRef(node.i, serialized);
346
+ }
398
347
 
399
- function serializeWithObjectAssign(
400
- ctx: SerializationContext,
401
- value: SerovalObjectRecordNode,
402
- id: number,
403
- serialized: string,
404
- ) {
405
- const fields = serializeObject(ctx, id, value);
406
- if (fields !== '{}') {
407
- return `Object.assign(${serialized},${fields})`;
348
+ serializeMap(
349
+ node: SerovalMapNode,
350
+ ) {
351
+ let serialized = 'new Map';
352
+ if (node.d.s) {
353
+ let result = '';
354
+ this.ctx.stack.push(node.i);
355
+ for (let i = 0; i < node.d.s; i++) {
356
+ // Check if key is a parent
357
+ const key = node.d.k[i];
358
+ const val = node.d.v[i];
359
+ if (this.isReferenceInStack(key)) {
360
+ // Create reference for the map instance
361
+ const keyRef = getRefParam(this.ctx, key.i);
362
+ // Check if value is a parent
363
+ if (this.isReferenceInStack(val)) {
364
+ const valueRef = getRefParam(this.ctx, val.i);
365
+ // Register an assignment since
366
+ // both key and value are a parent of this
367
+ // Map instance
368
+ createMapSet(this.ctx, node.i, keyRef, valueRef);
369
+ } else {
370
+ // Reset the stack
371
+ // This is required because the serialized
372
+ // value is no longer part of the expression
373
+ // tree and has been moved to the deferred
374
+ // assignment
375
+ const parent = this.ctx.stack;
376
+ this.ctx.stack = [];
377
+ createMapSet(this.ctx, node.i, keyRef, this.serialize(val));
378
+ this.ctx.stack = parent;
379
+ }
380
+ } else if (this.isReferenceInStack(val)) {
381
+ // Create ref for the Map instance
382
+ const valueRef = getRefParam(this.ctx, val.i);
383
+ // Reset stack for the key serialization
384
+ const parent = this.ctx.stack;
385
+ this.ctx.stack = [];
386
+ createMapSet(this.ctx, node.i, this.serialize(key), valueRef);
387
+ this.ctx.stack = parent;
388
+ } else {
389
+ result += `[${this.serialize(key)},${this.serialize(val)}],`;
390
+ }
391
+ }
392
+ this.ctx.stack.pop();
393
+ // Check if there are any values
394
+ // so that the empty Map constructor
395
+ // can be used instead
396
+ if (result) {
397
+ serialized += `([${result.substring(0, result.length - 1)}])`;
398
+ }
399
+ }
400
+ return this.assignRef(node.i, serialized);
408
401
  }
409
- return serialized;
410
- }
411
402
 
412
- function serializeAggregateError(
413
- ctx: SerializationContext,
414
- node: SerovalAggregateErrorNode,
415
- ) {
416
- // Serialize the required arguments
417
- ctx.stack.push(node.i);
418
- let serialized = `new AggregateError(${serializeTree(ctx, node.n)},${quote(node.m)})`;
419
- ctx.stack.pop();
420
- // `AggregateError` might've been extended
421
- // either through class or custom properties
422
- // Make sure to assign extra properties
423
- if (node.d) {
424
- if (ctx.features & Feature.ObjectAssign) {
425
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
426
- } else {
427
- markRef(ctx, node.i);
428
- const assignments = serializeAssignments(ctx, node.i, node.d);
429
- if (assignments) {
430
- const ref = getRefParam(ctx, node.i);
431
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
403
+ serializeAggregateError(
404
+ node: SerovalAggregateErrorNode,
405
+ ) {
406
+ // Serialize the required arguments
407
+ this.ctx.stack.push(node.i);
408
+ let serialized = `new AggregateError(${this.serialize(node.n)},${quote(node.m)})`;
409
+ this.ctx.stack.pop();
410
+ // `AggregateError` might've been extended
411
+ // either through class or custom properties
412
+ // Make sure to assign extra properties
413
+ if (node.d) {
414
+ if (this.ctx.features & Feature.ObjectAssign) {
415
+ serialized = this.serializeWithObjectAssign(node.d, node.i, serialized);
416
+ } else {
417
+ markRef(this.ctx, node.i);
418
+ const assignments = this.serializeAssignments(node.i, node.d);
419
+ if (assignments) {
420
+ const ref = getRefParam(this.ctx, node.i);
421
+ return `(${this.assignRef(node.i, serialized)},${assignments}${ref})`;
422
+ }
432
423
  }
433
424
  }
425
+ return this.assignRef(node.i, serialized);
434
426
  }
435
- return assignRef(ctx, node.i, serialized);
436
- }
437
427
 
438
- function serializeError(
439
- ctx: SerializationContext,
440
- node: SerovalErrorNode,
441
- ) {
442
- let serialized = `new ${node.c}(${quote(node.m)})`;
443
- if (node.d) {
444
- if (ctx.features & Feature.ObjectAssign) {
445
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
446
- } else {
447
- markRef(ctx, node.i);
448
- const assignments = serializeAssignments(ctx, node.i, node.d);
449
- if (assignments) {
450
- const ref = getRefParam(ctx, node.i);
451
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
428
+ serializeError(
429
+ node: SerovalErrorNode,
430
+ ) {
431
+ let serialized = `new ${node.c}(${quote(node.m)})`;
432
+ if (node.d) {
433
+ if (this.ctx.features & Feature.ObjectAssign) {
434
+ serialized = this.serializeWithObjectAssign(node.d, node.i, serialized);
435
+ } else {
436
+ markRef(this.ctx, node.i);
437
+ const assignments = this.serializeAssignments(node.i, node.d);
438
+ if (assignments) {
439
+ const ref = getRefParam(this.ctx, node.i);
440
+ return `(${this.assignRef(node.i, serialized)},${assignments}${ref})`;
441
+ }
452
442
  }
453
443
  }
444
+ return this.assignRef(node.i, serialized);
454
445
  }
455
- return assignRef(ctx, node.i, serialized);
456
- }
457
446
 
458
- function serializeIterable(
459
- ctx: SerializationContext,
460
- node: SerovalIterableNode,
461
- ) {
462
- const parent = ctx.stack;
463
- ctx.stack = [];
464
- const values = serializeNodeList(ctx, node);
465
- ctx.stack = parent;
466
- let serialized: string;
467
- if (ctx.features & Feature.ArrayPrototypeValues) {
468
- serialized = `${values}.values()`;
469
- } else {
470
- serialized = `${values}[Symbol.iterator]()`;
447
+ serializePromise(
448
+ node: SerovalPromiseNode,
449
+ ) {
450
+ let serialized: string;
451
+ // Check if resolved value is a parent expression
452
+ if (this.isReferenceInStack(node.n)) {
453
+ // A Promise trick, reference the value
454
+ // inside the `then` expression so that
455
+ // the Promise evaluates after the parent
456
+ // has initialized
457
+ serialized = `Promise.resolve().then(()=>${getRefParam(this.ctx, node.n.i)})`;
458
+ } else {
459
+ this.ctx.stack.push(node.i);
460
+ const result = this.serialize(node.n);
461
+ this.ctx.stack.pop();
462
+ // just inline the value/reference here
463
+ serialized = `Promise.resolve(${result})`;
464
+ }
465
+ return this.assignRef(node.i, serialized);
471
466
  }
472
- if (ctx.features & Feature.ArrowFunction) {
473
- serialized = `{[Symbol.iterator]:()=>${serialized}}`;
474
- } else if (ctx.features & Feature.MethodShorthand) {
475
- serialized = `{[Symbol.iterator](){return ${serialized}}}`;
476
- } else {
477
- serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`;
467
+
468
+ serializeTypedArray(
469
+ node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
470
+ ) {
471
+ let args = `[${node.s}]`;
472
+ if (node.l !== 0) {
473
+ args += `,${node.l}`;
474
+ }
475
+ return this.assignRef(node.i, `new ${node.c}(${args})`);
478
476
  }
479
- if (node.d) {
480
- if (ctx.features & Feature.ObjectAssign) {
481
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
477
+
478
+ serializeIterable(
479
+ node: SerovalIterableNode,
480
+ ) {
481
+ const parent = this.ctx.stack;
482
+ this.ctx.stack = [];
483
+ const values = this.serializeNodeList(node);
484
+ this.ctx.stack = parent;
485
+ let serialized: string;
486
+ if (this.ctx.features & Feature.ArrayPrototypeValues) {
487
+ serialized = `${values}.values()`;
482
488
  } else {
483
- markRef(ctx, node.i);
484
- const assignments = serializeAssignments(ctx, node.i, node.d);
485
- if (assignments) {
486
- const ref = getRefParam(ctx, node.i);
487
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
489
+ serialized = `${values}[Symbol.iterator]()`;
490
+ }
491
+ if (this.ctx.features & Feature.ArrowFunction) {
492
+ serialized = `{[Symbol.iterator]:()=>${serialized}}`;
493
+ } else if (this.ctx.features & Feature.MethodShorthand) {
494
+ serialized = `{[Symbol.iterator](){return ${serialized}}}`;
495
+ } else {
496
+ serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`;
497
+ }
498
+ if (node.d) {
499
+ if (this.ctx.features & Feature.ObjectAssign) {
500
+ serialized = this.serializeWithObjectAssign(node.d, node.i, serialized);
501
+ } else {
502
+ markRef(this.ctx, node.i);
503
+ const assignments = this.serializeAssignments(node.i, node.d);
504
+ if (assignments) {
505
+ const ref = getRefParam(this.ctx, node.i);
506
+ return `(${this.assignRef(node.i, serialized)},${assignments}${ref})`;
507
+ }
488
508
  }
489
509
  }
510
+ return this.assignRef(node.i, serialized);
490
511
  }
491
- return assignRef(ctx, node.i, serialized);
492
- }
493
512
 
494
- function serializeNullConstructor(
495
- ctx: SerializationContext,
496
- node: SerovalNullConstructorNode,
497
- ) {
498
- let serialized = 'Object.create(null)';
499
- if (ctx.features & Feature.ObjectAssign) {
500
- serialized = serializeWithObjectAssign(ctx, node.d, node.i, serialized);
501
- } else {
502
- markRef(ctx, node.i);
503
- const assignments = serializeAssignments(ctx, node.i, node.d);
504
- if (assignments) {
505
- const ref = getRefParam(ctx, node.i);
506
- return `(${assignRef(ctx, node.i, serialized)},${assignments}${ref})`;
513
+ serialize(node: SerovalNode): string {
514
+ switch (node.t) {
515
+ case SerovalNodeType.Primitive:
516
+ return String(node.s);
517
+ case SerovalNodeType.BigInt:
518
+ return node.s;
519
+ case SerovalNodeType.Reference:
520
+ return getRefParam(this.ctx, node.i);
521
+ case SerovalNodeType.Array:
522
+ return this.serializeArray(node);
523
+ case SerovalNodeType.Object:
524
+ return this.assignRef(node.i, this.serializeObject(node.i, node.d));
525
+ case SerovalNodeType.NullConstructor:
526
+ return this.serializeNullConstructor(node);
527
+ case SerovalNodeType.Date:
528
+ return this.assignRef(node.i, `new Date("${node.s}")`);
529
+ case SerovalNodeType.RegExp:
530
+ return this.assignRef(node.i, node.s);
531
+ case SerovalNodeType.Set:
532
+ return this.serializeSet(node);
533
+ case SerovalNodeType.Map:
534
+ return this.serializeMap(node);
535
+ case SerovalNodeType.BigIntTypedArray:
536
+ case SerovalNodeType.TypedArray:
537
+ return this.serializeTypedArray(node);
538
+ case SerovalNodeType.AggregateError:
539
+ return this.serializeAggregateError(node);
540
+ case SerovalNodeType.Error:
541
+ return this.serializeError(node);
542
+ case SerovalNodeType.Iterable:
543
+ return this.serializeIterable(node);
544
+ case SerovalNodeType.Promise:
545
+ return this.serializePromise(node);
546
+ default:
547
+ throw new Error('Unsupported type');
507
548
  }
508
549
  }
509
- return assignRef(ctx, node.i, serialized);
510
- }
511
-
512
- export default function serializeTree(
513
- ctx: SerializationContext,
514
- node: SerovalNode,
515
- ): string {
516
- switch (node.t) {
517
- case SerovalNodeType.Primitive:
518
- return String(node.s);
519
- case SerovalNodeType.Reference:
520
- return getRefParam(ctx, node.i);
521
- case SerovalNodeType.Promise:
522
- return serializePromise(ctx, node);
523
- case SerovalNodeType.BigInt:
524
- return node.s;
525
- case SerovalNodeType.Date:
526
- return assignRef(ctx, node.i, `new Date("${node.s}")`);
527
- case SerovalNodeType.RegExp:
528
- return assignRef(ctx, node.i, node.s);
529
- case SerovalNodeType.BigIntTypedArray:
530
- case SerovalNodeType.TypedArray:
531
- return serializeTypedArray(ctx, node);
532
- case SerovalNodeType.Set:
533
- return serializeSet(ctx, node);
534
- case SerovalNodeType.Map:
535
- return serializeMap(ctx, node);
536
- case SerovalNodeType.Array:
537
- return serializeArray(ctx, node);
538
- case SerovalNodeType.AggregateError:
539
- return serializeAggregateError(ctx, node);
540
- case SerovalNodeType.Error:
541
- return serializeError(ctx, node);
542
- case SerovalNodeType.Iterable:
543
- return serializeIterable(ctx, node);
544
- case SerovalNodeType.NullConstructor:
545
- return serializeNullConstructor(ctx, node);
546
- case SerovalNodeType.Object:
547
- return assignRef(ctx, node.i, serializeObject(ctx, node.i, node.d));
548
- default:
549
- throw new Error('Unsupported type');
550
- }
551
550
  }