seroval 0.4.0-alpha.3 → 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,10 @@
1
+ import { SerializationContext, ParserContext } from '../context';
2
+ import { ErrorValue, PrimitiveValue } from '../types';
3
+ import { SerovalNode, SerovalReferenceNode } from './types';
4
+ export declare function getErrorConstructor(error: ErrorValue): "EvalError" | "RangeError" | "ReferenceError" | "SyntaxError" | "TypeError" | "URIError" | "Error";
5
+ export declare function getErrorOptions(error: Error): Record<string, any> | undefined;
6
+ export declare function getIterableOptions(obj: Iterable<any>): Record<string, unknown> | undefined;
7
+ export declare function isReferenceInStack(ctx: SerializationContext, node: SerovalNode): node is SerovalReferenceNode;
8
+ export declare function generateRef(ctx: ParserContext, current: unknown): number | SerovalReferenceNode;
9
+ export declare function generateSemiPrimitiveValue(ctx: ParserContext, current: unknown, id: number): SerovalNode | undefined;
10
+ export declare function serializePrimitive(value: PrimitiveValue): string | number | null;
@@ -0,0 +1,4 @@
1
+ import { ParserContext } from '../context';
2
+ import { ServerValue } from '../types';
3
+ import { SerovalNode } from './types';
4
+ export default function parseSync(ctx: ParserContext, current: ServerValue): readonly [SerovalNode, number, boolean];
@@ -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;
@@ -1,7 +1,7 @@
1
- export type PrimitiveValue = boolean | string | number | undefined | null | bigint;
1
+ export type PrimitiveValue = boolean | string | number | undefined | null;
2
2
  export type ErrorValue = Error | AggregateError | EvalError | RangeError | ReferenceError | TypeError | SyntaxError | URIError;
3
3
  export type TypedArrayValue = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
4
- export type SemiPrimitiveValue = RegExp | Date | TypedArrayValue;
4
+ export type SemiPrimitiveValue = RegExp | Date | TypedArrayValue | bigint;
5
5
  export type CommonServerValue = PrimitiveValue | SemiPrimitiveValue | ErrorValue;
6
6
  export type ServerValue = CommonServerValue | Array<ServerValue> | readonly ServerValue[] | Iterable<ServerValue> | {
7
7
  [key: string | number]: ServerValue;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "seroval",
3
3
  "type": "module",
4
- "version": "0.4.0-alpha.3",
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": "22da3fb0f57fdb4a935cac70d1b9a7e5fdc02816"
67
+ "gitHead": "b6e4a61b67d1ba6e6e209dd5ba81cd94fb33d021"
68
68
  }
package/src/checks.ts CHANGED
@@ -6,8 +6,7 @@ export function isPrimitive(current: unknown): current is PrimitiveValue {
6
6
  }
7
7
  const type = typeof current;
8
8
  return type === 'number'
9
- || type === 'string'
10
- || type === 'bigint';
9
+ || type === 'string';
11
10
  }
12
11
 
