seroval 0.4.0-alpha.4 → 0.4.0-alpha.5

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 = [key: string[], value: SerovalNode[], size: number];
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.5",
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": "b6e4a61b67d1ba6e6e209dd5ba81cd94fb33d021"
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,260 @@
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 serializePropertiesAsync(
34
+ ctx: ParserContext,
35
+ properties: Record<string, unknown>,
36
+ ): Promise<SerovalDictionaryNode> {
37
+ const keys = Object.keys(properties);
38
+ const size = keys.length;
39
+ const keyNodes = new Array<string>(size);
40
+ const valueNodes = new Array<SerovalNode>(size);
41
+ const deferredKeys = new Array<string>(size);
42
+ const deferredValues = new Array<AsyncServerValue>(size);
43
+ let deferredSize = 0;
44
+ let nodesSize = 0;
45
+ for (const key of keys) {
46
+ if (isIterable(properties[key])) {
47
+ deferredKeys[deferredSize] = key;
48
+ deferredValues[deferredSize] = properties[key] as AsyncServerValue;
49
+ deferredSize++;
50
+ } else {
51
+ keyNodes[nodesSize] = key;
52
+ valueNodes[nodesSize] = await generateTreeAsync(ctx, properties[key] as AsyncServerValue);
53
+ nodesSize++;
54
+ }
55
+ }
56
+ for (let i = 0; i < deferredSize; i++) {
57
+ keyNodes[nodesSize + i] = deferredKeys[i];
58
+ valueNodes[nodesSize + i] = await generateTreeAsync(ctx, deferredValues[i]);
59
+ }
60
+ return [keyNodes, valueNodes, size];
61
+ }
62
+
63
+ async function generateSetNode(
64
+ ctx: ParserContext,
65
+ current: Set<AsyncServerValue>,
66
+ id: number,
67
+ ): Promise<SerovalSetNode> {
68
+ assert(ctx.features & Feature.Set, 'Unsupported type "Set"');
69
+ const len = current.size;
70
+ const nodes = new Array<SerovalNode>(len);
71
+ const deferred = new Array<AsyncServerValue>(len);
72
+ let deferredSize = 0;
73
+ let nodeSize = 0;
74
+ for (const item of current.keys()) {
75
+ // Iterables are lazy, so the evaluation must be deferred
76
+ if (isIterable(item)) {
77
+ deferred[deferredSize++] = item;
78
+ } else {
79
+ nodes[nodeSize++] = await generateTreeAsync(ctx, item);
80
+ }
81
+ }
82
+ // Parse deferred items
83
+ for (let i = 0; i < deferredSize; i++) {
84
+ nodes[nodeSize + i] = await generateTreeAsync(ctx, deferred[i]);
85
+ }
86
+ return [SerovalNodeType.Set, nodes, id];
87
+ }
88
+
89
+ async function generateMapNode(
90
+ ctx: ParserContext,
91
+ current: Map<AsyncServerValue, AsyncServerValue>,
92
+ id: number,
93
+ ): Promise<SerovalMapNode> {
94
+ assert(ctx.features & Feature.Map, 'Unsupported type "Map"');
95
+ const len = current.size;
96
+ const keyNodes = new Array<SerovalNode>(len);
97
+ const valueNodes = new Array<SerovalNode>(len);
98
+ const deferredKey = new Array<AsyncServerValue>(len);
99
+ const deferredValue = new Array<AsyncServerValue>(len);
100
+ let deferredSize = 0;
101
+ let nodeSize = 0;
102
+ for (const [key, value] of current.entries()) {
103
+ // Either key or value might be an iterable
104
+ if (isIterable(key) || isIterable(value)) {
105
+ deferredKey[deferredSize] = key;
106
+ deferredValue[deferredSize] = value;
107
+ deferredSize++;
108
+ } else {
109
+ keyNodes[nodeSize] = await generateTreeAsync(ctx, key);
110
+ valueNodes[nodeSize] = await generateTreeAsync(ctx, value);
111
+ nodeSize++;
112
+ }
113
+ }
114
+ for (let i = 0; i < deferredSize; i++) {
115
+ keyNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredKey[i]);
116
+ valueNodes[nodeSize + i] = await generateTreeAsync(ctx, deferredValue[i]);
117
+ }
118
+ return [SerovalNodeType.Map, [keyNodes, valueNodes, len], id];
119
+ }
120
+
121
+ async function generateNodeList(
122
+ ctx: ParserContext,
123
+ current: AsyncServerValue[],
124
+ ) {
125
+ const size = current.length;
126
+ const nodes = new Array<SerovalNode>(size);
127
+ const deferred = new Array<AsyncServerValue>(size);
128
+ for (let i = 0; i < size; i++) {
129
+ if (i in current) {
130
+ if (isIterable(current[i])) {
131
+ deferred[i] = current[i];
132
+ } else {
133
+ nodes[i] = await generateTreeAsync(ctx, current[i]);
134
+ }
135
+ }
136
+ }
137
+ for (let i = 0; i < size; i++) {
138
+ if (i in deferred) {
139
+ nodes[i] = await generateTreeAsync(ctx, deferred[i]);
140
+ }
141
+ }
142
+ return nodes;
143
+ }
144
+
145
+ async function generateArrayNode(
146
+ ctx: ParserContext,
147
+ current: AsyncServerValue[],
148
+ id: number,
149
+ ): Promise<SerovalArrayNode> {
150
+ return [SerovalNodeType.Array, await generateNodeList(ctx, current), id];
151
+ }
152
+
153
+ async function generateAggregateErrorNode(
154
+ ctx: ParserContext,
155
+ current: AggregateError,
156
+ id: number,
157
+ ): Promise<SerovalAggregateErrorNode> {
158
+ const options = getErrorOptions(current);
159
+ const optionsNode = options
160
+ ? await serializePropertiesAsync(ctx, options)
161
+ : undefined;
162
+ const errorsNode = await generateTreeAsync(ctx, current.errors as AsyncServerValue);
163
+ return [
164
+ SerovalNodeType.AggregateError,
165
+ [current.message, optionsNode, errorsNode],
166
+ id,
167
+ ];
168
+ }
169
+
170
+ async function generateErrorNode(
171
+ ctx: ParserContext,
172
+ current: Error,
173
+ id: number,
174
+ ): Promise<SerovalErrorNode> {
175
+ const options = getErrorOptions(current);
176
+ const optionsNode = options
177
+ ? await serializePropertiesAsync(ctx, options)
178
+ : undefined;
179
+ return [
180
+ SerovalNodeType.Error,
181
+ [getErrorConstructor(current), current.message, optionsNode],
182
+ id,
183
+ ];
184
+ }
185
+
186
+ async function generateIterableNode(
187
+ ctx: ParserContext,
188
+ current: Iterable<AsyncServerValue>,
189
+ id: number,
190
+ ): Promise<SerovalIterableNode> {
191
+ assert(ctx.features & Feature.SymbolIterator, 'Unsupported type "Iterable"');
192
+ const options = getIterableOptions(current);
193
+ return [SerovalNodeType.Iterable, [
194
+ // Parse options first before the items
195
+ options ? await serializePropertiesAsync(ctx, options) : undefined,
196
+ await generateNodeList(ctx, Array.from(current)),
197
+ ], id];
198
+ }
199
+
200
+ async function generateTreeAsync(
201
+ ctx: ParserContext,
202
+ current: AsyncServerValue,
203
+ ): Promise<SerovalNode> {
204
+ if (isPrimitive(current)) {
205
+ return [SerovalNodeType.Primitive, serializePrimitive(current)];
206
+ }
207
+ // Non-primitive values needs a reference ID
208
+ // mostly because the values themselves are stateful
209
+ const id = generateRef(ctx, current);
210
+ if (Array.isArray(id)) {
211
+ return id;
212
+ }
213
+ const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id);
214
+ if (semiPrimitive) {
215
+ return semiPrimitive;
216
+ }
217
+ if (isPromise(current)) {
218
+ assert(ctx.features & Feature.Promise, 'Unsupported type "Promise"');
219
+ return current.then(async (value) => [
220
+ SerovalNodeType.Promise,
221
+ await generateTreeAsync(ctx, value),
222
+ id,
223
+ ]);
224
+ }
225
+ if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
226
+ return generateSetNode(ctx, current, id);
227
+ }
228
+ if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
229
+ return generateMapNode(ctx, current, id);
230
+ }
231
+ if (Array.isArray(current)) {
232
+ return generateArrayNode(ctx, current, id);
233
+ }
234
+ if (current instanceof AggregateError && ctx.features & Feature.AggregateError) {
235
+ return generateAggregateErrorNode(ctx, current, id);
236
+ }
237
+ if (current instanceof Error) {
238
+ return generateErrorNode(ctx, current, id);
239
+ }
240
+ if (isIterable(current)) {
241
+ return generateIterableNode(ctx, current, id);
242
+ }
243
+ const empty = current.constructor == null;
244
+ if (current.constructor === Object || empty) {
245
+ return [
246
+ empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
247
+ await serializePropertiesAsync(ctx, current as Record<string, unknown>),
248
+ id,
249
+ ];
250
+ }
251
+ throw new Error('Unsupported value');
252
+ }
253
+
254
+ export default async function parseAsync(
255
+ ctx: ParserContext,
256
+ current: AsyncServerValue,
257
+ ) {
258
+ const result = await generateTreeAsync(ctx, current);
259
+ return [result, createRef(ctx, current), result[0] === SerovalNodeType.Object] as const;
260
+ }