Comparing version 0.3.0 to 0.4.0-alpha.0
@@ -0,6 +1,7 @@ | ||
import { Options } from './tree'; | ||
import { AsyncServerValue, PrimitiveValue, ServerValue, CommonServerValue, SemiPrimitiveValue, ErrorValue } from './types'; | ||
export { AsyncServerValue, ServerValue, PrimitiveValue, CommonServerValue, SemiPrimitiveValue, ErrorValue, }; | ||
export declare function serialize(source: ServerValue): string; | ||
export declare function serializeAsync(source: AsyncServerValue): Promise<string>; | ||
export declare function serialize<T extends ServerValue>(source: T, options?: Partial<Options>): string; | ||
export declare function serializeAsync<T extends AsyncServerValue>(source: T, options?: Partial<Options>): Promise<string>; | ||
export declare function deserialize<T extends AsyncServerValue>(source: string): T; | ||
export default serialize; |
import { AsyncServerValue, PrimitiveValue, ServerValue, TypedArrayValue } from './types'; | ||
import { Feature } from './compat'; | ||
interface IndexAssignment { | ||
@@ -26,5 +27,9 @@ type: 'index'; | ||
assignments: Assignment[]; | ||
features: Set<Feature>; | ||
} | ||
export declare function createSerializationContext(): SerializationContext; | ||
export declare function resolvePatches(ctx: SerializationContext): string; | ||
export interface Options { | ||
target: string | string[]; | ||
} | ||
export declare function createSerializationContext(options?: Partial<Options>): SerializationContext; | ||
export declare function resolvePatches(ctx: SerializationContext): string | undefined; | ||
/** | ||
@@ -59,37 +64,51 @@ * Creates a reference ID from the given values | ||
type: SerovalNodeType.TypedArray, | ||
value: { | ||
constructor: string; | ||
array: TypedArrayValue; | ||
}, | ||
value: [constructor: string, array: TypedArrayValue], | ||
id: number | ||
]; | ||
type SerovalNode = SerovalPrimitiveNode | SerovalReferenceNode | SerovalSemiPrimitiveNode | [type: SerovalNodeType.Set, value: SerovalNode[], id: number] | [type: SerovalNodeType.Map, value: [key: SerovalNode, value: SerovalNode][], id: number] | [type: SerovalNodeType.Array, value: SerovalNode[], id: number] | [type: SerovalNodeType.Object, value: Record<string, SerovalNode>, id: number] | [type: SerovalNodeType.NullConstructor, value: Record<string, SerovalNode>, id: number] | [type: SerovalNodeType.Promise, value: SerovalNode, id: number] | [ | ||
type SerovalDictionaryNode = [key: string[], value: SerovalNode[], size: number]; | ||
type SerovalSetNode = [type: SerovalNodeType.Set, value: SerovalNode[], id: number]; | ||
type SerovalMapNode = [ | ||
type: SerovalNodeType.Map, | ||
value: [key: SerovalNode[], value: SerovalNode[], size: number], | ||
id: number | ||
]; | ||
type SerovalArrayNode = [type: SerovalNodeType.Array, value: SerovalNode[], id: number]; | ||
type SerovalObjectNode = [type: SerovalNodeType.Object, value: SerovalDictionaryNode, id: number]; | ||
type SerovalNullConstructorNode = [ | ||
type: SerovalNodeType.NullConstructor, | ||
value: SerovalDictionaryNode, | ||
id: number | ||
]; | ||
type SerovalPromiseNode = [type: SerovalNodeType.Promise, value: SerovalNode, id: number]; | ||
type SerovalErrorNode = [ | ||
type: SerovalNodeType.Error, | ||
value: { | ||
constructor: string; | ||
message: string; | ||
options?: SerovalNode; | ||
cause?: SerovalNode; | ||
}, | ||
value: [ | ||
constructor: string, | ||
message: string, | ||
options?: SerovalDictionaryNode | ||
], | ||
id: number | ||
] | [ | ||
]; | ||
type SerovalAggregateErrorNode = [ | ||
type: SerovalNodeType.AggregateError, | ||
value: { | ||
message: string; | ||
options?: SerovalNode; | ||
cause?: SerovalNode; | ||
errors: SerovalNode; | ||
}, | ||
value: [ | ||
message: string, | ||
options: SerovalDictionaryNode | undefined, | ||
errors: SerovalNode | ||
], | ||
id: number | ||
] | [ | ||
]; | ||
type SerovalIterableNode = [ | ||
type: SerovalNodeType.Iterable, | ||
value: { | ||
items: SerovalNode; | ||
options?: SerovalNode; | ||
}, | ||
value: [ | ||
options: SerovalDictionaryNode | undefined, | ||
items: SerovalNode | ||
], | ||
id: number | ||
]; | ||
type SerovalNode = SerovalPrimitiveNode | SerovalReferenceNode | SerovalSemiPrimitiveNode | SerovalSetNode | SerovalMapNode | SerovalArrayNode | SerovalObjectNode | SerovalNullConstructorNode | SerovalPromiseNode | SerovalErrorNode | SerovalAggregateErrorNode | SerovalIterableNode; | ||
export declare function generateTreeSync(ctx: SerializationContext, current: ServerValue): SerovalNode; | ||
export declare function generateTreeAsync(ctx: SerializationContext, current: AsyncServerValue): Promise<SerovalNode>; | ||
export declare function serializePrimitive(ctx: SerializationContext, value: PrimitiveValue): string; | ||
export declare function serializeTree(ctx: SerializationContext, [type, value, id]: SerovalNode): string; | ||
export {}; |
{ | ||
"name": "seroval", | ||
"type": "module", | ||
"version": "0.3.0", | ||
"version": "0.4.0-alpha.0", | ||
"files": [ | ||
@@ -67,3 +67,3 @@ "dist", | ||
}, | ||
"gitHead": "3be59fefb6ffb73b1bad693d956d1fd9d8d6d720" | ||
"gitHead": "4b01f180e0a2548360ec618fe8b6cd4d36fccaff" | ||
} |
@@ -61,7 +61,7 @@ # seroval | ||
```js | ||
((h,j,k,m)=>(m={number:[0.4178420745429774,-0,NaN,1/0,-1/0],string:["hello world","\x3Cscript>Hello World\x3C/script>"],boolean:[!0,!1],null:null,undefined:void 0,bigint:9007199254740991n,array:h=[,,,,j=new Map([["hello","world"],["mutual",k=new Set(["hello","world"])]])],regexp:/[a-z0-9]+/i,date:new Date("2023-03-13T00:52:11.335Z"),map:j,set:k},h[3]=h,j.set("self",j),k.add(k).add(h),m.self=m,m))() | ||
((h,j,k,m)=>(m={number:[0.5623457676854244,-0,NaN,1/0,-1/0],string:["hello world","\x3Cscript>Hello World\x3C/script>"],boolean:[!0,!1],null:null,undefined:void 0,bigint:9007199254740991n,array:h=[,,,,j=new Map([["hello","world"],["mutual",k=new Set(["hello","world"])]])],regexp:/[a-z0-9]+/i,date:new Date("2023-03-14T11:16:24.879Z"),map:j,set:k},h[3]=h,j.set("self",j),k.add(k).add(h),m.self=m,m))() | ||
// Formatted for readability | ||
((h, j, k, m) => (m = { | ||
number: [0.4178420745429774, -0, NaN, 1/0, -1/0], | ||
number: [0.5623457676854244, -0, NaN, 1 / 0, -1 / 0], | ||
string: ["hello world", "\x3Cscript>Hello World\x3C/script>"], | ||
@@ -77,3 +77,3 @@ boolean: [!0, !1], | ||
regexp: /[a-z0-9]+/i, | ||
date: new Date("2023-03-13T00:52:11.335Z"), | ||
date: new Date("2023-03-14T11:16:24.879Z"), | ||
map: j, | ||
@@ -201,2 +201,87 @@ set: k | ||
## Compat | ||
`serialize` and `serializeAsync` can accept a `{ target: string | string[] }` options. The `target` property decides what the serialization output would look like. The default target is `es2023`. | ||
```js | ||
import { serialize } from 'seroval'; | ||
const y = Object.create(null); | ||
y.self = y; | ||
y.example = 'Hello World'; | ||
function serializeWithTarget(value, target) { | ||
console.log('Target is', target) | ||
const result = serialize(value, { | ||
target, | ||
}); | ||
console.log(result); | ||
} | ||
serializeWithTarget(y, 'es5'); | ||
serializeWithTarget(y, 'es2023'); | ||
``` | ||
Output | ||
``` | ||
Target is es5 | ||
(function(h){return (h=Object.create(null),h.self=h,h.example="Hello World",h)})() | ||
Target is es2023 | ||
(h=>(h=Object.assign(Object.create(null),{example:"Hello World"}),h.self=h,h))() | ||
``` | ||
You can also combine targets: | ||
```js | ||
serialize(value, { | ||
targets: ['chrome85', 'edge85'], | ||
}); | ||
``` | ||
Supported runtimes: | ||
- `es` | ||
- Valid values are `es5`, `es6` and above (e.g. `es2020`). | ||
- Desktop | ||
- `chrome` | ||
- `edge` | ||
- `safari` | ||
- `firefox` | ||
- `opera` | ||
- Mobile | ||
- `ios` | ||
- `samsung` | ||
- Runtimes | ||
- `deno` | ||
- `node` | ||
> **Note** | ||
> Version for runtimes excluding `es` can use semver format (`major.minor.patch`) e.g. `chrome110`, `node0.12` | ||
Feature flags and compat attempt: | ||
- `AggregateError` | ||
- Compiles down to `Error` instead. | ||
- `Array.prototype.values` | ||
- Used for iterables, uses `Symbol.iterator` instead. | ||
- Arrow functions | ||
- Uses function expressions for top-level, and either method shorthands or function expressions for iterables. | ||
- `BigInt` | ||
- Throws when attempted to use, includes `BigInt64Array` and `BigUint64Array` | ||
- `Map` | ||
- Throws when attempted to use. | ||
- Method shorthands | ||
- Uses function expressions instead. | ||
- `Object.assign` | ||
- Uses manual object assignments instead. | ||
- `Promise` | ||
- Throws when attempted to use, specially in `serializeAsync`. | ||
- `Set` | ||
- Throws when attempted to use. | ||
- `Symbol.iterator` | ||
- Throws when attempted to use. | ||
- `TypedArray` | ||
- Throws when attempted to use. | ||
## Sponsors | ||
@@ -203,0 +288,0 @@ |
/* eslint-disable no-await-in-loop */ | ||
import { isPrimitive } from './checks'; | ||
import serializePrimitive from './serialize-primitive'; | ||
import { | ||
@@ -10,4 +9,6 @@ createRef, | ||
getRefParam, | ||
Options, | ||
resolvePatches, | ||
SerializationContext, | ||
serializePrimitive, | ||
serializeTree, | ||
@@ -34,5 +35,5 @@ } from './tree'; | ||
function finalize<T extends ServerValue | AsyncServerValue>( | ||
function finalize<T extends NonPrimitiveServerValue<ServerValue | AsyncServerValue>>( | ||
ctx: SerializationContext, | ||
source: NonPrimitiveServerValue<T>, | ||
source: T, | ||
result: string, | ||
@@ -42,13 +43,24 @@ ) { | ||
if (ctx.vars.length) { | ||
// Get (or create) a ref from the source | ||
const index = getRefParam(ctx, createRef(ctx, source)); | ||
const patches = resolvePatches(ctx); | ||
const params = ctx.vars.length > 1 | ||
? `(${ctx.vars.join(',')})` | ||
let body = result; | ||
if (patches) { | ||
// Get (or create) a ref from the source | ||
const index = getRefParam(ctx, createRef(ctx, source)); | ||
if (result.startsWith(`${index}=`)) { | ||
body = `${result},${patches}${index}`; | ||
} else { | ||
body = `${index}=${result},${patches}${index}`; | ||
} | ||
} | ||
let params = ctx.vars.length > 1 | ||
? ctx.vars.join(',') | ||
: ctx.vars[0]; | ||
// Source is probably already assigned | ||
if (result.startsWith(`${index}=`)) { | ||
return `(${params}=>(${result},${patches}${index}))()`; | ||
if (ctx.features.has('arrow-function')) { | ||
params = ctx.vars.length > 1 || ctx.vars.length === 0 | ||
? `(${params})` | ||
: params; | ||
return `(${params}=>(${body}))()`; | ||
} | ||
return `(${params}=>(${index}=${result},${patches}${index}))()`; | ||
return `(function(${params}){return ${body}})()`; | ||
} | ||
@@ -61,7 +73,10 @@ if (source.constructor === Object) { | ||
export function serialize(source: ServerValue) { | ||
export function serialize<T extends ServerValue>( | ||
source: T, | ||
options?: Partial<Options>, | ||
) { | ||
const ctx = createSerializationContext(options); | ||
if (isPrimitive(source)) { | ||
return serializePrimitive(source); | ||
return serializePrimitive(ctx, source); | ||
} | ||
const ctx = createSerializationContext(); | ||
const tree = generateTreeSync(ctx, source); | ||
@@ -72,7 +87,10 @@ const result = serializeTree(ctx, tree); | ||
export async function serializeAsync(source: AsyncServerValue) { | ||
export async function serializeAsync<T extends AsyncServerValue>( | ||
source: T, | ||
options?: Partial<Options>, | ||
) { | ||
const ctx = createSerializationContext(options); | ||
if (isPrimitive(source)) { | ||
return serializePrimitive(source); | ||
return serializePrimitive(ctx, source); | ||
} | ||
const ctx = createSerializationContext(); | ||
const tree = await generateTreeAsync(ctx, source); | ||
@@ -79,0 +97,0 @@ const result = serializeTree(ctx, tree); |
753
src/tree.ts
@@ -19,3 +19,4 @@ /* eslint-disable guard-for-in */ | ||
import getIdentifier from './get-identifier'; | ||
import serializePrimitive from './serialize-primitive'; | ||
import { Feature, parseTargets } from './compat'; | ||
import assert from './assert'; | ||
@@ -59,5 +60,17 @@ interface IndexAssignment { | ||
assignments: Assignment[]; | ||
// Supported features | ||
features: Set<Feature>; | ||
} | ||
export function createSerializationContext(): SerializationContext { | ||
export interface Options { | ||
target: string | string[]; | ||
} | ||
const DEFAULT_OPTIONS: Options = { | ||
target: 'es2023', | ||
}; | ||
export function createSerializationContext(options: Partial<Options> = {}): SerializationContext { | ||
// eslint-disable-next-line prefer-object-spread | ||
const result = Object.assign({}, DEFAULT_OPTIONS, options || {}); | ||
return { | ||
@@ -70,2 +83,3 @@ markedRefs: [], | ||
validRefs: new Map(), | ||
features: parseTargets(result.target), | ||
}; | ||
@@ -87,8 +101,8 @@ } | ||
function mergeAssignments(ctx: SerializationContext) { | ||
function mergeAssignments(assignments: Assignment[]) { | ||
const newAssignments = []; | ||
let current = ctx.assignments[0]; | ||
let current = assignments[0]; | ||
let prev = current; | ||
for (let i = 1, len = ctx.assignments.length; i < len; i += 1) { | ||
const item = ctx.assignments[i]; | ||
for (let i = 1, len = assignments.length; i < len; i++) { | ||
const item = assignments[i]; | ||
if (item.type === prev.type) { | ||
@@ -135,13 +149,18 @@ if (item.type === 'index' && item.value === prev.value) { | ||
export function resolvePatches(ctx: SerializationContext) { | ||
if (ctx.assignments.length) { | ||
function resolveAssignments(assignments: Assignment[]) { | ||
if (assignments.length) { | ||
let result = ''; | ||
for (const assignment of mergeAssignments(ctx)) { | ||
result += `${getAssignmentExpression(assignment)},`; | ||
const merged = mergeAssignments(assignments); | ||
for (let i = 0, len = merged.length; i < len; i++) { | ||
result += `${getAssignmentExpression(merged[i])},`; | ||
} | ||
return result; | ||
} | ||
return ''; | ||
return undefined; | ||
} | ||
export function resolvePatches(ctx: SerializationContext) { | ||
return resolveAssignments(ctx.assignments); | ||
} | ||
/** | ||
@@ -311,10 +330,5 @@ * Increments the number of references the referenced value has | ||
const EXCLUDED_ERROR_KEYS = { | ||
name: true, | ||
cause: true, | ||
stack: true, | ||
message: true, | ||
}; | ||
function getErrorOptions(error: Error) { | ||
function getErrorOptions( | ||
error: Error, | ||
) { | ||
let options: Record<string, any> | undefined; | ||
@@ -332,3 +346,3 @@ const constructor = getErrorConstructor(error); | ||
for (const name of names) { | ||
if (!(name in EXCLUDED_ERROR_KEYS)) { | ||
if (name !== 'name' && name !== 'message') { | ||
options = options || {}; | ||
@@ -377,6 +391,49 @@ options[name] = error[name as keyof Error]; | ||
type: SerovalNodeType.TypedArray, | ||
value: { constructor: string, array: TypedArrayValue }, | ||
value: [constructor: string, array: TypedArrayValue], | ||
id: number | ||
]; | ||
type SerovalDictionaryNode = [key: string[], value: SerovalNode[], size: number]; | ||
type SerovalSetNode = [type: SerovalNodeType.Set, value: SerovalNode[], id: number]; | ||
type SerovalMapNode = [ | ||
type: SerovalNodeType.Map, | ||
value: [key: SerovalNode[], value: SerovalNode[], size: number], | ||
id: number | ||
]; | ||
type SerovalArrayNode = [type: SerovalNodeType.Array, value: SerovalNode[], id: number]; | ||
type SerovalObjectNode = [type: SerovalNodeType.Object, value: SerovalDictionaryNode, id: number]; | ||
type SerovalNullConstructorNode = [ | ||
type: SerovalNodeType.NullConstructor, | ||
value: SerovalDictionaryNode, | ||
id: number, | ||
]; | ||
type SerovalPromiseNode = [type: SerovalNodeType.Promise, value: SerovalNode, id: number]; | ||
type SerovalErrorNode = [ | ||
type: SerovalNodeType.Error, | ||
value: [ | ||
constructor: string, | ||
message: string, | ||
options?: SerovalDictionaryNode, | ||
], | ||
id: number | ||
]; | ||
type SerovalAggregateErrorNode = [ | ||
type: SerovalNodeType.AggregateError, | ||
value: [ | ||
message: string, | ||
options: SerovalDictionaryNode | undefined, | ||
errors: SerovalNode, | ||
], | ||
id: number | ||
]; | ||
type SerovalIterableNode = [ | ||
type: SerovalNodeType.Iterable, | ||
value: [ | ||
options: SerovalDictionaryNode | undefined, | ||
items: SerovalNode, | ||
], | ||
id: number, | ||
]; | ||
type SerovalNode = | ||
@@ -386,36 +443,11 @@ | SerovalPrimitiveNode | ||
| SerovalSemiPrimitiveNode | ||
| [type: SerovalNodeType.Set, value: SerovalNode[], id: number] | ||
| [type: SerovalNodeType.Map, value: [key: SerovalNode, value: SerovalNode][], id: number] | ||
| [type: SerovalNodeType.Array, value: SerovalNode[], id: number] | ||
| [type: SerovalNodeType.Object, value: Record<string, SerovalNode>, id: number] | ||
| [type: SerovalNodeType.NullConstructor, value: Record<string, SerovalNode>, id: number] | ||
| [type: SerovalNodeType.Promise, value: SerovalNode, id: number] | ||
| [ | ||
type: SerovalNodeType.Error, | ||
value: { | ||
constructor: string; | ||
message: string; | ||
options?: SerovalNode; | ||
cause?: SerovalNode; | ||
}, | ||
id: number | ||
] | ||
| [ | ||
type: SerovalNodeType.AggregateError, | ||
value: { | ||
message: string; | ||
options?: SerovalNode; | ||
cause?: SerovalNode; | ||
errors: SerovalNode; | ||
}, | ||
id: number | ||
] | ||
| [ | ||
type: SerovalNodeType.Iterable, | ||
value: { | ||
items: SerovalNode, | ||
options?: SerovalNode, | ||
}, | ||
id: number, | ||
]; | ||
| SerovalSetNode | ||
| SerovalMapNode | ||
| SerovalArrayNode | ||
| SerovalObjectNode | ||
| SerovalNullConstructorNode | ||
| SerovalPromiseNode | ||
| SerovalErrorNode | ||
| SerovalAggregateErrorNode | ||
| SerovalIterableNode; | ||
@@ -450,2 +482,3 @@ function isReferenceInStack( | ||
function generateSemiPrimitiveValue( | ||
ctx: SerializationContext, | ||
current: unknown, | ||
@@ -461,33 +494,52 @@ id: number, | ||
if (constructorCheck<Int8Array>(current, Int8Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Int8Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Int8Array"'); | ||
return [SerovalNodeType.TypedArray, ['Int8Array', current], id]; | ||
} | ||
if (constructorCheck<Int16Array>(current, Int16Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Int16Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Int16Array"'); | ||
return [SerovalNodeType.TypedArray, ['Int16Array', current], id]; | ||
} | ||
if (constructorCheck<Int32Array>(current, Int32Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Int32Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Int32Array"'); | ||
return [SerovalNodeType.TypedArray, ['Int32Array', current], id]; | ||
} | ||
if (constructorCheck<Uint8Array>(current, Uint8Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Uint8Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint8Array"'); | ||
return [SerovalNodeType.TypedArray, ['Uint8Array', current], id]; | ||
} | ||
if (constructorCheck<Uint16Array>(current, Uint16Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Uint16Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint16Array"'); | ||
return [SerovalNodeType.TypedArray, ['Uint16Array', current], id]; | ||
} | ||
if (constructorCheck<Uint32Array>(current, Uint32Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Uint32Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint32Array"'); | ||
return [SerovalNodeType.TypedArray, ['Uint32Array', current], id]; | ||
} | ||
if (constructorCheck<Uint8ClampedArray>(current, Uint8ClampedArray)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Uint8ClampedArray', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Uint8ClampedArray"'); | ||
return [SerovalNodeType.TypedArray, ['Uint8ClampedArray', current], id]; | ||
} | ||
if (constructorCheck<Float32Array>(current, Float32Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Float32Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Float32Array"'); | ||
return [SerovalNodeType.TypedArray, ['Float32Array', current], id]; | ||
} | ||
if (constructorCheck<Float64Array>(current, Float64Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'Float64Array', array: current }, id]; | ||
assert(ctx.features.has('typed-arrays'), 'Unsupported value type "Float64Array"'); | ||
return [SerovalNodeType.TypedArray, ['Float64Array', current], id]; | ||
} | ||
if (constructorCheck<BigInt64Array>(current, BigInt64Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'BigInt64Array', array: current }, id]; | ||
assert( | ||
ctx.features.has('typed-arrays') | ||
&& ctx.features.has('bigint'), | ||
'Unsupported value type "BigInt64Array"', | ||
); | ||
return [SerovalNodeType.TypedArray, ['BigInt64Array', current], id]; | ||
} | ||
if (constructorCheck<BigUint64Array>(current, BigUint64Array)) { | ||
return [SerovalNodeType.TypedArray, { constructor: 'BigUint64Array', array: current }, id]; | ||
assert( | ||
ctx.features.has('typed-arrays') | ||
&& ctx.features.has('bigint'), | ||
'Unsupported value type "BigUint64Array"', | ||
); | ||
return [SerovalNodeType.TypedArray, ['BigUint64Array', current], id]; | ||
} | ||
@@ -501,2 +553,27 @@ return undefined; | ||
): SerovalNode { | ||
function serializeProperties( | ||
properties: Record<string, unknown>, | ||
): SerovalDictionaryNode { | ||
const keyNodes: string[] = []; | ||
const valueNodes: SerovalNode[] = []; | ||
const deferredKeys: string[] = []; | ||
const deferredValues: ServerValue[] = []; | ||
const keys = Object.keys(properties); | ||
let deferredSize = 0; | ||
for (const key of keys) { | ||
if (isIterable(properties[key])) { | ||
deferredKeys.push(key); | ||
deferredValues.push(properties[key] as ServerValue); | ||
deferredSize++; | ||
} else { | ||
keyNodes.push(key); | ||
valueNodes.push(generateTreeSync(ctx, properties[key] as ServerValue)); | ||
} | ||
} | ||
for (let i = 0; i < deferredSize; i++) { | ||
keyNodes.push(deferredKeys[i]); | ||
valueNodes.push(generateTreeSync(ctx, deferredValues[i])); | ||
} | ||
return [keyNodes, valueNodes, keys.length]; | ||
} | ||
if (isPrimitive(current)) { | ||
@@ -511,3 +588,3 @@ return [SerovalNodeType.Primitive, current]; | ||
} | ||
const semiPrimitive = generateSemiPrimitiveValue(current, id); | ||
const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id); | ||
if (semiPrimitive) { | ||
@@ -517,2 +594,3 @@ return semiPrimitive; | ||
if (constructorCheck<Set<ServerValue>>(current, Set)) { | ||
assert(ctx.features.has('set'), 'Unsupported type "Set"'); | ||
const nodes: SerovalNode[] = []; | ||
@@ -529,4 +607,4 @@ const deferred: ServerValue[] = []; | ||
// Parse deferred items | ||
for (const item of deferred) { | ||
nodes.push(generateTreeSync(ctx, item)); | ||
for (let i = 0, len = deferred.length; i < len; i++) { | ||
nodes.push(generateTreeSync(ctx, deferred[i])); | ||
} | ||
@@ -536,88 +614,78 @@ return [SerovalNodeType.Set, nodes, id]; | ||
if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) { | ||
const nodes: [key: SerovalNode, value: SerovalNode][] = []; | ||
const deferred: [ServerValue, ServerValue][] = []; | ||
assert(ctx.features.has('map'), 'Unsupported type "Map"'); | ||
const keyNodes: SerovalNode[] = []; | ||
const valueNodes: SerovalNode[] = []; | ||
const deferredKey: ServerValue[] = []; | ||
const deferredValue: ServerValue[] = []; | ||
let deferredSize = 0; | ||
for (const [key, value] of current.entries()) { | ||
// Either key or value might be an iterable | ||
if (isIterable(key) || isIterable(value)) { | ||
deferred.push([key, value]); | ||
deferredKey.push(key); | ||
deferredValue.push(value); | ||
deferredSize++; | ||
} else { | ||
const keyNode = generateTreeSync(ctx, key); | ||
const valueNode = generateTreeSync(ctx, value); | ||
nodes.push([keyNode, valueNode]); | ||
keyNodes.push(generateTreeSync(ctx, key)); | ||
valueNodes.push(generateTreeSync(ctx, value)); | ||
} | ||
} | ||
for (const [key, value] of deferred) { | ||
const keyNode = generateTreeSync(ctx, key); | ||
const valueNode = generateTreeSync(ctx, value); | ||
nodes.push([keyNode, valueNode]); | ||
for (let i = 0; i < deferredSize; i++) { | ||
keyNodes.push(generateTreeSync(ctx, deferredKey[i])); | ||
valueNodes.push(generateTreeSync(ctx, deferredValue[i])); | ||
} | ||
return [SerovalNodeType.Map, nodes, id]; | ||
return [SerovalNodeType.Map, [keyNodes, valueNodes, current.size], id]; | ||
} | ||
if (Array.isArray(current)) { | ||
const nodes = new Array<SerovalNode>(current.length); | ||
const deferred = new Array<ServerValue>(current.length); | ||
for (const key in current) { | ||
const item = current[key]; | ||
if (isIterable(item)) { | ||
deferred[key] = item; | ||
} else { | ||
nodes[key] = generateTreeSync(ctx, item); | ||
const size = current.length; | ||
const nodes = new Array<SerovalNode>(size); | ||
const deferred = new Array<ServerValue>(size); | ||
for (let i = 0; i < size; i++) { | ||
if (i in current) { | ||
if (isIterable(current[i])) { | ||
deferred[i] = current[i]; | ||
} else { | ||
nodes[i] = generateTreeSync(ctx, current[i]); | ||
} | ||
} | ||
} | ||
for (const key in deferred) { | ||
nodes[key] = generateTreeSync(ctx, deferred[key]); | ||
for (let i = 0; i < size; i++) { | ||
if (i in deferred) { | ||
nodes[i] = generateTreeSync(ctx, deferred[i]); | ||
} | ||
} | ||
return [SerovalNodeType.Array, nodes, id]; | ||
} | ||
if (current instanceof AggregateError) { | ||
if (current instanceof AggregateError && ctx.features.has('aggregate-error')) { | ||
const options = getErrorOptions(current); | ||
return [SerovalNodeType.AggregateError, { | ||
message: current.message, | ||
options: options | ||
? generateTreeSync(ctx, options) | ||
: undefined, | ||
cause: 'cause' in current | ||
? generateTreeSync(ctx, { cause: current.cause as ServerValue }) | ||
: undefined, | ||
errors: generateTreeSync(ctx, current.errors as ServerValue), | ||
}, id]; | ||
const optionsNode = options | ||
? serializeProperties(options) | ||
: undefined; | ||
const errorsNode = generateTreeSync(ctx, current.errors as ServerValue); | ||
return [SerovalNodeType.AggregateError, [current.message, optionsNode, errorsNode], id]; | ||
} | ||
if (current instanceof Error) { | ||
const options = getErrorOptions(current); | ||
return [SerovalNodeType.Error, { | ||
constructor: getErrorConstructor(current), | ||
message: current.message, | ||
options: options ? generateTreeSync(ctx, options) : undefined, | ||
cause: 'cause' in current | ||
? generateTreeSync(ctx, { cause: current.cause as ServerValue }) | ||
: undefined, | ||
}, id]; | ||
const optionsNode = options | ||
? serializeProperties(options) | ||
: undefined; | ||
return [ | ||
SerovalNodeType.Error, | ||
[getErrorConstructor(current), current.message, optionsNode], | ||
id, | ||
]; | ||
} | ||
if (isIterable(current)) { | ||
assert(ctx.features.has('symbol-iterator'), 'Unsupported type "Iterable"'); | ||
const options = getIterableOptions(current); | ||
return [SerovalNodeType.Iterable, { | ||
return [SerovalNodeType.Iterable, [ | ||
// Parse options first before the items | ||
options: options | ||
? generateTreeSync(ctx, options as ServerValue) | ||
: undefined, | ||
items: generateTreeSync(ctx, Array.from(current)), | ||
}, id]; | ||
options ? serializeProperties(options) : undefined, | ||
generateTreeSync(ctx, Array.from(current)), | ||
], id]; | ||
} | ||
const empty = current.constructor == null; | ||
if (current.constructor === Object || empty) { | ||
const nodes: Record<string, SerovalNode> = {}; | ||
const deferred: Record<string, ServerValue> = {}; | ||
for (const [key, item] of Object.entries(current)) { | ||
if (isIterable(item)) { | ||
deferred[key] = item as ServerValue; | ||
} else { | ||
nodes[key] = generateTreeSync(ctx, item as ServerValue); | ||
} | ||
} | ||
for (const [key, item] of Object.entries(deferred)) { | ||
nodes[key] = generateTreeSync(ctx, item); | ||
} | ||
return [ | ||
empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object, | ||
nodes, | ||
serializeProperties(current as Record<string, unknown>), | ||
id, | ||
@@ -633,2 +701,27 @@ ]; | ||
): Promise<SerovalNode> { | ||
async function serializeProperties( | ||
properties: Record<string, unknown>, | ||
): Promise<SerovalDictionaryNode> { | ||
const keyNodes: string[] = []; | ||
const valueNodes: SerovalNode[] = []; | ||
const deferredKeys: string[] = []; | ||
const deferredValues: ServerValue[] = []; | ||
const keys = Object.keys(properties); | ||
let deferredSize = 0; | ||
for (const key of keys) { | ||
if (isIterable(properties[key])) { | ||
deferredKeys.push(key); | ||
deferredValues.push(properties[key] as ServerValue); | ||
deferredSize++; | ||
} else { | ||
keyNodes.push(key); | ||
valueNodes.push(await generateTreeAsync(ctx, properties[key] as ServerValue)); | ||
} | ||
} | ||
for (let i = 0; i < deferredSize; i++) { | ||
keyNodes.push(deferredKeys[i]); | ||
valueNodes.push(await generateTreeAsync(ctx, deferredValues[i])); | ||
} | ||
return [keyNodes, valueNodes, keys.length]; | ||
} | ||
if (isPrimitive(current)) { | ||
@@ -641,3 +734,3 @@ return [SerovalNodeType.Primitive, current]; | ||
} | ||
const semiPrimitive = generateSemiPrimitiveValue(current, id); | ||
const semiPrimitive = generateSemiPrimitiveValue(ctx, current, id); | ||
if (semiPrimitive) { | ||
@@ -647,2 +740,3 @@ return semiPrimitive; | ||
if (isPromise(current)) { | ||
assert(ctx.features.has('promise'), 'Unsupported type "Promise"'); | ||
return current.then(async (value) => [ | ||
@@ -654,5 +748,6 @@ SerovalNodeType.Promise, | ||
} | ||
if (constructorCheck<Set<ServerValue>>(current, Set)) { | ||
if (constructorCheck<Set<AsyncServerValue>>(current, Set)) { | ||
assert(ctx.features.has('set'), 'Unsupported type "Set"'); | ||
const nodes: SerovalNode[] = []; | ||
const deferred: ServerValue[] = []; | ||
const deferred: AsyncServerValue[] = []; | ||
for (const item of current.keys()) { | ||
@@ -665,39 +760,48 @@ if (isIterable(item)) { | ||
} | ||
for (const item of deferred) { | ||
nodes.push(await generateTreeAsync(ctx, item)); | ||
for (let i = 0, len = deferred.length; i < len; i++) { | ||
nodes.push(await generateTreeAsync(ctx, deferred[i])); | ||
} | ||
return [SerovalNodeType.Set, nodes, id]; | ||
} | ||
if (constructorCheck<Map<ServerValue, ServerValue>>(current, Map)) { | ||
const nodes: [key: SerovalNode, value: SerovalNode][] = []; | ||
const deferred: [ServerValue, ServerValue][] = []; | ||
if (constructorCheck<Map<AsyncServerValue, AsyncServerValue>>(current, Map)) { | ||
assert(ctx.features.has('map'), 'Unsupported type "Map"'); | ||
const keyNodes: SerovalNode[] = []; | ||
const valueNodes: SerovalNode[] = []; | ||
const deferredKey: AsyncServerValue[] = []; | ||
const deferredValue: AsyncServerValue[] = []; | ||
let deferredSize = 0; | ||
for (const [key, value] of current.entries()) { | ||
if (isIterable(key) || isIterable(value)) { | ||
deferred.push([key, value]); | ||
deferredKey.push(key); | ||
deferredValue.push(value); | ||
deferredSize++; | ||
} else { | ||
const keyNode = await generateTreeAsync(ctx, key); | ||
const valueNode = await generateTreeAsync(ctx, value); | ||
nodes.push([keyNode, valueNode]); | ||
keyNodes.push(await generateTreeAsync(ctx, key)); | ||
valueNodes.push(await generateTreeAsync(ctx, value)); | ||
} | ||
} | ||
for (const [key, value] of deferred) { | ||
const keyNode = await generateTreeAsync(ctx, key); | ||
const valueNode = await generateTreeAsync(ctx, value); | ||
nodes.push([keyNode, valueNode]); | ||
for (let i = 0; i < deferredSize; i++) { | ||
keyNodes.push(await generateTreeAsync(ctx, deferredKey[i])); | ||
valueNodes.push(await generateTreeAsync(ctx, deferredValue[i])); | ||
} | ||
return [SerovalNodeType.Map, nodes, id]; | ||
return [SerovalNodeType.Map, [keyNodes, valueNodes, current.size], id]; | ||
} | ||
if (Array.isArray(current)) { | ||
const nodes = new Array<SerovalNode>(current.length); | ||
const deferred = new Array<AsyncServerValue>(current.length); | ||
for (const key in current) { | ||
const item = current[key]; | ||
if (isIterable(item)) { | ||
deferred[key] = item; | ||
} else { | ||
nodes[key] = await generateTreeAsync(ctx, item); | ||
const size = current.length; | ||
const nodes = new Array<SerovalNode>(size); | ||
const deferred = new Array<AsyncServerValue>(size); | ||
for (let i = 0; i < size; i++) { | ||
const item = current[i]; | ||
if (i in current) { | ||
if (isIterable(item)) { | ||
deferred[i] = item; | ||
} else { | ||
nodes[i] = await generateTreeAsync(ctx, item); | ||
} | ||
} | ||
} | ||
for (const key in deferred) { | ||
nodes[key] = await generateTreeAsync(ctx, deferred[key]); | ||
for (let i = 0; i < size; i++) { | ||
if (i in deferred) { | ||
nodes[i] = await generateTreeAsync(ctx, deferred[i]); | ||
} | ||
} | ||
@@ -708,50 +812,34 @@ return [SerovalNodeType.Array, nodes, id]; | ||
const options = getErrorOptions(current); | ||
return [SerovalNodeType.AggregateError, { | ||
message: current.message, | ||
options: options | ||
? generateTreeSync(ctx, options) | ||
: undefined, | ||
cause: 'cause' in current | ||
? await generateTreeAsync(ctx, { cause: current.cause as ServerValue }) | ||
: undefined, | ||
errors: await generateTreeAsync(ctx, current.errors as ServerValue), | ||
}, id]; | ||
const optionsNode = options | ||
? await serializeProperties(options) | ||
: undefined; | ||
const errorsNode = await generateTreeAsync(ctx, current.errors as ServerValue); | ||
return [SerovalNodeType.AggregateError, [current.message, optionsNode, errorsNode], id]; | ||
} | ||
if (current instanceof Error) { | ||
const options = getErrorOptions(current); | ||
return [SerovalNodeType.Error, { | ||
constructor: getErrorConstructor(current), | ||
message: current.message, | ||
options: options ? await generateTreeAsync(ctx, options) : undefined, | ||
cause: 'cause' in current | ||
? await generateTreeAsync(ctx, { cause: current.cause as ServerValue }) | ||
: undefined, | ||
}, id]; | ||
const optionsNode = options | ||
? await serializeProperties(options) | ||
: undefined; | ||
return [ | ||
SerovalNodeType.Error, | ||
[getErrorConstructor(current), current.message, optionsNode], | ||
id, | ||
]; | ||
} | ||
if (isIterable(current)) { | ||
assert(ctx.features.has('symbol-iterator'), 'Unsupported type "Iterable"'); | ||
const options = getIterableOptions(current); | ||
return [SerovalNodeType.Iterable, { | ||
options: options | ||
? await generateTreeAsync(ctx, options as ServerValue) | ||
return [SerovalNodeType.Iterable, [ | ||
options | ||
? await serializeProperties(options) | ||
: undefined, | ||
items: await generateTreeAsync(ctx, Array.from(current)), | ||
}, id]; | ||
await generateTreeAsync(ctx, Array.from(current)), | ||
], id]; | ||
} | ||
const empty = current.constructor == null; | ||
if (current.constructor === Object || empty) { | ||
const nodes: Record<string, SerovalNode> = {}; | ||
const deferred: Record<string, AsyncServerValue> = {}; | ||
for (const [key, item] of Object.entries(current)) { | ||
if (isIterable(item)) { | ||
deferred[key] = item as ServerValue; | ||
} else { | ||
nodes[key] = await generateTreeAsync(ctx, item as ServerValue); | ||
} | ||
} | ||
for (const [key, item] of Object.entries(deferred)) { | ||
nodes[key] = await generateTreeAsync(ctx, item); | ||
} | ||
return [ | ||
empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object, | ||
nodes, | ||
await serializeProperties(current as Record<string, unknown>), | ||
id, | ||
@@ -763,2 +851,39 @@ ]; | ||
export function serializePrimitive( | ||
ctx: SerializationContext, | ||
value: PrimitiveValue, | ||
): string { | ||
// Shortened forms | ||
if (value === true) { | ||
return '!0'; | ||
} | ||
if (value === false) { | ||
return '!1'; | ||
} | ||
if (value === undefined) { | ||
return 'void 0'; | ||
} | ||
if (value === null) { | ||
return 'null'; | ||
} | ||
if (typeof value === 'bigint') { | ||
assert(ctx.features.has('bigint'), 'Unsupported type "BigInt"'); | ||
return `${value}n`; | ||
} | ||
if (typeof value === 'string') { | ||
return quote(value); | ||
} | ||
// negative 0 isn't the same as 0 | ||
if (Object.is(value, -0)) { | ||
return '-0'; | ||
} | ||
if (Object.is(value, Infinity)) { | ||
return '1/0'; | ||
} | ||
if (Object.is(value, -Infinity)) { | ||
return '-1/0'; | ||
} | ||
return String(value); | ||
} | ||
export function serializeTree( | ||
@@ -768,5 +893,75 @@ ctx: SerializationContext, | ||
): string { | ||
function serializeAssignments( | ||
targetRef: number, | ||
[keys, values, size]: SerovalDictionaryNode, | ||
) { | ||
ctx.stack.push(targetRef); | ||
const mainAssignments: Assignment[] = []; | ||
for (let i = 0; i < size; i++) { | ||
const parentStack = ctx.stack; | ||
ctx.stack = []; | ||
const refParam = serializeTree(ctx, values[i]); | ||
ctx.stack = parentStack; | ||
const key = keys[i]; | ||
const check = Number(key); | ||
// Test if key is a valid number or JS identifier | ||
// so that we don't have to serialize the key and wrap with brackets | ||
const parentAssignment = ctx.assignments; | ||
ctx.assignments = mainAssignments; | ||
if ( | ||
check >= 0 | ||
|| (Number.isNaN(check) && /^([$A-Z_][0-9A-Z_$]*)$/i.test(key)) | ||
) { | ||
if (!Number.isNaN(check)) { | ||
createObjectComputedAssign(ctx, targetRef, key, refParam); | ||
} else { | ||
createObjectIdentifierAssign(ctx, targetRef, key, refParam); | ||
} | ||
} else { | ||
createObjectComputedAssign(ctx, targetRef, quote(key), refParam); | ||
} | ||
ctx.assignments = parentAssignment; | ||
} | ||
ctx.stack.pop(); | ||
return resolveAssignments(mainAssignments); | ||
} | ||
function serializeObject( | ||
sourceID: number, | ||
[keys, values, size]: SerovalDictionaryNode, | ||
) { | ||
let result = ''; | ||
ctx.stack.push(sourceID); | ||
for (let i = 0; i < size; i++) { | ||
const key = keys[i]; | ||
const val = values[i]; | ||
const check = Number(key); | ||
// Test if key is a valid number or JS identifier | ||
// so that we don't have to serialize the key and wrap with brackets | ||
if ( | ||
check >= 0 | ||
|| (Number.isNaN(check) && /^([$A-Z_][0-9A-Z_$]*)$/i.test(key)) | ||
) { | ||
if (isReferenceInStack(ctx, val)) { | ||
const refParam = getRefParam(ctx, val[1]); | ||
if (!Number.isNaN(check)) { | ||
createObjectComputedAssign(ctx, sourceID, key, refParam); | ||
} else { | ||
createObjectIdentifierAssign(ctx, sourceID, key, refParam); | ||
} | ||
} else { | ||
result += `${key}:${serializeTree(ctx, val)},`; | ||
} | ||
} else if (isReferenceInStack(ctx, val)) { | ||
const refParam = getRefParam(ctx, val[1]); | ||
createObjectComputedAssign(ctx, sourceID, quote(key), refParam); | ||
} else { | ||
result += `${quote(key)}:${serializeTree(ctx, val)},`; | ||
} | ||
} | ||
ctx.stack.pop(); | ||
return `{${result.substring(0, result.length - 1)}}`; | ||
} | ||
switch (type) { | ||
case SerovalNodeType.Primitive: | ||
return serializePrimitive(value); | ||
return serializePrimitive(ctx, value); | ||
case SerovalNodeType.Reference: | ||
@@ -798,15 +993,20 @@ return getRefParam(ctx, value); | ||
// BigInt typed arrays are broken for toString() | ||
const values = [...value.array].map(serializePrimitive).join(','); | ||
let args = `[${values}]`; | ||
if (value.array.byteOffset !== 0) { | ||
args += `,${value.array.byteOffset}`; | ||
let values = ''; | ||
for (let i = 0, len = value[1].length; i < len; i++) { | ||
values += `${serializePrimitive(ctx, value[1][i])},`; | ||
} | ||
return assignRef(ctx, id, `new ${value.constructor}(${args})`); | ||
let args = values ? `[${values.substring(0, values.length - 1)}]` : '[]'; | ||
if (value[1].byteOffset !== 0) { | ||
args += `,${value[1].byteOffset}`; | ||
} | ||
return assignRef(ctx, id, `new ${value[0]}(${args})`); | ||
} | ||
case SerovalNodeType.Set: { | ||
let serialized = 'new Set'; | ||
if (value.length) { | ||
const values: string[] = []; | ||
const size = value.length; | ||
if (size) { | ||
let result = ''; | ||
ctx.stack.push(id); | ||
for (const item of value) { | ||
for (let i = 0; i < size; i++) { | ||
const item = value[i]; | ||
if (isReferenceInStack(ctx, item)) { | ||
@@ -816,8 +1016,8 @@ createSetAdd(ctx, id, getRefParam(ctx, item[1])); | ||
// Push directly | ||
values.push(serializeTree(ctx, item)); | ||
result += `${serializeTree(ctx, item)},`; | ||
} | ||
} | ||
ctx.stack.pop(); | ||
if (values.length) { | ||
serialized += `([${values.join(',')}])`; | ||
if (result) { | ||
serialized += `([${result.substring(0, result.length - 1)}])`; | ||
} | ||
@@ -829,7 +1029,10 @@ } | ||
let serialized = 'new Map'; | ||
if (value.length) { | ||
const values: string[] = []; | ||
const size = value[2]; | ||
if (size) { | ||
let result = ''; | ||
ctx.stack.push(id); | ||
for (const [key, val] of value) { | ||
for (let i = 0; i < size; i++) { | ||
// Check if key is a parent | ||
const key = value[0][i]; | ||
const val = value[1][i]; | ||
if (isReferenceInStack(ctx, key)) { | ||
@@ -865,3 +1068,3 @@ // Create reference for the map instance | ||
} else { | ||
values.push(`[${serializeTree(ctx, key)},${serializeTree(ctx, val)}]`); | ||
result += `[${serializeTree(ctx, key)},${serializeTree(ctx, val)}],`; | ||
} | ||
@@ -873,4 +1076,4 @@ } | ||
// can be used instead | ||
if (values.length) { | ||
serialized += `([${values.join(',')}])`; | ||
if (result) { | ||
serialized += `([${result.substring(0, result.length - 1)}])`; | ||
} | ||
@@ -884,6 +1087,6 @@ } | ||
// the holes of the Array | ||
const size = value.length; | ||
let values = ''; | ||
ctx.stack.push(id); | ||
for (let i = 0, len = value.length; i < len; i++) { | ||
for (let i = 0; i < size; i++) { | ||
const item = value[i]; | ||
@@ -898,3 +1101,3 @@ // Check if index is a hole | ||
values += serializeTree(ctx, item); | ||
if (i < value.length - 1) { | ||
if (i < size - 1) { | ||
values += ','; | ||
@@ -913,14 +1116,20 @@ } | ||
// Serialize the required arguments | ||
const args = [serializeTree(ctx, value.errors), quote(value.message)]; | ||
// cause is an optional argument | ||
if (value.cause) { | ||
args.push(serializeTree(ctx, value.cause)); | ||
} | ||
let serialized = `new AggregateError(${args.join(',')})`; | ||
ctx.stack.push(id); | ||
let serialized = `new AggregateError(${serializeTree(ctx, value[2])},${quote(value[0])})`; | ||
ctx.stack.pop(); | ||
// `AggregateError` might've been extended | ||
// either through class or custom properties | ||
// Make sure to assign extra properties | ||
if (value.options) { | ||
const options = serializeTree(ctx, value.options); | ||
serialized = `Object.assign(${serialized},${options})`; | ||
if (value[1]) { | ||
if (ctx.features.has('object-assign')) { | ||
const options = serializeObject(id, value[1]); | ||
serialized = `Object.assign(${serialized},${options})`; | ||
} else { | ||
markRef(ctx, id); | ||
const assignments = serializeAssignments(id, value[1]); | ||
if (assignments) { | ||
const ref = getRefParam(ctx, id); | ||
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`; | ||
} | ||
} | ||
} | ||
@@ -930,11 +1139,16 @@ return assignRef(ctx, id, serialized); | ||
case SerovalNodeType.Error: { | ||
const args = [quote(value.message)]; | ||
if (value.cause) { | ||
args.push(serializeTree(ctx, value.cause)); | ||
let serialized = `new ${value[0]}(${quote(value[1])})`; | ||
if (value[2]) { | ||
if (ctx.features.has('object-assign')) { | ||
const options = serializeObject(id, value[2]); | ||
serialized = `Object.assign(${serialized},${options})`; | ||
} else { | ||
markRef(ctx, id); | ||
const assignments = serializeAssignments(id, value[2]); | ||
if (assignments) { | ||
const ref = getRefParam(ctx, id); | ||
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`; | ||
} | ||
} | ||
} | ||
let serialized = `new ${value.constructor}(${args.join(',')})`; | ||
if (value.options) { | ||
const options = serializeTree(ctx, value.options); | ||
serialized = `Object.assign(${serialized},${options})`; | ||
} | ||
return assignRef(ctx, id, serialized); | ||
@@ -945,50 +1159,51 @@ } | ||
ctx.stack = []; | ||
const values = serializeTree(ctx, value.items); | ||
const values = serializeTree(ctx, value[1]); | ||
ctx.stack = parent; | ||
let serialized = `{[Symbol.iterator]:()=>${values}.values()}`; | ||
if (value.options) { | ||
ctx.stack.push(id); | ||
const options = serializeTree(ctx, value.options); | ||
ctx.stack.pop(); | ||
serialized = `Object.assign(${serialized},${options})`; | ||
let serialized: string; | ||
if (ctx.features.has('array-values')) { | ||
serialized = `${values}.values()`; | ||
} else { | ||
serialized = `${values}[Symbol.iterator]()`; | ||
} | ||
if (ctx.features.has('arrow-function')) { | ||
serialized = `{[Symbol.iterator]:()=>${serialized}}`; | ||
} else if (ctx.features.has('method-shorthand')) { | ||
serialized = `{[Symbol.iterator](){return ${serialized}}}`; | ||
} else { | ||
serialized = `{[Symbol.iterator]:function(){return ${serialized}}}`; | ||
} | ||
if (value[0]) { | ||
if (ctx.features.has('object-assign')) { | ||
const options = serializeObject(id, value[0]); | ||
serialized = `Object.assign(${serialized},${options})`; | ||
} else { | ||
markRef(ctx, id); | ||
const assignments = serializeAssignments(id, value[0]); | ||
if (assignments) { | ||
const ref = getRefParam(ctx, id); | ||
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`; | ||
} | ||
} | ||
} | ||
return assignRef(ctx, id, serialized); | ||
} | ||
case SerovalNodeType.NullConstructor: | ||
case SerovalNodeType.Object: { | ||
const values: string[] = []; | ||
ctx.stack.push(id); | ||
for (const [key, val] of Object.entries(value)) { | ||
const check = Number(key); | ||
// Test if key is a valid number or JS identifier | ||
// so that we don't have to serialize the key and wrap with brackets | ||
if ( | ||
check >= 0 | ||
|| (Number.isNaN(check) && /^([$A-Z_][0-9A-Z_$]*)$/i.test(key)) | ||
) { | ||
if (isReferenceInStack(ctx, val)) { | ||
const refParam = getRefParam(ctx, val[1]); | ||
if (!Number.isNaN(check)) { | ||
createObjectComputedAssign(ctx, id, key, refParam); | ||
} else { | ||
createObjectIdentifierAssign(ctx, id, key, refParam); | ||
} | ||
} else { | ||
values.push(`${key}:${serializeTree(ctx, val)}`); | ||
} | ||
} else if (isReferenceInStack(ctx, val)) { | ||
const refParam = getRefParam(ctx, val[1]); | ||
createObjectComputedAssign(ctx, id, quote(key), refParam); | ||
} else { | ||
values.push(`${quote(key)}:${serializeTree(ctx, val)}`); | ||
case SerovalNodeType.NullConstructor: { | ||
let serialized = 'Object.create(null)'; | ||
if (ctx.features.has('object-assign')) { | ||
const fields = serializeObject(id, value); | ||
if (fields !== '{}') { | ||
serialized = `Object.assign(${serialized},${fields})`; | ||
} | ||
} else { | ||
markRef(ctx, id); | ||
const assignments = serializeAssignments(id, value); | ||
if (assignments) { | ||
const ref = getRefParam(ctx, id); | ||
return `(${assignRef(ctx, id, serialized)},${assignments}${ref})`; | ||
} | ||
} | ||
ctx.stack.pop(); | ||
let serialized = `{${values.join(',')}}`; | ||
if (type === SerovalNodeType.NullConstructor) { | ||
serialized = `Object.assign(Object.create(null),${serialized})`; | ||
} | ||
return assignRef(ctx, id, serialized); | ||
} | ||
case SerovalNodeType.Object: | ||
return assignRef(ctx, id, serializeObject(id, value)); | ||
default: | ||
@@ -995,0 +1210,0 @@ throw new Error('Unsupported type'); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
341700
26
4325
291