seroval 0.2.0 → 0.3.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/src/types.ts CHANGED
@@ -5,21 +5,58 @@ export type PrimitiveValue =
5
5
  | number
6
6
  | undefined
7
7
  | null
8
- | bigint
8
+ | bigint;
9
+
10
+ export type ErrorValue =
11
+ | Error
12
+ | AggregateError
13
+ | EvalError
14
+ | RangeError
15
+ | ReferenceError
16
+ | TypeError
17
+ | SyntaxError
18
+ | URIError;
19
+
20
+ export type TypedArrayValue =
21
+ | Int8Array
22
+ | Int16Array
23
+ | Int32Array
24
+ | Uint8Array
25
+ | Uint16Array
26
+ | Uint32Array
27
+ | Uint8ClampedArray
28
+ | Float32Array
29
+ | Float64Array
30
+ | BigInt64Array
31
+ | BigUint64Array;
32
+
33
+ export type SemiPrimitiveValue =
34
+ | RegExp
9
35
  | Date
10
- | RegExp;
36
+ | TypedArrayValue;
11
37
 
12
- export type ServerValue =
38
+ export type CommonServerValue =
13
39
  | PrimitiveValue
40
+ | SemiPrimitiveValue
41
+ | ErrorValue;
42
+
43
+ export type ServerValue =
44
+ | CommonServerValue
14
45
  | Array<ServerValue>
46
+ | readonly ServerValue[]
47
+ | Iterable<ServerValue>
15
48
  | { [key: string | number]: ServerValue }
49
+ | { readonly [key: string | number]: ServerValue }
16
50
  | Set<ServerValue>
17
51
  | Map<ServerValue, ServerValue>;
18
52
 
19
53
  export type AsyncServerValue =
20
- | PrimitiveValue
54
+ | CommonServerValue
21
55
  | Array<AsyncServerValue>
56
+ | readonly AsyncServerValue[]
57
+ | Iterable<AsyncServerValue>
22
58
  | { [key: string | number]: AsyncServerValue }
59
+ | { readonly [key: string | number]: AsyncServerValue }
23
60
  | Set<AsyncServerValue>
24
61
  | Map<AsyncServerValue, AsyncServerValue>
25
62
  | PromiseLike<AsyncServerValue>;
