seroval 0.1.0 → 0.2.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/LICENSE +1 -1
- package/README.md +35 -4
- package/dist/cjs/development/index.cjs +423 -255
- package/dist/cjs/development/index.cjs.map +4 -4
- package/dist/cjs/production/index.cjs +2 -2
- package/dist/esm/development/index.mjs +423 -255
- package/dist/esm/development/index.mjs.map +4 -4
- package/dist/esm/production/index.mjs +2 -2
- package/dist/types/async.d.ts +4 -0
- package/dist/types/context.d.ts +33 -0
- package/dist/types/index.d.ts +6 -5
- package/dist/types/is-primitive.d.ts +2 -0
- package/dist/types/is-promise.d.ts +1 -0
- package/dist/types/serialize-primitive.d.ts +2 -0
- package/dist/types/sync.d.ts +4 -0
- package/dist/types/types.d.ts +9 -0
- package/package.json +2 -2
- package/src/async.ts +229 -0
- package/src/context.ts +200 -0
- package/src/get-identifier.ts +1 -0
- package/src/index.ts +69 -453
- package/src/is-primitive.ts +14 -0
- package/src/is-promise.ts +6 -0
- package/src/serialize-primitive.ts +32 -0
- package/src/sync.ts +199 -0
- package/src/types.ts +32 -0
package/src/index.ts
CHANGED
|
@@ -1,479 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let VARS: string[] = [];
|
|
40
|
-
|
|
41
|
-
// Array of assignments to be done (used for recursion)
|
|
42
|
-
type Assignment = [source: string, value: string];
|
|
43
|
-
let ASSIGNMENTS: Assignment[] = [];
|
|
44
|
-
|
|
45
|
-
// Array of Map.prototype.set calls
|
|
46
|
-
type MapSet = [source: string, key: string, value: string];
|
|
47
|
-
let MAP_SETS: MapSet[] = [];
|
|
48
|
-
|
|
49
|
-
// Array of Set.prototype.add calls
|
|
50
|
-
type SetAdd = [source: string, value: string];
|
|
51
|
-
let SET_ADDS: SetAdd[] = [];
|
|
52
|
-
|
|
53
|
-
function reset() {
|
|
54
|
-
ASSIGNMENTS = [];
|
|
55
|
-
MAP_SETS = [];
|
|
56
|
-
SET_ADDS = [];
|
|
57
|
-
VARS = [];
|
|
58
|
-
REF_COUNT.clear();
|
|
59
|
-
REFS.clear();
|
|
60
|
-
ASSIGNED_REFS.clear();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function resolveAssignments() {
|
|
64
|
-
if (ASSIGNMENTS.length) {
|
|
65
|
-
const result: Record<string, string> = {};
|
|
66
|
-
|
|
67
|
-
// Merge all assignments with similar source
|
|
68
|
-
forEach(ASSIGNMENTS, ([source, value]) => {
|
|
69
|
-
if (value in result) {
|
|
70
|
-
result[value] = `${source}=${result[value]}`;
|
|
71
|
-
} else {
|
|
72
|
-
result[value] = `${source}=${value}`;
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return `${join(Object.values(result), ',')},`;
|
|
77
|
-
}
|
|
78
|
-
return '';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function resolveMapSets() {
|
|
82
|
-
if (MAP_SETS.length) {
|
|
83
|
-
const result: Record<string, string> = {};
|
|
84
|
-
|
|
85
|
-
// Merge all assignments with similar source
|
|
86
|
-
forEach(MAP_SETS, ([source, key, value]) => {
|
|
87
|
-
if (source in result) {
|
|
88
|
-
result[source] = `${result[source]}.set(${key},${value})`;
|
|
89
|
-
} else {
|
|
90
|
-
result[source] = `${source}.set(${key},${value})`;
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
return `${join(Object.values(result), ',')},`;
|
|
95
|
-
}
|
|
96
|
-
return '';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function resolveSetAdds() {
|
|
100
|
-
if (SET_ADDS.length) {
|
|
101
|
-
const result: Record<string, string> = {};
|
|
102
|
-
|
|
103
|
-
// Merge all assignments with similar source
|
|
104
|
-
forEach(SET_ADDS, ([source, value]) => {
|
|
105
|
-
if (source in result) {
|
|
106
|
-
result[source] = `${result[source]}.add(${value})`;
|
|
107
|
-
} else {
|
|
108
|
-
result[source] = `${source}.add(${value})`;
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return `${join(Object.values(result), ',')},`;
|
|
113
|
-
}
|
|
114
|
-
return '';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function resolvePatches() {
|
|
118
|
-
return `${resolveAssignments()}${resolveMapSets()}${resolveSetAdds()}`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function insertRef(current: ServerValue) {
|
|
122
|
-
const count = REF_COUNT.get(current) || 0;
|
|
123
|
-
REF_COUNT.set(current, count + 1);
|
|
124
|
-
return count === 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function constructorCheck<T extends NonNullable<ServerValue>>(
|
|
128
|
-
value: NonNullable<ServerValue>,
|
|
129
|
-
constructor: unknown,
|
|
130
|
-
): value is T {
|
|
131
|
-
return value.constructor === constructor;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function isPrimitive(current: ServerValue): current is PrimitiveValue {
|
|
135
|
-
if (!current || current === true) {
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
const type = typeof current;
|
|
139
|
-
const classConst = current.constructor;
|
|
140
|
-
return type === 'number'
|
|
141
|
-
|| type === 'string'
|
|
142
|
-
|| type === 'bigint'
|
|
143
|
-
|| classConst === Date
|
|
144
|
-
|| classConst === RegExp;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function lookupRefs(current: ServerValue) {
|
|
148
|
-
if (isPrimitive(current)) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (Array.isArray(current)) {
|
|
152
|
-
if (insertRef(current)) {
|
|
153
|
-
forEach(current, lookupRefs);
|
|
154
|
-
}
|
|
155
|
-
} else if (constructorCheck<Set<ServerValue>>(current, Set)) {
|
|
156
|
-
if (insertRef(current)) {
|
|
157
|
-
for (const item of current.keys()) {
|
|
158
|
-
lookupRefs(item);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
} else if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) {
|
|
162
|
-
if (insertRef(current)) {
|
|
163
|
-
for (const [key, value] of current.entries()) {
|
|
164
|
-
lookupRefs(key);
|
|
165
|
-
lookupRefs(value);
|
|
166
|
-
}
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import { join } from './array';
|
|
3
|
+
import { lookupRefsAsync, traverseAsync } from './async';
|
|
4
|
+
import {
|
|
5
|
+
SerializationContext,
|
|
6
|
+
getRefParam,
|
|
7
|
+
createRef,
|
|
8
|
+
resolvePatches,
|
|
9
|
+
createSerializationContext,
|
|
10
|
+
} from './context';
|
|
11
|
+
import isPrimitive from './is-primitive';
|
|
12
|
+
import serializePrimitive from './serialize-primitive';
|
|
13
|
+
import { lookupRefs, traverseSync } from './sync';
|
|
14
|
+
import {
|
|
15
|
+
AsyncServerValue,
|
|
16
|
+
NonPrimitiveServerValue,
|
|
17
|
+
PrimitiveValue,
|
|
18
|
+
ServerValue,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
export { AsyncServerValue, ServerValue, PrimitiveValue };
|
|
22
|
+
|
|
23
|
+
function finalize<T extends ServerValue | AsyncServerValue>(
|
|
24
|
+
ctx: SerializationContext,
|
|
25
|
+
source: NonPrimitiveServerValue<T>,
|
|
26
|
+
result: string,
|
|
27
|
+
) {
|
|
28
|
+
// Shared references detected
|
|
29
|
+
if (ctx.vars.length) {
|
|
30
|
+
// Get (or create) a ref from the source
|
|
31
|
+
const index = getRefParam(ctx, createRef(ctx, source));
|
|
32
|
+
const patches = resolvePatches(ctx);
|
|
33
|
+
const params = ctx.vars.length > 1
|
|
34
|
+
? `(${join(ctx.vars, ',')})`
|
|
35
|
+
: ctx.vars[0];
|
|
36
|
+
// Source is probably already assigned
|
|
37
|
+
if (result.startsWith(`${index}=`)) {
|
|
38
|
+
return `(${params}=>(${result},${patches}${index}))()`;
|
|
167
39
|
}
|
|
168
|
-
|
|
169
|
-
forEach(Object.values(current), lookupRefs);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function createRef(value: ServerValue) {
|
|
174
|
-
const current = REFS.get(value);
|
|
175
|
-
if (current != null) {
|
|
176
|
-
return current;
|
|
40
|
+
return `(${params}=>(${index}=${result},${patches}${index}))()`;
|
|
177
41
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return id;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function getRefParam(index: number) {
|
|
184
|
-
if (VARS[index]) {
|
|
185
|
-
return VARS[index];
|
|
42
|
+
if (source.constructor === Object) {
|
|
43
|
+
return `(${result})`;
|
|
186
44
|
}
|
|
187
|
-
const result = getIdentifier(index);
|
|
188
|
-
VARS[index] = result;
|
|
189
45
|
return result;
|
|
190
46
|
}
|
|
191
47
|
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
function createAssignment(source: string, value: string) {
|
|
197
|
-
ASSIGNMENTS.push([source, value]);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function createSetAdd(ref: number, value: string) {
|
|
201
|
-
SET_ADDS.push([getRefParam(ref), value]);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function createMapSet(ref: number, key: string, value: string) {
|
|
205
|
-
MAP_SETS.push([getRefParam(ref), key, value]);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function createArrayAssign(ref: number, index: number, value: string) {
|
|
209
|
-
createAssignment(`${getRefParam(ref)}[${index}]`, value);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function createObjectIdentifierAssign(ref: number, key: string, value: string) {
|
|
213
|
-
createAssignment(`${getRefParam(ref)}.${key}`, value);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function createObjectStringAssign(ref: number, key: string, value: string) {
|
|
217
|
-
createAssignment(`${getRefParam(ref)}[${quote(key)}]`, value);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function serializePrimitive(value: PrimitiveValue): string {
|
|
221
|
-
// Shortened forms
|
|
222
|
-
if (value === true) {
|
|
223
|
-
return '!0';
|
|
224
|
-
}
|
|
225
|
-
if (value === false) {
|
|
226
|
-
return '!1';
|
|
227
|
-
}
|
|
228
|
-
if (value === undefined) {
|
|
229
|
-
return 'void 0';
|
|
230
|
-
}
|
|
231
|
-
if (value === null) {
|
|
232
|
-
return 'null';
|
|
233
|
-
}
|
|
234
|
-
// negative 0 isn't the same as 0
|
|
235
|
-
if (Object.is(value, -0)) {
|
|
236
|
-
return '-0';
|
|
237
|
-
}
|
|
238
|
-
if (typeof value === 'bigint') {
|
|
239
|
-
return `BigInt("${value}")`;
|
|
240
|
-
}
|
|
241
|
-
if (typeof value === 'string') {
|
|
242
|
-
return quote(value);
|
|
243
|
-
}
|
|
244
|
-
if (value.constructor === Date) {
|
|
245
|
-
return `new Date("${value.toISOString()}")`;
|
|
246
|
-
}
|
|
247
|
-
return String(value);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function traverse(current: ServerValue): string | { referred: string } {
|
|
251
|
-
// Skip primitives
|
|
252
|
-
if (isPrimitive(current)) {
|
|
253
|
-
return serializePrimitive(current);
|
|
254
|
-
}
|
|
255
|
-
// Get reference of the current object
|
|
256
|
-
let refResult = REFS.get(current);
|
|
257
|
-
if (refResult != null) {
|
|
258
|
-
// Return the reference if already assigned a value
|
|
259
|
-
if (ASSIGNED_REFS.has(refResult)) {
|
|
260
|
-
return { referred: getRefParam(refResult) };
|
|
261
|
-
}
|
|
262
|
-
// Early sign (to prevent recursion)
|
|
263
|
-
ASSIGNED_REFS.add(refResult);
|
|
264
|
-
}
|
|
265
|
-
// Transform Set
|
|
266
|
-
if (constructorCheck<Set<ServerValue>>(current, Set)) {
|
|
267
|
-
if (current.size) {
|
|
268
|
-
const values: string[] = [];
|
|
269
|
-
|
|
270
|
-
STACK.add(current);
|
|
271
|
-
for (const value of current.keys()) {
|
|
272
|
-
if (STACK.has(value)) {
|
|
273
|
-
// This object received a ref, we must generate a setter ref
|
|
274
|
-
if (refResult == null) {
|
|
275
|
-
refResult = createRef(current);
|
|
276
|
-
}
|
|
277
|
-
// Received a ref, this might be a recursive ref, defer an assignment
|
|
278
|
-
createSetAdd(refResult, getRefParam(createRef(value)));
|
|
279
|
-
} else {
|
|
280
|
-
const result = traverse(value);
|
|
281
|
-
if (typeof result === 'string') {
|
|
282
|
-
values.push(result);
|
|
283
|
-
} else {
|
|
284
|
-
values.push(result.referred);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
STACK.delete(current);
|
|
289
|
-
|
|
290
|
-
const value = values.length ? `new Set([${join(values, ',')}])` : EMPTY_SET;
|
|
291
|
-
if (refResult != null) {
|
|
292
|
-
return assignRef(refResult, value);
|
|
293
|
-
}
|
|
294
|
-
return value;
|
|
295
|
-
}
|
|
296
|
-
return EMPTY_SET;
|
|
297
|
-
}
|
|
298
|
-
if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) {
|
|
299
|
-
if (current.size) {
|
|
300
|
-
const values: string[] = [];
|
|
301
|
-
|
|
302
|
-
STACK.add(current);
|
|
303
|
-
for (const [key, value] of current.entries()) {
|
|
304
|
-
if (STACK.has(key)) {
|
|
305
|
-
if (refResult == null) {
|
|
306
|
-
refResult = createRef(current);
|
|
307
|
-
}
|
|
308
|
-
const keyRef = getRefParam(createRef(key));
|
|
309
|
-
if (STACK.has(value)) {
|
|
310
|
-
createMapSet(refResult, keyRef, getRefParam(createRef(value)));
|
|
311
|
-
} else {
|
|
312
|
-
const valueResult = traverse(value);
|
|
313
|
-
if (typeof valueResult === 'string') {
|
|
314
|
-
createMapSet(refResult, keyRef, valueResult);
|
|
315
|
-
} else {
|
|
316
|
-
createMapSet(refResult, keyRef, valueResult.referred);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
} else if (STACK.has(value)) {
|
|
320
|
-
if (refResult == null) {
|
|
321
|
-
refResult = createRef(current);
|
|
322
|
-
}
|
|
323
|
-
const valueRef = getRefParam(createRef(value));
|
|
324
|
-
const keyResult = traverse(key);
|
|
325
|
-
if (typeof keyResult === 'string') {
|
|
326
|
-
createMapSet(refResult, keyResult, valueRef);
|
|
327
|
-
} else {
|
|
328
|
-
createMapSet(refResult, keyResult.referred, valueRef);
|
|
329
|
-
}
|
|
330
|
-
} else {
|
|
331
|
-
const keyResult = traverse(key);
|
|
332
|
-
const valueResult = traverse(value);
|
|
333
|
-
if (typeof keyResult === 'string') {
|
|
334
|
-
if (typeof valueResult === 'string') {
|
|
335
|
-
values.push(`[${keyResult},${valueResult}]`);
|
|
336
|
-
} else {
|
|
337
|
-
values.push(`[${keyResult},${valueResult.referred}]`);
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
if (typeof valueResult === 'string') {
|
|
341
|
-
values.push(`[${keyResult.referred},${valueResult}]`);
|
|
342
|
-
} else {
|
|
343
|
-
values.push(`[${keyResult.referred},${valueResult.referred}]`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
STACK.delete(current);
|
|
349
|
-
const value = values.length ? `new Map([${join(values, ',')}])` : EMPTY_MAP;
|
|
350
|
-
if (refResult != null) {
|
|
351
|
-
return assignRef(refResult, value);
|
|
352
|
-
}
|
|
353
|
-
return value;
|
|
354
|
-
}
|
|
355
|
-
return EMPTY_MAP;
|
|
48
|
+
export function serialize(source: ServerValue) {
|
|
49
|
+
if (isPrimitive(source)) {
|
|
50
|
+
return serializePrimitive(source);
|
|
356
51
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (STACK.has(item)) {
|
|
365
|
-
if (refResult == null) {
|
|
366
|
-
refResult = createRef(current);
|
|
367
|
-
}
|
|
368
|
-
createArrayAssign(refResult, i, getRefParam(createRef(item)));
|
|
369
|
-
values += ',';
|
|
370
|
-
} else {
|
|
371
|
-
const result = traverse(item);
|
|
372
|
-
if (typeof result === 'string') {
|
|
373
|
-
values += result;
|
|
374
|
-
} else {
|
|
375
|
-
values += result.referred;
|
|
376
|
-
}
|
|
377
|
-
if (i < current.length - 1) {
|
|
378
|
-
values += ',';
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
values += ',';
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
STACK.delete(current);
|
|
386
|
-
|
|
387
|
-
const value = `[${values}]`;
|
|
388
|
-
if (refResult != null) {
|
|
389
|
-
return assignRef(refResult, value);
|
|
390
|
-
}
|
|
391
|
-
return value;
|
|
52
|
+
const ctx = createSerializationContext();
|
|
53
|
+
// Lookup possible shared references
|
|
54
|
+
lookupRefs(ctx, source);
|
|
55
|
+
// Add shared references to refs
|
|
56
|
+
for (const [key, value] of ctx.refCount.entries()) {
|
|
57
|
+
if (value > 1) {
|
|
58
|
+
createRef(ctx, key);
|
|
392
59
|
}
|
|
393
|
-
return EMPTY_ARRAY;
|
|
394
60
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
forEach(Object.entries(current), ([key, value]) => {
|
|
400
|
-
const check = Number(key);
|
|
401
|
-
// Test if key is a valid number or JS identifier
|
|
402
|
-
// so that we don't have to serialize the key and wrap with brackets
|
|
403
|
-
if (Number.isNaN(check) || /^([$A-Z_][0-9A-Z_$]*)$/i.test(key)) {
|
|
404
|
-
if (STACK.has(value)) {
|
|
405
|
-
if (refResult == null) {
|
|
406
|
-
refResult = createRef(current);
|
|
407
|
-
}
|
|
408
|
-
createObjectIdentifierAssign(refResult, key, getRefParam(createRef(value)));
|
|
409
|
-
} else {
|
|
410
|
-
const result = traverse(value);
|
|
411
|
-
if (typeof result === 'string') {
|
|
412
|
-
values.push(`${key}:${result}`);
|
|
413
|
-
} else {
|
|
414
|
-
values.push(`${key}:${result.referred}`);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
} else if (STACK.has(value)) {
|
|
418
|
-
if (refResult == null) {
|
|
419
|
-
refResult = createRef(current);
|
|
420
|
-
}
|
|
421
|
-
createObjectStringAssign(refResult, key, getRefParam(createRef(value)));
|
|
422
|
-
} else {
|
|
423
|
-
const result = traverse(value);
|
|
424
|
-
if (typeof result === 'string') {
|
|
425
|
-
values.push(`${quote(key)}:${result}`);
|
|
426
|
-
} else {
|
|
427
|
-
values.push(`${quote(key)}:${result.referred}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
STACK.delete(current);
|
|
432
|
-
|
|
433
|
-
const value = `{${join(values, ',')}}`;
|
|
434
|
-
if (refResult != null) {
|
|
435
|
-
return assignRef(refResult, value);
|
|
436
|
-
}
|
|
437
|
-
return value;
|
|
61
|
+
// Get top-level serialization
|
|
62
|
+
const result = traverseSync(ctx, source);
|
|
63
|
+
if (typeof result !== 'string') {
|
|
64
|
+
throw new Error('Unreachable error');
|
|
438
65
|
}
|
|
439
|
-
|
|
66
|
+
return finalize(ctx, source, result);
|
|
440
67
|
}
|
|
441
68
|
|
|
442
|
-
export
|
|
69
|
+
export async function serializeAsync(source: AsyncServerValue) {
|
|
443
70
|
if (isPrimitive(source)) {
|
|
444
71
|
return serializePrimitive(source);
|
|
445
72
|
}
|
|
446
|
-
|
|
73
|
+
const ctx = createSerializationContext();
|
|
447
74
|
// Lookup possible shared references
|
|
448
|
-
|
|
75
|
+
await lookupRefsAsync(ctx, source);
|
|
449
76
|
// Add shared references to refs
|
|
450
|
-
for (const [key, value] of
|
|
77
|
+
for (const [key, value] of ctx.refCount.entries()) {
|
|
451
78
|
if (value > 1) {
|
|
452
|
-
createRef(key);
|
|
79
|
+
createRef(ctx, key);
|
|
453
80
|
}
|
|
454
81
|
}
|
|
455
82
|
// Get top-level serialization
|
|
456
|
-
const result =
|
|
83
|
+
const result = await traverseAsync(ctx, source);
|
|
457
84
|
if (typeof result !== 'string') {
|
|
458
85
|
throw new Error('Unreachable error');
|
|
459
86
|
}
|
|
87
|
+
return finalize(ctx, source, result);
|
|
88
|
+
}
|
|
460
89
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const index = getRefParam(createRef(source));
|
|
465
|
-
const patches = resolvePatches();
|
|
466
|
-
const params = VARS.length > 1
|
|
467
|
-
? `(${join(VARS, ',')})`
|
|
468
|
-
: VARS[0];
|
|
469
|
-
// Source is probably already assigned
|
|
470
|
-
if (result.startsWith(index)) {
|
|
471
|
-
return `(${params}=>(${result},${patches}${index}))()`;
|
|
472
|
-
}
|
|
473
|
-
return `(${params}=>(${index}=${result},${patches}${index}))()`;
|
|
474
|
-
}
|
|
475
|
-
if (source.constructor === Object) {
|
|
476
|
-
return `(${result})`;
|
|
477
|
-
}
|
|
478
|
-
return result;
|
|
90
|
+
export function deserialize<T extends AsyncServerValue>(source: string): T {
|
|
91
|
+
// eslint-disable-next-line no-eval
|
|
92
|
+
return (0, eval)(source) as T;
|
|
479
93
|
}
|
|
94
|
+
|
|
95
|
+
export default serialize;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PrimitiveValue } from './types';
|
|
2
|
+
|
|
3
|
+
export default function isPrimitive(current: unknown): current is PrimitiveValue {
|
|
4
|
+
if (!current || current === true) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
const type = typeof current;
|
|
8
|
+
const classConst = current.constructor;
|
|
9
|
+
return type === 'number'
|
|
10
|
+
|| type === 'string'
|
|
11
|
+
|| type === 'bigint'
|
|
12
|
+
|| classConst === Date
|
|
13
|
+
|| classConst === RegExp;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import quote from './quote';
|
|
2
|
+
import { PrimitiveValue } from './types';
|
|
3
|
+
|
|
4
|
+
export default function serializePrimitive(value: PrimitiveValue): string {
|
|
5
|
+
// Shortened forms
|
|
6
|
+
if (value === true) {
|
|
7
|
+
return '!0';
|
|
8
|
+
}
|
|
9
|
+
if (value === false) {
|
|
10
|
+
return '!1';
|
|
11
|
+
}
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
return 'void 0';
|
|
14
|
+
}
|
|
15
|
+
if (value === null) {
|
|
16
|
+
return 'null';
|
|
17
|
+
}
|
|
18
|
+
// negative 0 isn't the same as 0
|
|
19
|
+
if (Object.is(value, -0)) {
|
|
20
|
+
return '-0';
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === 'bigint') {
|
|
23
|
+
return `BigInt("${value}")`;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
return quote(value);
|
|
27
|
+
}
|
|
28
|
+
if (value.constructor === Date) {
|
|
29
|
+
return `new Date("${value.toISOString()}")`;
|
|
30
|
+
}
|
|
31
|
+
return String(value);
|
|
32
|
+
}
|