seroval 0.4.0-alpha.4 → 0.4.0-alpha.6

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.
@@ -0,0 +1,79 @@
1
+ import { PrimitiveValue } from '../types';
2
+ export declare const enum SerovalNodeType {
3
+ Primitive = 0,
4
+ BigInt = 1,
5
+ Reference = 2,
6
+ Date = 3,
7
+ RegExp = 4,
8
+ Set = 5,
9
+ Map = 6,
10
+ Array = 7,
11
+ Object = 8,
12
+ NullConstructor = 9,
13
+ Promise = 10,
14
+ Error = 11,
15
+ AggregateError = 12,
16
+ Iterable = 13,
17
+ TypedArray = 14,
18
+ BigIntTypedArray = 15
19
+ }
20
+ export type SerovalPrimitiveNode = [
21
+ type: SerovalNodeType.Primitive,
22
+ value: PrimitiveValue
23
+ ];
24
+ export type SerovalReferenceNode = [type: SerovalNodeType.Reference, value: number];
25
+ export type SerovalSemiPrimitiveNode = [type: SerovalNodeType.BigInt, value: string] | [type: SerovalNodeType.Date, value: string, id: number] | [type: SerovalNodeType.RegExp, value: string, id: number] | [
26
+ type: SerovalNodeType.TypedArray,
27
+ value: [constructor: string, array: string, byteOffset: number],
28
+ id: number
29
+ ] | [
30
+ type: SerovalNodeType.BigIntTypedArray,
31
+ value: [constructor: string, array: string, byteOffset: number],
32
+ id: number
33
+ ];
34
+ export type SerovalDictionaryNode = Record<string, SerovalNode>;
35
+ export type SerovalSetNode = [type: SerovalNodeType.Set, value: SerovalNode[], id: number];
36
+ export type SerovalMapNode = [
37
+ type: SerovalNodeType.Map,
38
+ value: [key: SerovalNode[], value: SerovalNode[], size: number],
39
+ id: number
40
+ ];
41
+ export type SerovalArrayNode = [type: SerovalNodeType.Array, value: SerovalNode[], id: number];
42
+ export type SerovalObjectNode = [
43
+ type: SerovalNodeType.Object,
44
+ value: SerovalDictionaryNode,
45
+ id: number
46
+ ];
47
+ export type SerovalNullConstructorNode = [
48
+ type: SerovalNodeType.NullConstructor,
49
+ value: SerovalDictionaryNode,
50
+ id: number
51
+ ];
52
+ export type SerovalPromiseNode = [type: SerovalNodeType.Promise, value: SerovalNode, id: number];
53
+ export type SerovalErrorNode = [
54
+ type: SerovalNodeType.Error,
55
+ value: [
56
+ constructor: string,
57
+ message: string,
58
+ options?: SerovalDictionaryNode
59
+ ],
60
+ id: number
61
+ ];
62
+ export type SerovalAggregateErrorNode = [
63
+ type: SerovalNodeType.AggregateError,
64
+ value: [
65
+ message: string,
66
+ options: SerovalDictionaryNode | undefined,
67
+ errors: SerovalNode
68
+ ],
69
+ id: number
70
+ ];
71
+ export type SerovalIterableNode = [
72
+ type: SerovalNodeType.Iterable,
73
+ value: [
74
+ options: SerovalDictionaryNode | undefined,
75
+ items: SerovalNode[]
76
+ ],
77
+ id: number
78
+ ];
79
+ export type SerovalNode = SerovalPrimitiveNode | SerovalReferenceNode | SerovalSemiPrimitiveNode | SerovalSetNode | SerovalMapNode | SerovalArrayNode | SerovalObjectNode | SerovalNullConstructorNode | SerovalPromiseNode | SerovalErrorNode | SerovalAggregateErrorNode | SerovalIterableNode;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "seroval",
3
3
  "type": "module",
4
- "version": "0.4.0-alpha.4",
4
+ "version": "0.4.0-alpha.6",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
@@ -64,5 +64,5 @@
64
64
  "typesVersions": {
65
65
  "*": {}
66
66
  },
67
- "gitHead": "1e96ac1ab8304fb09ac7a1bc451b7768f12b7f6b"
67
+ "gitHead": "91199038f92d170421d2cfe02b928fb24202d9a8"
68
68
  }
