seroval 0.4.0-alpha.9 → 0.4.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.
package/src/tree/async.ts CHANGED
@@ -1,22 +1,33 @@
1
1
  /* eslint-disable no-await-in-loop */
2
2
  /* eslint-disable @typescript-eslint/no-use-before-define */
3
3
  import assert from '../assert';
4
- import {
5
- isIterable,
6
- isPrimitive,
7
- constructorCheck,
8
- isPromise,
9
- } from '../checks';
10
4
  import { Feature } from '../compat';
11
5
  import { createRef, ParserContext } from '../context';
12
- import { AsyncServerValue } from '../types';
6
+ import quote from '../quote';
7
+ import {
8
+ AsyncServerValue,
9
+ BigIntTypedArrayValue,
10
+ TypedArrayValue,
11
+ } from '../types';
12
+ import {
13
+ createBigIntNode,
14
+ createBigIntTypedArrayNode,
15
+ createDateNode,
16
+ createPrimitiveNode,
17
+ createReferenceNode,
18
+ createRegExpNode,
19
+ createTypedArrayNode,
20
+ FALSE_NODE,
21
+ NEG_ZERO_NODE,
22
+ NULL_NODE,
23
+ TRUE_NODE,
24
+ UNDEFINED_NODE,
25
+ } from './primitives';
13
26
  import {
14
- generateRef,
15
- generateSemiPrimitiveValue,
16
27
  getErrorConstructor,
17
28
  getErrorOptions,
18
29
  getIterableOptions,
19
- serializePrimitive,
30
+ isIterable,
20
31
  } from './shared';
