seroval 0.4.0-alpha.11 → 0.4.0-alpha.13

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