seroval 0.4.0-alpha.8 → 0.4.0

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,69 +1,150 @@
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,
23
34
  SerovalArrayNode,
24
- SerovalDictionaryNode,
25
35
  SerovalErrorNode,
26
36
  SerovalIterableNode,
27
37
  SerovalMapNode,
28
38
  SerovalNode,
29
39
  SerovalNodeType,
40
+ SerovalNullConstructorNode,
41
+ SerovalObjectNode,
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<SerovalDictionaryNode> {
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
- return [keyNodes, valueNodes, size];
131
+ return {
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,
141
+ };
61
142
  }
62
143
 
63
144
  async function generateSetNode(
64
145
  ctx: ParserContext,
65
- current: Set<AsyncServerValue>,
66
146
  id: number,
147
+ current: Set<AsyncServerValue>,
67
148
  ): Promise<SerovalSetNode> {
68
149
  assert(ctx.features & Feature.Set, 'Unsupported type "Set"');
69
150
  const len = current.size;
@@ -76,185 +157,303 @@ async function generateSetNode(
76
157
  if (isIterable(item)) {
77
158
  deferred[deferredSize++] = item;
78
159
  } else {
79
- nodes[nodeSize++] = await generateTreeAsync(ctx, item);
160
+ nodes[nodeSize++] = await parse(ctx, item);
80
161
  }
81
162
  }
82
163
  // Parse deferred items
83
164
  for (let i = 0; i < deferredSize; i++) {
84
- nodes[nodeSize + i] = await generateTreeAsync(ctx, deferred[i]);
165
+ nodes[nodeSize + i] = await parse(ctx, deferred[i]);
85
166
  }
86
- return [SerovalNodeType.Set, nodes, id];
167
+ return {
168
+ t: SerovalNodeType.Set,
169
+ i: id,
170
+ s: undefined,
171
+ l: undefined,
172
+ c: undefined,
173
+ m: undefined,
174
+ d: undefined,
175
+ a: nodes,
176
+ n: undefined,
177
+ };
87
178
  }
88
179
 
89
- async function generateMapNode(
180
+ async function generateProperties(
90
181
  ctx: ParserContext,
91
- current: Map<AsyncServerValue, AsyncServerValue>,
92
- id: number,
93
- ): Promise<SerovalMapNode> {
94
- assert(ctx.features & Feature.Map, 'Unsupported type "Map"');
95
- const len = current.size;
96
- const keyNodes = new Array<SerovalNode>(len);
97
- const valueNodes = new Array<SerovalNode>(len);
98
- const deferredKey = new Array<AsyncServerValue>(len);
99
- 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);
100
190
  let deferredSize = 0;
101
- let nodeSize = 0;
102
- for (const [key, value] of current.entries()) {
103
- // Either key or value might be an iterable
104
- if (isIterable(key) || isIterable(value)) {
105
- deferredKey[deferredSize] = key;
106
- 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;
107
198
  deferredSize++;
108
199
  } else {
109
- keyNodes[nodeSize] = await generateTreeAsync(ctx, key);
110
- valueNodes[nodeSize] = await generateTreeAsync(ctx, value);
111
- nodeSize++;
200
+ keyNodes[nodesSize] = key;
201
+ valueNodes[nodesSize] = await parse(ctx, item);
202
+ nodesSize++;
112
203
  }
113
204
  }
114
205
  for (let i = 0; i < deferredSize; i++) {
115
- keyNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredKey[i]);
116
- valueNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredValue[i]);
206
+ keyNodes[nodesSize + i] = deferredKeys[i];
207
+ valueNodes[nodesSize + i] = await parse(ctx, deferredValues[i]);
117
208
  }
118
- return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
209
+ return {
210
+ k: keyNodes,
211
+ v: valueNodes,
212
+ s: size,
213
+ };
119
214
  }
120
215
 
121
- async function generateNodeList(
216
+ async function generateIterableNode(
122
217
  ctx: ParserContext,
123
- current: AsyncServerValue[],
124
- ) {
125
- const size = current.length;
126
- const nodes = new Array<SerovalNode>(size);
127
- const deferred = new Array<AsyncServerValue>(size);
128
- for (let i = 0; i < size; i++) {
129
- if (i in current) {
130
- if (isIterable(current[i])) {
131
- deferred[i] = current[i];
132
- } else {
133
- nodes[i] = await generateTreeAsync(ctx, current[i]);
134
- }
135
- }
136
- }
137
- for (let i = 0; i < size; i++) {
138
- if (i in deferred) {
139
- nodes[i] = await generateTreeAsync(ctx, deferred[i]);
140
- }
141
- }
142
- return nodes;
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,
225
+ i: id,
226
+ s: undefined,
227
+ l: undefined,
228
+ c: undefined,
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)),
235
+ n: undefined,
236
+ };
143
237
  }
144
238
 