package/src/context.ts ADDED
@@ -0,0 +1,137 @@
1
+ import { parseTargets } from './compat';
2
+ import getIdentifier from './get-identifier';
3
+
4
+ interface IndexAssignment {
5
+ type: 'index';
6
+ source: string;
7
+ value: string;
8
+ }
9
+
10
+ interface MapAssignment {
11
+ type: 'map';
12
+ source: string;
13
+ key: string;
14
+ value: string;
15
+ }
16
+
17
+ interface SetAssignment {
18
+ type: 'set';
19
+ source: string;
20
+ value: string;
21
+ }
22
+
23
+ // Array of assignments to be done (used for recursion)
24
+ export type Assignment =
25
+ | IndexAssignment
26
+ | MapAssignment
27
+ | SetAssignment;
28
+
29
+ export interface ParserContext {
30
+ refs: Map<unknown, number>;
31
+ markedRefs: Set<number>;
32
+ features: number;
33
+ }
34
+
35
+ export interface SerializationContext {
36
+ stack: number[];
37
+ // Map tree refs to actual refs
38
+ validRefs: Map<number, number>;
39
+ // Refs that are...referenced
40
+ markedRefs: Set<number>;
41
+ // Variables
42
+ vars: string[];
43
+ // Array of assignments to be done (used for recursion)
44
+ assignments: Assignment[];
45
+ // Supported features
46
+ features: number;
47
+ }
48
+
49
+ export interface Options {
50
+ target: string | string[];
51
+ }
52
+
53
+ const DEFAULT_OPTIONS: Options = {
54
+ target: 'es2023',
55
+ };
56
+
57
+ export function createParserContext(options: Partial<Options> = {}): ParserContext {
58
+ // eslint-disable-next-line prefer-object-spread
59
+ const result = Object.assign({}, DEFAULT_OPTIONS, options || {});
60
+ return {
61
+ markedRefs: new Set(),
62
+ refs: new Map(),
63
+ features: parseTargets(result.target),
64
+ };
65
+ }
66
+
67
+ export interface SerializationOptions {
68
+ markedRefs: number[] | Set<number>;
69
+ features: number;
70
+ }
71
+
72
+ export function createSerializationContext(options: SerializationOptions): SerializationContext {
73
+ return {
74
+ stack: [],
75
+ vars: [],
76
+ assignments: [],
77
+ validRefs: new Map(),
78
+ features: options.features,
79
+ markedRefs: new Set(options.markedRefs),
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Increments the number of references the referenced value has
85
+ */
86
+ export function markRef(ctx: ParserContext | SerializationContext, current: number) {
87
+ ctx.markedRefs.add(current);
88
+ }
89
+
90
+ /**
91
+ * Creates a new reference ID from a given reference ID
92
+ * This new reference ID means that the reference itself
93
+ * has been referenced at least once, and is used to generate
94
+ * the variables
95
+ */
96
+ function createValidRef(
97
+ ctx: SerializationContext,
98
+ index: number,
99
+ ) {
100
+ const current = ctx.validRefs.get(index);
101
+ if (current != null) {
102
+ return current;
103
+ }
104
+ const id = ctx.validRefs.size;
105
+ ctx.validRefs.set(index, id);
106
+ return id;
107
+ }
108
+
109
+ /**
110
+ * Creates the reference param (identifier) from the given reference ID
111
+ * Calling this function means the value has been referenced somewhere
112
+ */
113
+ export function getRefParam(ctx: SerializationContext, index: number) {
114
+ const actualIndex = createValidRef(ctx, index);
115
+ if (ctx.vars[actualIndex]) {
116
+ return ctx.vars[actualIndex];
117
+ }
118
+ const result = getIdentifier(actualIndex);
119
+ ctx.vars[actualIndex] = result;
120
+ return result;
121
+ }
122
+
123
+ /**
124
+ * Creates a reference ID from the given values
125
+ */
126
+ export function createRef(
127
+ ctx: ParserContext,
128
+ index: unknown,
129
+ ) {
130
+ const current = ctx.refs.get(index);
131
+ if (current != null) {
132
+ return current;
133
+ }
134
+ const id = ctx.refs.size;
135
+ ctx.refs.set(index, id);
136
+ return id;
137
+ }
package/src/index.ts CHANGED
@@ -2,18 +2,17 @@
2
2
  import { isPrimitive } from './checks';
3
3
  import { Feature } from './compat';
4
4
  import {
5
- createParserContext,
6
- createSerializationContext,
5
+ SerializationContext,
7
6
  getRefParam,
8
7
  Options,
9
- parseAsync,
10
- parseSync,
11
- resolvePatches,
12
- SerializationContext,
13
- serializePrimitive,
14
- serializeTree,
15
- SerovalNode,
16
- } from './tree';
8
+ createParserContext,
9
+ createSerializationContext,
10
+ } from './context';
11
+ import parseAsync from './tree/async';
12
+ import serializeTree, { resolvePatches } from './tree/serialize';
13
+ import { serializePrimitive } from './tree/shared';
14
+ import parseSync from './tree/sync';
15
+ import { SerovalNode } from './tree/types';
17
16
  import {
18
17
  AsyncServerValue,
19
18
  PrimitiveValue,
@@ -0,0 +1,252 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /* eslint-disable @typescript-eslint/no-use-before-define */
3
+ import assert from '../assert';
4
+ import {
5
+ isIterable,
6
+ isPrimitive,
7
+ constructorCheck,
8
+ isPromise,
9
+ } from '../checks';
10
+ import { Feature } from '../compat';
11
+ import { createRef, ParserContext } from '../context';
12
+ import { AsyncServerValue } from '../types';
13
+ import {
14
+ generateRef,
15
+ generateSemiPrimitiveValue,
16
+ getErrorConstructor,
17
+ getErrorOptions,
18
+ getIterableOptions,
19
+ serializePrimitive,
20
+ } from './shared';
21
+ import {
22
+ SerovalAggregateErrorNode,
23
+ SerovalArrayNode,
24
+ SerovalDictionaryNode,
25
+ SerovalErrorNode,
26
+ SerovalIterableNode,
27
+ SerovalMapNode,
28
+ SerovalNode,
29
+ SerovalNodeType,
30
+ SerovalSetNode,
31
+ } from './types';
32
+
33
+ async function generateDictionary(
34
+ ctx: ParserContext,
35
+ properties: Record<string, AsyncServerValue>,
36
+ ): Promise<SerovalDictionaryNode> {
37
+ const keys = Object.keys(properties);
38
+ const nodes: SerovalDictionaryNode = {};
39
+ const deferred: Record<string, AsyncServerValue> = {};
40
+ for (const key of keys) {
41
+ if (isIterable(properties[key])) {
42
+ deferred[key] = properties[key];
43
+ } else {
44
+ nodes[key] = await generateTreeAsync(ctx, properties[key]);
45
+ }
46
+ }
47
+ for (const key of Object.keys(deferred)) {
48
+ nodes[key] = await generateTreeAsync(ctx, deferred[key]);
49
+ }
50
+ return nodes;
51
+ }
52
+
53
+ async function generateSetNode(
54
+ ctx: ParserContext,
55
+ current: Set<AsyncServerValue>,
56
+ id: number,
57
+ ): Promise<SerovalSetNode> {
58
+ assert(ctx.features & Feature.Set, 'Unsupported type "Set"');
59
+ const len = current.size;
60
+ const nodes = new Array<SerovalNode>(len);
61
+ const deferred = new Array<AsyncServerValue>(len);
62
+ let deferredSize = 0;
63
+ let nodeSize = 0;
64
+ for (const item of current.keys()) {
65
+ // Iterables are lazy, so the evaluation must be deferred
66
+ if (isIterable(item)) {
67
+ deferred[deferredSize++] = item;
68
+ } else {
69
+ nodes[nodeSize++] = await generateTreeAsync(ctx, item);
70
+ }
71
+ }
72
+ // Parse deferred items
73
+ for (let i = 0; i < deferredSize; i++) {
74
+ nodes[nodeSize + i] = await generateTreeAsync(ctx, deferred[i]);
75
+ }
76
+ return [SerovalNodeType.Set, nodes, id];
77
+ }
78
+
79
+ async function generateMapNode(
80
+ ctx: ParserContext,
81
+ current: Map<AsyncServerValue, AsyncServerValue>,
82
+ id: number,
83
+ ): Promise<SerovalMapNode> {
84
+ assert(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<AsyncServerValue>(len);
89
+ const deferredValue = new Array<AsyncServerValue>(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] = await generateTreeAsync(ctx, key);
100
+ valueNodes[nodeSize] = await generateTreeAsync(ctx, value);
101
+ nodeSize++;
102
+ }
103
+ }
104
+ for (let i = 0; i < deferredSize; i++) {
105
+ keyNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredKey[i]);
106
+ valueNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredValue[i]);
107
+ }
108
+ return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
109
+ }
110
+
111
+ async function generateNodeList(
112
+ ctx: ParserContext,
113
+ current: AsyncServerValue[],
114
+ ) {
115
+ const size = current.length;
116
+ const nodes = new Array<SerovalNode>(size);
117
+ const deferred = new Array<AsyncServerValue>(size);
118
+ for (let i = 0; i < size; i++) {
119
+ if (i in current) {
120
+ if (isIterable(current[i])) {
121
+ deferred[i] = current[i];
122
+ } else {
123
+ nodes[i] = await generateTreeAsync(ctx, current[i]);
124
+ }
125
+ }
126
+ }
127
+ for (let i = 0; i < size; i++) {
128
+ if (i in deferred) {
129
+ nodes[i] = await generateTreeAsync(ctx, deferred[i]);
130
+ }
131
+ }
132
+ return nodes;
133
+ }
134
+
135
+ async function generateArrayNode(
136
+ ctx: ParserContext,
137
+ current: AsyncServerValue[],
138
+ id: number,
139
+ ): Promise<SerovalArrayNode> {
140
+ return [SerovalNodeType.Array, await generateNodeList(ctx, current), id];
141
+ }
142
+
143
+ async function generateAggregateErrorNode(
144
+ ctx: ParserContext,
145
+ current: AggregateError,
146
+ id: number,
147
+ ): Promise<SerovalAggregateErrorNode> {
148
+ const options = getErrorOptions(current);
149
+ const optionsNode = options
150
+ ? await generateDictionary(ctx, options)
151
+ : undefined;
152
+ const errorsNode = await generateTreeAsync(ctx, current.errors as AsyncServerValue);
153
+ return [
154
+ SerovalNodeType.AggregateError,
155
+ [current.message, optionsNode, errorsNode],
156
+ id,
157
+ ];
158
+ }
159
+
160
+ async function generateErrorNode(
161
+ ctx: ParserContext,
162
+ current: Error,
163
+ id: number,
164
+ ): Promise<SerovalErrorNode> {
165
+ const options = getErrorOptions(current);
166
+ const optionsNode = options
167
+ ? await generateDictionary(ctx, options)
168
+ : undefined;
169
+ return [
170
+ SerovalNodeType.Error,
171
+ [getErrorConstructor(current), current.message, optionsNode],
172
+ id,
173
+ ];
174
+ }
175
+
176
+ async function generateIterableNode(
177
+ ctx: ParserContext,
178
+ current: Iterable<AsyncServerValue>,
179
+ id: number,
180
+ ): Promise<SerovalIterableNode> {
181
+ assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
182
+ const options = getIterableOptions(current);
183
+ return [SerovalNodeType.Iterable, [
184
+ // Parse options first before the items
185
+ options
186
+ ? await generateDictionary(ctx, options as Record<string, AsyncServerValue>)
187
+ : undefined,
188
+ await generateNodeList(ctx, Array.from(current)),
189
+ ], id];
190
+ }
191
+
192
+ async function generateTreeAsync(
193
+ ctx: ParserContext,
194
+ current: AsyncServerValue,
195
+ ): Promise<SerovalNode> {
196
+ if (isPrimitive(current)) {
197
+ return [SerovalNodeType.Primitive, serializePrimitive(current)];
198
+ }
199
+ // Non-primitive values needs a reference ID
200
+ // mostly because the values themselves are stateful
201
+ const id = generateRef(ctx, current);
202
+ if (Array.isArray(id)) {
203
+ return id;
204
+ }
205
+ const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
206
+ if (semiPrimitive) {
207
+ return semiPrimitive;
208
+ }
209
+ if (isPromise(current)) {
210
+ assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
211
+ return current.then(async (value) => [
212
+ SerovalNodeType.Promise,
213
+ await generateTreeAsync(ctx, value),
214
+ id,
215
+ ]);
216
+ }
217
+ if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
218
+ return generateSetNode(ctx, current, id);
219
+ }
220
+ if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
221
+ return generateMapNode(ctx, current, id);
222
+ }
223
+ if (Array.isArray(current)) {
224
+ return generateArrayNode(ctx, current, id);
225
+ }
226
+ if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
227
+ return generateAggregateErrorNode(ctx, current, id);
228
+ }
229
+ if (current instanceof Error) {
230
+ return generateErrorNode(ctx, current, id);
231
+ }
232
+ if (isIterable(current)) {
233
+ return generateIterableNode(ctx, current, id);
234
+ }
235
+ const empty = current.constructor == null;
236
+ if (current.constructor === Object || empty) {
237
+ return [
238
+ empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
239
+ await generateDictionary(ctx, current as Record<string, AsyncServerValue>),
240
+ id,
241
+ ];
242
+ }
243
+ throw new Error('Unsupported value');
244
+ }
245
+
246
+ export default async function parseAsync(
247
+ ctx: ParserContext,
248
+ current: AsyncServerValue,
249
+ ) {
250
+ const result = await generateTreeAsync(ctx, current);
251
+ return [result, createRef(ctx, current), result[0] === SerovalNodeType.Object] as const;
252
+ }