21
32
  import {
22
33
  SerovalAggregateErrorNode,
@@ -26,48 +37,114 @@ import {
26
37
  SerovalMapNode,
27
38
  SerovalNode,
28
39
  SerovalNodeType,
40
+ SerovalNullConstructorNode,
41
+ SerovalObjectNode,
29
42
  SerovalObjectRecordNode,
43
+ SerovalPromiseNode,
30
44
  SerovalSetNode,
31
45
  } from './types';
32
46
 
33
- async function serializePropertiesAsync(
47
+ type ObjectLikeNode =
48
+ | SerovalObjectNode
49
+ | SerovalNullConstructorNode
50
+ | SerovalIterableNode
51
+ | SerovalPromiseNode;
52
+
53
+ type ObjectLikeValue =
54
+ | Record<string, AsyncServerValue>
55
+ | Iterable<AsyncServerValue>
56
+ | PromiseLike<AsyncServerValue>;
57
+
58
+ async function generateNodeList(
34
59
  ctx: ParserContext,
35
- properties: Record<string, unknown>,
36
- ): Promise<SerovalObjectRecordNode> {
37
- const keys = Object.keys(properties);
38
- const size = keys.length;
39
- const keyNodes = new Array<string>(size);
40
- const valueNodes = new Array<SerovalNode>(size);
41
- const deferredKeys = new Array<string>(size);
42
- const deferredValues = new Array<AsyncServerValue>(size);
60
+ current: AsyncServerValue[],
61
+ ) {
62
+ const size = current.length;
63
+ const nodes = new Array<SerovalNode>(size);
64
+ const deferred = new Array<AsyncServerValue>(size);
65
+ let item: AsyncServerValue;
66
+ for (let i = 0; i < size; i++) {
67
+ if (i in current) {
68
+ item = current[i];
69
+ if (isIterable(item)) {
70
+ deferred[i] = item;
71
+ } else {
72
+ nodes[i] = await parse(ctx, item);
73
+ }
74
+ }
75
+ }
76
+ for (let i = 0; i < size; i++) {
77
+ if (i in deferred) {
78
+ nodes[i] = await parse(ctx, deferred[i]);
79
+ }
80
+ }
81
+ return nodes;
82
+ }
83
+
84
+ async function generateArrayNode(
85
+ ctx: ParserContext,
86
+ id: number,
87
+ current: AsyncServerValue[],
88
+ ): Promise<SerovalArrayNode> {
89
+ return {
90
+ t: SerovalNodeType.Array,
91
+ i: id,
92
+ s: undefined,
93
+ l: undefined,
94
+ c: undefined,
95
+ m: undefined,
96
+ d: undefined,
97
+ a: await generateNodeList(ctx, current),
98
+ n: undefined,
99
+ };
100
+ }
101
+
102
+ async function generateMapNode(
103
+ ctx: ParserContext,
104
+ id: number,
105
+ current: Map<AsyncServerValue, AsyncServerValue>,
106
+ ): Promise<SerovalMapNode> {
107
+ assert(ctx.features & Feature.Map, 'Unsupported type "Map"');
108
+ const len = current.size;
109
+ const keyNodes = new Array<SerovalNode>(len);
110
+ const valueNodes = new Array<SerovalNode>(len);
111
+ const deferredKey = new Array<AsyncServerValue>(len);
112
+ const deferredValue = new Array<AsyncServerValue>(len);
43
113
  let deferredSize = 0;
44
- let nodesSize = 0;
45
- for (const key of keys) {
46
- if (isIterable(properties[key])) {
47
- deferredKeys[deferredSize] = key;
48
- deferredValues[deferredSize] = properties[key] as AsyncServerValue;
114
+ let nodeSize = 0;
115
+ for (const [key, value] of current.entries()) {
116
+ // Either key or value might be an iterable
117
+ if (isIterable(key) || isIterable(value)) {
118
+ deferredKey[deferredSize] = key;
119
+ deferredValue[deferredSize] = value;
49
120
  deferredSize++;
50
121
  } else {
51
- keyNodes[nodesSize] = key;
52
- valueNodes[nodesSize] = await generateTreeAsync(ctx, properties[key] as AsyncServerValue);
53
- nodesSize++;
122
+ keyNodes[nodeSize] = await parse(ctx, key);
123
+ valueNodes[nodeSize] = await parse(ctx, value);
124
+ nodeSize++;
54
125
  }
55
126
  }
56
127
  for (let i = 0; i < deferredSize; i++) {
57
- keyNodes[nodesSize + i] = deferredKeys[i];
58
- valueNodes[nodesSize + i] = await generateTreeAsync(ctx, deferredValues[i]);
128
+ keyNodes[nodeSize + i] = await parse(ctx, deferredKey[i]);
129
+ valueNodes[nodeSize + i] = await parse(ctx, deferredValue[i]);
59
130
  }
60
131
  return {
61
- k: keyNodes,
62
- v: valueNodes,
63
- s: size,
132
+ t: SerovalNodeType.Map,
133
+ i: id,
134
+ s: undefined,
135
+ l: undefined,
136
+ c: undefined,
137
+ m: undefined,
138
+ d: { k: keyNodes, v: valueNodes, s: len },
139
+ a: undefined,
140
+ n: undefined,
64
141
  };
65
142
  }
66
143
 
67
144
  async function generateSetNode(
68
145
  ctx: ParserContext,
69
- current: Set<AsyncServerValue>,
70
146
  id: number,
147
+ current: Set<AsyncServerValue>,
71
148
  ): Promise<SerovalSetNode> {
72
149
  assert(ctx.features & Feature.Set, 'Unsupported type "Set"');
73
150
  const len = current.size;
@@ -80,258 +157,303 @@ async function generateSetNode(
80
157
  if (isIterable(item)) {
81
158
  deferred[deferredSize++] = item;
82
159
  } else {
83
- nodes[nodeSize++] = await generateTreeAsync(ctx, item);
160
+ nodes[nodeSize++] = await parse(ctx, item);
84
161
  }
85
162
  }
86
163
  // Parse deferred items
87
164
  for (let i = 0; i < deferredSize; i++) {
88
- nodes[nodeSize + i] = await generateTreeAsync(ctx, deferred[i]);
165
+ nodes[nodeSize + i] = await parse(ctx, deferred[i]);
89
166
  }
90
167
  return {
91
168
  t: SerovalNodeType.Set,
92
169
  i: id,
93
- a: nodes,
94
170
  s: undefined,
95
171
  l: undefined,
96
- m: undefined,
97
172
  c: undefined,
173
+ m: undefined,
98
174
  d: undefined,
175
+ a: nodes,
99
176
  n: undefined,
100
177
  };
101
178
  }
102
179
 
103
- async function generateMapNode(
180
+ async function generateProperties(
104
181
  ctx: ParserContext,
105
- current: Map<AsyncServerValue, AsyncServerValue>,
106
- id: number,
107
- ): Promise<SerovalMapNode> {
108
- assert(ctx.features & Feature.Map, 'Unsupported type "Map"');
109
- const len = current.size;
110
- const keyNodes = new Array<SerovalNode>(len);
111
- const valueNodes = new Array<SerovalNode>(len);
112
- const deferredKey = new Array<AsyncServerValue>(len);
113
- const deferredValue = new Array<AsyncServerValue>(len);
182
+ properties: Record<string, AsyncServerValue>,
183
+ ): Promise<SerovalObjectRecordNode> {
184
+ const keys = Object.keys(properties);
185
+ const size = keys.length;
186
+ const keyNodes = new Array<string>(size);
187
+ const valueNodes = new Array<SerovalNode>(size);
188
+ const deferredKeys = new Array<string>(size);
189
+ const deferredValues = new Array<AsyncServerValue>(size);
114
190
  let deferredSize = 0;
115
- let nodeSize = 0;
116
- for (const [key, value] of current.entries()) {
117
- // Either key or value might be an iterable
118
- if (isIterable(key) || isIterable(value)) {
119
- deferredKey[deferredSize] = key;
120
- deferredValue[deferredSize] = value;
191
+ let nodesSize = 0;
192
+ let item: AsyncServerValue;
193
+ for (const key of keys) {
194
+ item = properties[key];
195
+ if (isIterable(item)) {
196
+ deferredKeys[deferredSize] = key;
197
+ deferredValues[deferredSize] = item;
121
198
  deferredSize++;
122
199
  } else {
123
- keyNodes[nodeSize] = await generateTreeAsync(ctx, key);
124
- valueNodes[nodeSize] = await generateTreeAsync(ctx, value);
125
- nodeSize++;
200
+ keyNodes[nodesSize] = key;
201
+ valueNodes[nodesSize] = await parse(ctx, item);
202
+ nodesSize++;
126
203
  }
127
204
  }
128
205
  for (let i = 0; i < deferredSize; i++) {
129
- keyNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredKey[i]);
130
- valueNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredValue[i]);
206
+ keyNodes[nodesSize + i] = deferredKeys[i];
207
+ valueNodes[nodesSize + i] = await parse(ctx, deferredValues[i]);
131
208
  }
132
209
  return {
133
- t: SerovalNodeType.Map,
210
+ k: keyNodes,
211
+ v: valueNodes,
212
+ s: size,
213
+ };
214
+ }
215
+
216
+ async function generateIterableNode(
217
+ ctx: ParserContext,
218
+ id: number,
219
+ current: Iterable<AsyncServerValue>,
220
+ ): Promise<SerovalIterableNode> {
221
+ assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
222
+ const options = getIterableOptions(current);
223
+ return {
224
+ t: SerovalNodeType.Iterable,
134
225
  i: id,
135
- a: undefined,
136
226
  s: undefined,
137
227
  l: undefined,
138
- m: undefined,
139
228
  c: undefined,
140
- d: { k: keyNodes, v: valueNodes, s: len },
229
+ m: undefined,
230
+ // Parse options first before the items
231
+ d: options
232
+ ? await generateProperties(ctx, options as Record<string, AsyncServerValue>)
233
+ : undefined,
234
+ a: await generateNodeList(ctx, Array.from(current)),
141
235
  n: undefined,
142
236
  };
143
237
  }
144
238
 
145
- async function generateNodeList(
239
+ async function generatePromiseNode(
146
240
  ctx: ParserContext,
147
- current: AsyncServerValue[],
148
- ) {
149
- const size = current.length;
150
- const nodes = new Array<SerovalNode>(size);
151
- const deferred = new Array<AsyncServerValue>(size);
152
- for (let i = 0; i < size; i++) {
153
- if (i in current) {
154
- if (isIterable(current[i])) {
155
- deferred[i] = current[i];
156
- } else {
157
- nodes[i] = await generateTreeAsync(ctx, current[i]);
158
- }
159
- }
160
- }
161
- for (let i = 0; i < size; i++) {
162
- if (i in deferred) {
163
- nodes[i] = await generateTreeAsync(ctx, deferred[i]);
164
- }
165
- }
166
- return nodes;
241
+ id: number,
242
+ current: PromiseLike<AsyncServerValue>,
243
+ ): Promise<SerovalPromiseNode> {
244
+ assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
245
+ return current.then(async (value) => ({
246
+ t: SerovalNodeType.Promise,
247
+ i: id,
248
+ s: undefined,
249
+ l: undefined,
250
+ c: undefined,
251
+ m: undefined,
252
+ // Parse options first before the items
253
+ d: undefined,
254
+ a: undefined,
255
+ n: await parse(ctx, value),
256
+ }));
167
257
  }
168
258
 
169
- async function generateArrayNode(
259
+ async function generateObjectNode(
170
260
  ctx: ParserContext,
171
- current: AsyncServerValue[],
172
261
  id: number,
173
- ): Promise<SerovalArrayNode> {
262
+ current: ObjectLikeValue,
263
+ empty: boolean,
264
+ ): Promise<ObjectLikeNode> {
265
+ if (Symbol.iterator in current) {
266
+ return generateIterableNode(ctx, id, current);
267
+ }
268
+ if ('then' in current && typeof current.then === 'function') {
269
+ return generatePromiseNode(ctx, id, current as PromiseLike<AsyncServerValue>);
270
+ }
174
271
  return {
175
- t: SerovalNodeType.Array,
272
+ t: empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
176
273
  i: id,
177
- a: await generateNodeList(ctx, current),
178
274
  s: undefined,
179
275
  l: undefined,
180
- m: undefined,
181
276
  c: undefined,
182
- d: undefined,
277
+ m: undefined,
278
+ d: await generateProperties(ctx, current as Record<string, AsyncServerValue>),
279
+ a: undefined,
183
280
  n: undefined,
184
281
  };
185
282
  }
186
283
 
187
284
  async function generateAggregateErrorNode(
188
285
  ctx: ParserContext,
189
- current: AggregateError,
190
286
  id: number,
287
+ current: AggregateError,
191
288
  ): Promise<SerovalAggregateErrorNode> {
192
- const options = getErrorOptions(current);
289
+ const options = getErrorOptions(ctx, current);
193
290
  const optionsNode = options
194
- ? await serializePropertiesAsync(ctx, options)
291
+ ? await generateProperties(ctx, options)
195
292
  : undefined;
196
293
  return {
197
294
  t: SerovalNodeType.AggregateError,
198
295
  i: id,
199
- a: undefined,
200
296
  s: undefined,
201
297
  l: undefined,
202
- m: current.message,
203
298
  c: undefined,
299
+ m: current.message,
204
300
  d: optionsNode,
205
- n: await generateTreeAsync(ctx, current.errors as AsyncServerValue),
301
+ a: undefined,
302
+ n: await parse(ctx, current.errors),
206
303
  };
207
304
  }
208
305
 
209
306
  async function generateErrorNode(
210
307
  ctx: ParserContext,
211
- current: Error,
212
308
  id: number,
309
+ current: Error,
213
310
  ): Promise<SerovalErrorNode> {
214
- const options = getErrorOptions(current);
311
+ const options = getErrorOptions(ctx, current);
215
312
  const optionsNode = options
216
- ? await serializePropertiesAsync(ctx, options)
313
+ ? await generateProperties(ctx, options)
217
314
  : undefined;
218
315
  return {
219
316
  t: SerovalNodeType.Error,
220
317
  i: id,
221
- a: undefined,
222
318
  s: undefined,
223
319
  l: undefined,
224
- m: current.message,
225
320
  c: getErrorConstructor(current),
321
+ m: current.message,
226
322
  d: optionsNode,
323
+ a: undefined,
227
324
  n: undefined,
228
325
  };
229
326
  }
230
327
 
231
- async function generateIterableNode(
232
- ctx: ParserContext,
233
- current: Iterable<AsyncServerValue>,
234
- id: number,
235
- ): Promise<SerovalIterableNode> {
236
- assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
237
- const options = getIterableOptions(current);
238
- return {
239
- t: SerovalNodeType.Iterable,
240
- i: id,
241
- s: undefined,
242
- l: undefined,
243
- m: undefined,
244
- c: undefined,
245
- // Parse options first before the items
246
- d: options ? await serializePropertiesAsync(ctx, options) : undefined,
247
- a: await generateNodeList(ctx, Array.from(current)),
248
- n: undefined,
249
- };
250
- }
251
-
252
- async function generateTreeAsync(
328
+ async function parse(
253
329
  ctx: ParserContext,
254
330
  current: AsyncServerValue,
255
331
  ): Promise<SerovalNode> {
256
- if (isPrimitive(current)) {
257
- return {
258
- t: SerovalNodeType.Primitive,
259
- i: undefined,
260
- s: serializePrimitive(current),
261
- l: undefined,
262
- m: undefined,
263
- c: undefined,
264
- // Parse options first before the items
265
- d: undefined,
266
- a: undefined,
267
- n: undefined,
268
- };
269
- }
270
- // Non-primitive values needs a reference ID
271
- // mostly because the values themselves are stateful
272
- const id = generateRef(ctx, current);
273
- if (typeof id !== 'number') {
274
- return id;
275
- }
276
- const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
277
- if (semiPrimitive) {
278
- return semiPrimitive;
279
- }
280
- if (isPromise(current)) {
281
- assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
282
- return current.then(async (value) => ({
283
- t: SerovalNodeType.Promise,
284
- i: id,
285
- s: undefined,
286
- l: undefined,
287
- m: undefined,
288
- c: undefined,
289
- // Parse options first before the items
290
- d: undefined,
291
- a: undefined,
292
- n: await generateTreeAsync(ctx, value),
293
- }));
294
- }
295
- if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
296
- return generateSetNode(ctx, current, id);
297
- }
298
- if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
299
- return generateMapNode(ctx, current, id);
300
- }
301
- if (Array.isArray(current)) {
302
- return generateArrayNode(ctx, current, id);
303
- }
304
- if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
305
- return generateAggregateErrorNode(ctx, current, id);
306
- }
307
- if (current instanceof Error) {
308
- return generateErrorNode(ctx, current, id);
309
- }
310
- if (isIterable(current)) {
311
- return generateIterableNode(ctx, current, id);
312
- }
313
- const empty = current.constructor == null;
314
- if (current.constructor === Object || empty) {
315
- return {
316
- t: empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
317
- i: id,
318
- s: undefined,
319
- l: undefined,
320
- m: undefined,
321
- c: undefined,
322
- // Parse options first before the items
323
- d: await serializePropertiesAsync(ctx, current as Record<string, unknown>),
324
- a: undefined,
325
- n: undefined,
326
- };
332
+ switch (typeof current) {
333
+ case 'boolean':
334
+ return current ? TRUE_NODE : FALSE_NODE;
335
+ case 'undefined':
336
+ return UNDEFINED_NODE;
337
+ case 'string':
338
+ return createPrimitiveNode(quote(current));
339
+ case 'number':
340
+ if (Object.is(current, -0)) {
341
+ return NEG_ZERO_NODE;
342
+ }
343
+ if (Object.is(current, Infinity)) {
344
+ return createPrimitiveNode('1/0');
345
+ }
346
+ if (Object.is(current, -Infinity)) {
347
+ return createPrimitiveNode('-1/0');
348
+ }
349
+ return createPrimitiveNode(current);
350
+ case 'bigint':
351
+ return createBigIntNode(ctx, current);
352
+ case 'object': {
353
+ if (!current) {
354
+ return NULL_NODE;
355
+ }
356
+ // Non-primitive values needs a reference ID
357
+ // mostly because the values themselves are stateful
358
+ const id = createRef(ctx, current, true);
359
+ if (ctx.markedRefs.has(id)) {
360
+ return createReferenceNode(id);
361
+ }
362
+ if (Array.isArray(current)) {
363
+ return generateArrayNode(ctx, id, current);
364
+ }
365
+ switch (current.constructor) {
366
+ case Date:
367
+ return createDateNode(id, current as Date);
368
+ case RegExp:
369
+ return createRegExpNode(id, current as RegExp);
370
+ case Promise:
371
+ return generatePromiseNode(ctx, id, current as Promise<AsyncServerValue>);
372
+ case Int8Array:
373
+ case Int16Array:
374
+ case Int32Array:
375
+ case Uint8Array:
376
+ case Uint16Array:
377
+ case Uint32Array:
378
+ case Uint8ClampedArray:
379
+ case Float32Array:
380
+ case Float64Array:
381
+ return createTypedArrayNode(ctx, id, current as TypedArrayValue);
382
+ case BigInt64Array:
383
+ case BigUint64Array:
384
+ return createBigIntTypedArrayNode(ctx, id, current as BigIntTypedArrayValue);
385
+ case Map:
386
+ return generateMapNode(
387
+ ctx,
388
+ id,
389
+ current as Map<AsyncServerValue, AsyncServerValue>,
390
+ );
391
+ case Set:
392
+ return generateSetNode(
393
+ ctx,
394
+ id,
395
+ current as Set<AsyncServerValue>,
396
+ );
397
+ case Object:
398
+ return generateObjectNode(
399
+ ctx,
400
+ id,
401
+ current as Record<string, AsyncServerValue>,
402
+ false,
403
+ );
404
+ case undefined:
405
+ return generateObjectNode(
406
+ ctx,
407
+ id,
408
+ current as Record<string, AsyncServerValue>,
409
+ true,
410
+ );
411
+ case AggregateError:
412
+ if (ctx.features & Feature.AggregateError) {
413
+ return generateAggregateErrorNode(ctx, id, current as AggregateError);
414
+ }
415
+ return generateErrorNode(ctx, id, current as AggregateError);
416
+ case Error:
417
+ case EvalError:
418
+ case RangeError:
419
+ case ReferenceError:
420
+ case SyntaxError:
421
+ case TypeError:
422
+ case URIError:
423
+ return generateErrorNode(ctx, id, current as Error);
424
+ default:
425
+ break;
426
+ }
427
+ if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
428
+ return generateAggregateErrorNode(ctx, id, current);
429
+ }
430
+ if (current instanceof Error) {
431
+ return generateErrorNode(ctx, id, current);
432
+ }
433
+ if (current instanceof Promise) {
434
+ return generatePromiseNode(ctx, id, current);
435
+ }
436
+ // Generator functions don't have a global constructor
437
+ if (Symbol.iterator in current) {
438
+ return generateIterableNode(ctx, id, current);
439
+ }
440
+ // For Promise-like objects
441
+ if ('then' in current && typeof current.then === 'function') {
442
+ return generatePromiseNode(ctx, id, current as PromiseLike<AsyncServerValue>);
443
+ }
444
+ throw new Error('Unsupported type');
445
+ }
446
+ default:
447
+ throw new Error('Unsupported type');
327
448
  }
328
- throw new Error('Unsupported value');
329
449
  }
330
450
 
331
451
  export default async function parseAsync(
332
452
  ctx: ParserContext,
333
453
  current: AsyncServerValue,
334
454
  ) {
335
- const result = await generateTreeAsync(ctx, current);
336
- return [result, createRef(ctx, current), result.t === SerovalNodeType.Object] as const;
455
+ const result = await parse(ctx, current);
456
+ const isObject = result.t === SerovalNodeType.Object
457
+ || result.t === SerovalNodeType.Iterable;
458
+ return [result, createRef(ctx, current, false), isObject] as const;
337
459
  }