13
12
  export function isPromise<T>(obj: unknown): obj is PromiseLike<T> {
package/src/compat.ts CHANGED
@@ -40,17 +40,18 @@ export interface PlatformVersion {
40
40
  export type Platform = keyof PlatformVersion;
41
41
 
42
42
  export const enum Feature {
43
- AggregateError,
44
- ArrayPrototypeValues,
45
- ArrowFunction,
46
- BigInt,
47
- Map,
48
- MethodShorthand,
49
- ObjectAssign,
50
- Promise,
51
- Set,
52
- SymbolIterator,
53
- TypedArray,
43
+ AggregateError = 0x01,
44
+ ArrayPrototypeValues = 0x02,
45
+ ArrowFunction = 0x04,
46
+ BigInt = 0x08,
47
+ Map = 0x10,
48
+ MethodShorthand = 0x20,
49
+ ObjectAssign = 0x40,
50
+ Promise = 0x80,
51
+ Set = 0x100,
52
+ SymbolIterator = 0x200,
53
+ TypedArray = 0x400,
54
+ BigIntTypedArray = 0x800,
54
55
  }
55
56
 
56
57
  type VersionTable = {
@@ -201,6 +202,19 @@ const VERSION_TABLE: VersionTable = {
201
202
  deno: [1, 0, 0],
202
203
  node: [0, 10, 0],
203
204
  },
205
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array#browser_compatibility
206
+ [Feature.BigIntTypedArray]: {
207
+ es: [2020, 0, 0],
208
+ chrome: [67, 0, 0],
209
+ edge: [79, 0, 0],
210
+ firefox: [68, 0, 0],
211
+ opera: [54, 6, 0],
212
+ safari: [15, 1, 0],
213
+ ios: [15, 2, 0],
214
+ samsung: [9, 0, 0],
215
+ deno: [1, 0, 0],
216
+ node: [10, 4, 0],
217
+ },
204
218
  };
205
219
 
206
220
  const TARGET_REGEX = /^(es|chrome|edge|safari|firefox|opera|ios|samsung|deno|node)([0-9]+(\.[0-9]+(\.[0-9]+)?)?)$/i;
@@ -226,9 +240,9 @@ function getTargetVersions(targets: string | string[]): Target[] {
226
240
  return [parseTarget(targets)];
227
241
  }
228
242
 
229
- export function parseTargets(targets: string | string[]): Set<Feature> {
243
+ export function parseTargets(targets: string | string[]): number {
230
244
  const parsed = getTargetVersions(targets);
231
- const flags = new Set<Feature>();
245
+ let flags = 0;
232
246
 
233
247
  for (const key in VERSION_TABLE) {
234
248
  const base = VERSION_TABLE[key as unknown as Feature];
@@ -238,7 +252,7 @@ export function parseTargets(targets: string | string[]): Set<Feature> {
238
252
  flag = flag && !!baseVersion && (compareVersion(baseVersion, version) <= 0);
239
253
  }
240
254
  if (flag) {
241
- flags.add(Number(key) as unknown as Feature);
255
+ flags |= Number(key);
242
256
  }
243
257
  }
244
258
 
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,20 +2,19 @@
2
2
  import { isPrimitive } from './checks';
3
3
  import { Feature } from './compat';
4
4
  import {
5
- createRef,
6
- createSerializationContext,
7
- generateTreeAsync,
8
- generateTreeSync,
5
+ SerializationContext,
9
6
  getRefParam,
10
7
  Options,
11
- resolvePatches,
12
- SerializationContext,
13
- serializePrimitive,
14
- serializeTree,
15
- } 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';
16
16
  import {
17
17
  AsyncServerValue,
18
- NonPrimitiveServerValue,
19
18
  PrimitiveValue,
20
19
  ServerValue,
21
20
  CommonServerValue,
@@ -32,9 +31,10 @@ export {
32
31
  ErrorValue,
33
32
  };
34
33
 
35
- function finalize<T extends NonPrimitiveServerValue<ServerValue | AsyncServerValue>>(
34
+ function finalize(
36
35
  ctx: SerializationContext,
37
- source: T,
36
+ rootID: number,
37
+ isObject: boolean,
38
38
  result: string,
39
39
  ) {
40
40
  // Shared references detected
@@ -43,7 +43,7 @@ function finalize<T extends NonPrimitiveServerValue<ServerValue | AsyncServerVal
43
43
  let body = result;
44
44
  if (patches) {
45
45
  // Get (or create) a ref from the source
46
- const index = getRefParam(ctx, createRef(ctx, source));
46
+ const index = getRefParam(ctx, rootID);
47
47
  if (result.startsWith(`${index}=`)) {
48
48
  body = `${result},${patches}${index}`;
49
49
  } else {
@@ -54,7 +54,7 @@ function finalize<T extends NonPrimitiveServerValue<ServerValue | AsyncServerVal
54
54
  ? ctx.vars.join(',')
55
55
  : ctx.vars[0];
56
56
  // Source is probably already assigned
57
- if (ctx.features.has(Feature.ArrowFunction)) {
57
+ if (ctx.features & Feature.ArrowFunction) {
58
58
  params = ctx.vars.length > 1 || ctx.vars.length === 0
59
59
  ? `(${params})`
60
60
  : params;
@@ -62,7 +62,7 @@ function finalize<T extends NonPrimitiveServerValue<ServerValue | AsyncServerVal
62
62
  }
63
63
  return `(function(${params}){return ${body}})()`;
64
64
  }
65
- if (source.constructor === Object) {
65
+ if (isObject) {
66
66
  return `(${result})`;
67
67
  }
68
68
  return result;
@@ -72,26 +72,28 @@ export function serialize<T extends ServerValue>(
72
72
  source: T,
73
73
  options?: Partial<Options>,
74
74
  ) {
75
- const ctx = createSerializationContext(options);
75
+ const ctx = createParserContext(options);
76
76
  if (isPrimitive(source)) {
77
- return serializePrimitive(ctx, source);
77
+ return String(serializePrimitive(source));
78
78
  }
79
- const tree = generateTreeSync(ctx, source);
80
- const result = serializeTree(ctx, tree);
81
- return finalize(ctx, source, result);
79
+ const [tree, rootID, isObject] = parseSync(ctx, source);
80
+ const serial = createSerializationContext(ctx);
81
+ const result = serializeTree(serial, tree);
82
+ return finalize(serial, rootID, isObject, result);
82
83
  }
83
84
 
84
85
  export async function serializeAsync<T extends AsyncServerValue>(
85
86
  source: T,
86
87
  options?: Partial<Options>,
87
88
  ) {
88
- const ctx = createSerializationContext(options);
89
+ const ctx = createParserContext(options);
89
90
  if (isPrimitive(source)) {
90
- return serializePrimitive(ctx, source);
91
+ return String(serializePrimitive(source));
91
92
  }
92
- const tree = await generateTreeAsync(ctx, source);
93
- const result = serializeTree(ctx, tree);
94
- return finalize(ctx, source, result);
93
+ const [tree, rootID, isObject] = await parseAsync(ctx, source);
94
+ const serial = createSerializationContext(ctx);
95
+ const result = serializeTree(serial, tree);
96
+ return finalize(serial, rootID, isObject, result);
95
97
  }
96
98
 
97
99
  export function deserialize<T extends AsyncServerValue>(source: string): T {
@@ -99,4 +101,56 @@ export function deserialize<T extends AsyncServerValue>(source: string): T {
99
101
  return (0, eval)(source) as T;
100
102
  }
101
103
 
104
+ type SerovalJSON = [
105
+ tree: SerovalNode,
106
+ root: number,
107
+ isObject: boolean,
108
+ feature: number,
109
+ markedRefs: number[],
110
+ ];
111
+
112
+ export function toJSON<T extends ServerValue>(
113
+ source: T,
114
+ options?: Partial<Options>,
115
+ ) {
116
+ const ctx = createParserContext(options);
117
+ const [tree, root, isObject] = parseSync(ctx, source);
118
+ return JSON.stringify([
119
+ tree,
120
+ root,
121
+ isObject,
122
+ ctx.features,
123
+ [...ctx.markedRefs],
124
+ ]);
125
+ }
126
+
127
+ export async function toJSONAsync<T extends AsyncServerValue>(
128
+ source: T,
129
+ options?: Partial<Options>,
130
+ ) {
131
+ const ctx = createParserContext(options);
132
+ const [tree, root, isObject] = await parseAsync(ctx, source);
133
+ return JSON.stringify([
134
+ tree,
135
+ root,
136
+ isObject,
137
+ ctx.features,
138
+ [...ctx.markedRefs],
139
+ ]);
140
+ }
141
+
142
+ export function compileJSON(source: string): string {
143
+ const parsed = JSON.parse(source) as SerovalJSON;
144
+ const serial = createSerializationContext({
145
+ features: parsed[3],
146
+ markedRefs: parsed[4],
147
+ });
148
+ const result = serializeTree(serial, parsed[0]);
149
+ return finalize(serial, parsed[1], parsed[2], result);
150
+ }
151
+
152
+ export function fromJSON<T extends AsyncServerValue>(source: string): T {
153
+ return deserialize<T>(compileJSON(source));
154
+ }
155
+
102
156
  export default serialize;