@@ -1,2 +0,0 @@
1
- export declare function forEach<T>(arr: T[], callback: (value: T, index: number) => (boolean | void)): void;
2
- export declare function join(arr: string[], joiner: string): string;
@@ -1,4 +0,0 @@
1
- import { SerializationContext } from './context';
2
- import { AsyncServerValue } from './types';
3
- export declare function lookupRefsAsync(ctx: SerializationContext, current: AsyncServerValue): Promise<void>;
4
- export declare function traverseAsync(ctx: SerializationContext, current: AsyncServerValue): Promise<string>;
@@ -1,33 +0,0 @@
1
- import { AsyncServerValue } from './types';
2
- export declare const EMPTY_SET = "new Set";
3
- export declare const EMPTY_MAP = "new Map";
4
- export declare const EMPTY_ARRAY = "[]";
5
- export type Assignment = [source: string, value: string];
6
- export type MapSet = [source: string, key: string, value: string];
7
- export type SetAdd = [source: string, value: string];
8
- export interface SerializationContext {
9
- stack: Set<unknown>;
10
- assignedRefs: Set<number>;
11
- vars: string[];
12
- assignments: Assignment[];
13
- mapSets: MapSet[];
14
- setAdds: SetAdd[];
15
- refCount: Map<unknown, number>;
16
- refs: Map<unknown, number>;
17
- }
18
- export declare function createSerializationContext(): SerializationContext;
19
- export declare function resolveAssignments(ctx: SerializationContext): string;
20
- export declare function resolveMapSets(ctx: SerializationContext): string;
21
- export declare function resolveSetAdds(ctx: SerializationContext): string;
22
- export declare function resolvePatches(ctx: SerializationContext): string;
23
- export declare function insertRef(ctx: SerializationContext, current: unknown): boolean;
24
- export declare function constructorCheck<T extends NonNullable<AsyncServerValue>>(value: NonNullable<AsyncServerValue>, constructor: unknown): value is T;
25
- export declare function createRef(ctx: SerializationContext, value: unknown): number;
26
- export declare function getRefParam(ctx: SerializationContext, index: number): string;
27
- export declare function assignRef(ctx: SerializationContext, index: number, value: string): string;
28
- export declare function createAssignment(ctx: SerializationContext, source: string, value: string): void;
29
- export declare function createSetAdd(ctx: SerializationContext, ref: number, value: string): void;
30
- export declare function createMapSet(ctx: SerializationContext, ref: number, key: string, value: string): void;
31
- export declare function createArrayAssign(ctx: SerializationContext, ref: number, index: number, value: string): void;
32
- export declare function createObjectIdentifierAssign(ctx: SerializationContext, ref: number, key: string, value: string): void;
33
- export declare function createObjectComputedAssign(ctx: SerializationContext, ref: number, key: string, value: string): void;
@@ -1,2 +0,0 @@
1
- import { PrimitiveValue } from './types';
2
- export default function isPrimitive(current: unknown): current is PrimitiveValue;
@@ -1 +0,0 @@
1
- export default function isPromise<T>(obj: unknown): obj is PromiseLike<T>;
@@ -1,4 +0,0 @@
1
- import { SerializationContext } from './context';
2
- import { ServerValue } from './types';
3
- export declare function lookupRefs(ctx: SerializationContext, current: ServerValue): void;
4
- export declare function traverseSync(ctx: SerializationContext, current: ServerValue): string;
package/src/array.ts DELETED
@@ -1,18 +0,0 @@
1
- export function forEach<T>(arr: T[], callback: (value: T, index: number) => (boolean | void)) {
2
- for (let i = 0, len = arr.length; i < len; i++) {
3
- if (callback(arr[i], i)) {
4
- break;
5
- }
6
- }
7
- }
8
-
9
- export function join(arr: string[], joiner: string): string {
10
- let result = '';
11
- forEach(arr, (value, index) => {
12
- result += value;
13
- if (index < arr.length - 1) {
14
- result += joiner;
15
- }
16
- });
17
- return result;
18
- }
package/src/async.ts DELETED
@@ -1,229 +0,0 @@
1
- /* eslint-disable no-await-in-loop */
2
- import { join } from './array';
3
- import {
4
- SerializationContext,
5
- insertRef,
6
- constructorCheck,
7
- getRefParam,
8
- createRef,
9
- assignRef,
10
- createSetAdd,
11
- EMPTY_SET,
12
- createMapSet,
13
- EMPTY_MAP,
14
- createArrayAssign,
15
- EMPTY_ARRAY,
16
- createObjectIdentifierAssign,
17
- createObjectComputedAssign,
18
- } from './context';
19
- import isPrimitive from './is-primitive';
20
- import isPromise from './is-promise';
21
- import quote from './quote';
22
- import serializePrimitive from './serialize-primitive';
23
- import { ServerValue, AsyncServerValue } from './types';
24
-
25
- export async function lookupRefsAsync(
26
- ctx: SerializationContext,
27
- current: AsyncServerValue,
28
- ) {
29
- if (isPrimitive(current)) {
30
- return;
31
- }
32
- if (isPromise(current)) {
33
- if (insertRef(ctx, current)) {
34
- await current.then((value) => lookupRefsAsync(ctx, value));
35
- }
36
- } else if (Array.isArray(current)) {
37
- if (insertRef(ctx, current)) {
38
- for (const value of current) {
39
- await lookupRefsAsync(ctx, value);
40
- }
41
- }
42
- } else if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
43
- if (insertRef(ctx, current)) {
44
- for (const item of current.keys()) {
45
- await lookupRefsAsync(ctx, item);
46
- }
47
- }
48
- } else if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) {
49
- if (insertRef(ctx, current)) {
50
- for (const [key, value] of current.entries()) {
51
- await lookupRefsAsync(ctx, key);
52
- await lookupRefsAsync(ctx, value);
53
- }
54
- }
55
- } else if (
56
- (current.constructor === Object || current.constructor == null)
57
- && insertRef(ctx, current)
58
- ) {
59
- for (const value of Object.values(current)) {
60
- await lookupRefsAsync(ctx, value);
61
- }
62
- }
63
- }
64
-
65
- export async function traverseAsync(
66
- ctx: SerializationContext,
67
- current: AsyncServerValue,
68
- ): Promise<string> {
69
- // Skip primitives
70
- if (isPrimitive(current)) {
71
- return serializePrimitive(current);
72
- }
73
- // Get reference of the current object
74
- let refResult = ctx.refs.get(current);
75
- if (refResult != null) {
76
- // Return the reference if already assigned a value
77
- if (ctx.assignedRefs.has(refResult)) {
78
- return getRefParam(ctx, refResult);
79
- }
80
- // Early sign (to prevent recursion)
81
- ctx.assignedRefs.add(refResult);
82
- }
83
- function getCurrentRef() {
84
- // This object received a ref, we must generate a setter ref
85
- if (refResult == null) {
86
- refResult = createRef(ctx, current);
87
- }
88
- return refResult;
89
- }
90
- function processRef(value: string) {
91
- if (refResult != null) {
92
- return assignRef(ctx, refResult, value);
93
- }
94
- return value;
95
- }
96
- if (isPromise(current)) {
97
- return current.then(async (value) => {
98
- ctx.stack.add(current);
99
- let result: string;
100
- if (ctx.stack.has(value)) {
101
- const refParam = getRefParam(ctx, createRef(ctx, value));
102
- result = `Promise.resolve().then(()=>${refParam})`;
103
- } else {
104
- result = `Promise.resolve(${await traverseAsync(ctx, value)})`;
105
- }
106
- ctx.stack.delete(current);
107
- return processRef(result);
108
- });
109
- }
110
- // Transform Set
111
- if (constructorCheck<Set<AsyncServerValue>>(current, Set)) {
112
- if (current.size) {
113
- const values: string[] = [];
114
-
115
- ctx.stack.add(current);
116
- for (const value of current.keys()) {
117
- if (ctx.stack.has(value)) {
118
- // Received a ref, this might be a recursive ref, defer an assignment
119
- createSetAdd(ctx, getCurrentRef(), getRefParam(ctx, createRef(ctx, value)));
120
- } else {
121
- values.push(await traverseAsync(ctx, value));
122
- }
123
- }
124
- ctx.stack.delete(current);
125
-
126
- return processRef(values.length ? `new Set([${join(values, ',')}])` : EMPTY_SET);
127
- }
128
- return EMPTY_SET;
129
- }
130
- if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) {
131
- if (current.size) {
132
- const values: string[] = [];
133
-
134
- ctx.stack.add(current);
135
- for (const [key, value] of current.entries()) {
136
- if (ctx.stack.has(key)) {
137
- const ref = getCurrentRef();
138
- const keyRef = getRefParam(ctx, createRef(ctx, key));
139
- if (ctx.stack.has(value)) {
140
- const valueRef = getRefParam(ctx, createRef(ctx, value));
141
- createMapSet(ctx, ref, keyRef, valueRef);
142
- } else {
143
- const valueResult = await traverseAsync(ctx, value);
144
- createMapSet(ctx, ref, keyRef, valueResult);
145
- }
146
- } else if (ctx.stack.has(value)) {
147
- const valueRef = getRefParam(ctx, createRef(ctx, value));
148
- const keyResult = await traverseAsync(ctx, key);
149
- createMapSet(ctx, getCurrentRef(), keyResult, valueRef);
150
- } else {
151
- const keyResult = await traverseAsync(ctx, key);
152
- const valueResult = await traverseAsync(ctx, value);
153
- values.push(`[${keyResult},${valueResult}]`);
154
- }
155
- }
156
- ctx.stack.delete(current);
157
- return processRef(values.length ? `new Map([${join(values, ',')}])` : EMPTY_MAP);
158
- }
159
- return EMPTY_MAP;
160
- }
161
- if (Array.isArray(current)) {
162
- if (current.length) {
163
- let values = '';
164
-
165
- ctx.stack.add(current);
166
- for (let i = 0, len = current.length; i < len; i += 1) {
167
- if (i in current) {
168
- const item = current[i];
169
- if (ctx.stack.has(item)) {
170
- const refParam = getRefParam(ctx, createRef(ctx, item));
171
- createArrayAssign(ctx, getCurrentRef(), i, refParam);
172
- values += ',';
173
- } else {
174
- values += await traverseAsync(ctx, item);
175
- if (i < current.length - 1) {
176
- values += ',';
177
- }
178
- }
179
- } else {
180
- values += ',';
181
- }
182
- }
183
- ctx.stack.delete(current);
184
- return processRef(`[${values}]`);
185
- }
186
- return EMPTY_ARRAY;
187
- }
188
- const empty = current.constructor == null;
189
- if (current.constructor === Object || empty) {
190
- const values: string[] = [];
191
-
192
- ctx.stack.add(current);
193
- for (const [key, value] of Object.entries(current)) {
194
- const check = Number(key);
195
- // Test if key is a valid number or JS identifier
196
- // so that we don't have to serialize the key and wrap with brackets
197
- if (
198
- check >= 0
199
- || (Number.isNaN(check) && /^([$A-Z_][0-9A-Z_$]*)$/i.test(key))
200
- ) {
201
- if (ctx.stack.has(value)) {
202
- const ref = getCurrentRef();
203
- const refParam = getRefParam(ctx, createRef(ctx, value));
204
- if (Number.isNaN(check)) {
205
- createObjectIdentifierAssign(ctx, ref, key, refParam);
206
- } else {
207
- createObjectComputedAssign(ctx, ref, key, refParam);
208
- }
209
- } else {
210
- const valueResult = await traverseAsync(ctx, value as AsyncServerValue);
211
- values.push(`${key}:${valueResult}`);
212
- }
213
- } else if (ctx.stack.has(value)) {
214
- const refParam = getRefParam(ctx, createRef(ctx, value));
215
- createObjectComputedAssign(ctx, getCurrentRef(), quote(key), refParam);
216
- } else {
217
- const valueResult = await traverseAsync(ctx, value as AsyncServerValue);
218
- values.push(`${quote(key)}:${valueResult}`);
219
- }
220
- }
221
- ctx.stack.delete(current);
222
- const value = `{${join(values, ',')}}`;
223
- if (empty) {
224
- return processRef(`Object.assign(Object.create(null),${value})`);
225
- }
226
- return processRef(value);
227
- }
228
- throw new Error('Unserializable value');
229
- }
package/src/context.ts DELETED
@@ -1,200 +0,0 @@
1
- import { forEach, join } from './array';
2
- import getIdentifier from './get-identifier';
3
- import { AsyncServerValue } from './types';
4
-
5
- export const EMPTY_SET = 'new Set';
6
- export const EMPTY_MAP = 'new Map';
7
- export const EMPTY_ARRAY = '[]';
8
-
9
- // Array of assignments to be done (used for recursion)
10
- export type Assignment = [source: string, value: string];
11
-
12
- // Array of Map.prototype.set calls
13
- export type MapSet = [source: string, key: string, value: string];
14
-
15
- // Array of Set.prototype.add calls
16
- export type SetAdd = [source: string, value: string];
17
-
18
- export interface SerializationContext {
19
- stack: Set<unknown>;
20
- // Set to check if ref id already has an assigned value
21
- assignedRefs: Set<number>;
22
- // Variables
23
- vars: string[];
24
- // Array of assignments to be done (used for recursion)
25
- assignments: Assignment[];
26
- // Array of Map.prototype.set calls
27
- mapSets: MapSet[];
28
- // Array of Set.prototype.add calls
29
- setAdds: SetAdd[];
30
- // Reference counter
31
- refCount: Map<unknown, number>;
32
- // Value-to-ref map
33
- refs: Map<unknown, number>;
34
- }
35
-
36
- export function createSerializationContext(): SerializationContext {
37
- return {
38
- refCount: new Map(),
39
- stack: new Set(),
40
- refs: new Map(),
41
- assignedRefs: new Set(),
42
- vars: [],
43
- assignments: [],
44
- mapSets: [],
45
- setAdds: [],
46
- };
47
- }
48
-
49
- export function resolveAssignments(ctx: SerializationContext) {
50
- if (ctx.assignments.length) {
51
- const result: Record<string, string> = {};
52
-
53
- // Merge all assignments with similar source
54
- forEach(ctx.assignments, ([source, value]) => {
55
- if (value in result) {
56
- result[value] = `${source}=${result[value]}`;
57
- } else {
58
- result[value] = `${source}=${value}`;
59
- }
60
- });
61
-
62
- return `${join(Object.values(result), ',')},`;
63
- }
64
- return '';
65
- }
66
-
67
- export function resolveMapSets(ctx: SerializationContext) {
68
- if (ctx.mapSets.length) {
69
- const result: Record<string, string> = {};
70
-
71
- // Merge all assignments with similar source
72
- forEach(ctx.mapSets, ([source, key, value]) => {
73
- if (source in result) {
74
- result[source] = `${result[source]}.set(${key},${value})`;
75
- } else {
76
- result[source] = `${source}.set(${key},${value})`;
77
- }
78
- });
79
-
80
- return `${join(Object.values(result), ',')},`;
81
- }
82
- return '';
83
- }
84
-
85
- export function resolveSetAdds(ctx: SerializationContext) {
86
- if (ctx.setAdds.length) {
87
- const result: Record<string, string> = {};
88
-
89
- // Merge all assignments with similar source
90
- forEach(ctx.setAdds, ([source, value]) => {
91
- if (source in result) {
92
- result[source] = `${result[source]}.add(${value})`;
93
- } else {
94
- result[source] = `${source}.add(${value})`;
95
- }
96
- });
97
-
98
- return `${join(Object.values(result), ',')},`;
99
- }
100
- return '';
101
- }
102
-
103
- export function resolvePatches(ctx: SerializationContext) {
104
- return `${resolveAssignments(ctx)}${resolveMapSets(ctx)}${resolveSetAdds(ctx)}`;
105
- }
106
-
107
- export function insertRef(ctx: SerializationContext, current: unknown) {
108
- const count = ctx.refCount.get(current) || 0;
109
- ctx.refCount.set(current, count + 1);
110
- return count === 0;
111
- }
112
-
113
- export function constructorCheck<T extends NonNullable<AsyncServerValue>>(
114
- value: NonNullable<AsyncServerValue>,
115
- constructor: unknown,
116
- ): value is T {
117
- return value.constructor === constructor;
118
- }
119
-
120
- export function createRef(
121
- ctx: SerializationContext,
122
- value: unknown,
123
- ) {
124
- const current = ctx.refs.get(value);
125
- if (current != null) {
126
- return current;
127
- }
128
- const id = ctx.refs.size;
129
- ctx.refs.set(value, id);
130
- return id;
131
- }
132
-
133
- export function getRefParam(ctx: SerializationContext, index: number) {
134
- if (ctx.vars[index]) {
135
- return ctx.vars[index];
136
- }
137
- const result = getIdentifier(index);
138
- ctx.vars[index] = result;
139
- return result;
140
- }
141
-
142
- export function assignRef(
143
- ctx: SerializationContext,
144
- index: number,
145
- value: string,
146
- ) {
147
- return `${getRefParam(ctx, index)}=${value}`;
148
- }
149
-
150
- export function createAssignment(
151
- ctx: SerializationContext,
152
- source: string,
153
- value: string,
154
- ) {
155
- ctx.assignments.push([source, value]);
156
- }
157
-
158
- export function createSetAdd(
159
- ctx: SerializationContext,
160
- ref: number,
161
- value: string,
162
- ) {
163
- ctx.setAdds.push([getRefParam(ctx, ref), value]);
164
- }
165
-
166
- export function createMapSet(
167
- ctx: SerializationContext,
168
- ref: number,
169
- key: string,
170
- value: string,
171
- ) {
172
- ctx.mapSets.push([getRefParam(ctx, ref), key, value]);
173
- }
174
-
175
- export function createArrayAssign(
176
- ctx: SerializationContext,
177
- ref: number,
178
- index: number,
179
- value: string,
180
- ) {
181
- createAssignment(ctx, `${getRefParam(ctx, ref)}[${index}]`, value);
182
- }
183
-
184
- export function createObjectIdentifierAssign(
185
- ctx: SerializationContext,
186
- ref: number,
187
- key: string,
188
- value: string,
189
- ) {
190
- createAssignment(ctx, `${getRefParam(ctx, ref)}.${key}`, value);
191
- }
192
-
193
- export function createObjectComputedAssign(
194
- ctx: SerializationContext,
195
- ref: number,
196
- key: string,
197
- value: string,
198
- ) {
199
- createAssignment(ctx, `${getRefParam(ctx, ref)}[${key}]`, value);
200
- }
@@ -1,14 +0,0 @@
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
- }
package/src/is-promise.ts DELETED
@@ -1,6 +0,0 @@
1
- export default function isPromise<T>(obj: unknown): obj is PromiseLike<T> {
2
- return !!obj
3
- && (typeof obj === 'object' || typeof obj === 'function')
4
- && 'then' in obj
5
- && typeof obj.then === 'function';
6
- }