145
- async function generateArrayNode(
239
+ async function generatePromiseNode(
146
240
  ctx: ParserContext,
147
- current: AsyncServerValue[],
148
241
  id: number,
149
- ): Promise<SerovalArrayNode> {
150
- return [SerovalNodeType.Array, await generateNodeList(ctx, current), id];
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
+ }));
257
+ }
258
+
259
+ async function generateObjectNode(
260
+ ctx: ParserContext,
261
+ id: number,
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
+ }
271
+ return {
272
+ t: empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
273
+ i: id,
274
+ s: undefined,
275
+ l: undefined,
276
+ c: undefined,
277
+ m: undefined,
278
+ d: await generateProperties(ctx, current as Record<string, AsyncServerValue>),
279
+ a: undefined,
280
+ n: undefined,
281
+ };
151
282
  }
152
283
 
153
284
  async function generateAggregateErrorNode(
154
285
  ctx: ParserContext,
155
- current: AggregateError,
156
286
  id: number,
287
+ current: AggregateError,
157
288
  ): Promise<SerovalAggregateErrorNode> {
158
- const options = getErrorOptions(current);
289
+ const options = getErrorOptions(ctx, current);
159
290
  const optionsNode = options
160
- ? await serializePropertiesAsync(ctx, options)
291
+ ? await generateProperties(ctx, options)
161
292
  : undefined;
162
- const errorsNode = await generateTreeAsync(ctx, current.errors as AsyncServerValue);
163
- return [
164
- SerovalNodeType.AggregateError,
165
- [current.message, optionsNode, errorsNode],
166
- id,
167
- ];
293
+ return {
294
+ t: SerovalNodeType.AggregateError,
295
+ i: id,
296
+ s: undefined,
297
+ l: undefined,
298
+ c: undefined,
299
+ m: current.message,
300
+ d: optionsNode,
301
+ a: undefined,
302
+ n: await parse(ctx, current.errors),
303
+ };
168
304
  }
169
305
 
170
306
  async function generateErrorNode(
171
307
  ctx: ParserContext,
172
- current: Error,
173
308
  id: number,
309
+ current: Error,
174
310
  ): Promise<SerovalErrorNode> {
175
- const options = getErrorOptions(current);
311
+ const options = getErrorOptions(ctx, current);
176
312
  const optionsNode = options
177
- ? await serializePropertiesAsync(ctx, options)
313
+ ? await generateProperties(ctx, options)
178
314
  : undefined;
179
- return [
180
- SerovalNodeType.Error,
181
- [getErrorConstructor(current), current.message, optionsNode],
182
- id,
183
- ];
315
+ return {
316
+ t: SerovalNodeType.Error,
317
+ i: id,
318
+ s: undefined,
319
+ l: undefined,
320
+ c: getErrorConstructor(current),
321
+ m: current.message,
322
+ d: optionsNode,
323
+ a: undefined,
324
+ n: undefined,
325
+ };
184
326
  }
185
327
 
186
- async function generateIterableNode(
187
- ctx: ParserContext,
188
- current: Iterable<AsyncServerValue>,
189
- id: number,
190
- ): Promise<SerovalIterableNode> {
191
- assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
192
- const options = getIterableOptions(current);
193
- return [SerovalNodeType.Iterable, [
194
- // Parse options first before the items
195
- options ? await serializePropertiesAsync(ctx, options) : undefined,
196
- await generateNodeList(ctx, Array.from(current)),
197
- ], id];
198
- }
199
-
200
- async function generateTreeAsync(
328
+ async function parse(
201
329
  ctx: ParserContext,
202
330
  current: AsyncServerValue,
203
331
  ): Promise<SerovalNode> {
204
- if (isPrimitive(current)) {
205
- return [SerovalNodeType.Primitive, serializePrimitive(current)];
206
- }
207
- // Non-primitive values needs a reference ID
208
- // mostly because the values themselves are stateful
209
- const id = generateRef(ctx, current);
210
- if (Array.isArray(id)) {
211
- return id;
212
- }
213
- const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
214
- if (semiPrimitive) {
215
- return semiPrimitive;
216
- }
217
- if (isPromise(current)) {
218
- assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
219
- return current.then(async (value) => [
220
- SerovalNodeType.Promise,
221
- await generateTreeAsync(ctx, value),
222
- id,
223
- ]);
224
- }
225
- if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
226
- return generateSetNode(ctx, current, id);
227
- }
228
- if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
229
- return generateMapNode(ctx, current, id);
230
- }
231
- if (Array.isArray(current)) {
232
- return generateArrayNode(ctx, current, id);
233
- }
234
- if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
235
- return generateAggregateErrorNode(ctx, current, id);
236
- }
237
- if (current instanceof Error) {
238
- return generateErrorNode(ctx, current, id);
239
- }
240
- if (isIterable(current)) {
241
- return generateIterableNode(ctx, current, id);
242
- }
243
- const empty = current.constructor == null;
244
- if (current.constructor === Object || empty) {
245
- return [
246
- empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
247
- await serializePropertiesAsync(ctx, current as Record<string, unknown>),
248
- id,
249
- ];
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');
250
448
  }
251
- throw new Error('Unsupported value');
252
449
  }
253
450
 
254
451
  export default async function parseAsync(
255
452
  ctx: ParserContext,
256
453
  current: AsyncServerValue,
257
454
  ) {
258
- const result = await generateTreeAsync(ctx, current);
259
- return [result, createRef(ctx, current), result[0] === 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;
260
459
  }