seroval 0.2.1 → 0.4.0-alpha.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/README.md +121 -15
- package/dist/cjs/development/index.cjs +979 -439
- package/dist/cjs/development/index.cjs.map +4 -4
- package/dist/cjs/production/index.cjs +2 -2
- package/dist/esm/development/index.mjs +979 -439
- package/dist/esm/development/index.mjs.map +4 -4
- package/dist/esm/production/index.mjs +2 -2
- package/dist/types/assert.d.ts +1 -0
- package/dist/types/checks.d.ts +5 -0
- package/dist/types/compat.d.ts +37 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/tree.d.ts +112 -0
- package/dist/types/types.d.ts +7 -3
- package/package.json +2 -2
- package/src/assert.ts +5 -0
- package/src/checks.ts +41 -0
- package/src/compat.ts +247 -0
- package/src/index.ts +57 -51
- package/src/tree.ts +1178 -0
- package/src/types.ts +37 -4
- package/dist/types/array.d.ts +0 -2
- package/dist/types/async.d.ts +0 -4
- package/dist/types/context.d.ts +0 -33
- package/dist/types/is-primitive.d.ts +0 -2
- package/dist/types/is-promise.d.ts +0 -1
- package/dist/types/serialize-primitive.d.ts +0 -2
- package/dist/types/sync.d.ts +0 -4
- package/src/array.ts +0 -18
- package/src/async.ts +0 -229
- package/src/context.ts +0 -200
- package/src/is-primitive.ts +0 -14
- package/src/is-promise.ts +0 -6
- package/src/serialize-primitive.ts +0 -32
- package/src/sync.ts +0 -199
package/src/tree.ts
ADDED
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
/* eslint-disable guard-for-in */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-for-in-array */
|
|
3
|
+
/* eslint-disable no-await-in-loop */
|
|
4
|
+
import {
|
|
5
|
+
isIterable,
|
|
6
|
+
isPrimitive,
|
|
7
|
+
isPromise,
|
|
8
|
+
constructorCheck,
|
|
9
|
+
} from './checks';
|
|
10
|
+
import quote from './quote';
|
|
11
|
+
import {
|
|
12
|
+
AsyncServerValue,
|
|
13
|
+
ErrorValue,
|
|
14
|
+
PrimitiveValue,
|
|
15
|
+
ServerValue,
|
|
16
|
+
TypedArrayValue,
|
|
17
|
+
} from './types';
|
|
18
|
+
import getIdentifier from './get-identifier';
|
|
19
|
+
import { Feature, parseTargets } from './compat';
|
|
20
|
+
import assert from './assert';
|
|
21
|
+
|
|
22
|
+
interface IndexAssignment {
|
|
23
|
+
type: 'index';
|
|
24
|
+
source: string;
|
|
25
|
+
value: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface MapAssignment {
|
|
29
|
+
type: 'map';
|
|
30
|
+
source: string;
|
|
31
|
+
key: string;
|
|
32
|
+
value: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SetAssignment {
|
|
36
|
+
type: 'set';
|
|
37
|
+
source: string;
|
|
38
|
+
value: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Array of assignments to be done (used for recursion)
|
|
42
|
+
export type Assignment =
|
|
43
|
+
| IndexAssignment
|
|
44
|
+
| MapAssignment
|
|
45
|
+
| SetAssignment;
|
|
46
|
+
|
|
47
|
+
export interface SerializationContext {
|
|
48
|
+
stack: number[];
|
|
49
|
+
// Value-to-ref map
|
|
50
|
+
refs: Map<unknown, number>;
|
|
51
|
+
// Map tree refs to actual refs
|
|
52
|
+
validRefs: Map<number, number>;
|
|
53
|
+
// Refs that are...referenced
|
|
54
|
+
markedRefs: boolean[];
|
|
55
|
+
// Variables
|
|
56
|
+
vars: string[];
|
|
57
|
+
// Array of assignments to be done (used for recursion)
|
|
58
|
+
assignments: Assignment[];
|
|
59
|
+
// Supported features
|
|
60
|
+
features: Set<Feature>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Options {
|
|
64
|
+
target: string | string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DEFAULT_OPTIONS: Options = {
|
|
68
|
+
target: 'es2023',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function createSerializationContext(options: Partial<Options> = {}): SerializationContext {
|
|
72
|
+
// eslint-disable-next-line prefer-object-spread
|
|
73
|
+
const result = Object.assign({}, DEFAULT_OPTIONS, options || {});
|
|
74
|
+
return {
|
|
75
|
+
markedRefs: [],
|
|
76
|
+
stack: [],
|
|
77
|
+
refs: new Map(),
|
|
78
|
+
vars: [],
|
|
79
|
+
assignments: [],
|
|
80
|
+
validRefs: new Map(),
|
|
81
|
+
features: parseTargets(result.target),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getAssignmentExpression(assignment: Assignment): string {
|
|
86
|
+
switch (assignment.type) {
|
|
87
|
+
case 'index':
|
|
88
|
+
return `${assignment.source}=${assignment.value}`;
|
|
89
|
+
case 'map':
|
|
90
|
+
return `${assignment.source}.set(${assignment.key},${assignment.value})`;
|
|
91
|
+
case 'set':
|
|
92
|
+
return `${assignment.source}.add(${assignment.value})`;
|
|
93
|
+
default:
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function mergeAssignments(assignments: Assignment[]) {
|
|
99
|
+
const newAssignments = [];
|
|
100
|
+
let current = assignments[0];
|
|
101
|
+
let prev = current;
|
|
102
|
+
for (let i = 1, len = assignments.length; i < len; i++) {
|
|
103
|
+
const item = assignments[i];
|
|
104
|
+
if (item.type === prev.type) {
|
|
105
|
+
if (item.type === 'index' && item.value === prev.value) {
|
|
106
|
+
// Merge if the right-hand value is the same
|
|
107
|
+
// saves at least 2 chars
|
|
108
|
+
current = {
|
|
109
|
+
type: 'index',
|
|
110
|
+
source: item.source,
|
|
111
|
+
value: getAssignmentExpression(current),
|
|
112
|
+
};
|
|
113
|
+
} else if (item.type === 'map' && item.source === prev.source) {
|
|
114
|
+
// Maps has chaining methods, merge if source is the same
|
|
115
|
+
current = {
|
|
116
|
+
type: 'map',
|
|
117
|
+
source: getAssignmentExpression(current),
|
|
118
|
+
key: item.key,
|
|
119
|
+
value: item.value,
|
|
120
|
+
};
|
|
121
|
+
} else if (item.type === 'set' && item.source === prev.source) {
|
|
122
|
+
// Sets has chaining methods too
|
|
123
|
+
current = {
|
|
124
|
+
type: 'set',
|
|
125
|
+
source: getAssignmentExpression(current),
|
|
126
|
+
value: item.value,
|
|
127
|
+
};
|
|
128
|
+
} else {
|
|
129
|
+
// Different assignment, push current
|
|
130
|
+
newAssignments.push(current);
|
|
131
|
+
current = item;
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
newAssignments.push(current);
|
|
135
|
+
current = item;
|
|
136
|
+
}
|
|
137
|
+
prev = item;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
newAssignments.push(current);
|
|
141
|
+
|
|
142
|
+
return newAssignments;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveAssignments(assignments: Assignment[]) {
|
|
146
|
+
if (assignments.length) {
|
|
147
|
+
let result = '';
|
|
148
|
+
const merged = mergeAssignments(assignments);
|
|
149
|
+
for (let i = 0, len = merged.length; i < len; i++) {
|
|
150
|
+
result += `${getAssignmentExpression(merged[i])},`;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function resolvePatches(ctx: SerializationContext) {
|
|
158
|
+
return resolveAssignments(ctx.assignments);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Increments the number of references the referenced value has
|
|
163
|
+
*/
|
|
164
|
+
function markRef(ctx: SerializationContext, current: number) {
|
|
165
|
+
ctx.markedRefs[current] = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Creates a reference ID from the given values
|
|
170
|
+
*/
|
|
171
|
+
export function createRef(
|
|
172
|
+
ctx: SerializationContext,
|
|
173
|
+
index: unknown,
|
|
174
|
+
) {
|
|
175
|
+
const current = ctx.refs.get(index);
|
|
176
|
+
if (current != null) {
|
|
177
|
+
return current;
|
|
178
|
+
}
|
|
179
|
+
const id = ctx.refs.size;
|
|
180
|
+
ctx.refs.set(index, id);
|
|
181
|
+
return id;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Creates a new reference ID from a given reference ID
|
|
186
|
+
* This new reference ID means that the reference itself
|
|
187
|
+
* has been referenced at least once, and is used to generate
|
|
188
|
+
* the variables
|
|
189
|
+
*/
|
|
190
|
+
function createValidRef(
|
|
191
|
+
ctx: SerializationContext,
|
|
192
|
+
index: number,
|
|
193
|
+
) {
|
|
194
|
+
const current = ctx.validRefs.get(index);
|
|
195
|
+
if (current != null) {
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
198
|
+
const id = ctx.validRefs.size;
|
|
199
|
+
ctx.validRefs.set(index, id);
|
|
200
|
+
return id;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Creates the reference param (identifier) from the given reference ID
|
|
205
|
+
* Calling this function means the value has been referenced somewhere
|
|
206
|
+
*/
|
|
207
|
+
export function getRefParam(ctx: SerializationContext, index: number) {
|
|
208
|
+
const actualIndex = createValidRef(ctx, index);
|
|
209
|
+
if (ctx.vars[actualIndex]) {
|
|
210
|
+
return ctx.vars[actualIndex];
|
|
211
|
+
}
|
|
212
|
+
const result = getIdentifier(actualIndex);
|
|
213
|
+
ctx.vars[actualIndex] = result;
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Generates the inlined assignment for the reference
|
|
219
|
+
* This is different from the assignments array as this one
|
|
220
|
+
* signifies creation rather than mutation
|
|
221
|
+
*/
|
|
222
|
+
function assignRef(
|
|
223
|
+
ctx: SerializationContext,
|
|
224
|
+
index: number,
|
|
225
|
+
value: string,
|
|
226
|
+
) {
|
|
227
|
+
if (ctx.markedRefs[index]) {
|
|
228
|
+
return `${getRefParam(ctx, index)}=${value}`;
|
|
229
|
+
}
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function createAssignment(
|
|
234
|
+
ctx: SerializationContext,
|
|
235
|
+
source: string,
|
|
236
|
+
value: string,
|
|
237
|
+
) {
|
|
238
|
+
ctx.assignments.push({
|
|
239
|
+
type: 'index',
|
|
240
|
+
source,
|
|
241
|
+
value,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function createSetAdd(
|
|
246
|
+
ctx: SerializationContext,
|
|
247
|
+
ref: number,
|
|
248
|
+
value: string,
|
|
249
|
+
) {
|
|
250
|
+
markRef(ctx, ref);
|
|
251
|
+
ctx.assignments.push({
|
|
252
|
+
type: 'set',
|
|
253
|
+
source: getRefParam(ctx, ref),
|
|
254
|
+
value,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function createMapSet(
|
|
259
|
+
ctx: SerializationContext,
|
|
260
|
+
ref: number,
|
|
261
|
+
key: string,
|
|
262
|
+
value: string,
|
|
263
|
+
) {
|
|
264
|
+
markRef(ctx, ref);
|
|
265
|
+
ctx.assignments.push({
|
|
266
|
+
type: 'map',
|
|
267
|
+
source: getRefParam(ctx, ref),
|
|
268
|
+
key,
|
|
269
|
+
value,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createArrayAssign(
|
|
274
|
+
ctx: SerializationContext,
|
|
275
|
+
ref: number,
|
|
276
|
+
index: number,
|
|
277
|
+
value: string,
|
|
278
|
+
) {
|
|
279
|
+
markRef(ctx, ref);
|
|
280
|
+
createAssignment(ctx, `${getRefParam(ctx, ref)}[${index}]`, value);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function createObjectIdentifierAssign(
|
|
284
|
+
ctx: SerializationContext,
|
|
285
|
+
ref: number,
|
|
286
|
+
key: string,
|
|
287
|
+
value: string,
|
|
288
|
+
) {
|
|
289
|
+
markRef(ctx, ref);
|
|
290
|
+
createAssignment(ctx, `${getRefParam(ctx, ref)}.${key}`, value);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function createObjectComputedAssign(
|
|
294
|
+
ctx: SerializationContext,
|
|
295
|
+
ref: number,
|
|
296
|
+
key: string,
|
|
297
|
+
value: string,
|
|
298
|
+
) {
|
|
299
|
+
markRef(ctx, ref);
|
|
300
|
+
createAssignment(ctx, `${getRefParam(ctx, ref)}[${key}]`, value);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getErrorConstructor(error: ErrorValue) {
|
|
304
|
+
if (error instanceof EvalError) {
|
|
305
|
+
return 'EvalError';
|
|
306
|
+
}
|
|
307
|
+
if (error instanceof RangeError) {
|
|
308
|
+
return 'RangeError';
|
|
309
|
+
}
|
|
310
|
+
if (error instanceof ReferenceError) {
|
|
311
|
+
return 'ReferenceError';
|
|
312
|
+
}
|
|
313
|
+
if (error instanceof SyntaxError) {
|
|
314
|
+
return 'SyntaxError';
|
|
315
|
+
}
|
|
316
|
+
if (error instanceof TypeError) {
|
|
317
|
+
return 'TypeError';
|
|
318
|
+
}
|
|
319
|
+
if (error instanceof URIError) {
|
|
320
|
+
return 'URIError';
|
|
321
|
+
}
|
|
322
|
+
return 'Error';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function getErrorOptions(
|
|
326
|
+
error: Error,
|
|
327
|
+
) {
|
|
328
|
+
let options: Record<string, any> | undefined;
|
|
329
|
+
const constructor = getErrorConstructor(error);
|
|
330
|
+
// Name has been modified
|
|
331
|
+
if (error.name !== constructor) {
|
|
332
|
+
options = { name: error.name };
|
|
333
|
+
} else if (error.constructor.name !== constructor) {
|
|
334
|
+
// Otherwise, name is overriden because
|
|
335
|
+
// the Error class is extended
|
|
336
|
+
options = { name: error.constructor.name };
|
|
337
|
+
}
|
|
338
|
+
const names = Object.getOwnPropertyNames(error);
|
|
339
|
+
for (const name of names) {
|
|
340
|
+
if (name !== 'name' && name !== 'message') {
|
|
341
|
+
options = options || {};
|
|
342
|
+
options[name] = error[name as keyof Error];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return options;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getIterableOptions(obj: Iterable<any>) {
|
|
349
|
+
const names = Object.getOwnPropertyNames(obj);
|
|
350
|
+
if (names.length) {
|
|
351
|
+
const options: Record<string, unknown> = {};
|
|
352
|
+
for (const name of names) {
|
|
353
|
+
options[name] = obj[name as unknown as keyof typeof obj];
|
|
354
|
+
}
|
|
355
|
+
return options;
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const enum SerovalNodeType {
|
|
361
|
+
Primitive,
|
|
362
|
+
Reference,
|
|
363
|
+
Date,
|
|
364
|
+
RegExp,
|
|
365
|
+
Set,
|
|
366
|
+
Map,
|
|
367
|
+
Array,
|
|
368
|
+
Object,
|
|
369
|
+
NullConstructor,
|
|
370
|
+
Promise,
|
|
371
|
+
Error,
|
|
372
|
+
AggregateError,
|
|
373
|
+
Iterable,
|
|
374
|
+
TypedArray,
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
type SerovalPrimitiveNode = [type: SerovalNodeType.Primitive, value: PrimitiveValue];
|
|
378
|
+
type SerovalReferenceNode = [type: SerovalNodeType.Reference, value: number];
|
|
379
|
+
type SerovalSemiPrimitiveNode =
|
|
380
|
+
| [type: SerovalNodeType.Date, value: Date, id: number]
|
|
381
|
+
| [type: SerovalNodeType.RegExp, value: RegExp, id: number]
|
|
382
|
+
| [
|
|
383
|
+
type: SerovalNodeType.TypedArray,
|
|
384
|
+
value: [constructor: string, array: TypedArrayValue],
|
|
385
|
+
id: number
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
type SerovalDictionaryNode = [key: string[], value: SerovalNode[], size: number];
|
|
389
|
+
type SerovalSetNode = [type: SerovalNodeType.Set, value: SerovalNode[], id: number];
|
|
390
|
+
type SerovalMapNode = [
|
|
391
|
+
type: SerovalNodeType.Map,
|
|
392
|
+
value: [key: SerovalNode[], value: SerovalNode[], size: number],
|
|
393
|
+
id: number
|
|
394
|
+
];
|
|
395
|
+
type SerovalArrayNode = [type: SerovalNodeType.Array, value: SerovalNode[], id: number];
|
|
396
|
+
type SerovalObjectNode = [type: SerovalNodeType.Object, value: SerovalDictionaryNode, id: number];
|
|
397
|
+
type SerovalNullConstructorNode = [
|
|
398
|
+
type: SerovalNodeType.NullConstructor,
|
|
399
|
+
value: SerovalDictionaryNode,
|
|
400
|
+
id: number,
|
|
401
|
+
];
|
|
402
|
+
type SerovalPromiseNode = [type: SerovalNodeType.Promise, value: SerovalNode, id: number];
|
|
403
|
+
type SerovalErrorNode = [
|
|
404
|
+
type: SerovalNodeType.Error,
|
|
405
|
+
value: [
|
|
406
|
+
constructor: string,
|
|
407
|
+
message: string,
|
|
408
|
+
options?: SerovalDictionaryNode,
|
|
409
|
+
],
|
|
410
|
+
id: number
|
|
411
|
+
];
|
|
412
|
+
type SerovalAggregateErrorNode = [
|
|
413
|
+
type: SerovalNodeType.AggregateError,
|
|
414
|
+
value: [
|
|
415
|
+
message: string,
|
|
416
|
+
options: SerovalDictionaryNode | undefined,
|
|
417
|
+
errors: SerovalNode,
|
|
418
|
+
],
|
|
419
|
+
id: number
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
type SerovalIterableNode = [
|
|
423
|
+
type: SerovalNodeType.Iterable,
|
|
424
|
+
value: [
|
|
425
|
+
options: SerovalDictionaryNode | undefined,
|
|
426
|
+
items: SerovalNode,
|
|
427
|
+
],
|
|
428
|
+
id: number,
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
type SerovalNode =
|
|
432
|
+
| SerovalPrimitiveNode
|
|
433
|
+
| SerovalReferenceNode
|
|
434
|
+
| SerovalSemiPrimitiveNode
|
|
435
|
+
| SerovalSetNode
|
|
436
|
+
| SerovalMapNode
|
|
437
|
+
| SerovalArrayNode
|
|
438
|
+
| SerovalObjectNode
|
|
439
|
+
| SerovalNullConstructorNode
|
|
440
|
+
| SerovalPromiseNode
|
|
441
|
+
| SerovalErrorNode
|
|
442
|
+
| SerovalAggregateErrorNode
|
|
443
|
+
| SerovalIterableNode;
|
|
444
|
+
|
|
445
|
+
function isReferenceInStack(
|
|
446
|
+
ctx: SerializationContext,
|
|
447
|
+
node: SerovalNode,
|
|
448
|
+
): node is SerovalReferenceNode {
|
|
449
|
+
return node[0] === SerovalNodeType.Reference && ctx.stack.includes(node[1]);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function generateRef(
|
|
453
|
+
ctx: SerializationContext,
|
|
454
|
+
current: unknown,
|
|
455
|
+
): number | SerovalReferenceNode {
|
|
456
|
+
// Check if reference number already exists
|
|
457
|
+
const ref = ctx.refs.has(current);
|
|
458
|
+
if (ref) {
|
|
459
|
+
// Exists, means this value is currently
|
|
460
|
+
// being referenced
|
|
461
|
+
const refID = ctx.refs.get(current) || 0;
|
|
462
|
+
// Mark reference
|
|
463
|
+
markRef(ctx, refID);
|
|
464
|
+
return [SerovalNodeType.Reference, refID];
|
|
465
|
+
}
|
|
466
|
+
// Create a new reference ID
|
|
467
|
+
const id = ctx.refs.size;
|
|
468
|
+
ctx.refs.set(current, id);
|
|
469
|
+
return id;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function generateSemiPrimitiveValue(
|
|
473
|
+
ctx: SerializationContext,
|
|
474
|
+
current: unknown,
|
|
475
|
+
id: number,
|
|
476
|
+
): SerovalNode | undefined {
|
|
477
|
+
if (constructorCheck<Date>(current, Date)) {
|
|
478
|
+
return [SerovalNodeType.Date, current, id];
|
|
479
|
+
}
|
|
480
|
+
if (constructorCheck<RegExp>(current, RegExp)) {
|
|
481
|
+
return [SerovalNodeType.RegExp, current, id];
|
|
482
|
+
}
|
|
483
|
+
if (constructorCheck<Int8Array>(current, Int8Array)) {
|
|
484
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Int8Array"');
|
|
485
|
+
return [SerovalNodeType.TypedArray, ['Int8Array', current], id];
|
|
486
|
+
}
|
|
487
|
+
if (constructorCheck<Int16Array>(current, Int16Array)) {
|
|
488
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Int16Array"');
|
|
489
|
+
return [SerovalNodeType.TypedArray, ['Int16Array', current], id];
|
|
490
|
+
}
|
|
491
|
+
if (constructorCheck<Int32Array>(current, Int32Array)) {
|
|
492
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Int32Array"');
|
|
493
|
+
return [SerovalNodeType.TypedArray, ['Int32Array', current], id];
|
|
494
|
+
}
|
|
495
|
+
if (constructorCheck<Uint8Array>(current, Uint8Array)) {
|
|
496
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint8Array"');
|
|
497
|
+
return [SerovalNodeType.TypedArray, ['Uint8Array', current], id];
|
|
498
|
+
}
|
|
499
|
+
if (constructorCheck<Uint16Array>(current, Uint16Array)) {
|
|
500
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint16Array"');
|
|
501
|
+
return [SerovalNodeType.TypedArray, ['Uint16Array', current], id];
|
|
502
|
+
}
|
|
503
|
+
if (constructorCheck<Uint32Array>(current, Uint32Array)) {
|
|
504
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint32Array"');
|
|
505
|
+
return [SerovalNodeType.TypedArray, ['Uint32Array', current], id];
|
|
506
|
+
}
|
|
507
|
+
if (constructorCheck<Uint8ClampedArray>(current, Uint8ClampedArray)) {
|
|
508
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint8ClampedArray"');
|
|
509
|
+
return [SerovalNodeType.TypedArray, ['Uint8ClampedArray', current], id];
|
|
510
|
+
}
|
|
511
|
+
if (constructorCheck<Float32Array>(current, Float32Array)) {
|
|
512
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Float32Array"');
|
|
513
|
+
return [SerovalNodeType.TypedArray, ['Float32Array', current], id];
|
|
514
|
+
}
|
|
515
|
+
if (constructorCheck<Float64Array>(current, Float64Array)) {
|
|
516
|
+
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Float64Array"');
|
|
517
|
+
return [SerovalNodeType.TypedArray, ['Float64Array', current], id];
|
|
518
|
+
}
|
|
519
|
+
if (constructorCheck<BigInt64Array>(current, BigInt64Array)) {
|
|
520
|
+
assert(
|
|
521
|
+
ctx.features.has('typed-arrays')
|
|
522
|
+
&& ctx.features.has('bigint'),
|
|
523
|
+
'Unsupported value type "BigInt64Array"',
|
|
524
|
+
);
|
|
525
|
+
return [SerovalNodeType.TypedArray, ['BigInt64Array', current], id];
|
|
526
|
+
}
|
|
527
|
+
if (constructorCheck<BigUint64Array>(current, BigUint64Array)) {
|
|
528
|
+
assert(
|
|
529
|
+
ctx.features.has('typed-arrays')
|
|
530
|
+
&& ctx.features.has('bigint'),
|
|
531
|
+
'Unsupported value type "BigUint64Array"',
|
|
532
|
+
);
|
|
533
|
+
return [SerovalNodeType.TypedArray, ['BigUint64Array', current], id];
|
|
534
|
+
}
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function generateTreeSync(
|
|
539
|
+
ctx: SerializationContext,
|
|
540
|
+
current: ServerValue,
|
|
541
|
+
): SerovalNode {
|
|
542
|
+
function serializeProperties(
|
|
543
|
+
properties: Record<string, unknown>,
|
|
544
|
+
): SerovalDictionaryNode {
|
|
545
|
+
const keyNodes: string[] = [];
|
|
546
|
+
const valueNodes: SerovalNode[] = [];
|
|
547
|
+
const deferredKeys: string[] = [];
|
|
548
|
+
const deferredValues: ServerValue[] = [];
|
|
549
|
+
const keys = Object.keys(properties);
|
|
550
|
+
let deferredSize = 0;
|
|
551
|
+
for (const key of keys) {
|
|
552
|
+
if (isIterable(properties[key])) {
|
|
553
|
+
deferredKeys.push(key);
|
|
554
|
+
deferredValues.push(properties[key] as ServerValue);
|
|
555
|
+
deferredSize++;
|
|
556
|
+
} else {
|
|
557
|
+
keyNodes.push(key);
|
|
558
|
+
valueNodes.push(generateTreeSync(ctx, properties[key] as ServerValue));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
for (let i = 0; i < deferredSize; i++) {
|
|
562
|
+
keyNodes.push(deferredKeys[i]);
|
|
563
|
+
valueNodes.push(generateTreeSync(ctx, deferredValues[i]));
|
|
564
|
+
}
|
|
565
|
+
return [keyNodes, valueNodes, keys.length];
|
|
566
|
+
}
|
|
567
|
+
if (isPrimitive(current)) {
|
|
568
|
+
return [SerovalNodeType.Primitive, current];
|
|
569
|
+
}
|
|
570
|
+
// Non-primitive values needs a reference ID
|
|
571
|
+
// mostly because the values themselves are stateful
|
|
572
|
+
const id = generateRef(ctx, current);
|
|
573
|
+
if (Array.isArray(id)) {
|
|
574
|
+
return id;
|
|
575
|
+
}
|
|
576
|
+
const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
|
|
577
|
+
if (semiPrimitive) {
|
|
578
|
+
return semiPrimitive;
|
|
579
|
+
}
|
|
580
|
+
if (constructorCheck<Set<ServerValue>>(current, Set)) {
|
|
581
|
+
assert(ctx.features.has('set'), 'Unsupported type "Set"');
|
|
582
|
+
const nodes: SerovalNode[] = [];
|
|
583
|
+
const deferred: ServerValue[] = [];
|
|
584
|
+
for (const item of current.keys()) {
|
|
585
|
+
// Iterables are lazy, so the evaluation must be deferred
|
|
586
|
+
if (isIterable(item)) {
|
|
587
|
+
deferred.push(item);
|
|
588
|
+
} else {
|
|
589
|
+
nodes.push(generateTreeSync(ctx, item));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Parse deferred items
|
|
593
|
+
for (let i = 0, len = deferred.length; i < len; i++) {
|
|
594
|
+
nodes.push(generateTreeSync(ctx, deferred[i]));
|
|
595
|
+
}
|
|
596
|
+
return [SerovalNodeType.Set, nodes, id];
|
|
597
|
+
}
|
|
598
|
+
if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) {
|
|
599
|
+
assert(ctx.features.has('map'), 'Unsupported type "Map"');
|
|
600
|
+
const keyNodes: SerovalNode[] = [];
|
|
601
|
+
const valueNodes: SerovalNode[] = [];
|
|
602
|
+
const deferredKey: ServerValue[] = [];
|
|
603
|
+
const deferredValue: ServerValue[] = [];
|
|
604
|
+
let deferredSize = 0;
|
|
605
|
+
for (const [key, value] of current.entries()) {
|
|
606
|
+
// Either key or value might be an iterable
|
|
607
|
+
if (isIterable(key) || isIterable(value)) {
|
|
608
|
+
deferredKey.push(key);
|
|
609
|
+
deferredValue.push(value);
|
|
610
|
+
deferredSize++;
|
|
611
|
+
} else {
|
|
612
|
+
keyNodes.push(generateTreeSync(ctx, key));
|
|
613
|
+
valueNodes.push(generateTreeSync(ctx, value));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
for (let i = 0; i < deferredSize; i++) {
|
|
617
|
+
keyNodes.push(generateTreeSync(ctx, deferredKey[i]));
|
|
618
|
+
valueNodes.push(generateTreeSync(ctx, deferredValue[i]));
|
|
619
|
+
}
|
|
620
|
+
return [SerovalNodeType.Map, [keyNodes, valueNodes, current.size], id];
|
|
621
|
+
}
|
|
622
|
+
if (Array.isArray(current)) {
|
|
623
|
+
const size = current.length;
|
|
624
|
+
const nodes = new Array<SerovalNode>(size);
|
|
625
|
+
const deferred = new Array<ServerValue>(size);
|
|
626
|
+
for (let i = 0; i < size; i++) {
|
|
627
|
+
if (i in current) {
|
|
628
|
+
if (isIterable(current[i])) {
|
|
629
|
+
deferred[i] = current[i];
|
|
630
|
+
} else {
|
|
631
|
+
nodes[i] = generateTreeSync(ctx, current[i]);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
for (let i = 0; i < size; i++) {
|
|
636
|
+
if (i in deferred) {
|
|
637
|
+
nodes[i] = generateTreeSync(ctx, deferred[i]);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return [SerovalNodeType.Array, nodes, id];
|
|
641
|
+
}
|
|
642
|
+
if (current instanceof AggregateError && ctx.features.has('aggregate-error')) {
|
|
643
|
+
const options = getErrorOptions(current);
|
|
644
|
+
const optionsNode = options
|
|
645
|
+
? serializeProperties(options)
|
|
646
|
+
: undefined;
|
|
647
|
+
const errorsNode = generateTreeSync(ctx, current.errors as ServerValue);
|
|
648
|
+
return [SerovalNodeType.AggregateError, [current.message, optionsNode, errorsNode], id];
|
|
649
|
+
}
|
|
650
|
+
if (current instanceof Error) {
|
|
651
|
+
const options = getErrorOptions(current);
|
|
652
|
+
const optionsNode = options
|
|
653
|
+
? serializeProperties(options)
|
|
654
|
+
: undefined;
|
|
655
|
+
return [
|
|
656
|
+
SerovalNodeType.Error,
|
|
657
|
+
[getErrorConstructor(current), current.message, optionsNode],
|
|
658
|
+
id,
|
|
659
|
+
];
|
|
660
|
+
}
|
|
661
|
+
if (isIterable(current)) {
|
|
662
|
+
assert(ctx.features.has('symbol-iterator'), 'Unsupported type "Iterable"');
|
|
663
|
+
const options = getIterableOptions(current);
|
|
664
|
+
return [SerovalNodeType.Iterable, [
|
|
665
|
+
// Parse options first before the items
|
|
666
|
+
options ? serializeProperties(options) : undefined,
|
|
667
|
+
generateTreeSync(ctx, Array.from(current)),
|
|
668
|
+
], id];
|
|
669
|
+
}
|
|
670
|
+
const empty = current.constructor == null;
|
|
671
|
+
if (current.constructor === Object || empty) {
|
|
672
|
+
return [
|
|
673
|
+
empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
|
|
674
|
+
serializeProperties(current as Record<string, unknown>),
|
|
675
|
+
id,
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
throw new Error('Unsupported value');
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
export async function generateTreeAsync(
|
|
682
|
+
ctx: SerializationContext,
|
|
683
|
+
current: AsyncServerValue,
|
|
684
|
+
): Promise<SerovalNode> {
|
|
685
|
+
async function serializeProperties(
|
|
686
|
+
properties: Record<string, unknown>,
|
|
687
|
+
): Promise<SerovalDictionaryNode> {
|
|
688
|
+
const keyNodes: string[] = [];
|
|
689
|
+
const valueNodes: SerovalNode[] = [];
|
|
690
|
+
const deferredKeys: string[] = [];
|
|
691
|
+
const deferredValues: ServerValue[] = [];
|
|
692
|
+
const keys = Object.keys(properties);
|
|
693
|
+
let deferredSize = 0;
|
|
694
|
+
for (const key of keys) {
|
|
695
|
+
if (isIterable(properties[key])) {
|
|
696
|
+
deferredKeys.push(key);
|
|
697
|
+
deferredValues.push(properties[key] as ServerValue);
|
|
698
|
+
deferredSize++;
|
|
699
|
+
} else {
|
|
700
|
+
keyNodes.push(key);
|
|
701
|
+
valueNodes.push(await generateTreeAsync(ctx, properties[key] as ServerValue));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
for (let i = 0; i < deferredSize; i++) {
|
|
705
|
+
keyNodes.push(deferredKeys[i]);
|
|
706
|
+
valueNodes.push(await generateTreeAsync(ctx, deferredValues[i]));
|
|
707
|
+
}
|
|
708
|
+
return [keyNodes, valueNodes, keys.length];
|
|
709
|
+
}
|
|
710
|
+
if (isPrimitive(current)) {
|
|
711
|
+
return [SerovalNodeType.Primitive, current];
|
|
712
|
+
}
|
|
713
|
+
const id = generateRef(ctx, current);
|
|
714
|
+
if (Array.isArray(id)) {
|
|
715
|
+
return id;
|
|
716
|
+
}
|
|
717
|
+
const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
|
|
718
|
+
if (semiPrimitive) {
|
|
719
|
+
return semiPrimitive;
|
|
720
|
+
}
|
|
721
|
+
if (isPromise(current)) {
|
|
722
|
+
assert(ctx.features.has('promise'), 'Unsupported type "Promise"');
|
|
723
|
+
return current.then(async (value) => [
|
|
724
|
+
SerovalNodeType.Promise,
|
|
725
|
+
await generateTreeAsync(ctx, value),
|
|
726
|
+
id,
|
|
727
|
+
]);
|
|
728
|
+
}
|
|
729
|
+
if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
|
|
730
|
+
assert(ctx.features.has('set'), 'Unsupported type "Set"');
|
|
731
|
+
const nodes: SerovalNode[] = [];
|
|
732
|
+
const deferred: AsyncServerValue[] = [];
|
|
733
|
+
for (const item of current.keys()) {
|
|
734
|
+
if (isIterable(item)) {
|
|
735
|
+
deferred.push(item);
|
|
736
|
+
} else {
|
|
737
|
+
nodes.push(await generateTreeAsync(ctx, item));
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
for (let i = 0, len = deferred.length; i < len; i++) {
|
|
741
|
+
nodes.push(await generateTreeAsync(ctx, deferred[i]));
|
|
742
|
+
}
|
|
743
|
+
return [SerovalNodeType.Set, nodes, id];
|
|
744
|
+
}
|
|
745
|
+
if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
|
|
746
|
+
assert(ctx.features.has('map'), 'Unsupported type "Map"');
|
|
747
|
+
const keyNodes: SerovalNode[] = [];
|
|
748
|
+
const valueNodes: SerovalNode[] = [];
|
|
749
|
+
const deferredKey: AsyncServerValue[] = [];
|
|
750
|
+
const deferredValue: AsyncServerValue[] = [];
|
|
751
|
+
let deferredSize = 0;
|
|
752
|
+
for (const [key, value] of current.entries()) {
|
|
753
|
+
if (isIterable(key) || isIterable(value)) {
|
|
754
|
+
deferredKey.push(key);
|
|
755
|
+
deferredValue.push(value);
|
|
756
|
+
deferredSize++;
|
|
757
|
+
} else {
|
|
758
|
+
keyNodes.push(await generateTreeAsync(ctx, key));
|
|
759
|
+
valueNodes.push(await generateTreeAsync(ctx, value));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
for (let i = 0; i < deferredSize; i++) {
|
|
763
|
+
keyNodes.push(await generateTreeAsync(ctx, deferredKey[i]));
|
|
764
|
+
valueNodes.push(await generateTreeAsync(ctx, deferredValue[i]));
|
|
765
|
+
}
|
|
766
|
+
return [SerovalNodeType.Map, [keyNodes, valueNodes, current.size], id];
|
|
767
|
+
}
|
|
768
|
+
if (Array.isArray(current)) {
|
|
769
|
+
const size = current.length;
|
|
770
|
+
const nodes = new Array<SerovalNode>(size);
|
|
771
|
+
const deferred = new Array<AsyncServerValue>(size);
|
|
772
|
+
for (let i = 0; i < size; i++) {
|
|
773
|
+
const item = current[i];
|
|
774
|
+
if (i in current) {
|
|
775
|
+
if (isIterable(item)) {
|
|
776
|
+
deferred[i] = item;
|
|
777
|
+
} else {
|
|
778
|
+
nodes[i] = await generateTreeAsync(ctx, item);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
for (let i = 0; i < size; i++) {
|
|
783
|
+
if (i in deferred) {
|
|
784
|
+
nodes[i] = await generateTreeAsync(ctx, deferred[i]);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return [SerovalNodeType.Array, nodes, id];
|
|
788
|
+
}
|
|
789
|
+
if (current instanceof AggregateError) {
|
|
790
|
+
const options = getErrorOptions(current);
|
|
791
|
+
const optionsNode = options
|
|
792
|
+
? await serializeProperties(options)
|
|
793
|
+
: undefined;
|
|
794
|
+
const errorsNode = await generateTreeAsync(ctx, current.errors as ServerValue);
|
|
795
|
+
return [SerovalNodeType.AggregateError, [current.message, optionsNode, errorsNode], id];
|
|
796
|
+
}
|
|
797
|
+
if (current instanceof Error) {
|
|
798
|
+
const options = getErrorOptions(current);
|
|
799
|
+
const optionsNode = options
|
|
800
|
+
? await serializeProperties(options)
|
|
801
|
+
: undefined;
|
|
802
|
+
return [
|
|
803
|
+
SerovalNodeType.Error,
|
|
804
|
+
[getErrorConstructor(current), current.message, optionsNode],
|
|
805
|
+
id,
|
|
806
|
+
];
|
|
807
|
+
}
|
|
808
|
+
if (isIterable(current)) {
|
|
809
|
+
assert(ctx.features.has('symbol-iterator'), 'Unsupported type "Iterable"');
|
|
810
|
+
const options = getIterableOptions(current);
|
|
811
|
+
return [SerovalNodeType.Iterable, [
|
|
812
|
+
options
|
|
813
|
+
? await serializeProperties(options)
|
|
814
|
+
: undefined,
|
|
815
|
+
await generateTreeAsync(ctx, Array.from(current)),
|
|
816
|
+
], id];
|
|
817
|
+
}
|
|
818
|
+
const empty = current.constructor == null;
|
|
819
|
+
if (current.constructor === Object || empty) {
|
|
820
|
+
return [
|
|
821
|
+
empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
|
|
822
|
+
await serializeProperties(current as Record<string, unknown>),
|
|
823
|
+
id,
|
|
824
|
+
];
|
|
825
|
+
}
|
|
826
|
+
throw new Error('Unsupported value');
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export function serializePrimitive(
|
|
830
|
+
ctx: SerializationContext,
|
|
831
|
+
value: PrimitiveValue,
|
|
832
|
+
): string {
|
|
833
|
+
// Shortened forms
|
|
834
|
+
if (value === true) {
|
|
835
|
+
return '!0';
|
|
836
|
+
}
|
|
837
|
+
if (value === false) {
|
|
838
|
+
return '!1';
|
|
839
|
+
}
|
|
840
|
+
if (value === undefined) {
|
|
841
|
+
return 'void 0';
|
|
842
|
+
}
|
|
843
|
+
if (value === null) {
|
|
844
|
+
return 'null';
|
|
845
|
+
}
|
|
846
|
+
if (typeof value === 'bigint') {
|
|
847
|
+
assert(ctx.features.has('bigint'), 'Unsupported type "BigInt"');
|
|
848
|
+
return `${value}n`;
|
|
849
|
+
}
|
|
850
|
+
if (typeof value === 'string') {
|
|
851
|
+
return quote(value);
|
|
852
|
+
}
|
|
853
|
+
// negative 0 isn't the same as 0
|
|
854
|
+
if (Object.is(value, -0)) {
|
|
855
|
+
return '-0';
|
|
856
|
+
}
|
|
857
|
+
if (Object.is(value, Infinity)) {
|
|
858
|
+
return '1/0';
|
|
859
|
+
}
|
|
860
|
+
if (Object.is(value, -Infinity)) {
|
|
861
|
+
return '-1/0';
|
|
862
|
+
}
|
|
863
|
+
return String(value);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
export function serializeTree(
|
|
867
|
+
ctx: SerializationContext,
|
|
868
|
+
[type, value, id]: SerovalNode,
|
|
869
|
+
): string {
|
|
870
|
+
function serializeAssignments(
|
|
871
|
+
targetRef: number,
|
|
872
|
+
[keys, values, size]: SerovalDictionaryNode,
|
|
873
|
+
) {
|
|
874
|
+
ctx.stack.push(targetRef);
|
|
875
|
+
const mainAssignments: Assignment[] = [];
|
|
876
|
+
for (let i = 0; i < size; i++) {
|
|
877
|
+
const parentStack = ctx.stack;
|
|
878
|
+
ctx.stack = [];
|
|
879
|
+
const refParam = serializeTree(ctx, values[i]);
|
|
880
|
+
ctx.stack = parentStack;
|
|
881
|
+
const key = keys[i];
|
|
882
|
+
const check = Number(key);
|
|
883
|
+
// Test if key is a valid number or JS identifier
|
|
884
|
+
// so that we don't have to serialize the key and wrap with brackets
|
|
885
|
+
const parentAssignment = ctx.assignments;
|
|
886
|
+
ctx.assignments = mainAssignments;
|
|
887
|
+
if (
|
|
888
|
+
check >= 0
|
|
889
|
+
|| (Number.isNaN(check) && /^([$A-Z_][0-9A-Z_$]*)$/i.test(key))
|
|
890
|
+
) {
|
|
891
|
+
if (!Number.isNaN(check)) {
|
|
892
|
+
createObjectComputedAssign(ctx, targetRef, key, refParam);
|
|
893
|
+
} else {
|
|
894
|
+
createObjectIdentifierAssign(ctx, targetRef, key, refParam);
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
createObjectComputedAssign(ctx, targetRef, quote(key), refParam);
|
|
898
|
+
}
|
|
899
|
+
ctx.assignments = parentAssignment;
|
|
900
|
+
}
|
|
901
|
+
ctx.stack.pop();
|
|
902
|
+
return resolveAssignments(mainAssignments);
|
|
903
|
+
}
|
|
904
|
+
function serializeObject(
|
|
905
|
+
sourceID: number,
|
|
906
|
+
[keys, values, size]: SerovalDictionaryNode,
|
|
907
|
+
) {
|
|
908
|
+
let result = '';
|
|
909
|
+
ctx.stack.push(sourceID);
|
|
910
|
+
for (let i = 0; i < size; i++) {
|
|
911
|
+
const key = keys[i];
|
|
912
|
+
const val = values[i];
|
|
913
|
+
const check = Number(key);
|
|
914
|
+
// Test if key is a valid number or JS identifier
|
|
915
|
+
// so that we don't have to serialize the key and wrap with brackets
|
|
916
|
+
if (
|
|
917
|
+
check >= 0
|
|
918
|
+
|| (Number.isNaN(check) && /^([$A-Z_][0-9A-Z_$]*)$/i.test(key))
|
|
919
|
+
) {
|
|
920
|
+
if (isReferenceInStack(ctx, val)) {
|
|
921
|
+
const refParam = getRefParam(ctx, val[1]);
|
|
922
|
+
if (!Number.isNaN(check)) {
|
|
923
|
+
createObjectComputedAssign(ctx, sourceID, key, refParam);
|
|
924
|
+
} else {
|
|
925
|
+
createObjectIdentifierAssign(ctx, sourceID, key, refParam);
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
result += `${key}:${serializeTree(ctx, val)},`;
|
|
929
|
+
}
|
|
930
|
+
} else if (isReferenceInStack(ctx, val)) {
|
|
931
|
+
const refParam = getRefParam(ctx, val[1]);
|
|
932
|
+
createObjectComputedAssign(ctx, sourceID, quote(key), refParam);
|
|
933
|
+
} else {
|
|
934
|
+
result += `${quote(key)}:${serializeTree(ctx, val)},`;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
ctx.stack.pop();
|
|
938
|
+
return `{${result.substring(0, result.length - 1)}}`;
|
|
939
|
+
}
|
|
940
|
+
switch (type) {
|
|
941
|
+
case SerovalNodeType.Primitive:
|
|
942
|
+
return serializePrimitive(ctx, value);
|
|
943
|
+
case SerovalNodeType.Reference:
|
|
944
|
+
return getRefParam(ctx, value);
|
|
945
|
+
case SerovalNodeType.Promise: {
|
|
946
|
+
let serialized: string;
|
|
947
|
+
// Check if resolved value is a parent expression
|
|
948
|
+
if (isReferenceInStack(ctx, value)) {
|
|
949
|
+
// A Promise trick, reference the value
|
|
950
|
+
// inside the `then` expression so that
|
|
951
|
+
// the Promise evaluates after the parent
|
|
952
|
+
// has initialized
|
|
953
|
+
serialized = `Promise.resolve().then(()=>${getRefParam(ctx, value[1])})`;
|
|
954
|
+
} else {
|
|
955
|
+
ctx.stack.push(id);
|
|
956
|
+
const result = serializeTree(ctx, value);
|
|
957
|
+
ctx.stack.pop();
|
|
958
|
+
// just inline the value/reference here
|
|
959
|
+
serialized = `Promise.resolve(${result})`;
|
|
960
|
+
}
|
|
961
|
+
return assignRef(ctx, id, serialized);
|
|
962
|
+
}
|
|
963
|
+
case SerovalNodeType.Date:
|
|
964
|
+
return assignRef(ctx, id, `new Date("${value.toISOString()}")`);
|
|
965
|
+
case SerovalNodeType.RegExp:
|
|
966
|
+
return assignRef(ctx, id, String(value));
|
|
967
|
+
case SerovalNodeType.TypedArray: {
|
|
968
|
+
// BigInt typed arrays are broken for toString()
|
|
969
|
+
let values = '';
|
|
970
|
+
for (let i = 0, len = value[1].length; i < len; i++) {
|
|
971
|
+
values += `${serializePrimitive(ctx, value[1][i])},`;
|
|
972
|
+
}
|
|
973
|
+
let args = values ? `[${values.substring(0, values.length - 1)}]` : '[]';
|
|
974
|
+
if (value[1].byteOffset !== 0) {
|
|
975
|
+
args += `,${value[1].byteOffset}`;
|
|
976
|
+
}
|
|
977
|
+
return assignRef(ctx, id, `new ${value[0]}(${args})`);
|
|
978
|
+
}
|
|
979
|
+
case SerovalNodeType.Set: {
|
|
980
|
+
let serialized = 'new Set';
|
|
981
|
+
const size = value.length;
|
|
982
|
+
if (size) {
|
|
983
|
+
let result = '';
|
|
984
|
+
ctx.stack.push(id);
|
|
985
|
+
for (let i = 0; i < size; i++) {
|
|
986
|
+
const item = value[i];
|
|
987
|
+
if (isReferenceInStack(ctx, item)) {
|
|
988
|
+
createSetAdd(ctx, id, getRefParam(ctx, item[1]));
|
|
989
|
+
} else {
|
|
990
|
+
// Push directly
|
|
991
|
+
result += `${serializeTree(ctx, item)},`;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
ctx.stack.pop();
|
|
995
|
+
if (result) {
|
|
996
|
+
serialized += `([${result.substring(0, result.length - 1)}])`;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return assignRef(ctx, id, serialized);
|
|
1000
|
+
}
|
|
1001
|
+
case SerovalNodeType.Map: {
|
|
1002
|
+
let serialized = 'new Map';
|
|
1003
|
+
const size = value[2];
|
|
1004
|
+
if (size) {
|
|
1005
|
+
let result = '';
|
|
1006
|
+
ctx.stack.push(id);
|
|
1007
|
+
for (let i = 0; i < size; i++) {
|
|
1008
|
+
// Check if key is a parent
|
|
1009
|
+
const key = value[0][i];
|
|
1010
|
+
const val = value[1][i];
|
|
1011
|
+
if (isReferenceInStack(ctx, key)) {
|
|
1012
|
+
// Create reference for the map instance
|
|
1013
|
+
const keyRef = getRefParam(ctx, key[1]);
|
|
1014
|
+
// Check if value is a parent
|
|
1015
|
+
if (isReferenceInStack(ctx, val)) {
|
|
1016
|
+
const valueRef = getRefParam(ctx, val[1]);
|
|
1017
|
+
// Register an assignment since
|
|
1018
|
+
// both key and value are a parent of this
|
|
1019
|
+
// Map instance
|
|
1020
|
+
createMapSet(ctx, id, keyRef, valueRef);
|
|
1021
|
+
} else {
|
|
1022
|
+
// Reset the stack
|
|
1023
|
+
// This is required because the serialized
|
|
1024
|
+
// value is no longer part of the expression
|
|
1025
|
+
// tree and has been moved to the deferred
|
|
1026
|
+
// assignment
|
|
1027
|
+
const parent = ctx.stack;
|
|
1028
|
+
ctx.stack = [];
|
|
1029
|
+
createMapSet(ctx, id, keyRef, serializeTree(ctx, val));
|
|
1030
|
+
ctx.stack = parent;
|
|
1031
|
+
}
|
|
1032
|
+
} else if (isReferenceInStack(ctx, val)) {
|
|
1033
|
+
// Create ref for the Map instance
|
|
1034
|
+
const valueRef = getRefParam(ctx, val[1]);
|
|
1035
|
+
// Reset stack for the key serialization
|
|
1036
|
+
const parent = ctx.stack;
|
|
1037
|
+
ctx.stack = [];
|
|
1038
|
+
createMapSet(ctx, id, serializeTree(ctx, key), valueRef);
|
|
1039
|
+
ctx.stack = parent;
|
|
1040
|
+
} else {
|
|
1041
|
+
result += `[${serializeTree(ctx, key)},${serializeTree(ctx, val)}],`;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
ctx.stack.pop();
|
|
1045
|
+
// Check if there are any values
|
|
1046
|
+
// so that the empty Map constructor
|
|
1047
|
+
// can be used instead
|
|
1048
|
+
if (result) {
|
|
1049
|
+
serialized += `([${result.substring(0, result.length - 1)}])`;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return assignRef(ctx, id, serialized);
|
|
1053
|
+
}
|
|
1054
|
+
case SerovalNodeType.Array: {
|
|
1055
|
+
// This is different than Map and Set
|
|
1056
|
+
// because we also need to serialize
|
|
1057
|
+
// the holes of the Array
|
|
1058
|
+
const size = value.length;
|
|
1059
|
+
let values = '';
|
|
1060
|
+
ctx.stack.push(id);
|
|
1061
|
+
for (let i = 0; i < size; i++) {
|
|
1062
|
+
const item = value[i];
|
|
1063
|
+
// Check if index is a hole
|
|
1064
|
+
if (i in value) {
|
|
1065
|
+
// Check if item is a parent
|
|
1066
|
+
if (isReferenceInStack(ctx, item)) {
|
|
1067
|
+
createArrayAssign(ctx, id, i, getRefParam(ctx, item[1]));
|
|
1068
|
+
values += ',';
|
|
1069
|
+
} else {
|
|
1070
|
+
values += serializeTree(ctx, item);
|
|
1071
|
+
if (i < size - 1) {
|
|
1072
|
+
values += ',';
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
// Add an empty item
|
|
1077
|
+
values += ',';
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
ctx.stack.pop();
|
|
1081
|
+
return assignRef(ctx, id, `[${values}]`);
|
|
1082
|
+
}
|
|
1083
|
+
case SerovalNodeType.AggregateError: {
|
|
1084
|
+
// Serialize the required arguments
|
|
1085
|
+
ctx.stack.push(id);
|
|
1086
|
+
let serialized = `new AggregateError(${serializeTree(ctx, value[2])},${quote(value[0])})`;
|
|
1087
|
+
ctx.stack.pop();
|
|
1088
|
+
// `AggregateError` might've been extended
|
|
1089
|
+
// either through class or custom properties
|
|
1090
|
+
// Make sure to assign extra properties
|
|
1091
|
+
if (value[1]) {
|
|
1092
|
+
if (ctx.features.has('object-assign')) {
|
|
1093
|
+
const options = serializeObject(id, value[1]);
|
|
1094
|
+
serialized = `Object.assign(${serialized},${options})`;
|
|
1095
|
+
} else {
|
|
1096
|
+
markRef(ctx, id);
|
|
1097
|
+
const assignments = serializeAssignments(id, value[1]);
|
|
1098
|
+
if (assignments) {
|
|
1099
|
+
const ref = getRefParam(ctx, id);
|
|
1100
|
+
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return assignRef(ctx, id, serialized);
|
|
1105
|
+
}
|
|
1106
|
+
case SerovalNodeType.Error: {
|
|
1107
|
+
let serialized = `new ${value[0]}(${quote(value[1])})`;
|
|
1108
|
+
if (value[2]) {
|
|
1109
|
+
if (ctx.features.has('object-assign')) {
|
|
1110
|
+
const options = serializeObject(id, value[2]);
|
|
1111
|
+
serialized = `Object.assign(${serialized},${options})`;
|
|
1112
|
+
} else {
|
|
1113
|
+
markRef(ctx, id);
|
|
1114
|
+
const assignments = serializeAssignments(id, value[2]);
|
|
1115
|
+
if (assignments) {
|
|
1116
|
+
const ref = getRefParam(ctx, id);
|
|
1117
|
+
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return assignRef(ctx, id, serialized);
|
|
1122
|
+
}
|
|
1123
|
+
case SerovalNodeType.Iterable: {
|
|
1124
|
+
const parent = ctx.stack;
|
|
1125
|
+
ctx.stack = [];
|
|
1126
|
+
const values = serializeTree(ctx, value[1]);
|
|
1127
|
+
ctx.stack = parent;
|
|
1128
|
+
let serialized: string;
|
|
1129
|
+
if (ctx.features.has('array-values')) {
|
|
1130
|
+
serialized = `${values}.values()`;
|
|
1131
|
+
} else {
|
|
1132
|
+
serialized = `${values}[Symbol.iterator]()`;
|
|
1133
|
+
}
|
|
1134
|
+
if (ctx.features.has('arrow-function')) {
|
|
1135
|
+
serialized = `{[Symbol.iterator]:()=>${serialized}}`;
|
|
1136
|
+
} else if (ctx.features.has('method-shorthand')) {
|
|
1137
|
+
serialized = `{[Symbol.iterator](){return ${serialized}}}`;
|
|
1138
|
+
} else {
|
|
1139
|
+
serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`;
|
|
1140
|
+
}
|
|
1141
|
+
if (value[0]) {
|
|
1142
|
+
if (ctx.features.has('object-assign')) {
|
|
1143
|
+
const options = serializeObject(id, value[0]);
|
|
1144
|
+
serialized = `Object.assign(${serialized},${options})`;
|
|
1145
|
+
} else {
|
|
1146
|
+
markRef(ctx, id);
|
|
1147
|
+
const assignments = serializeAssignments(id, value[0]);
|
|
1148
|
+
if (assignments) {
|
|
1149
|
+
const ref = getRefParam(ctx, id);
|
|
1150
|
+
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return assignRef(ctx, id, serialized);
|
|
1155
|
+
}
|
|
1156
|
+
case SerovalNodeType.NullConstructor: {
|
|
1157
|
+
let serialized = 'Object.create(null)';
|
|
1158
|
+
if (ctx.features.has('object-assign')) {
|
|
1159
|
+
const fields = serializeObject(id, value);
|
|
1160
|
+
if (fields !== '{}') {
|
|
1161
|
+
serialized = `Object.assign(${serialized},${fields})`;
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
markRef(ctx, id);
|
|
1165
|
+
const assignments = serializeAssignments(id, value);
|
|
1166
|
+
if (assignments) {
|
|
1167
|
+
const ref = getRefParam(ctx, id);
|
|
1168
|
+
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return assignRef(ctx, id, serialized);
|
|
1172
|
+
}
|
|
1173
|
+
case SerovalNodeType.Object:
|
|
1174
|
+
return assignRef(ctx, id, serializeObject(id, value));
|
|
1175
|
+
default:
|
|
1176
|
+
throw new Error('Unsupported type');
|
|
1177
|
+
}
|
|
1178
|
+
}
|