Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

View file

@ -0,0 +1,119 @@
import { crossSerializeStream } from './cross';
import {
resolvePlugins,
type Plugin,
type PluginAccessOptions,
} from './plugin';
import { serializeString } from './string';
export interface SerializerOptions extends PluginAccessOptions {
globalIdentifier: string;
scopeId?: string;
disabledFeatures?: number;
onData: (result: string) => void;
onError: (error: unknown) => void;
onDone?: () => void;
}
export default class Serializer {
private alive = true;
private flushed = false;
private done = false;
private pending = 0;
private cleanups: (() => void)[] = [];
private refs = new Map<unknown, number>();
private plugins?: Plugin<any, any>[];
constructor(private options: SerializerOptions) {
this.plugins = resolvePlugins(options.plugins);
}
keys = new Set<string>();
write(key: string, value: unknown): void {
if (this.alive && !this.flushed) {
this.pending++;
this.keys.add(key);
this.cleanups.push(
crossSerializeStream(value, {
plugins: this.plugins,
scopeId: this.options.scopeId,
refs: this.refs,
disabledFeatures: this.options.disabledFeatures,
onError: this.options.onError,
onSerialize: (data, initial) => {
if (this.alive) {
this.options.onData(
initial
? this.options.globalIdentifier +
'["' +
serializeString(key) +
'"]=' +
data
: data,
);
}
},
onDone: () => {
if (this.alive) {
this.pending--;
if (
this.pending <= 0 &&
this.flushed &&
!this.done &&
this.options.onDone
) {
this.options.onDone();
this.done = true;
}
}
},
}),
);
}
}
ids = 0;
private getNextID(): string {
while (this.keys.has('' + this.ids)) {
this.ids++;
}
return '' + this.ids;
}
push(value: unknown): string {
const newID = this.getNextID();
this.write(newID, value);
return newID;
}
flush(): void {
if (this.alive) {
this.flushed = true;
if (this.pending <= 0 && !this.done && this.options.onDone) {
this.options.onDone();
this.done = true;
}
}
}
close(): void {
if (this.alive) {
for (let i = 0, len = this.cleanups.length; i < len; i++) {
this.cleanups[i]();
}
if (!this.done && this.options.onDone) {
this.options.onDone();
this.done = true;
}
this.alive = false;
}
}
}

View file

@ -0,0 +1,514 @@
import type { WellKnownSymbols } from './constants';
import { INV_SYMBOL_REF, NIL, SerovalNodeType } from './constants';
import {
INFINITY_NODE,
NAN_NODE,
NEG_INFINITY_NODE,
NEG_ZERO_NODE,
} from './literals';
import { createSerovalNode } from './node';
import { getReferenceID } from './reference';
import { serializeString } from './string';
import type {
SerovalAggregateErrorNode,
SerovalArrayNode,
SerovalAsyncIteratorFactoryInstanceNode,
SerovalBigIntNode,
SerovalBigIntTypedArrayNode,
SerovalBoxedNode,
SerovalConstantNode,
SerovalDataViewNode,
SerovalDateNode,
SerovalErrorNode,
SerovalIndexedValueNode,
SerovalIteratorFactoryInstanceNode,
SerovalNode,
SerovalNodeWithID,
SerovalNumberNode,
SerovalObjectRecordNode,
SerovalPluginNode,
SerovalReferenceNode,
SerovalRegExpNode,
SerovalSetNode,
SerovalStreamConstructorNode,
SerovalStreamNextNode,
SerovalStreamReturnNode,
SerovalStreamThrowNode,
SerovalStringNode,
SerovalTypedArrayNode,
SerovalWKSymbolNode,
} from './types';
import { getErrorConstructor } from './utils/error';
import { getObjectFlag } from './utils/get-object-flag';
import type {
BigIntTypedArrayValue,
TypedArrayValue,
} from './utils/typed-array';
export function createNumberNode(
value: number,
): SerovalConstantNode | SerovalNumberNode {
switch (value) {
case Number.POSITIVE_INFINITY:
return INFINITY_NODE;
case Number.NEGATIVE_INFINITY:
return NEG_INFINITY_NODE;
}
if (value !== value) {
return NAN_NODE;
}
if (Object.is(value, -0)) {
return NEG_ZERO_NODE;
}
return createSerovalNode(
SerovalNodeType.Number,
NIL,
value,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createStringNode(value: string): SerovalStringNode {
return createSerovalNode(
SerovalNodeType.String,
NIL,
serializeString(value),
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createBigIntNode(current: bigint): SerovalBigIntNode {
return createSerovalNode(
SerovalNodeType.BigInt,
NIL,
'' + current,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createIndexedValueNode(id: number): SerovalIndexedValueNode {
return createSerovalNode(
SerovalNodeType.IndexedValue,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createDateNode(id: number, current: Date): SerovalDateNode {
const timestamp = current.valueOf();
return createSerovalNode(
SerovalNodeType.Date,
id,
timestamp !== timestamp ? '' : current.toISOString(),
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createRegExpNode(
id: number,
current: RegExp,
): SerovalRegExpNode {
return createSerovalNode(
SerovalNodeType.RegExp,
id,
NIL,
serializeString(current.source),
current.flags,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createWKSymbolNode(
id: number,
current: WellKnownSymbols,
): SerovalWKSymbolNode {
return createSerovalNode(
SerovalNodeType.WKSymbol,
id,
INV_SYMBOL_REF[current],
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createReferenceNode<T>(
id: number,
ref: T,
): SerovalReferenceNode {
return createSerovalNode(
SerovalNodeType.Reference,
id,
serializeString(getReferenceID(ref)),
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createPluginNode(
id: number,
tag: string,
value: unknown,
): SerovalPluginNode {
return createSerovalNode(
SerovalNodeType.Plugin,
id,
value,
serializeString(tag),
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createArrayNode(
id: number,
current: unknown[],
parsedItems: SerovalArrayNode['a'],
): SerovalArrayNode {
return createSerovalNode(
SerovalNodeType.Array,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
parsedItems,
NIL,
NIL,
getObjectFlag(current),
NIL,
);
}
export function createBoxedNode(
id: number,
boxed: SerovalNode,
): SerovalBoxedNode {
return createSerovalNode(
SerovalNodeType.Boxed,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
boxed,
NIL,
NIL,
NIL,
);
}
export function createTypedArrayNode(
id: number,
current: TypedArrayValue,
buffer: SerovalNode,
): SerovalTypedArrayNode {
return createSerovalNode(
SerovalNodeType.TypedArray,
id,
NIL,
current.constructor.name,
NIL,
NIL,
NIL,
NIL,
buffer,
current.byteOffset,
NIL,
current.length,
);
}
export function createBigIntTypedArrayNode(
id: number,
current: BigIntTypedArrayValue,
buffer: SerovalNode,
): SerovalBigIntTypedArrayNode {
return createSerovalNode(
SerovalNodeType.BigIntTypedArray,
id,
NIL,
current.constructor.name,
NIL,
NIL,
NIL,
NIL,
buffer,
current.byteOffset,
NIL,
current.byteLength,
);
}
export function createDataViewNode(
id: number,
current: DataView,
buffer: SerovalNode,
): SerovalDataViewNode {
return createSerovalNode(
SerovalNodeType.DataView,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
buffer,
current.byteOffset,
NIL,
current.byteLength,
);
}
export function createErrorNode(
id: number,
current: Error,
options: SerovalObjectRecordNode | undefined,
): SerovalErrorNode {
return createSerovalNode(
SerovalNodeType.Error,
id,
getErrorConstructor(current),
NIL,
serializeString(current.message),
options,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createAggregateErrorNode(
id: number,
current: AggregateError,
options: SerovalObjectRecordNode | undefined,
): SerovalAggregateErrorNode {
return createSerovalNode(
SerovalNodeType.AggregateError,
id,
getErrorConstructor(current),
NIL,
serializeString(current.message),
options,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function createSetNode(
id: number,
items: SerovalNode[],
): SerovalSetNode {
return createSerovalNode(
SerovalNodeType.Set,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
items,
NIL,
NIL,
NIL,
NIL,
);
}
export function createIteratorFactoryInstanceNode(
factory: SerovalNodeWithID,
items: SerovalNode,
): SerovalIteratorFactoryInstanceNode {
return createSerovalNode(
SerovalNodeType.IteratorFactoryInstance,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
[factory, items],
NIL,
NIL,
NIL,
NIL,
);
}
export function createAsyncIteratorFactoryInstanceNode(
factory: SerovalNodeWithID,
items: SerovalNode,
): SerovalAsyncIteratorFactoryInstanceNode {
return createSerovalNode(
SerovalNodeType.AsyncIteratorFactoryInstance,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
[factory, items],
NIL,
NIL,
NIL,
NIL,
);
}
export function createStreamConstructorNode(
id: number,
factory: SerovalNodeWithID,
sequence: SerovalNode[],
): SerovalStreamConstructorNode {
return createSerovalNode(
SerovalNodeType.StreamConstructor,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
sequence,
factory,
NIL,
NIL,
NIL,
);
}
export function createStreamNextNode(
id: number,
parsed: SerovalNode,
): SerovalStreamNextNode {
return createSerovalNode(
SerovalNodeType.StreamNext,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
parsed,
NIL,
NIL,
NIL,
);
}
export function createStreamThrowNode(
id: number,
parsed: SerovalNode,
): SerovalStreamThrowNode {
return createSerovalNode(
SerovalNodeType.StreamThrow,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
parsed,
NIL,
NIL,
NIL,
);
}
export function createStreamReturnNode(
id: number,
parsed: SerovalNode,
): SerovalStreamReturnNode {
return createSerovalNode(
SerovalNodeType.StreamReturn,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
parsed,
NIL,
NIL,
NIL,
);
}

View file

@ -0,0 +1,23 @@
/**
* References
* - https://compat-table.github.io/compat-table/es6/
* - MDN
*/
export enum Feature {
AggregateError = 0x01,
// @deprecated
ArrowFunction = 0x02,
ErrorPrototypeStack = 0x04,
ObjectAssign = 0x08,
BigIntTypedArray = 0x10,
RegExp = 0x20,
}
export const ALL_ENABLED =
Feature.AggregateError |
Feature.ArrowFunction |
Feature.ErrorPrototypeStack |
Feature.ObjectAssign |
Feature.BigIntTypedArray |
Feature.RegExp;

View file

@ -0,0 +1,201 @@
import {
SYM_ASYNC_ITERATOR,
SYM_HAS_INSTANCE,
SYM_IS_CONCAT_SPREADABLE,
SYM_ITERATOR,
SYM_MATCH,
SYM_MATCH_ALL,
SYM_REPLACE,
SYM_SEARCH,
SYM_SPECIES,
SYM_SPLIT,
SYM_TO_PRIMITIVE,
SYM_TO_STRING_TAG,
SYM_UNSCOPABLES,
} from './symbols';
export const enum SerovalConstant {
Null = 0,
Undefined = 1,
True = 2,
False = 3,
NegZero = 4,
Inf = 5,
NegInf = 6,
Nan = 7,
}
export const enum SerovalNodeType {
Number = 0,
String = 1,
Constant = 2,
BigInt = 3,
IndexedValue = 4,
Date = 5,
RegExp = 6,
Set = 7,
Map = 8,
Array = 9,
Object = 10,
NullConstructor = 11,
Promise = 12,
Error = 13,
AggregateError = 14,
TypedArray = 15,
BigIntTypedArray = 16,
WKSymbol = 17,
Reference = 18,
ArrayBuffer = 19,
DataView = 20,
Boxed = 21,
PromiseConstructor = 22,
PromiseSuccess = 23,
PromiseFailure = 24,
Plugin = 25,
SpecialReference = 26,
IteratorFactory = 27,
IteratorFactoryInstance = 28,
AsyncIteratorFactory = 29,
AsyncIteratorFactoryInstance = 30,
StreamConstructor = 31,
StreamNext = 32,
StreamThrow = 33,
StreamReturn = 34,
}
export const enum SerovalObjectFlags {
None = 0,
NonExtensible = 1,
Sealed = 2,
Frozen = 3,
}
export const enum Symbols {
AsyncIterator = 0,
HasInstance = 1,
IsConcatSpreadable = 2,
Iterator = 3,
Match = 4,
MatchAll = 5,
Replace = 6,
Search = 7,
Species = 8,
Split = 9,
ToPrimitive = 10,
ToStringTag = 11,
Unscopables = 12,
}
export const SYMBOL_STRING: Record<Symbols, string> = {
[Symbols.AsyncIterator]: 'Symbol.asyncIterator',
[Symbols.HasInstance]: 'Symbol.hasInstance',
[Symbols.IsConcatSpreadable]: 'Symbol.isConcatSpreadable',
[Symbols.Iterator]: 'Symbol.iterator',
[Symbols.Match]: 'Symbol.match',
[Symbols.MatchAll]: 'Symbol.matchAll',
[Symbols.Replace]: 'Symbol.replace',
[Symbols.Search]: 'Symbol.search',
[Symbols.Species]: 'Symbol.species',
[Symbols.Split]: 'Symbol.split',
[Symbols.ToPrimitive]: 'Symbol.toPrimitive',
[Symbols.ToStringTag]: 'Symbol.toStringTag',
[Symbols.Unscopables]: 'Symbol.unscopables',
};
export const INV_SYMBOL_REF = /* @__PURE__ */ {
[SYM_ASYNC_ITERATOR]: Symbols.AsyncIterator,
[SYM_HAS_INSTANCE]: Symbols.HasInstance,
[SYM_IS_CONCAT_SPREADABLE]: Symbols.IsConcatSpreadable,
[SYM_ITERATOR]: Symbols.Iterator,
[SYM_MATCH]: Symbols.Match,
[SYM_MATCH_ALL]: Symbols.MatchAll,
[SYM_REPLACE]: Symbols.Replace,
[SYM_SEARCH]: Symbols.Search,
[SYM_SPECIES]: Symbols.Species,
[SYM_SPLIT]: Symbols.Split,
[SYM_TO_PRIMITIVE]: Symbols.ToPrimitive,
[SYM_TO_STRING_TAG]: Symbols.ToStringTag,
[SYM_UNSCOPABLES]: Symbols.Unscopables,
};
export type WellKnownSymbols = keyof typeof INV_SYMBOL_REF;
export const SYMBOL_REF: Record<Symbols, WellKnownSymbols> = {
[Symbols.AsyncIterator]: SYM_ASYNC_ITERATOR,
[Symbols.HasInstance]: SYM_HAS_INSTANCE,
[Symbols.IsConcatSpreadable]: SYM_IS_CONCAT_SPREADABLE,
[Symbols.Iterator]: SYM_ITERATOR,
[Symbols.Match]: SYM_MATCH,
[Symbols.MatchAll]: SYM_MATCH_ALL,
[Symbols.Replace]: SYM_REPLACE,
[Symbols.Search]: SYM_SEARCH,
[Symbols.Species]: SYM_SPECIES,
[Symbols.Split]: SYM_SPLIT,
[Symbols.ToPrimitive]: SYM_TO_PRIMITIVE,
[Symbols.ToStringTag]: SYM_TO_STRING_TAG,
[Symbols.Unscopables]: SYM_UNSCOPABLES,
};
export const CONSTANT_STRING: Record<SerovalConstant, string> = {
[SerovalConstant.True]: '!0',
[SerovalConstant.False]: '!1',
[SerovalConstant.Undefined]: 'void 0',
[SerovalConstant.Null]: 'null',
[SerovalConstant.NegZero]: '-0',
[SerovalConstant.Inf]: '1/0',
[SerovalConstant.NegInf]: '-1/0',
[SerovalConstant.Nan]: '0/0',
};
export const NIL = void 0;
export const CONSTANT_VAL: Record<SerovalConstant, unknown> = {
[SerovalConstant.True]: true,
[SerovalConstant.False]: false,
[SerovalConstant.Undefined]: NIL,
[SerovalConstant.Null]: null,
[SerovalConstant.NegZero]: -0,
[SerovalConstant.Inf]: Number.POSITIVE_INFINITY,
[SerovalConstant.NegInf]: Number.NEGATIVE_INFINITY,
[SerovalConstant.Nan]: Number.NaN,
};
export const enum ErrorConstructorTag {
Error = 0,
EvalError = 1,
RangeError = 2,
ReferenceError = 3,
SyntaxError = 4,
TypeError = 5,
URIError = 6,
}
export const ERROR_CONSTRUCTOR_STRING: Record<ErrorConstructorTag, string> = {
[ErrorConstructorTag.Error]: 'Error',
[ErrorConstructorTag.EvalError]: 'EvalError',
[ErrorConstructorTag.RangeError]: 'RangeError',
[ErrorConstructorTag.ReferenceError]: 'ReferenceError',
[ErrorConstructorTag.SyntaxError]: 'SyntaxError',
[ErrorConstructorTag.TypeError]: 'TypeError',
[ErrorConstructorTag.URIError]: 'URIError',
};
type ErrorConstructors =
| ErrorConstructor
| EvalErrorConstructor
| RangeErrorConstructor
| ReferenceErrorConstructor
| SyntaxErrorConstructor
| TypeErrorConstructor
| URIErrorConstructor;
export const ERROR_CONSTRUCTOR: Record<ErrorConstructorTag, ErrorConstructors> =
{
[ErrorConstructorTag.Error]: Error,
[ErrorConstructorTag.EvalError]: EvalError,
[ErrorConstructorTag.RangeError]: RangeError,
[ErrorConstructorTag.ReferenceError]: ReferenceError,
[ErrorConstructorTag.SyntaxError]: SyntaxError,
[ErrorConstructorTag.TypeError]: TypeError,
[ErrorConstructorTag.URIError]: URIError,
};

View file

@ -0,0 +1,270 @@
import type { Stream } from './stream';
type SpecialPromise = Promise<unknown> & { s?: 1 | 2; v?: unknown };
export interface PromiseConstructorResolver {
p: SpecialPromise;
s: (value: unknown) => void;
f: (value: unknown) => void;
}
export const PROMISE_CONSTRUCTOR = (): PromiseConstructorResolver => {
const resolver = {
p: 0,
s: 0,
f: 0,
} as unknown as PromiseConstructorResolver;
resolver.p = new Promise((resolve, reject) => {
resolver.s = resolve;
resolver.f = reject;
});
return resolver;
};
export const PROMISE_SUCCESS = (
resolver: PromiseConstructorResolver,
data: unknown,
): void => {
resolver.s(data);
resolver.p.s = 1;
resolver.p.v = data;
};
export const PROMISE_FAILURE = (
resolver: PromiseConstructorResolver,
data: unknown,
): void => {
resolver.f(data);
resolver.p.s = 2;
resolver.p.v = data;
};
export const SERIALIZED_PROMISE_CONSTRUCTOR =
/* @__PURE__ */ PROMISE_CONSTRUCTOR.toString();
export const SERIALIZED_PROMISE_SUCCESS =
/* @__PURE__ */ PROMISE_SUCCESS.toString();
export const SERIALIZED_PROMISE_FAILURE =
/* @__PURE__ */ PROMISE_FAILURE.toString();
interface StreamListener<T> {
next(value: T): void;
throw(value: unknown): void;
return(value: T): void;
}
export const STREAM_CONSTRUCTOR = () => {
const buffer: unknown[] = [];
const listeners: StreamListener<unknown>[] = [];
let alive = true;
let success = false;
let count = 0;
const flush = (
value: unknown,
mode: keyof StreamListener<unknown>,
x?: number,
) => {
for (x = 0; x < count; x++) {
if (listeners[x]) {
listeners[x][mode](value);
}
}
};
const up = (
listener: StreamListener<unknown>,
x?: number,
z?: number,
current?: unknown,
) => {
for (x = 0, z = buffer.length; x < z; x++) {
current = buffer[x];
if (!alive && x === z - 1) {
listener[success ? 'return' : 'throw'](current);
} else {
listener.next(current);
}
}
};
const on = (listener: StreamListener<unknown>, temp?: number) => {
if (alive) {
temp = count++;
listeners[temp] = listener;
}
up(listener);
return () => {
if (alive) {
listeners[temp!] = listeners[count];
listeners[count--] = undefined as any;
}
};
};
return {
__SEROVAL_STREAM__: true,
on: (listener: StreamListener<unknown>) => on(listener),
next: (value: unknown) => {
if (alive) {
buffer.push(value);
flush(value, 'next');
}
},
throw: (value: unknown) => {
if (alive) {
buffer.push(value);
flush(value, 'throw');
alive = false;
success = false;
listeners.length = 0;
}
},
return: (value: unknown) => {
if (alive) {
buffer.push(value);
flush(value, 'return');
alive = false;
success = true;
listeners.length = 0;
}
},
};
};
export const SERIALIZED_STREAM_CONSTRUCTOR =
/* @__PURE__ */ STREAM_CONSTRUCTOR.toString();
export interface Sequence {
v: unknown[];
t: number;
d: number;
}
export const ITERATOR_CONSTRUCTOR =
(symbol: symbol) => (sequence: Sequence) => () => {
let index = 0;
const instance = {
[symbol]: () => instance,
next: () => {
if (index > sequence.d) {
return {
done: true,
value: undefined,
};
}
const currentIndex = index++;
const data = sequence.v[currentIndex];
if (currentIndex === sequence.t) {
throw data;
}
return {
done: currentIndex === sequence.d,
value: data,
};
},
};
return instance;
};
export const SERIALIZED_ITERATOR_CONSTRUCTOR =
/* @__PURE__ */ ITERATOR_CONSTRUCTOR.toString();
export const ASYNC_ITERATOR_CONSTRUCTOR =
(symbol: symbol, createPromise: typeof PROMISE_CONSTRUCTOR) =>
(stream: Stream<unknown>) =>
() => {
let count = 0;
let doneAt = -1;
let isThrow = false;
const buffer: unknown[] = [];
const pending: PromiseConstructorResolver[] = [];
const finalize = (i = 0, len = pending.length) => {
for (; i < len; i++) {
pending[i].s({
done: true,
value: undefined,
});
}
};
stream.on({
next: value => {
const temp = pending.shift();
if (temp) {
temp.s({ done: false, value });
}
buffer.push(value);
},
throw: value => {
const temp = pending.shift();
if (temp) {
temp.f(value);
}
finalize();
doneAt = buffer.length;
isThrow = true;
buffer.push(value);
},
return: value => {
const temp = pending.shift();
if (temp) {
temp.s({ done: true, value });
}
finalize();
doneAt = buffer.length;
buffer.push(value);
},
});
const instance = {
[symbol]: () => instance,
next: () => {
if (doneAt === -1) {
const index = count++;
if (index >= buffer.length) {
const temp = createPromise();
pending.push(temp);
return temp.p;
}
return {
done: false,
value: buffer[index],
};
}
if (count > doneAt) {
return {
done: true,
value: undefined,
};
}
const index = count++;
const value = buffer[index];
if (index !== doneAt) {
return {
done: false,
value,
};
}
if (isThrow) {
throw value;
}
return {
done: true,
value,
};
},
};
return instance;
};
export const SERIALIZED_ASYNC_ITERATOR_CONSTRUCTOR =
/* @__PURE__ */ ASYNC_ITERATOR_CONSTRUCTOR.toString();
export const ARRAY_BUFFER_CONSTRUCTOR = (b64: string) => {
const decoded = atob(b64);
const length = decoded.length;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = decoded.charCodeAt(i);
}
return arr.buffer;
};
export const SERIALIZED_ARRAY_BUFFER_CONSTRUCTOR =
/* @__PURE__ */ ARRAY_BUFFER_CONSTRUCTOR.toString();

View file

@ -0,0 +1,638 @@
import {
createAggregateErrorNode,
createArrayNode,
createAsyncIteratorFactoryInstanceNode,
createBigIntNode,
createBigIntTypedArrayNode,
createBoxedNode,
createDataViewNode,
createDateNode,
createErrorNode,
createIteratorFactoryInstanceNode,
createNumberNode,
createPluginNode,
createRegExpNode,
createSetNode,
createStreamConstructorNode,
createStreamNextNode,
createStreamReturnNode,
createStreamThrowNode,
createStringNode,
createTypedArrayNode,
} from '../base-primitives';
import { Feature } from '../compat';
import { NIL, SerovalNodeType } from '../constants';
import { SerovalParserError, SerovalUnsupportedTypeError } from '../errors';
import { FALSE_NODE, NULL_NODE, TRUE_NODE, UNDEFINED_NODE } from '../literals';
import { createSerovalNode } from '../node';
import { OpaqueReference } from '../opaque-reference';
import type { SerovalMode } from '../plugin';
import { SpecialReference } from '../special-reference';
import type { Stream } from '../stream';
import { createStreamFromAsyncIterable, isStream } from '../stream';
import { serializeString } from '../string';
import {
SYM_ASYNC_ITERATOR,
SYM_IS_CONCAT_SPREADABLE,
SYM_ITERATOR,
SYM_TO_STRING_TAG,
} from '../symbols';
import type {
SerovalAggregateErrorNode,
SerovalArrayNode,
SerovalBigIntTypedArrayNode,
SerovalBoxedNode,
SerovalDataViewNode,
SerovalErrorNode,
SerovalMapNode,
SerovalNode,
SerovalNullConstructorNode,
SerovalObjectNode,
SerovalObjectRecordKey,
SerovalObjectRecordNode,
SerovalPluginNode,
SerovalPromiseNode,
SerovalSetNode,
SerovalStreamConstructorNode,
SerovalTypedArrayNode,
} from '../types';
import { getErrorOptions } from '../utils/error';
import { iteratorToSequence } from '../utils/iterator-to-sequence';
import promiseToResult from '../utils/promise-to-result';
import type {
BigIntTypedArrayValue,
TypedArrayValue,
} from '../utils/typed-array';
import type { BaseParserContext, BaseParserContextOptions } from './parser';
import {
createArrayBufferNode,
createBaseParserContext,
createMapNode,
createObjectNode,
getReferenceNode,
markParserRef,
parseAsyncIteratorFactory,
parseIteratorFactory,
ParserNodeType,
parseSpecialReference,
parseWellKnownSymbol,
} from './parser';
type ObjectLikeNode =
| SerovalObjectNode
| SerovalNullConstructorNode
| SerovalPromiseNode;
export type AsyncParserContextOptions = BaseParserContextOptions;
export interface AsyncParserContext {
base: BaseParserContext;
child: AsyncParsePluginContext | undefined;
}
export function createAsyncParserContext(
mode: SerovalMode,
options: AsyncParserContextOptions,
): AsyncParserContext {
return {
base: createBaseParserContext(mode, options),
child: undefined,
};
}
export class AsyncParsePluginContext {
constructor(
private _p: AsyncParserContext,
private depth: number,
) {}
parse<T>(current: T): Promise<SerovalNode> {
return parseAsync(this._p, this.depth, current);
}
}
async function parseItems(
ctx: AsyncParserContext,
depth: number,
current: unknown[],
): Promise<(SerovalNode | 0)[]> {
const nodes: (SerovalNode | 0)[] = [];
for (let i = 0, len = current.length; i < len; i++) {
// For consistency in holes
if (i in current) {
nodes[i] = await parseAsync(ctx, depth, current[i]);
} else {
nodes[i] = 0;
}
}
return nodes;
}
async function parseArray(
ctx: AsyncParserContext,
depth: number,
id: number,
current: unknown[],
): Promise<SerovalArrayNode> {
return createArrayNode(id, current, await parseItems(ctx, depth, current));
}
async function parseProperties(
ctx: AsyncParserContext,
depth: number,
properties: Record<string | symbol, unknown>,
): Promise<SerovalObjectRecordNode> {
const entries = Object.entries(properties);
const keyNodes: SerovalObjectRecordKey[] = [];
const valueNodes: SerovalNode[] = [];
for (let i = 0, len = entries.length; i < len; i++) {
keyNodes.push(serializeString(entries[i][0]));
valueNodes.push(await parseAsync(ctx, depth, entries[i][1]));
}
// Check special properties
if (SYM_ITERATOR in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_ITERATOR));
valueNodes.push(
createIteratorFactoryInstanceNode(
parseIteratorFactory(ctx.base),
await parseAsync(
ctx,
depth,
iteratorToSequence(properties as unknown as Iterable<unknown>),
),
),
);
}
if (SYM_ASYNC_ITERATOR in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_ASYNC_ITERATOR));
valueNodes.push(
createAsyncIteratorFactoryInstanceNode(
parseAsyncIteratorFactory(ctx.base),
await parseAsync(
ctx,
depth,
createStreamFromAsyncIterable(
properties as unknown as AsyncIterable<unknown>,
),
),
),
);
}
if (SYM_TO_STRING_TAG in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_TO_STRING_TAG));
valueNodes.push(createStringNode(properties[SYM_TO_STRING_TAG] as string));
}
if (SYM_IS_CONCAT_SPREADABLE in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_IS_CONCAT_SPREADABLE));
valueNodes.push(
properties[SYM_IS_CONCAT_SPREADABLE] ? TRUE_NODE : FALSE_NODE,
);
}
return {
k: keyNodes,
v: valueNodes,
};
}
async function parsePlainObject(
ctx: AsyncParserContext,
depth: number,
id: number,
current: Record<string, unknown>,
empty: boolean,
): Promise<ObjectLikeNode> {
return createObjectNode(
id,
current,
empty,
await parseProperties(ctx, depth, current),
);
}
// TODO: check if parseBoxedSync can be used
async function parseBoxed(
ctx: AsyncParserContext,
depth: number,
id: number,
current: object,
): Promise<SerovalBoxedNode> {
return createBoxedNode(id, await parseAsync(ctx, depth, current.valueOf()));
}
async function parseTypedArray(
ctx: AsyncParserContext,
depth: number,
id: number,
current: TypedArrayValue,
): Promise<SerovalTypedArrayNode> {
return createTypedArrayNode(
id,
current,
await parseAsync(ctx, depth, current.buffer),
);
}
async function parseBigIntTypedArray(
ctx: AsyncParserContext,
depth: number,
id: number,
current: BigIntTypedArrayValue,
): Promise<SerovalBigIntTypedArrayNode> {
return createBigIntTypedArrayNode(
id,
current,
await parseAsync(ctx, depth, current.buffer),
);
}
async function parseDataView(
ctx: AsyncParserContext,
depth: number,
id: number,
current: DataView,
): Promise<SerovalDataViewNode> {
return createDataViewNode(
id,
current,
await parseAsync(ctx, depth, current.buffer),
);
}
async function parseError(
ctx: AsyncParserContext,
depth: number,
id: number,
current: Error,
): Promise<SerovalErrorNode> {
const options = getErrorOptions(current, ctx.base.features);
return createErrorNode(
id,
current,
options ? await parseProperties(ctx, depth, options) : NIL,
);
}
async function parseAggregateError(
ctx: AsyncParserContext,
depth: number,
id: number,
current: AggregateError,
): Promise<SerovalAggregateErrorNode> {
const options = getErrorOptions(current, ctx.base.features);
return createAggregateErrorNode(
id,
current,
options ? await parseProperties(ctx, depth, options) : NIL,
);
}
async function parseMap(
ctx: AsyncParserContext,
depth: number,
id: number,
current: Map<unknown, unknown>,
): Promise<SerovalMapNode> {
const keyNodes: SerovalNode[] = [];
const valueNodes: SerovalNode[] = [];
for (const [key, value] of current.entries()) {
keyNodes.push(await parseAsync(ctx, depth, key));
valueNodes.push(await parseAsync(ctx, depth, value));
}
return createMapNode(ctx.base, id, keyNodes, valueNodes);
}
async function parseSet(
ctx: AsyncParserContext,
depth: number,
id: number,
current: Set<unknown>,
): Promise<SerovalSetNode> {
const items: SerovalNode[] = [];
for (const item of current.keys()) {
items.push(await parseAsync(ctx, depth, item));
}
return createSetNode(id, items);
}
async function parsePlugin(
ctx: AsyncParserContext,
depth: number,
id: number,
current: unknown,
): Promise<SerovalPluginNode | undefined> {
const currentPlugins = ctx.base.plugins;
if (currentPlugins) {
for (let i = 0, len = currentPlugins.length; i < len; i++) {
const plugin = currentPlugins[i];
if (plugin.parse.async && plugin.test(current)) {
return createPluginNode(
id,
plugin.tag,
await plugin.parse.async(
current,
new AsyncParsePluginContext(ctx, depth),
{
id,
},
),
);
}
}
}
return NIL;
}
async function parsePromise(
ctx: AsyncParserContext,
depth: number,
id: number,
current: Promise<unknown>,
): Promise<SerovalPromiseNode> {
const [status, result] = await promiseToResult(current);
return createSerovalNode(
SerovalNodeType.Promise,
id,
status,
NIL,
NIL,
NIL,
NIL,
NIL,
await parseAsync(ctx, depth, result),
NIL,
NIL,
NIL,
);
}
function parseStreamHandle<T>(
this: AsyncParserContext,
depth: number,
id: number,
current: Stream<T>,
resolve: (value: SerovalNode[] | PromiseLike<SerovalNode[]>) => void,
reject: (reason?: any) => void,
): void {
const sequence: SerovalNode[] = [];
// TODO Optimizable
const cleanup = current.on({
next: value => {
markParserRef(this.base, id);
parseAsync(this, depth, value).then(
data => {
sequence.push(createStreamNextNode(id, data));
},
data => {
reject(data);
cleanup();
},
);
},
throw: value => {
markParserRef(this.base, id);
parseAsync(this, depth, value).then(
data => {
sequence.push(createStreamThrowNode(id, data));
resolve(sequence);
cleanup();
},
data => {
reject(data);
cleanup();
},
);
},
return: value => {
markParserRef(this.base, id);
parseAsync(this, depth, value).then(
data => {
sequence.push(createStreamReturnNode(id, data));
resolve(sequence);
cleanup();
},
data => {
reject(data);
cleanup();
},
);
},
});
}
async function parseStream(
ctx: AsyncParserContext,
depth: number,
id: number,
current: Stream<unknown>,
): Promise<SerovalStreamConstructorNode> {
return createStreamConstructorNode(
id,
parseSpecialReference(ctx.base, SpecialReference.StreamConstructor),
await new Promise<SerovalNode[]>(
parseStreamHandle.bind(ctx, depth, id, current),
),
);
}
export async function parseObjectAsync(
ctx: AsyncParserContext,
depth: number,
id: number,
current: object,
): Promise<SerovalNode> {
if (Array.isArray(current)) {
return parseArray(ctx, depth, id, current);
}
if (isStream(current)) {
return parseStream(ctx, depth, id, current);
}
const currentClass = current.constructor;
if (currentClass === OpaqueReference) {
return parseAsync(
ctx,
depth,
(current as OpaqueReference<unknown, unknown>).replacement,
);
}
const parsed = await parsePlugin(ctx, depth, id, current);
if (parsed) {
return parsed;
}
switch (currentClass) {
case Object:
return parsePlainObject(
ctx,
depth,
id,
current as Record<string, unknown>,
false,
);
case NIL:
return parsePlainObject(
ctx,
depth,
id,
current as Record<string, unknown>,
true,
);
case Date:
return createDateNode(id, current as unknown as Date);
case Error:
case EvalError:
case RangeError:
case ReferenceError:
case SyntaxError:
case TypeError:
case URIError:
return parseError(ctx, depth, id, current as unknown as Error);
case Number:
case Boolean:
case String:
case BigInt:
return parseBoxed(ctx, depth, id, current);
case ArrayBuffer:
return createArrayBufferNode(
ctx.base,
id,
current as unknown as ArrayBuffer,
);
case Int8Array:
case Int16Array:
case Int32Array:
case Uint8Array:
case Uint16Array:
case Uint32Array:
case Uint8ClampedArray:
case Float32Array:
case Float64Array:
return parseTypedArray(
ctx,
depth,
id,
current as unknown as TypedArrayValue,
);
case DataView:
return parseDataView(ctx, depth, id, current as unknown as DataView);
case Map:
return parseMap(
ctx,
depth,
id,
current as unknown as Map<unknown, unknown>,
);
case Set:
return parseSet(ctx, depth, id, current as unknown as Set<unknown>);
default:
break;
}
// Promises
if (currentClass === Promise || current instanceof Promise) {
return parsePromise(ctx, depth, id, current as unknown as Promise<unknown>);
}
const currentFeatures = ctx.base.features;
if (currentFeatures & Feature.RegExp && currentClass === RegExp) {
return createRegExpNode(id, current as unknown as RegExp);
}
// BigInt Typed Arrays
if (currentFeatures & Feature.BigIntTypedArray) {
switch (currentClass) {
case BigInt64Array:
case BigUint64Array:
return parseBigIntTypedArray(
ctx,
depth,
id,
current as unknown as BigIntTypedArrayValue,
);
default:
break;
}
}
if (
currentFeatures & Feature.AggregateError &&
typeof AggregateError !== 'undefined' &&
(currentClass === AggregateError || current instanceof AggregateError)
) {
return parseAggregateError(
ctx,
depth,
id,
current as unknown as AggregateError,
);
}
// Slow path. We only need to handle Errors and Iterators
// since they have very broad implementations.
if (current instanceof Error) {
return parseError(ctx, depth, id, current);
}
// Generator functions don't have a global constructor
// despite existing
if (SYM_ITERATOR in current || SYM_ASYNC_ITERATOR in current) {
return parsePlainObject(ctx, depth, id, current, !!currentClass);
}
throw new SerovalUnsupportedTypeError(current);
}
export async function parseFunctionAsync(
ctx: AsyncParserContext,
depth: number,
current: unknown,
): Promise<SerovalNode> {
const ref = getReferenceNode(ctx.base, current);
if (ref.type !== ParserNodeType.Fresh) {
return ref.value;
}
const plugin = await parsePlugin(ctx, depth, ref.value, current);
if (plugin) {
return plugin;
}
throw new SerovalUnsupportedTypeError(current);
}
export async function parseAsync<T>(
ctx: AsyncParserContext,
depth: number,
current: T,
): Promise<SerovalNode> {
switch (typeof current) {
case 'boolean':
return current ? TRUE_NODE : FALSE_NODE;
case 'undefined':
return UNDEFINED_NODE;
case 'string':
return createStringNode(current as string);
case 'number':
return createNumberNode(current as number);
case 'bigint':
return createBigIntNode(current as bigint);
case 'object': {
if (current) {
const ref = getReferenceNode(ctx.base, current);
return ref.type === 0
? await parseObjectAsync(ctx, depth + 1, ref.value, current as object)
: ref.value;
}
return NULL_NODE;
}
case 'symbol':
return parseWellKnownSymbol(ctx.base, current);
case 'function':
return parseFunctionAsync(ctx, depth, current);
default:
throw new SerovalUnsupportedTypeError(current);
}
}
export async function parseTopAsync<T>(
ctx: AsyncParserContext,
current: T,
): Promise<SerovalNode> {
try {
return await parseAsync(ctx, 0, current);
} catch (error) {
throw error instanceof SerovalParserError
? error
: new SerovalParserError(error);
}
}

View file

@ -0,0 +1,810 @@
import { ALL_ENABLED, Feature } from '../compat';
import {
CONSTANT_VAL,
ERROR_CONSTRUCTOR,
NIL,
SerovalNodeType,
SerovalObjectFlags,
SYMBOL_REF,
} from '../constants';
import {
ARRAY_BUFFER_CONSTRUCTOR,
PROMISE_CONSTRUCTOR,
type PromiseConstructorResolver,
} from '../constructors';
import {
SerovalDepthLimitError,
SerovalDeserializationError,
SerovalMalformedNodeError,
SerovalMissingInstanceError,
SerovalMissingPluginError,
SerovalUnsupportedNodeError,
} from '../errors';
import type { PluginAccessOptions } from '../plugin';
import { SerovalMode } from '../plugin';
import { getReference } from '../reference';
import type { Stream } from '../stream';
import { createStream, isStream, streamToAsyncIterable } from '../stream';
import { deserializeString } from '../string';
import {
SYM_ASYNC_ITERATOR,
SYM_IS_CONCAT_SPREADABLE,
SYM_ITERATOR,
SYM_TO_STRING_TAG,
} from '../symbols';
import type {
SerovalAggregateErrorNode,
SerovalArrayBufferNode,
SerovalArrayNode,
SerovalAsyncIteratorFactoryInstanceNode,
SerovalAsyncIteratorFactoryNode,
SerovalBigIntTypedArrayNode,
SerovalBoxedNode,
SerovalDataViewNode,
SerovalDateNode,
SerovalErrorNode,
SerovalIteratorFactoryInstanceNode,
SerovalIteratorFactoryNode,
SerovalMapNode,
SerovalNode,
SerovalNullConstructorNode,
SerovalObjectNode,
SerovalObjectRecordNode,
SerovalPluginNode,
SerovalPromiseConstructorNode,
SerovalPromiseNode,
SerovalPromiseRejectNode,
SerovalPromiseResolveNode,
SerovalReferenceNode,
SerovalRegExpNode,
SerovalSetNode,
SerovalStreamConstructorNode,
SerovalStreamNextNode,
SerovalStreamReturnNode,
SerovalStreamThrowNode,
SerovalTypedArrayNode,
} from '../types';
import type { Sequence } from '../utils/iterator-to-sequence';
import { sequenceToIterator } from '../utils/iterator-to-sequence';
import type {
BigIntTypedArrayValue,
TypedArrayValue,
} from '../utils/typed-array';
import { getTypedArrayConstructor } from '../utils/typed-array';
const MAX_BASE64_LENGTH = 1_000_000; // ~0.75MB decoded
const MAX_BIGINT_LENGTH = 10_000;
const MAX_REGEXP_SOURCE_LENGTH = 20_000;
function applyObjectFlag(obj: unknown, flag: SerovalObjectFlags): unknown {
switch (flag) {
case SerovalObjectFlags.Frozen:
return Object.freeze(obj);
case SerovalObjectFlags.NonExtensible:
return Object.preventExtensions(obj);
case SerovalObjectFlags.Sealed:
return Object.seal(obj);
default:
return obj;
}
}
type AssignableValue = AggregateError | Error | Iterable<unknown>;
type AssignableNode = SerovalAggregateErrorNode | SerovalErrorNode;
export interface BaseDeserializerContextOptions extends PluginAccessOptions {
refs?: Map<number, unknown>;
features?: number;
disabledFeatures?: number;
depthLimit?: number;
}
export interface BaseDeserializerContext extends PluginAccessOptions {
readonly mode: SerovalMode;
/**
* Mapping ids to values
*/
refs: Map<number, unknown>;
features: number;
depthLimit: number;
}
const DEFAULT_DEPTH_LIMIT = 1000;
export function createBaseDeserializerContext(
mode: SerovalMode,
options: BaseDeserializerContextOptions,
): BaseDeserializerContext {
return {
mode,
plugins: options.plugins,
refs: options.refs || new Map(),
features: options.features ?? ALL_ENABLED ^ (options.disabledFeatures || 0),
depthLimit: options.depthLimit || DEFAULT_DEPTH_LIMIT,
};
}
export interface VanillaDeserializerContextOptions
extends Omit<BaseDeserializerContextOptions, 'refs'> {
markedRefs: number[] | Set<number>;
}
export interface VanillaDeserializerState {
marked: Set<number>;
}
export interface VanillaDeserializerContext {
mode: SerovalMode.Vanilla;
base: BaseDeserializerContext;
child: DeserializePluginContext | undefined;
state: VanillaDeserializerState;
}
export function createVanillaDeserializerContext(
options: VanillaDeserializerContextOptions,
): VanillaDeserializerContext {
return {
mode: SerovalMode.Vanilla,
base: createBaseDeserializerContext(SerovalMode.Vanilla, options),
child: NIL,
state: {
marked: new Set(options.markedRefs),
},
};
}
export interface CrossDeserializerContext {
mode: SerovalMode.Cross;
base: BaseDeserializerContext;
child: DeserializePluginContext | undefined;
}
export type CrossDeserializerContextOptions = BaseDeserializerContextOptions;
export function createCrossDeserializerContext(
options: CrossDeserializerContextOptions,
): CrossDeserializerContext {
return {
mode: SerovalMode.Cross,
base: createBaseDeserializerContext(SerovalMode.Cross, options),
child: NIL,
};
}
type DeserializerContext =
| VanillaDeserializerContext
| CrossDeserializerContext;
export class DeserializePluginContext {
constructor(
private _p: DeserializerContext,
private depth: number,
) {}
deserialize<T>(node: SerovalNode): T {
return deserialize(this._p, this.depth, node) as T;
}
}
function guardIndexedValue(ctx: BaseDeserializerContext, id: number): void {
if (id < 0 || !Number.isFinite(id) || !Number.isInteger(id)) {
throw new SerovalMalformedNodeError({
t: SerovalNodeType.IndexedValue,
i: id,
} as SerovalNode);
}
if (ctx.refs.has(id)) {
throw new Error('Conflicted ref id: ' + id);
}
}
function assignIndexedValueVanilla<T>(
ctx: VanillaDeserializerContext,
id: number,
value: T,
): T {
guardIndexedValue(ctx.base, id);
if (ctx.state.marked.has(id)) {
ctx.base.refs.set(id, value);
}
return value;
}
function assignIndexedValueCross<T>(
ctx: CrossDeserializerContext,
id: number,
value: T,
): T {
guardIndexedValue(ctx.base, id);
ctx.base.refs.set(id, value);
return value;
}
function assignIndexedValue<T>(
ctx: DeserializerContext,
id: number,
value: T,
): T {
return ctx.mode === SerovalMode.Vanilla
? assignIndexedValueVanilla(ctx, id, value)
: assignIndexedValueCross(ctx, id, value);
}
function deserializeKnownValue<
T extends Record<string, unknown>,
K extends keyof T,
>(node: SerovalNode, record: T, key: K): T[K] {
if (Object.hasOwn(record, key)) {
return record[key];
}
throw new SerovalMalformedNodeError(node);
}
function deserializeReference(
ctx: DeserializerContext,
node: SerovalReferenceNode,
): unknown {
return assignIndexedValue(
ctx,
node.i,
getReference(deserializeString(node.s)),
);
}
function deserializeArray(
ctx: DeserializerContext,
depth: number,
node: SerovalArrayNode,
): unknown[] {
const items = node.a;
const len = items.length;
const result: unknown[] = assignIndexedValue(
ctx,
node.i,
new Array<unknown>(len),
);
for (let i = 0, item: SerovalNode | 0; i < len; i++) {
item = items[i];
if (item) {
result[i] = deserialize(ctx, depth, item);
}
}
applyObjectFlag(result, node.o);
return result;
}
function isValidKey(key: string): boolean {
switch (key) {
case 'constructor':
case '__proto__':
case 'prototype':
case '__defineGetter__':
case '__defineSetter__':
case '__lookupGetter__':
case '__lookupSetter__':
// case 'then':
return false;
default:
return true;
}
}
function isValidSymbol(symbol: symbol): boolean {
switch (symbol) {
case SYM_ASYNC_ITERATOR:
case SYM_IS_CONCAT_SPREADABLE:
case SYM_TO_STRING_TAG:
case SYM_ITERATOR:
return true;
default:
return false;
}
}
function assignStringProperty(
object: Record<string | symbol, unknown>,
key: string,
value: unknown,
): void {
if (isValidKey(key)) {
object[key] = value;
} else {
Object.defineProperty(object, key, {
value,
configurable: true,
enumerable: true,
writable: true,
});
}
}
function assignProperty(
ctx: DeserializerContext,
depth: number,
object: Record<string | symbol, unknown>,
key: string | SerovalNode,
value: SerovalNode,
): void {
if (typeof key === 'string') {
assignStringProperty(object, key, deserialize(ctx, depth, value));
} else {
const actual = deserialize(ctx, depth, key);
switch (typeof actual) {
case 'string':
assignStringProperty(object, actual, deserialize(ctx, depth, value));
break;
case 'symbol':
if (isValidSymbol(actual)) {
object[actual] = deserialize(ctx, depth, value);
}
break;
default:
throw new SerovalMalformedNodeError(key);
}
}
}
function deserializeProperties(
ctx: DeserializerContext,
depth: number,
node: SerovalObjectRecordNode,
result: Record<string | symbol, unknown>,
): Record<string | symbol, unknown> {
const keys = node.k;
const len = keys.length;
if (len > 0) {
for (let i = 0, vals = node.v, len = keys.length; i < len; i++) {
assignProperty(ctx, depth, result, keys[i], vals[i]);
}
}
return result;
}
function deserializeObject(
ctx: DeserializerContext,
depth: number,
node: SerovalObjectNode | SerovalNullConstructorNode,
): Record<string, unknown> {
const result = assignIndexedValue(
ctx,
node.i,
(node.t === SerovalNodeType.Object ? {} : Object.create(null)) as Record<
string,
unknown
>,
);
deserializeProperties(ctx, depth, node.p, result);
applyObjectFlag(result, node.o);
return result;
}
function deserializeDate(
ctx: DeserializerContext,
node: SerovalDateNode,
): Date {
return assignIndexedValue(ctx, node.i, new Date(node.s));
}
function deserializeRegExp(
ctx: DeserializerContext,
node: SerovalRegExpNode,
): RegExp {
if (ctx.base.features & Feature.RegExp) {
const source = deserializeString(node.c);
if (source.length > MAX_REGEXP_SOURCE_LENGTH) {
throw new SerovalMalformedNodeError(node);
}
return assignIndexedValue(ctx, node.i, new RegExp(source, node.m));
}
throw new SerovalUnsupportedNodeError(node);
}
function deserializeSet(
ctx: DeserializerContext,
depth: number,
node: SerovalSetNode,
): Set<unknown> {
const result = assignIndexedValue(ctx, node.i, new Set<unknown>());
for (let i = 0, items = node.a, len = items.length; i < len; i++) {
result.add(deserialize(ctx, depth, items[i]));
}
return result;
}
function deserializeMap(
ctx: DeserializerContext,
depth: number,
node: SerovalMapNode,
): Map<unknown, unknown> {
const result = assignIndexedValue(ctx, node.i, new Map<unknown, unknown>());
for (
let i = 0, keys = node.e.k, vals = node.e.v, len = keys.length;
i < len;
i++
) {
result.set(
deserialize(ctx, depth, keys[i]),
deserialize(ctx, depth, vals[i]),
);
}
return result;
}
function deserializeArrayBuffer(
ctx: DeserializerContext,
node: SerovalArrayBufferNode,
): ArrayBuffer {
if (node.s.length > MAX_BASE64_LENGTH) {
throw new SerovalMalformedNodeError(node);
}
const result = assignIndexedValue(
ctx,
node.i,
ARRAY_BUFFER_CONSTRUCTOR(deserializeString(node.s)),
);
return result;
}
function deserializeTypedArray(
ctx: DeserializerContext,
depth: number,
node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
): TypedArrayValue | BigIntTypedArrayValue {
const construct = getTypedArrayConstructor(node.c) as Int8ArrayConstructor;
const source = deserialize(ctx, depth, node.f) as ArrayBuffer;
const offset = node.b ?? 0;
if (offset < 0 || offset > source.byteLength) {
throw new SerovalMalformedNodeError(node);
}
const result = assignIndexedValue(
ctx,
node.i,
new construct(source, offset, node.l),
);
return result;
}
function deserializeDataView(
ctx: DeserializerContext,
depth: number,
node: SerovalDataViewNode,
): DataView {
const source = deserialize(ctx, depth, node.f) as ArrayBuffer;
const offset = node.b ?? 0;
if (offset < 0 || offset > source.byteLength) {
throw new SerovalMalformedNodeError(node);
}
const result = assignIndexedValue(
ctx,
node.i,
new DataView(source, offset, node.l),
);
return result;
}
function deserializeDictionary<T extends AssignableValue>(
ctx: DeserializerContext,
depth: number,
node: AssignableNode,
result: T,
): T {
if (node.p) {
const fields = deserializeProperties(ctx, depth, node.p, {});
Object.defineProperties(result, Object.getOwnPropertyDescriptors(fields));
}
return result;
}
function deserializeAggregateError(
ctx: DeserializerContext,
depth: number,
node: SerovalAggregateErrorNode,
): AggregateError {
// Serialize the required arguments
const result = assignIndexedValue(
ctx,
node.i,
new AggregateError([], deserializeString(node.m)),
);
// `AggregateError` might've been extended
// either through class or custom properties
// Make sure to assign extra properties
return deserializeDictionary(ctx, depth, node, result);
}
function deserializeError(
ctx: DeserializerContext,
depth: number,
node: SerovalErrorNode,
): Error {
const construct = deserializeKnownValue(node, ERROR_CONSTRUCTOR, node.s);
const result = assignIndexedValue(
ctx,
node.i,
new construct(deserializeString(node.m)),
);
return deserializeDictionary(ctx, depth, node, result);
}
function deserializePromise(
ctx: DeserializerContext,
depth: number,
node: SerovalPromiseNode,
): Promise<unknown> {
const deferred = PROMISE_CONSTRUCTOR();
const result = assignIndexedValue(ctx, node.i, deferred.p);
const deserialized = deserialize(ctx, depth, node.f);
if (node.s) {
deferred.s(deserialized);
} else {
deferred.f(deserialized);
}
return result;
}
function deserializeBoxed(
ctx: DeserializerContext,
depth: number,
node: SerovalBoxedNode,
): unknown {
return assignIndexedValue(
ctx,
node.i,
// biome-ignore lint/style/useConsistentBuiltinInstantiation: intended
Object(deserialize(ctx, depth, node.f)),
);
}
function deserializePlugin(
ctx: DeserializerContext,
depth: number,
node: SerovalPluginNode,
): unknown {
const currentPlugins = ctx.base.plugins;
if (currentPlugins) {
const tag = deserializeString(node.c);
for (let i = 0, len = currentPlugins.length; i < len; i++) {
const plugin = currentPlugins[i];
if (plugin.tag === tag) {
return assignIndexedValue(
ctx,
node.i,
plugin.deserialize(node.s, new DeserializePluginContext(ctx, depth), {
id: node.i,
}),
);
}
}
}
throw new SerovalMissingPluginError(node.c);
}
function deserializePromiseConstructor(
ctx: DeserializerContext,
node: SerovalPromiseConstructorNode,
): unknown {
return assignIndexedValue(
ctx,
node.i,
assignIndexedValue(ctx, node.s, PROMISE_CONSTRUCTOR()).p,
);
}
function deserializePromiseResolve(
ctx: DeserializerContext,
depth: number,
node: SerovalPromiseResolveNode,
): unknown {
const deferred = ctx.base.refs.get(node.i) as
| PromiseConstructorResolver
| undefined;
if (deferred) {
deferred.s(deserialize(ctx, depth, node.a[1]));
return NIL;
}
throw new SerovalMissingInstanceError('Promise');
}
function deserializePromiseReject(
ctx: DeserializerContext,
depth: number,
node: SerovalPromiseRejectNode,
): unknown {
const deferred = ctx.base.refs.get(node.i) as
| PromiseConstructorResolver
| undefined;
if (deferred) {
deferred.f(deserialize(ctx, depth, node.a[1]));
return NIL;
}
throw new SerovalMissingInstanceError('Promise');
}
function deserializeIteratorFactoryInstance(
ctx: DeserializerContext,
depth: number,
node: SerovalIteratorFactoryInstanceNode,
): unknown {
deserialize(ctx, depth, node.a[0]);
const source = deserialize(ctx, depth, node.a[1]);
return sequenceToIterator(source as Sequence);
}
function deserializeAsyncIteratorFactoryInstance(
ctx: DeserializerContext,
depth: number,
node: SerovalAsyncIteratorFactoryInstanceNode,
): unknown {
deserialize(ctx, depth, node.a[0]);
const source = deserialize(ctx, depth, node.a[1]);
return streamToAsyncIterable(source as Stream<any>);
}
function deserializeStreamConstructor(
ctx: DeserializerContext,
depth: number,
node: SerovalStreamConstructorNode,
): unknown {
const result = assignIndexedValue(ctx, node.i, createStream());
const items = node.a;
const len = items.length;
if (len) {
for (let i = 0; i < len; i++) {
deserialize(ctx, depth, items[i]);
}
}
return result;
}
function deserializeStreamNext(
ctx: DeserializerContext,
depth: number,
node: SerovalStreamNextNode,
): unknown {
const deferred = ctx.base.refs.get(node.i) as Stream<unknown> | undefined;
if (deferred && isStream(deferred)) {
deferred.next(deserialize(ctx, depth, node.f));
return NIL;
}
throw new SerovalMissingInstanceError('Stream');
}
function deserializeStreamThrow(
ctx: DeserializerContext,
depth: number,
node: SerovalStreamThrowNode,
): unknown {
const deferred = ctx.base.refs.get(node.i) as Stream<unknown> | undefined;
if (deferred && isStream(deferred)) {
deferred.throw(deserialize(ctx, depth, node.f));
return NIL;
}
throw new SerovalMissingInstanceError('Stream');
}
function deserializeStreamReturn(
ctx: DeserializerContext,
depth: number,
node: SerovalStreamReturnNode,
): unknown {
const deferred = ctx.base.refs.get(node.i) as Stream<unknown> | undefined;
if (deferred && isStream(deferred)) {
deferred.return(deserialize(ctx, depth, node.f));
return NIL;
}
throw new SerovalMissingInstanceError('Stream');
}
function deserializeIteratorFactory(
ctx: DeserializerContext,
depth: number,
node: SerovalIteratorFactoryNode,
): unknown {
deserialize(ctx, depth, node.f);
return NIL;
}
function deserializeAsyncIteratorFactory(
ctx: DeserializerContext,
depth: number,
node: SerovalAsyncIteratorFactoryNode,
): unknown {
deserialize(ctx, depth, node.a[1]);
return NIL;
}
function deserialize(
ctx: DeserializerContext,
depth: number,
node: SerovalNode,
): unknown {
if (depth > ctx.base.depthLimit) {
throw new SerovalDepthLimitError(ctx.base.depthLimit);
}
depth += 1;
switch (node.t) {
case SerovalNodeType.Constant:
return deserializeKnownValue(node, CONSTANT_VAL, node.s);
case SerovalNodeType.Number:
return Number(node.s);
case SerovalNodeType.String:
return deserializeString(String(node.s));
case SerovalNodeType.BigInt:
if (String(node.s).length > MAX_BIGINT_LENGTH) {
throw new SerovalMalformedNodeError(node);
}
return BigInt(node.s);
case SerovalNodeType.IndexedValue:
return ctx.base.refs.get(node.i);
case SerovalNodeType.Reference:
return deserializeReference(ctx, node);
case SerovalNodeType.Array:
return deserializeArray(ctx, depth, node);
case SerovalNodeType.Object:
case SerovalNodeType.NullConstructor:
return deserializeObject(ctx, depth, node);
case SerovalNodeType.Date:
return deserializeDate(ctx, node);
case SerovalNodeType.RegExp:
return deserializeRegExp(ctx, node);
case SerovalNodeType.Set:
return deserializeSet(ctx, depth, node);
case SerovalNodeType.Map:
return deserializeMap(ctx, depth, node);
case SerovalNodeType.ArrayBuffer:
return deserializeArrayBuffer(ctx, node);
case SerovalNodeType.BigIntTypedArray:
case SerovalNodeType.TypedArray:
return deserializeTypedArray(ctx, depth, node);
case SerovalNodeType.DataView:
return deserializeDataView(ctx, depth, node);
case SerovalNodeType.AggregateError:
return deserializeAggregateError(ctx, depth, node);
case SerovalNodeType.Error:
return deserializeError(ctx, depth, node);
case SerovalNodeType.Promise:
return deserializePromise(ctx, depth, node);
case SerovalNodeType.WKSymbol:
return deserializeKnownValue(node, SYMBOL_REF, node.s);
case SerovalNodeType.Boxed:
return deserializeBoxed(ctx, depth, node);
case SerovalNodeType.Plugin:
return deserializePlugin(ctx, depth, node);
case SerovalNodeType.PromiseConstructor:
return deserializePromiseConstructor(ctx, node);
case SerovalNodeType.PromiseSuccess:
return deserializePromiseResolve(ctx, depth, node);
case SerovalNodeType.PromiseFailure:
return deserializePromiseReject(ctx, depth, node);
case SerovalNodeType.IteratorFactoryInstance:
return deserializeIteratorFactoryInstance(ctx, depth, node);
case SerovalNodeType.AsyncIteratorFactoryInstance:
return deserializeAsyncIteratorFactoryInstance(ctx, depth, node);
case SerovalNodeType.StreamConstructor:
return deserializeStreamConstructor(ctx, depth, node);
case SerovalNodeType.StreamNext:
return deserializeStreamNext(ctx, depth, node);
case SerovalNodeType.StreamThrow:
return deserializeStreamThrow(ctx, depth, node);
case SerovalNodeType.StreamReturn:
return deserializeStreamReturn(ctx, depth, node);
case SerovalNodeType.IteratorFactory:
return deserializeIteratorFactory(ctx, depth, node);
case SerovalNodeType.AsyncIteratorFactory:
return deserializeAsyncIteratorFactory(ctx, depth, node);
// case SerovalNodeType.SpecialReference:
default:
throw new SerovalUnsupportedNodeError(node);
}
}
export function deserializeTop(
ctx: DeserializerContext,
node: SerovalNode,
): unknown {
try {
return deserialize(ctx, 0, node);
} catch (error) {
throw new SerovalDeserializationError(error);
}
}

View file

@ -0,0 +1,335 @@
import {
createIndexedValueNode,
createReferenceNode,
createWKSymbolNode,
} from '../base-primitives';
import { ALL_ENABLED } from '../compat';
import type { WellKnownSymbols } from '../constants';
import { INV_SYMBOL_REF, NIL, SerovalNodeType } from '../constants';
import { SerovalUnsupportedTypeError } from '../errors';
import { createSerovalNode } from '../node';
import type { PluginAccessOptions, SerovalMode } from '../plugin';
import { hasReferenceID } from '../reference';
import {
ASYNC_ITERATOR,
ITERATOR,
SPECIAL_REFS,
SpecialReference,
} from '../special-reference';
import { serializeString } from '../string';
import { SYM_ASYNC_ITERATOR, SYM_ITERATOR } from '../symbols';
import type {
SerovalArrayBufferNode,
SerovalAsyncIteratorFactoryNode,
SerovalIndexedValueNode,
SerovalIteratorFactoryNode,
SerovalMapNode,
SerovalNode,
SerovalNullConstructorNode,
SerovalObjectNode,
SerovalObjectRecordNode,
SerovalPromiseConstructorNode,
SerovalReferenceNode,
SerovalSpecialReferenceNode,
SerovalWKSymbolNode,
} from '../types';
import { getObjectFlag } from '../utils/get-object-flag';
export interface BaseParserContextOptions extends PluginAccessOptions {
disabledFeatures?: number;
refs?: Map<unknown, number>;
depthLimit?: number;
}
export const enum ParserNodeType {
Fresh = 0,
Indexed = 1,
Referenced = 2,
}
export interface FreshNode {
type: ParserNodeType.Fresh;
value: number;
}
export interface IndexedNode {
type: ParserNodeType.Indexed;
value: SerovalIndexedValueNode;
}
export interface ReferencedNode {
type: ParserNodeType.Referenced;
value: SerovalReferenceNode;
}
type ObjectNode = FreshNode | IndexedNode | ReferencedNode;
export interface BaseParserContext extends PluginAccessOptions {
readonly mode: SerovalMode;
marked: Set<number>;
refs: Map<unknown, number>;
features: number;
depthLimit: number;
}
export function createBaseParserContext(
mode: SerovalMode,
options: BaseParserContextOptions,
): BaseParserContext {
return {
plugins: options.plugins,
mode,
marked: new Set(),
features: ALL_ENABLED ^ (options.disabledFeatures || 0),
refs: options.refs || new Map(),
depthLimit: options.depthLimit || 1000,
};
}
/**
* Ensures that the value (based on an identifier) has been visited by the parser.
* @param ctx
* @param id
*/
export function markParserRef(ctx: BaseParserContext, id: number): void {
ctx.marked.add(id);
}
export function isParserRefMarked(ctx: BaseParserContext, id: number): boolean {
return ctx.marked.has(id);
}
/**
* Creates an identifier for a value
* @param ctx
* @param current
*/
export function createIndexForValue<T>(
ctx: BaseParserContext,
current: T,
): number {
const id = ctx.refs.size;
ctx.refs.set(current, id);
return id;
}
export function getNodeForIndexedValue<T>(
ctx: BaseParserContext,
current: T,
): FreshNode | IndexedNode {
const registeredId = ctx.refs.get(current);
if (registeredId != null) {
markParserRef(ctx, registeredId);
return {
type: ParserNodeType.Indexed,
value: createIndexedValueNode(registeredId),
};
}
return {
type: ParserNodeType.Fresh,
value: createIndexForValue(ctx, current),
};
}
export function getReferenceNode<T>(
ctx: BaseParserContext,
current: T,
): ObjectNode {
const indexed = getNodeForIndexedValue(ctx, current);
if (indexed.type === ParserNodeType.Indexed) {
return indexed;
}
// Special references are special ;)
if (hasReferenceID(current)) {
return {
type: ParserNodeType.Referenced,
value: createReferenceNode(indexed.value, current),
};
}
return indexed;
}
/**
* Parsing methods
*/
export function parseWellKnownSymbol(
ctx: BaseParserContext,
current: symbol,
): SerovalIndexedValueNode | SerovalWKSymbolNode | SerovalReferenceNode {
const ref = getReferenceNode(ctx, current);
if (ref.type !== ParserNodeType.Fresh) {
return ref.value;
}
if (current in INV_SYMBOL_REF) {
return createWKSymbolNode(ref.value, current as WellKnownSymbols);
}
throw new SerovalUnsupportedTypeError(current);
}
export function parseSpecialReference(
ctx: BaseParserContext,
ref: SpecialReference,
): SerovalIndexedValueNode | SerovalSpecialReferenceNode {
const result = getNodeForIndexedValue(ctx, SPECIAL_REFS[ref]);
if (result.type === ParserNodeType.Indexed) {
return result.value;
}
return createSerovalNode(
SerovalNodeType.SpecialReference,
result.value,
ref,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export function parseIteratorFactory(
ctx: BaseParserContext,
): SerovalIndexedValueNode | SerovalIteratorFactoryNode {
const result = getNodeForIndexedValue(ctx, ITERATOR);
if (result.type === ParserNodeType.Indexed) {
return result.value;
}
return createSerovalNode(
SerovalNodeType.IteratorFactory,
result.value,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
parseWellKnownSymbol(ctx, SYM_ITERATOR),
NIL,
NIL,
NIL,
);
}
export function parseAsyncIteratorFactory(
ctx: BaseParserContext,
): SerovalIndexedValueNode | SerovalAsyncIteratorFactoryNode {
const result = getNodeForIndexedValue(ctx, ASYNC_ITERATOR);
if (result.type === ParserNodeType.Indexed) {
return result.value;
}
return createSerovalNode(
SerovalNodeType.AsyncIteratorFactory,
result.value,
NIL,
NIL,
NIL,
NIL,
NIL,
[
parseSpecialReference(ctx, SpecialReference.PromiseConstructor),
parseWellKnownSymbol(ctx, SYM_ASYNC_ITERATOR),
],
NIL,
NIL,
NIL,
NIL,
);
}
export function createObjectNode(
id: number,
current: Record<string, unknown>,
empty: boolean,
record: SerovalObjectRecordNode,
): SerovalObjectNode | SerovalNullConstructorNode {
return createSerovalNode(
empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
id,
NIL,
NIL,
NIL,
record,
NIL,
NIL,
NIL,
NIL,
getObjectFlag(current),
NIL,
);
}
export function createMapNode(
ctx: BaseParserContext,
id: number,
k: SerovalNode[],
v: SerovalNode[],
): SerovalMapNode {
return createSerovalNode(
SerovalNodeType.Map,
id,
NIL,
NIL,
NIL,
NIL,
{ k, v },
NIL,
parseSpecialReference(ctx, SpecialReference.MapSentinel),
NIL,
NIL,
NIL,
);
}
export function createPromiseConstructorNode(
ctx: BaseParserContext,
id: number,
resolver: number,
): SerovalPromiseConstructorNode {
return createSerovalNode(
SerovalNodeType.PromiseConstructor,
id,
resolver,
NIL,
NIL,
NIL,
NIL,
NIL,
parseSpecialReference(ctx, SpecialReference.PromiseConstructor),
NIL,
NIL,
NIL,
);
}
export function createArrayBufferNode(
ctx: BaseParserContext,
id: number,
current: ArrayBuffer,
): SerovalArrayBufferNode {
const bytes = new Uint8Array(current);
let result = '';
for (let i = 0, len = bytes.length; i < len; i++) {
result += String.fromCharCode(bytes[i]);
}
return createSerovalNode(
SerovalNodeType.ArrayBuffer,
id,
serializeString(btoa(result)),
NIL,
NIL,
NIL,
NIL,
NIL,
parseSpecialReference(ctx, SpecialReference.ArrayBufferConstructor),
NIL,
NIL,
NIL,
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,907 @@
import {
createAggregateErrorNode,
createArrayNode,
createAsyncIteratorFactoryInstanceNode,
createBigIntNode,
createBigIntTypedArrayNode,
createBoxedNode,
createDataViewNode,
createDateNode,
createErrorNode,
createIteratorFactoryInstanceNode,
createNumberNode,
createPluginNode,
createRegExpNode,
createSetNode,
createStreamConstructorNode,
createStreamNextNode,
createStreamReturnNode,
createStreamThrowNode,
createStringNode,
createTypedArrayNode,
} from '../base-primitives';
import { Feature } from '../compat';
import { NIL, SerovalNodeType } from '../constants';
import {
SerovalDepthLimitError,
SerovalParserError,
SerovalUnsupportedTypeError,
} from '../errors';
import { FALSE_NODE, NULL_NODE, TRUE_NODE, UNDEFINED_NODE } from '../literals';
import { createSerovalNode } from '../node';
import { OpaqueReference } from '../opaque-reference';
import { type Plugin, SerovalMode } from '../plugin';
import { SpecialReference } from '../special-reference';
import type { Stream } from '../stream';
import {
createStream,
createStreamFromAsyncIterable,
isStream,
} from '../stream';
import { serializeString } from '../string';
import {
SYM_ASYNC_ITERATOR,
SYM_IS_CONCAT_SPREADABLE,
SYM_ITERATOR,
SYM_TO_STRING_TAG,
} from '../symbols';
import type {
SerovalAggregateErrorNode,
SerovalArrayNode,
SerovalBigIntTypedArrayNode,
SerovalBoxedNode,
SerovalDataViewNode,
SerovalErrorNode,
SerovalMapNode,
SerovalNode,
SerovalNullConstructorNode,
SerovalObjectNode,
SerovalObjectRecordKey,
SerovalObjectRecordNode,
SerovalPluginNode,
SerovalPromiseConstructorNode,
SerovalSetNode,
SerovalTypedArrayNode,
} from '../types';
import { getErrorOptions } from '../utils/error';
import { iteratorToSequence } from '../utils/iterator-to-sequence';
import type {
BigIntTypedArrayValue,
TypedArrayValue,
} from '../utils/typed-array';
import type { BaseParserContext, BaseParserContextOptions } from './parser';
import {
createArrayBufferNode,
createBaseParserContext,
createIndexForValue,
createMapNode,
createObjectNode,
createPromiseConstructorNode,
getReferenceNode,
parseAsyncIteratorFactory,
parseIteratorFactory,
ParserNodeType,
parseSpecialReference,
parseWellKnownSymbol,
} from './parser';
type ObjectLikeNode = SerovalObjectNode | SerovalNullConstructorNode;
export type SyncParserContextOptions = BaseParserContextOptions;
const enum ParserMode {
Sync = 1,
Stream = 2,
}
export interface SyncParserContext {
type: ParserMode.Sync;
base: BaseParserContext;
child: SyncParsePluginContext | undefined;
}
export function createSyncParserContext(
mode: SerovalMode,
options: SyncParserContextOptions,
): SyncParserContext {
return {
type: ParserMode.Sync,
base: createBaseParserContext(mode, options),
child: NIL,
};
}
export class SyncParsePluginContext {
constructor(
private _p: SyncParserContext,
private depth: number,
) {}
parse<T>(current: T): SerovalNode {
return parseSOS(this._p, this.depth, current);
}
}
export interface StreamParserContextOptions extends SyncParserContextOptions {
onParse: (node: SerovalNode, initial: boolean) => void;
onError?: (error: unknown) => void;
onDone?: () => void;
}
export interface StreamParserContext {
type: ParserMode.Stream;
base: BaseParserContext;
state: StreamParserState;
}
export class StreamParsePluginContext {
constructor(
private _p: StreamParserContext,
private depth: number,
) {}
parse<T>(current: T): SerovalNode {
return parseSOS(this._p, this.depth, current);
}
parseWithError<T>(current: T): SerovalNode | undefined {
return parseWithError(this._p, this.depth, current);
}
isAlive(): boolean {
return this._p.state.alive;
}
pushPendingState(): void {
pushPendingState(this._p);
}
popPendingState(): void {
popPendingState(this._p);
}
onParse(node: SerovalNode): void {
onParse(this._p, node);
}
onError(error: unknown): void {
onError(this._p, error);
}
}
interface StreamParserState {
// Life cycle
alive: boolean;
// Number of pending things
pending: number;
//
initial: boolean;
//
buffer: SerovalNode[];
// Callbacks
onParse: (node: SerovalNode, initial: boolean) => void;
onError?: (error: unknown) => void;
onDone?: () => void;
}
function createStreamParserState(
options: StreamParserContextOptions,
): StreamParserState {
return {
alive: true,
pending: 0,
initial: true,
buffer: [],
onParse: options.onParse,
onError: options.onError,
onDone: options.onDone,
};
}
export function createStreamParserContext(
options: StreamParserContextOptions,
): StreamParserContext {
return {
type: ParserMode.Stream,
base: createBaseParserContext(SerovalMode.Cross, options),
state: createStreamParserState(options),
};
}
type SOSParserContext = SyncParserContext | StreamParserContext;
function parseItems(
ctx: SOSParserContext,
depth: number,
current: unknown[],
): (SerovalNode | 0)[] {
const nodes: (SerovalNode | 0)[] = [];
for (let i = 0, len = current.length; i < len; i++) {
if (i in current) {
nodes[i] = parseSOS(ctx, depth, current[i]);
} else {
nodes[i] = 0;
}
}
return nodes;
}
function parseArray(
ctx: SOSParserContext,
depth: number,
id: number,
current: unknown[],
): SerovalArrayNode {
return createArrayNode(id, current, parseItems(ctx, depth, current));
}
function parseProperties(
ctx: SOSParserContext,
depth: number,
properties: Record<string | symbol, unknown>,
): SerovalObjectRecordNode {
const entries = Object.entries(properties);
const keyNodes: SerovalObjectRecordKey[] = [];
const valueNodes: SerovalNode[] = [];
for (let i = 0, len = entries.length; i < len; i++) {
keyNodes.push(serializeString(entries[i][0]));
valueNodes.push(parseSOS(ctx, depth, entries[i][1]));
}
// Check special properties, symbols in this case
if (SYM_ITERATOR in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_ITERATOR));
valueNodes.push(
createIteratorFactoryInstanceNode(
parseIteratorFactory(ctx.base),
parseSOS(
ctx,
depth,
iteratorToSequence(properties as unknown as Iterable<unknown>),
),
),
);
}
if (SYM_ASYNC_ITERATOR in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_ASYNC_ITERATOR));
valueNodes.push(
createAsyncIteratorFactoryInstanceNode(
parseAsyncIteratorFactory(ctx.base),
parseSOS(
ctx,
depth,
ctx.type === ParserMode.Sync
? createStream()
: createStreamFromAsyncIterable(
properties as unknown as AsyncIterable<unknown>,
),
),
),
);
}
if (SYM_TO_STRING_TAG in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_TO_STRING_TAG));
valueNodes.push(createStringNode(properties[SYM_TO_STRING_TAG] as string));
}
if (SYM_IS_CONCAT_SPREADABLE in properties) {
keyNodes.push(parseWellKnownSymbol(ctx.base, SYM_IS_CONCAT_SPREADABLE));
valueNodes.push(
properties[SYM_IS_CONCAT_SPREADABLE] ? TRUE_NODE : FALSE_NODE,
);
}
return {
k: keyNodes,
v: valueNodes,
};
}
function parsePlainObject(
ctx: SOSParserContext,
depth: number,
id: number,
current: Record<string, unknown>,
empty: boolean,
): ObjectLikeNode {
return createObjectNode(
id,
current,
empty,
parseProperties(ctx, depth, current),
);
}
function parseBoxed(
ctx: SOSParserContext,
depth: number,
id: number,
current: object,
): SerovalBoxedNode {
return createBoxedNode(id, parseSOS(ctx, depth, current.valueOf()));
}
function parseTypedArray(
ctx: SOSParserContext,
depth: number,
id: number,
current: TypedArrayValue,
): SerovalTypedArrayNode {
return createTypedArrayNode(
id,
current,
parseSOS(ctx, depth, current.buffer),
);
}
function parseBigIntTypedArray(
ctx: SOSParserContext,
depth: number,
id: number,
current: BigIntTypedArrayValue,
): SerovalBigIntTypedArrayNode {
return createBigIntTypedArrayNode(
id,
current,
parseSOS(ctx, depth, current.buffer),
);
}
function parseDataView(
ctx: SOSParserContext,
depth: number,
id: number,
current: DataView,
): SerovalDataViewNode {
return createDataViewNode(id, current, parseSOS(ctx, depth, current.buffer));
}
function parseError(
ctx: SOSParserContext,
depth: number,
id: number,
current: Error,
): SerovalErrorNode {
const options = getErrorOptions(current, ctx.base.features);
return createErrorNode(
id,
current,
options ? parseProperties(ctx, depth, options) : NIL,
);
}
function parseAggregateError(
ctx: SOSParserContext,
depth: number,
id: number,
current: AggregateError,
): SerovalAggregateErrorNode {
const options = getErrorOptions(current, ctx.base.features);
return createAggregateErrorNode(
id,
current,
options ? parseProperties(ctx, depth, options) : NIL,
);
}
function parseMap(
ctx: SOSParserContext,
depth: number,
id: number,
current: Map<unknown, unknown>,
): SerovalMapNode {
const keyNodes: SerovalNode[] = [];
const valueNodes: SerovalNode[] = [];
for (const [key, value] of current.entries()) {
keyNodes.push(parseSOS(ctx, depth, key));
valueNodes.push(parseSOS(ctx, depth, value));
}
return createMapNode(ctx.base, id, keyNodes, valueNodes);
}
function parseSet(
ctx: SOSParserContext,
depth: number,
id: number,
current: Set<unknown>,
): SerovalSetNode {
const items: SerovalNode[] = [];
for (const item of current.keys()) {
items.push(parseSOS(ctx, depth, item));
}
return createSetNode(id, items);
}
function parseStream(
ctx: SOSParserContext,
depth: number,
id: number,
current: Stream<unknown>,
): SerovalNode {
const result = createStreamConstructorNode(
id,
parseSpecialReference(ctx.base, SpecialReference.StreamConstructor),
[],
);
if (ctx.type === ParserMode.Sync) {
return result;
}
pushPendingState(ctx);
current.on({
next: value => {
if (ctx.state.alive) {
const parsed = parseWithError(ctx, depth, value);
if (parsed) {
onParse(ctx, createStreamNextNode(id, parsed));
}
}
},
throw: value => {
if (ctx.state.alive) {
const parsed = parseWithError(ctx, depth, value);
if (parsed) {
onParse(ctx, createStreamThrowNode(id, parsed));
}
}
popPendingState(ctx);
},
return: value => {
if (ctx.state.alive) {
const parsed = parseWithError(ctx, depth, value);
if (parsed) {
onParse(ctx, createStreamReturnNode(id, parsed));
}
}
popPendingState(ctx);
},
});
return result;
}
function handlePromiseSuccess(
this: StreamParserContext,
id: number,
depth: number,
data: unknown,
): void {
if (this.state.alive) {
const parsed = parseWithError(this, depth, data);
if (parsed) {
onParse(
this,
createSerovalNode(
SerovalNodeType.PromiseSuccess,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
[
parseSpecialReference(this.base, SpecialReference.PromiseSuccess),
parsed,
],
NIL,
NIL,
NIL,
NIL,
),
);
}
popPendingState(this);
}
}
function handlePromiseFailure(
this: StreamParserContext,
id: number,
depth: number,
data: unknown,
): void {
if (this.state.alive) {
const parsed = parseWithError(this, depth, data);
if (parsed) {
onParse(
this,
createSerovalNode(
SerovalNodeType.PromiseFailure,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
[
parseSpecialReference(this.base, SpecialReference.PromiseFailure),
parsed,
],
NIL,
NIL,
NIL,
NIL,
),
);
}
}
popPendingState(this);
}
function parsePromise(
ctx: SOSParserContext,
depth: number,
id: number,
current: Promise<unknown>,
): SerovalPromiseConstructorNode {
// Creates a unique reference for the promise resolver
const resolver = createIndexForValue(ctx.base, {});
if (ctx.type === ParserMode.Stream) {
pushPendingState(ctx);
current.then(
handlePromiseSuccess.bind(ctx, resolver, depth),
handlePromiseFailure.bind(ctx, resolver, depth),
);
}
return createPromiseConstructorNode(ctx.base, id, resolver);
}
function parsePluginSync(
ctx: SyncParserContext,
depth: number,
id: number,
current: unknown,
currentPlugins: Plugin<any, any>[],
): SerovalPluginNode | undefined {
for (let i = 0, len = currentPlugins.length; i < len; i++) {
const plugin = currentPlugins[i];
if (plugin.parse.sync && plugin.test(current)) {
return createPluginNode(
id,
plugin.tag,
plugin.parse.sync(current, new SyncParsePluginContext(ctx, depth), {
id,
}),
);
}
}
return NIL;
}
function parsePluginStream(
ctx: StreamParserContext,
depth: number,
id: number,
current: unknown,
currentPlugins: Plugin<any, any>[],
): SerovalPluginNode | undefined {
for (let i = 0, len = currentPlugins.length; i < len; i++) {
const plugin = currentPlugins[i];
if (plugin.parse.stream && plugin.test(current)) {
return createPluginNode(
id,
plugin.tag,
plugin.parse.stream(current, new StreamParsePluginContext(ctx, depth), {
id,
}),
);
}
}
return NIL;
}
function parsePlugin(
ctx: SOSParserContext,
depth: number,
id: number,
current: unknown,
): SerovalPluginNode | undefined {
const currentPlugins = ctx.base.plugins;
if (currentPlugins) {
return ctx.type === ParserMode.Sync
? parsePluginSync(ctx, depth, id, current, currentPlugins)
: parsePluginStream(ctx, depth, id, current, currentPlugins);
}
return NIL;
}
function parseObjectPhase2(
ctx: SOSParserContext,
depth: number,
id: number,
current: object,
currentClass: unknown,
): SerovalNode {
switch (currentClass) {
case Object:
return parsePlainObject(
ctx,
depth,
id,
current as Record<string, unknown>,
false,
);
case NIL:
return parsePlainObject(
ctx,
depth,
id,
current as Record<string, unknown>,
true,
);
case Date:
return createDateNode(id, current as unknown as Date);
case Error:
case EvalError:
case RangeError:
case ReferenceError:
case SyntaxError:
case TypeError:
case URIError:
return parseError(ctx, depth, id, current as unknown as Error);
case Number:
case Boolean:
case String:
case BigInt:
return parseBoxed(ctx, depth, id, current);
case ArrayBuffer:
return createArrayBufferNode(
ctx.base,
id,
current as unknown as ArrayBuffer,
);
case Int8Array:
case Int16Array:
case Int32Array:
case Uint8Array:
case Uint16Array:
case Uint32Array:
case Uint8ClampedArray:
case Float32Array:
case Float64Array:
return parseTypedArray(
ctx,
depth,
id,
current as unknown as TypedArrayValue,
);
case DataView:
return parseDataView(ctx, depth, id, current as unknown as DataView);
case Map:
return parseMap(
ctx,
depth,
id,
current as unknown as Map<unknown, unknown>,
);
case Set:
return parseSet(ctx, depth, id, current as unknown as Set<unknown>);
default:
break;
}
// Promises
if (currentClass === Promise || current instanceof Promise) {
return parsePromise(ctx, depth, id, current as unknown as Promise<unknown>);
}
const currentFeatures = ctx.base.features;
if (currentFeatures & Feature.RegExp && currentClass === RegExp) {
return createRegExpNode(id, current as unknown as RegExp);
}
// BigInt Typed Arrays
if (currentFeatures & Feature.BigIntTypedArray) {
switch (currentClass) {
case BigInt64Array:
case BigUint64Array:
return parseBigIntTypedArray(
ctx,
depth,
id,
current as unknown as BigIntTypedArrayValue,
);
default:
break;
}
}
if (
currentFeatures & Feature.AggregateError &&
typeof AggregateError !== 'undefined' &&
(currentClass === AggregateError || current instanceof AggregateError)
) {
return parseAggregateError(
ctx,
depth,
id,
current as unknown as AggregateError,
);
}
// Slow path. We only need to handle Errors and Iterators
// since they have very broad implementations.
if (current instanceof Error) {
return parseError(ctx, depth, id, current);
}
// Generator functions don't have a global constructor
// despite existing
if (SYM_ITERATOR in current || SYM_ASYNC_ITERATOR in current) {
return parsePlainObject(ctx, depth, id, current, !!currentClass);
}
throw new SerovalUnsupportedTypeError(current);
}
function parseObject(
ctx: SOSParserContext,
depth: number,
id: number,
current: object,
): SerovalNode {
if (Array.isArray(current)) {
return parseArray(ctx, depth, id, current);
}
if (isStream(current)) {
return parseStream(ctx, depth, id, current);
}
const currentClass = current.constructor;
if (currentClass === OpaqueReference) {
return parseSOS(
ctx,
depth,
(current as OpaqueReference<unknown, unknown>).replacement,
);
}
const parsed = parsePlugin(ctx, depth, id, current);
if (parsed) {
return parsed;
}
return parseObjectPhase2(ctx, depth, id, current, currentClass);
}
function parseFunction(
ctx: SOSParserContext,
depth: number,
current: unknown,
): SerovalNode {
const ref = getReferenceNode(ctx.base, current);
if (ref.type !== ParserNodeType.Fresh) {
return ref.value;
}
const plugin = parsePlugin(ctx, depth, ref.value, current);
if (plugin) {
return plugin;
}
throw new SerovalUnsupportedTypeError(current);
}
export function parseSOS<T>(
ctx: SOSParserContext,
depth: number,
current: T,
): SerovalNode {
if (depth >= ctx.base.depthLimit) {
throw new SerovalDepthLimitError(ctx.base.depthLimit);
}
switch (typeof current) {
case 'boolean':
return current ? TRUE_NODE : FALSE_NODE;
case 'undefined':
return UNDEFINED_NODE;
case 'string':
return createStringNode(current as string);
case 'number':
return createNumberNode(current as number);
case 'bigint':
return createBigIntNode(current as bigint);
case 'object': {
if (current) {
const ref = getReferenceNode(ctx.base, current);
return ref.type === ParserNodeType.Fresh
? parseObject(ctx, depth + 1, ref.value, current as object)
: ref.value;
}
return NULL_NODE;
}
case 'symbol':
return parseWellKnownSymbol(ctx.base, current);
case 'function': {
return parseFunction(ctx, depth, current);
}
default:
throw new SerovalUnsupportedTypeError(current);
}
}
export function parseTop<T>(ctx: SyncParserContext, current: T): SerovalNode {
try {
return parseSOS(ctx, 0, current);
} catch (error) {
throw error instanceof SerovalParserError
? error
: new SerovalParserError(error);
}
}
function onParse(ctx: StreamParserContext, node: SerovalNode): void {
// If the value emitted happens to be during parsing, we push to the
// buffer and emit after the initial parsing is done.
if (ctx.state.initial) {
ctx.state.buffer.push(node);
} else {
onParseInternal(ctx, node, false);
}
}
function onError(ctx: StreamParserContext, error: unknown): void {
if (ctx.state.onError) {
ctx.state.onError(error);
} else {
throw error instanceof SerovalParserError
? error
: new SerovalParserError(error);
}
}
function onDone(ctx: StreamParserContext): void {
if (ctx.state.onDone) {
ctx.state.onDone();
}
}
function onParseInternal(
ctx: StreamParserContext,
node: SerovalNode,
initial: boolean,
): void {
try {
ctx.state.onParse(node, initial);
} catch (error) {
onError(ctx, error);
}
}
function pushPendingState(ctx: StreamParserContext): void {
ctx.state.pending++;
}
function popPendingState(ctx: StreamParserContext): void {
if (--ctx.state.pending <= 0) {
onDone(ctx);
}
}
function parseWithError<T>(
ctx: StreamParserContext,
depth: number,
current: T,
): SerovalNode | undefined {
try {
return parseSOS(ctx, depth, current);
} catch (err) {
onError(ctx, err);
return NIL;
}
}
export function startStreamParse<T>(
ctx: StreamParserContext,
current: T,
): void {
const parsed = parseWithError(ctx, 0, current);
if (parsed) {
onParseInternal(ctx, parsed, true);
ctx.state.initial = false;
flushStreamParse(ctx, ctx.state);
// Check if there's any pending pushes
if (ctx.state.pending <= 0) {
destroyStreamParse(ctx);
}
}
}
function flushStreamParse(
ctx: StreamParserContext,
state: StreamParserState,
): void {
for (let i = 0, len = state.buffer.length; i < len; i++) {
onParseInternal(ctx, state.buffer[i], false);
}
}
export function destroyStreamParse(ctx: StreamParserContext): void {
if (ctx.state.alive) {
onDone(ctx);
ctx.state.alive = false;
}
}

View file

@ -0,0 +1,188 @@
import type { AsyncParserContextOptions } from '../context/async-parser';
import {
createAsyncParserContext,
parseTopAsync,
} from '../context/async-parser';
import type { CrossDeserializerContextOptions } from '../context/deserializer';
import {
createCrossDeserializerContext,
deserializeTop,
} from '../context/deserializer';
import type { CrossContextOptions } from '../context/serializer';
import {
createCrossSerializerContext,
serializeTopCross,
} from '../context/serializer';
import type {
StreamParserContextOptions,
SyncParserContextOptions,
} from '../context/sync-parser';
import {
createStreamParserContext,
createSyncParserContext,
destroyStreamParse,
parseTop,
startStreamParse,
} from '../context/sync-parser';
import { resolvePlugins, SerovalMode } from '../plugin';
import type { SerovalNode } from '../types';
export interface CrossSerializeOptions
extends SyncParserContextOptions,
CrossContextOptions {}
export function crossSerialize<T>(
source: T,
options: CrossSerializeOptions = {},
): string {
const plugins = resolvePlugins(options.plugins);
const ctx = createSyncParserContext(SerovalMode.Cross, {
plugins,
disabledFeatures: options.disabledFeatures,
refs: options.refs,
});
const tree = parseTop(ctx, source);
const serial = createCrossSerializerContext({
plugins,
features: ctx.base.features,
scopeId: options.scopeId,
markedRefs: ctx.base.marked,
});
return serializeTopCross(serial, tree);
}
export interface CrossSerializeAsyncOptions
extends AsyncParserContextOptions,
CrossContextOptions {}
export async function crossSerializeAsync<T>(
source: T,
options: CrossSerializeAsyncOptions = {},
): Promise<string> {
const plugins = resolvePlugins(options.plugins);
const ctx = createAsyncParserContext(SerovalMode.Cross, {
plugins,
disabledFeatures: options.disabledFeatures,
refs: options.refs,
});
const tree = await parseTopAsync(ctx, source);
const serial = createCrossSerializerContext({
plugins,
features: ctx.base.features,
scopeId: options.scopeId,
markedRefs: ctx.base.marked,
});
return serializeTopCross(serial, tree);
}
export type ToCrossJSONOptions = SyncParserContextOptions;
export function toCrossJSON<T>(
source: T,
options: ToCrossJSONOptions = {},
): SerovalNode {
const plugins = resolvePlugins(options.plugins);
const ctx = createSyncParserContext(SerovalMode.Cross, {
plugins,
disabledFeatures: options.disabledFeatures,
refs: options.refs,
});
return parseTop(ctx, source);
}
export type ToCrossJSONAsyncOptions = AsyncParserContextOptions;
export async function toCrossJSONAsync<T>(
source: T,
options: ToCrossJSONAsyncOptions = {},
): Promise<SerovalNode> {
const plugins = resolvePlugins(options.plugins);
const ctx = createAsyncParserContext(SerovalMode.Cross, {
plugins,
disabledFeatures: options.disabledFeatures,
refs: options.refs,
});
return await parseTopAsync(ctx, source);
}
export interface CrossSerializeStreamOptions
extends Omit<StreamParserContextOptions, 'onParse'>,
CrossContextOptions {
onSerialize: (data: string, initial: boolean) => void;
}
export function crossSerializeStream<T>(
source: T,
options: CrossSerializeStreamOptions,
): () => void {
const plugins = resolvePlugins(options.plugins);
const ctx = createStreamParserContext({
plugins,
refs: options.refs,
disabledFeatures: options.disabledFeatures,
onParse(node, initial): void {
const serial = createCrossSerializerContext({
plugins,
features: ctx.base.features,
scopeId: options.scopeId,
markedRefs: ctx.base.marked,
});
let serialized: string;
try {
serialized = serializeTopCross(serial, node);
} catch (err) {
if (options.onError) {
options.onError(err);
}
return;
}
options.onSerialize(serialized, initial);
},
onError: options.onError,
onDone: options.onDone,
});
startStreamParse(ctx, source);
return destroyStreamParse.bind(null, ctx);
}
export type ToCrossJSONStreamOptions = StreamParserContextOptions;
export function toCrossJSONStream<T>(
source: T,
options: ToCrossJSONStreamOptions,
): () => void {
const plugins = resolvePlugins(options.plugins);
const ctx = createStreamParserContext({
plugins,
refs: options.refs,
disabledFeatures: options.disabledFeatures,
onParse: options.onParse,
onError: options.onError,
onDone: options.onDone,
});
startStreamParse(ctx, source);
return destroyStreamParse.bind(null, ctx);
}
export type FromCrossJSONOptions = CrossDeserializerContextOptions;
export function fromCrossJSON<T>(
source: SerovalNode,
options: FromCrossJSONOptions,
): T {
const plugins = resolvePlugins(options.plugins);
const ctx = createCrossDeserializerContext({
plugins,
refs: options.refs,
features: options.features,
disabledFeatures: options.disabledFeatures,
});
return deserializeTop(ctx, source) as T;
}

View file

@ -0,0 +1,196 @@
/// <reference types="pridepack/env" />
import { serializeString } from './string';
import type { SerovalNode } from './types';
const { toString: objectToString } = /* @__PURE__ */ Object.prototype;
const enum StepErrorCodes {
Parse = 1,
Serialize = 2,
Deserialize = 3,
}
function getErrorMessageDev(type: string, cause: any): string {
if (cause instanceof Error) {
return `Seroval caught an error during the ${type} process.
${cause.name}
${cause.message}
- For more information, please check the "cause" property of this error.
- If you believe this is an error in Seroval, please submit an issue at https://github.com/lxsmnsyc/seroval/issues/new`;
}
return `Seroval caught an error during the ${type} process.
"${objectToString.call(cause)}"
For more information, please check the "cause" property of this error.`;
}
const STEP_ERROR_CODES: Record<string, StepErrorCodes> = {
parsing: StepErrorCodes.Parse,
serialization: StepErrorCodes.Serialize,
deserialization: StepErrorCodes.Deserialize,
};
function getErrorMessageProd(type: string): string {
return `Seroval Error (step: ${STEP_ERROR_CODES[type]})`;
}
const getErrorMessage = (type: string, cause: any) =>
import.meta.env.PROD
? getErrorMessageProd(type)
: getErrorMessageDev(type, cause);
export class SerovalError extends Error {
constructor(
type: string,
public cause: any,
) {
super(getErrorMessage(type, cause));
}
}
export class SerovalParserError extends SerovalError {
constructor(cause: any) {
super('parsing', cause);
}
}
export class SerovalSerializationError extends SerovalError {
constructor(cause: any) {
super('serialization', cause);
}
}
export class SerovalDeserializationError extends SerovalError {
constructor(cause: any) {
super('deserialization', cause);
}
}
const enum SpecificErrorCodes {
UnsupportedType = 1,
UnsupportedNode = 2,
MissingPlugin = 3,
MissingInstance = 4,
MissingReference = 5,
MissingReferenceForId = 6,
UnknownTypedArray = 7,
MalformedNode = 8,
ConflictedNodeId = 9,
DepthLimit = 10,
}
function getSpecificErrorMessage(code: SpecificErrorCodes): string {
return `Seroval Error (specific: ${code})`;
}
export class SerovalUnsupportedTypeError extends Error {
constructor(public value: unknown) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.UnsupportedType)
: `The value ${objectToString.call(value)} of type "${typeof value}" cannot be parsed/serialized.
There are few workarounds for this problem:
- Transform the value in a way that it can be serialized.
- If the reference is present on multiple runtimes (isomorphic), you can use the Reference API to map the references.`,
);
}
}
export class SerovalUnsupportedNodeError extends Error {
constructor(node: SerovalNode) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.UnsupportedNode)
: 'Unsupported node type "' + node.t + '".',
);
}
}
export class SerovalMissingPluginError extends Error {
constructor(tag: string) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.MissingPlugin)
: 'Missing plugin for tag "' + tag + '".',
);
}
}
export class SerovalMissingInstanceError extends Error {
constructor(tag: string) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.MissingInstance)
: 'Missing "' + tag + '" instance.',
);
}
}
export class SerovalMissingReferenceError extends Error {
constructor(public value: unknown) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.MissingReference)
: 'Missing reference for the value "' +
objectToString.call(value) +
'" of type "' +
typeof value +
'"',
);
}
}
export class SerovalMissingReferenceForIdError extends Error {
constructor(id: string) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.MissingReferenceForId)
: 'Missing reference for id "' + serializeString(id) + '"',
);
}
}
export class SerovalUnknownTypedArrayError extends Error {
constructor(name: string) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.UnknownTypedArray)
: 'Unknown TypedArray "' + name + '"',
);
}
}
export class SerovalMalformedNodeError extends Error {
constructor(node: SerovalNode) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.MalformedNode)
: 'Malformed node type "' + node.t + '".',
);
}
}
export class SerovalConflictedNodeIdError extends Error {
constructor(node: SerovalNode) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.ConflictedNodeId)
: 'Conflicted node id "' + node.i + '".',
);
}
}
export class SerovalDepthLimitError extends Error {
constructor(limit: number) {
super(
import.meta.env.PROD
? getSpecificErrorMessage(SpecificErrorCodes.ConflictedNodeId)
: 'Depth limit of ' + limit + ' reached',
);
}
}

View file

@ -0,0 +1,32 @@
declare const T: unknown;
const RETURN = () => T;
const SERIALIZED_RETURN = /* @__PURE__ */ RETURN.toString();
const IS_MODERN = /* @__PURE__ */ /=>/.test(SERIALIZED_RETURN);
export function createFunction(parameters: string[], body: string): string {
if (IS_MODERN) {
const joined =
parameters.length === 1
? parameters[0]
: '(' + parameters.join(',') + ')';
return joined + '=>' + (body.startsWith('{') ? '(' + body + ')' : body);
}
return 'function(' + parameters.join(',') + '){return ' + body + '}';
}
export function createEffectfulFunction(
parameters: string[],
body: string,
): string {
if (IS_MODERN) {
const joined =
parameters.length === 1
? parameters[0]
: '(' + parameters.join(',') + ')';
return joined + '=>{' + body + '}';
}
return 'function(' + parameters.join(',') + '){' + body + '}';
}

17
Frontend-Learner/node_modules/seroval/src/core/keys.ts generated vendored Normal file
View file

@ -0,0 +1,17 @@
import { serializeString } from './string';
// Used for mapping isomorphic references
export const REFERENCES_KEY = '__SEROVAL_REFS__';
export const GLOBAL_CONTEXT_REFERENCES = '$R';
const GLOBAL_CONTEXT_R = `self.${GLOBAL_CONTEXT_REFERENCES}`;
export function getCrossReferenceHeader(id?: string): string {
if (id == null) {
return `${GLOBAL_CONTEXT_R}=${GLOBAL_CONTEXT_R}||[]`;
}
return `(${GLOBAL_CONTEXT_R}=${GLOBAL_CONTEXT_R}||{})["${serializeString(
id,
)}"]=[]`;
}

View file

@ -0,0 +1,43 @@
import { NIL, SerovalConstant, SerovalNodeType } from './constants';
import { createSerovalNode } from './node';
import type { SerovalConstantNode } from './types';
function createConstantNode(value: SerovalConstant): SerovalConstantNode {
return createSerovalNode(
SerovalNodeType.Constant,
NIL,
value,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
export const TRUE_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.True,
);
export const FALSE_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.False,
);
export const UNDEFINED_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.Undefined,
);
export const NULL_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.Null,
);
export const NEG_ZERO_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.NegZero,
);
export const INFINITY_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.Inf,
);
export const NEG_INFINITY_NODE = /* @__PURE__ */ createConstantNode(
SerovalConstant.NegInf,
);
export const NAN_NODE = /* @__PURE__ */ createConstantNode(SerovalConstant.Nan);

40
Frontend-Learner/node_modules/seroval/src/core/node.ts generated vendored Normal file
View file

@ -0,0 +1,40 @@
import type { SerovalNodeType } from './constants';
import type { SerovalNode } from './types';
type ExtractedNodeType<T extends SerovalNodeType> = Extract<
SerovalNode,
{ t: T }
>;
export function createSerovalNode<
T extends SerovalNodeType,
N extends ExtractedNodeType<T>,
>(
t: T,
i: N['i'],
s: N['s'],
c: N['c'],
m: N['m'],
p: N['p'],
e: N['e'],
a: N['a'],
f: N['f'],
b: N['b'],
o: N['o'],
l: N['l'],
): N {
return {
t,
i,
s,
c,
m,
p,
e,
a,
f,
b,
o,
l,
} as N;
}

View file

@ -0,0 +1,9 @@
/**
* An opaque reference allows hiding values from the serializer.
*/
export class OpaqueReference<V, R = undefined> {
constructor(
public readonly value: V,
public readonly replacement?: R,
) {}
}

View file

@ -0,0 +1,100 @@
import type { AsyncParsePluginContext } from './context/async-parser';
import type { DeserializePluginContext } from './context/deserializer';
import type { SerializePluginContext } from './context/serializer';
import type {
StreamParsePluginContext,
SyncParsePluginContext,
} from './context/sync-parser';
export const enum SerovalMode {
Vanilla = 1,
Cross = 2,
}
export interface PluginData {
id: number;
}
export interface Plugin<Value, Node> {
/**
* A unique string that helps idenfity the plugin
*/
tag: string;
/**
* List of dependency plugins
*/
extends?: Plugin<any, any>[];
/**
* Method to test if a value is an expected value of the plugin
* @param value
*/
test(value: unknown): boolean;
/**
* Parsing modes
*/
parse: {
sync?: (
value: Value,
ctx: SyncParsePluginContext,
data: PluginData,
) => Node;
async?: (
value: Value,
ctx: AsyncParsePluginContext,
data: PluginData,
) => Promise<Node>;
stream?: (
value: Value,
ctx: StreamParsePluginContext,
data: PluginData,
) => Node;
};
/**
* Convert the parsed node into a JS string
*/
serialize(node: Node, ctx: SerializePluginContext, data: PluginData): string;
/**
* Convert the parsed node into its runtime equivalent.
*/
deserialize(
node: Node,
ctx: DeserializePluginContext,
data: PluginData,
): Value;
}
export function createPlugin<Value, Node>(
plugin: Plugin<Value, Node>,
): Plugin<Value, Node> {
return plugin;
}
export interface PluginAccessOptions {
plugins?: Plugin<any, any>[];
}
function dedupePlugins(
deduped: Set<Plugin<any, any>>,
plugins: Plugin<any, any>[],
): void {
for (let i = 0, len = plugins.length; i < len; i++) {
const current = plugins[i];
if (!deduped.has(current)) {
deduped.add(current);
if (current.extends) {
dedupePlugins(deduped, current.extends);
}
}
}
}
export function resolvePlugins(
plugins?: Plugin<any, any>[],
): Plugin<any, any>[] | undefined {
if (plugins) {
const deduped = new Set<Plugin<any, any>>();
dedupePlugins(deduped, plugins);
return [...deduped];
}
return undefined;
}

View file

@ -0,0 +1,66 @@
import {
SerovalMissingReferenceError,
SerovalMissingReferenceForIdError,
} from '..';
import { REFERENCES_KEY } from './keys';
const REFERENCE = new Map<unknown, string>();
const INV_REFERENCE = new Map<string, unknown>();
export function createReference<T>(id: string, value: T): T {
REFERENCE.set(value, id);
INV_REFERENCE.set(id, value);
return value;
}
export function hasReferenceID<T>(value: T): boolean {
return REFERENCE.has(value);
}
export function hasReference(id: string): boolean {
return INV_REFERENCE.has(id);
}
export function getReferenceID<T>(value: T): string {
if (hasReferenceID(value)) {
return REFERENCE.get(value)!;
}
throw new SerovalMissingReferenceError(value);
}
export function getReference<T>(id: string): T {
if (hasReference(id)) {
return INV_REFERENCE.get(id) as T;
}
throw new SerovalMissingReferenceForIdError(id);
}
if (typeof globalThis !== 'undefined') {
Object.defineProperty(globalThis, REFERENCES_KEY, {
value: INV_REFERENCE,
configurable: true,
writable: false,
enumerable: false,
});
} else if (typeof window !== 'undefined') {
Object.defineProperty(window, REFERENCES_KEY, {
value: INV_REFERENCE,
configurable: true,
writable: false,
enumerable: false,
});
} else if (typeof self !== 'undefined') {
Object.defineProperty(self, REFERENCES_KEY, {
value: INV_REFERENCE,
configurable: true,
writable: false,
enumerable: false,
});
} else if (typeof global !== 'undefined') {
Object.defineProperty(global, REFERENCES_KEY, {
value: INV_REFERENCE,
configurable: true,
writable: false,
enumerable: false,
});
}

View file

@ -0,0 +1,42 @@
import {
SERIALIZED_ARRAY_BUFFER_CONSTRUCTOR,
SERIALIZED_PROMISE_CONSTRUCTOR,
SERIALIZED_PROMISE_FAILURE,
SERIALIZED_PROMISE_SUCCESS,
SERIALIZED_STREAM_CONSTRUCTOR,
} from './constructors';
export const ITERATOR = {};
export const ASYNC_ITERATOR = {};
export const enum SpecialReference {
MapSentinel = 0,
PromiseConstructor = 1,
PromiseSuccess = 2,
PromiseFailure = 3,
StreamConstructor = 4,
ArrayBufferConstructor = 5,
}
/**
* Placeholder references
*/
export const SPECIAL_REFS: Record<SpecialReference, unknown> = {
[SpecialReference.MapSentinel]: {},
[SpecialReference.PromiseConstructor]: {},
[SpecialReference.PromiseSuccess]: {},
[SpecialReference.PromiseFailure]: {},
[SpecialReference.StreamConstructor]: {},
[SpecialReference.ArrayBufferConstructor]: {},
};
export const SPECIAL_REF_STRING: Record<SpecialReference, string> = {
[SpecialReference.MapSentinel]: '[]',
[SpecialReference.PromiseConstructor]: SERIALIZED_PROMISE_CONSTRUCTOR,
[SpecialReference.PromiseSuccess]: SERIALIZED_PROMISE_SUCCESS,
[SpecialReference.PromiseFailure]: SERIALIZED_PROMISE_FAILURE,
[SpecialReference.StreamConstructor]: SERIALIZED_STREAM_CONSTRUCTOR,
[SpecialReference.ArrayBufferConstructor]:
SERIALIZED_ARRAY_BUFFER_CONSTRUCTOR,
};

View file

@ -0,0 +1,71 @@
import {
ASYNC_ITERATOR_CONSTRUCTOR,
PROMISE_CONSTRUCTOR,
STREAM_CONSTRUCTOR,
} from './constructors';
import { SYM_ASYNC_ITERATOR } from './symbols';
export interface StreamListener<T> {
next(value: T): void;
throw(value: unknown): void;
return(value: T): void;
}
export interface Stream<T> {
__SEROVAL_STREAM__: true;
on(listener: StreamListener<T>): () => void;
next(value: T): void;
throw(value: unknown): void;
return(value: T): void;
}
export function isStream<T>(value: object): value is Stream<T> {
return '__SEROVAL_STREAM__' in value;
}
export function createStream<T>(): Stream<T> {
return STREAM_CONSTRUCTOR() as unknown as Stream<T>;
}
export function createStreamFromAsyncIterable<T>(
iterable: AsyncIterable<T>,
): Stream<T> {
const stream = createStream<T>();
const iterator = iterable[SYM_ASYNC_ITERATOR]();
async function push(): Promise<void> {
try {
const value = await iterator.next();
if (value.done) {
stream.return(value.value as T);
} else {
stream.next(value.value);
await push();
}
} catch (error) {
stream.throw(error);
}
}
push().catch(() => {
// no-op
});
return stream;
}
const createAsyncIterable = ASYNC_ITERATOR_CONSTRUCTOR(
SYM_ASYNC_ITERATOR,
PROMISE_CONSTRUCTOR,
);
export function streamToAsyncIterable<T>(
stream: Stream<T>,
): () => AsyncIterableIterator<T> {
return createAsyncIterable(
stream,
) as unknown as () => AsyncIterableIterator<T>;
}

View file

@ -0,0 +1,86 @@
import { NIL } from './constants';
export function serializeChar(str: string): string | undefined {
switch (str) {
case '"':
return '\\"';
case '\\':
return '\\\\';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '\b':
return '\\b';
case '\t':
return '\\t';
case '\f':
return '\\f';
case '<':
return '\\x3C';
case '\u2028':
return '\\u2028';
case '\u2029':
return '\\u2029';
default:
return NIL;
}
}
// Written by https://github.com/DylanPiercey and is distributed under the MIT license.
// Creates a JavaScript double quoted string and escapes all characters
// not listed as DoubleStringCharacters on
// Also includes "<" to escape "</script>" and "\" to avoid invalid escapes in the output.
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
export function serializeString(str: string): string {
let result = '';
let lastPos = 0;
let replacement: string | undefined;
for (let i = 0, len = str.length; i < len; i++) {
replacement = serializeChar(str[i]);
if (replacement) {
result += str.slice(lastPos, i) + replacement;
lastPos = i + 1;
}
}
if (lastPos === 0) {
result = str;
} else {
result += str.slice(lastPos);
}
return result;
}
function deserializeReplacer(str: string): string {
switch (str) {
case '\\\\':
return '\\';
case '\\"':
return '"';
case '\\n':
return '\n';
case '\\r':
return '\r';
case '\\b':
return '\b';
case '\\t':
return '\t';
case '\\f':
return '\f';
case '\\x3C':
return '\x3C';
case '\\u2028':
return '\u2028';
case '\\u2029':
return '\u2029';
default:
return str;
}
}
export function deserializeString(str: string): string {
return str.replace(
/(\\\\|\\"|\\n|\\r|\\b|\\t|\\f|\\u2028|\\u2029|\\x3C)/g,
deserializeReplacer,
);
}

View file

@ -0,0 +1,18 @@
export const SYM_ASYNC_ITERATOR: typeof Symbol.asyncIterator = Symbol.asyncIterator;
export const SYM_HAS_INSTANCE: typeof Symbol.hasInstance = Symbol.hasInstance;
export const SYM_IS_CONCAT_SPREADABLE: typeof Symbol.isConcatSpreadable = Symbol.isConcatSpreadable;
export const SYM_ITERATOR: typeof Symbol.iterator = Symbol.iterator;
export const SYM_MATCH: typeof Symbol.match = Symbol.match;
export const SYM_MATCH_ALL: typeof Symbol.matchAll = Symbol.matchAll;
export const SYM_REPLACE: typeof Symbol.replace = Symbol.replace;
export const SYM_SEARCH: typeof Symbol.search = Symbol.search;
export const SYM_SPECIES: typeof Symbol.species = Symbol.species;
export const SYM_SPLIT: typeof Symbol.split = Symbol.split;
export const SYM_TO_PRIMITIVE: typeof Symbol.toPrimitive = Symbol.toPrimitive;
export const SYM_TO_STRING_TAG: typeof Symbol.toStringTag = Symbol.toStringTag;
export const SYM_UNSCOPABLES: typeof Symbol.unscopables = Symbol.unscopables;
// For the future
// export const SYM_DISPOSE = Symbol.dispose;
// export const SYM_ASYNC_DISPOSE = Symbol.asyncDispose;

View file

@ -0,0 +1,134 @@
import {
createAsyncParserContext,
parseTopAsync,
} from '../context/async-parser';
import {
createVanillaDeserializerContext,
deserializeTop,
} from '../context/deserializer';
import type { BaseParserContextOptions } from '../context/parser';
import {
createVanillaSerializerContext,
serializeTopVanilla,
} from '../context/serializer';
import { createSyncParserContext, parseTop } from '../context/sync-parser';
import {
type PluginAccessOptions,
resolvePlugins,
SerovalMode,
} from '../plugin';
import type { SerovalNode } from '../types';
import { ALL_ENABLED } from '../compat';
export type SyncParserContextOptions = Omit<BaseParserContextOptions, 'refs'>;
export type AsyncParserContextOptions = Omit<BaseParserContextOptions, 'refs'>;
export function serialize<T>(
source: T,
options: SyncParserContextOptions = {},
): string {
const plugins = resolvePlugins(options.plugins);
const ctx = createSyncParserContext(SerovalMode.Vanilla, {
plugins,
disabledFeatures: options.disabledFeatures,
});
const tree = parseTop(ctx, source);
const serial = createVanillaSerializerContext({
plugins,
features: ctx.base.features,
markedRefs: ctx.base.marked,
});
return serializeTopVanilla(serial, tree);
}
export async function serializeAsync<T>(
source: T,
options: AsyncParserContextOptions = {},
): Promise<string> {
const plugins = resolvePlugins(options.plugins);
const ctx = createAsyncParserContext(SerovalMode.Vanilla, {
plugins,
disabledFeatures: options.disabledFeatures,
});
const tree = await parseTopAsync(ctx, source);
const serial = createVanillaSerializerContext({
plugins,
features: ctx.base.features,
markedRefs: ctx.base.marked,
});
return serializeTopVanilla(serial, tree);
}
export function deserialize<T>(source: string): T {
return (0, eval)(source) as T;
}
export interface SerovalJSON {
t: SerovalNode;
f: number;
m: number[];
}
export interface FromJSONOptions extends PluginAccessOptions {
disabledFeatures?: number;
}
export function toJSON<T>(
source: T,
options: SyncParserContextOptions = {},
): SerovalJSON {
const plugins = resolvePlugins(options.plugins);
const ctx = createSyncParserContext(SerovalMode.Vanilla, {
plugins,
disabledFeatures: options.disabledFeatures,
});
return {
t: parseTop(ctx, source),
f: ctx.base.features,
m: Array.from(ctx.base.marked),
};
}
export async function toJSONAsync<T>(
source: T,
options: AsyncParserContextOptions = {},
): Promise<SerovalJSON> {
const plugins = resolvePlugins(options.plugins);
const ctx = createAsyncParserContext(SerovalMode.Vanilla, {
plugins,
disabledFeatures: options.disabledFeatures,
});
return {
t: await parseTopAsync(ctx, source),
f: ctx.base.features,
m: Array.from(ctx.base.marked),
};
}
export function compileJSON(
source: SerovalJSON,
options: PluginAccessOptions = {},
): string {
const plugins = resolvePlugins(options.plugins);
const ctx = createVanillaSerializerContext({
plugins,
features: source.f,
markedRefs: source.m,
});
return serializeTopVanilla(ctx, source.t);
}
export function fromJSON<T>(
source: SerovalJSON,
options: FromJSONOptions = {},
): T {
const plugins = resolvePlugins(options.plugins);
const disabledFeatures = options.disabledFeatures || 0;
const sourceFeatures = source.f ?? ALL_ENABLED;
const ctx = createVanillaDeserializerContext({
plugins,
markedRefs: source.m,
features: sourceFeatures & ~disabledFeatures,
disabledFeatures,
});
return deserializeTop(ctx, source.t) as T;
}

366
Frontend-Learner/node_modules/seroval/src/core/types.ts generated vendored Normal file
View file

@ -0,0 +1,366 @@
import type {
ErrorConstructorTag,
SerovalConstant,
SerovalNodeType,
SerovalObjectFlags,
Symbols,
} from './constants';
import type { SpecialReference } from './special-reference';
export interface SerovalBaseNode {
// Type of the node
t: SerovalNodeType;
// Reference ID
i: number | undefined;
// Serialized value
s: unknown;
// Constructor name / RegExp source
c: string | undefined;
// message/flags
m: string | undefined;
// properties (objects)
p: SerovalObjectRecordNode | undefined;
// entries (for Map, etc.)
e: SerovalMapRecordNode | undefined;
// array of nodes
a: (SerovalNode | 0)[] | undefined;
// fulfilled node
f: SerovalNode | undefined;
// byte offset/object flags
b: number | undefined;
// object flag
o: SerovalObjectFlags | undefined;
// length
l: number | undefined;
}
export type SerovalObjectRecordKey = string | SerovalNode;
export interface SerovalObjectRecordNode {
k: SerovalObjectRecordKey[];
v: SerovalNode[];
}
export interface SerovalMapRecordNode {
k: SerovalNode[];
v: SerovalNode[];
}
export interface SerovalNumberNode extends SerovalBaseNode {
t: SerovalNodeType.Number;
s: number;
}
export interface SerovalStringNode extends SerovalBaseNode {
t: SerovalNodeType.String;
s: string;
}
export interface SerovalConstantNode extends SerovalBaseNode {
t: SerovalNodeType.Constant;
s: SerovalConstant;
}
export type SerovalPrimitiveNode =
| SerovalNumberNode
| SerovalStringNode
| SerovalConstantNode;
export interface SerovalIndexedValueNode extends SerovalBaseNode {
t: SerovalNodeType.IndexedValue;
// id
i: number;
}
export interface SerovalBigIntNode extends SerovalBaseNode {
t: SerovalNodeType.BigInt;
// value in string
s: string;
}
export interface SerovalDateNode extends SerovalBaseNode {
t: SerovalNodeType.Date;
// id (Dates are stateful)
i: number;
// value in ISO string
s: string;
}
export interface SerovalRegExpNode extends SerovalBaseNode {
t: SerovalNodeType.RegExp;
// id (RegExp are stateful)
i: number;
// source
c: string;
// flags
m: string;
}
export interface SerovalArrayBufferNode extends SerovalBaseNode {
t: SerovalNodeType.ArrayBuffer;
// id
i: number;
// byte string
s: string;
// array buffer constructor
f: SerovalNodeWithID;
}
export interface SerovalTypedArrayNode extends SerovalBaseNode {
t: SerovalNodeType.TypedArray;
// id
i: number;
// TypedArray Constructor
c: string;
// ArrayBuffer reference
f: SerovalNode;
// Byte Offset
b: number;
// length
l: number;
}
export interface SerovalBigIntTypedArrayNode extends SerovalBaseNode {
t: SerovalNodeType.BigIntTypedArray;
i: number;
// TypedArray Constructor
c: string;
// ArrayBuffer reference
f: SerovalNode;
// Byte Offset
b: number;
// length
l: number;
}
export type SerovalSemiPrimitiveNode =
| SerovalBigIntNode
| SerovalDateNode
| SerovalRegExpNode
| SerovalTypedArrayNode
| SerovalBigIntTypedArrayNode;
export interface SerovalSetNode extends SerovalBaseNode {
t: SerovalNodeType.Set;
// id
i: number;
// Items in Set (as array)
a: SerovalNode[];
}
export interface SerovalMapNode extends SerovalBaseNode {
t: SerovalNodeType.Map;
i: number;
// key/value pairs
e: SerovalMapRecordNode;
f: SerovalNodeWithID;
}
export interface SerovalArrayNode extends SerovalBaseNode {
t: SerovalNodeType.Array;
// items
a: (SerovalNode | 0)[];
i: number;
o: SerovalObjectFlags;
}
export interface SerovalObjectNode extends SerovalBaseNode {
t: SerovalNodeType.Object;
// key/value pairs
p: SerovalObjectRecordNode;
i: number;
o: SerovalObjectFlags;
}
export interface SerovalNullConstructorNode extends SerovalBaseNode {
t: SerovalNodeType.NullConstructor;
// key/value pairs
p: SerovalObjectRecordNode;
i: number;
o: SerovalObjectFlags;
}
export interface SerovalPromiseNode extends SerovalBaseNode {
t: SerovalNodeType.Promise;
s: 0 | 1;
// resolved value
f: SerovalNode;
i: number;
}
export interface SerovalErrorNode extends SerovalBaseNode {
t: SerovalNodeType.Error;
// constructor name
s: ErrorConstructorTag;
// message
m: string;
// other properties
p: SerovalObjectRecordNode | undefined;
i: number;
}
export interface SerovalAggregateErrorNode extends SerovalBaseNode {
t: SerovalNodeType.AggregateError;
i: number;
// message
m: string;
// other properties
p: SerovalObjectRecordNode | undefined;
}
export interface SerovalWKSymbolNode extends SerovalBaseNode {
t: SerovalNodeType.WKSymbol;
i: number;
s: Symbols;
}
export interface SerovalReferenceNode extends SerovalBaseNode {
t: SerovalNodeType.Reference;
i: number;
// id of the reference in the map
s: string;
}
export interface SerovalDataViewNode extends SerovalBaseNode {
t: SerovalNodeType.DataView;
i: number;
// reference to array buffer
f: SerovalNode;
// byte offset
b: number;
// byte length
l: number;
}
export interface SerovalBoxedNode extends SerovalBaseNode {
t: SerovalNodeType.Boxed;
i: number;
f: SerovalNode;
}
export interface SerovalPromiseConstructorNode extends SerovalBaseNode {
t: SerovalNodeType.PromiseConstructor;
i: number;
s: number;
f: SerovalNodeWithID;
}
export interface SerovalPromiseResolveNode extends SerovalBaseNode {
t: SerovalNodeType.PromiseSuccess;
i: number;
a: [resolver: SerovalNodeWithID, resolved: SerovalNode];
}
export interface SerovalPromiseRejectNode extends SerovalBaseNode {
t: SerovalNodeType.PromiseFailure;
i: number;
a: [resolver: SerovalNodeWithID, resolved: SerovalNode];
}
export interface SerovalPluginNode extends SerovalBaseNode {
t: SerovalNodeType.Plugin;
i: number;
// value
s: unknown;
// tag name
c: string;
}
/**
* Represents special values as placeholders
*/
export interface SerovalSpecialReferenceNode extends SerovalBaseNode {
t: SerovalNodeType.SpecialReference;
i: number;
s: SpecialReference;
}
export interface SerovalIteratorFactoryNode extends SerovalBaseNode {
t: SerovalNodeType.IteratorFactory;
i: number;
f: SerovalNodeWithID;
}
export interface SerovalIteratorFactoryInstanceNode extends SerovalBaseNode {
t: SerovalNodeType.IteratorFactoryInstance;
a: [instance: SerovalNodeWithID, sequence: SerovalNode];
}
export interface SerovalAsyncIteratorFactoryNode extends SerovalBaseNode {
t: SerovalNodeType.AsyncIteratorFactory;
i: number;
a: [promise: SerovalNodeWithID, symbol: SerovalNodeWithID];
}
export interface SerovalAsyncIteratorFactoryInstanceNode
extends SerovalBaseNode {
t: SerovalNodeType.AsyncIteratorFactoryInstance;
a: [instance: SerovalNodeWithID, sequence: SerovalNode];
}
export interface SerovalStreamConstructorNode extends SerovalBaseNode {
t: SerovalNodeType.StreamConstructor;
i: number;
a: SerovalNode[];
// special reference to the constructor
f: SerovalNodeWithID;
}
export interface SerovalStreamNextNode extends SerovalBaseNode {
t: SerovalNodeType.StreamNext;
i: number;
// Next value
f: SerovalNode;
}
export interface SerovalStreamThrowNode extends SerovalBaseNode {
t: SerovalNodeType.StreamThrow;
i: number;
// Throw value
f: SerovalNode;
}
export interface SerovalStreamReturnNode extends SerovalBaseNode {
t: SerovalNodeType.StreamReturn;
i: number;
// Return value
f: SerovalNode;
}
export type SerovalSyncNode =
| SerovalPrimitiveNode
| SerovalIndexedValueNode
| SerovalSemiPrimitiveNode
| SerovalSetNode
| SerovalMapNode
| SerovalArrayNode
| SerovalObjectNode
| SerovalNullConstructorNode
| SerovalPromiseNode
| SerovalErrorNode
| SerovalAggregateErrorNode
| SerovalWKSymbolNode
| SerovalReferenceNode
| SerovalArrayBufferNode
| SerovalDataViewNode
| SerovalBoxedNode
| SerovalPluginNode
| SerovalSpecialReferenceNode
| SerovalIteratorFactoryNode
| SerovalIteratorFactoryInstanceNode
| SerovalAsyncIteratorFactoryNode
| SerovalAsyncIteratorFactoryInstanceNode;
export type SerovalAsyncNode =
| SerovalPromiseNode
| SerovalPromiseConstructorNode
| SerovalPromiseResolveNode
| SerovalPromiseRejectNode
| SerovalStreamConstructorNode
| SerovalStreamNextNode
| SerovalStreamThrowNode
| SerovalStreamReturnNode;
export type SerovalNode = SerovalSyncNode | SerovalAsyncNode;
export type SerovalNodeWithID = Extract<SerovalNode, { i: number }>;

View file

@ -0,0 +1,73 @@
import { Feature } from '../compat';
import { ERROR_CONSTRUCTOR_STRING, ErrorConstructorTag } from '../constants';
type ErrorValue =
| Error
| AggregateError
| EvalError
| RangeError
| ReferenceError
| TypeError
| SyntaxError
| URIError;
export function getErrorConstructor(error: ErrorValue): ErrorConstructorTag {
if (error instanceof EvalError) {
return ErrorConstructorTag.EvalError;
}
if (error instanceof RangeError) {
return ErrorConstructorTag.RangeError;
}
if (error instanceof ReferenceError) {
return ErrorConstructorTag.ReferenceError;
}
if (error instanceof SyntaxError) {
return ErrorConstructorTag.SyntaxError;
}
if (error instanceof TypeError) {
return ErrorConstructorTag.TypeError;
}
if (error instanceof URIError) {
return ErrorConstructorTag.URIError;
}
return ErrorConstructorTag.Error;
}
function getInitialErrorOptions(
error: Error,
): Record<string, unknown> | undefined {
const construct = ERROR_CONSTRUCTOR_STRING[getErrorConstructor(error)];
// Name has been modified
if (error.name !== construct) {
return { name: error.name };
}
if (error.constructor.name !== construct) {
// Otherwise, name is overriden because
// the Error class is extended
return { name: error.constructor.name };
}
return {};
}
export function getErrorOptions(
error: Error,
features: number,
): Record<string, unknown> | undefined {
let options = getInitialErrorOptions(error);
const names = Object.getOwnPropertyNames(error);
for (let i = 0, len = names.length, name: string; i < len; i++) {
name = names[i];
if (name !== 'name' && name !== 'message') {
if (name === 'stack') {
if (features & Feature.ErrorPrototypeStack) {
options = options || {};
options[name] = error[name as keyof Error];
}
} else {
options = options || {};
options[name] = error[name as keyof Error];
}
}
}
return options;
}

View file

@ -0,0 +1,18 @@
// Written by https://github.com/DylanPiercey and is distributed under the MIT license.
const REF_START_CHARS = /* @__PURE__ */ 'hjkmoquxzABCDEFGHIJKLNPQRTUVWXYZ$_'; // Avoids chars that could evaluate to a reserved word.
const REF_START_CHARS_LEN = /* @__PURE__ */ REF_START_CHARS.length;
const REF_CHARS =
/* @__PURE__ */ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_';
const REF_CHARS_LEN = /* @__PURE__ */ REF_CHARS.length;
export default function getIdentifier(index: number): string {
let mod = index % REF_START_CHARS_LEN;
let ref = REF_START_CHARS[mod];
index = (index - mod) / REF_START_CHARS_LEN;
while (index > 0) {
mod = index % REF_CHARS_LEN;
ref += REF_CHARS[mod];
index = (index - mod) / REF_CHARS_LEN;
}
return ref;
}

View file

@ -0,0 +1,14 @@
import { SerovalObjectFlags } from '../constants';
export function getObjectFlag(obj: unknown): SerovalObjectFlags {
if (Object.isFrozen(obj)) {
return SerovalObjectFlags.Frozen;
}
if (Object.isSealed(obj)) {
return SerovalObjectFlags.Sealed;
}
if (Object.isExtensible(obj)) {
return SerovalObjectFlags.None;
}
return SerovalObjectFlags.NonExtensible;
}

View file

@ -0,0 +1,12 @@
const IDENTIFIER_CHECK = /^[$A-Z_][0-9A-Z_$]*$/i;
export function isValidIdentifier(name: string): boolean {
const char = name[0];
return (
(char === '$' ||
char === '_' ||
(char >= 'A' && char <= 'Z') ||
(char >= 'a' && char <= 'z')) &&
IDENTIFIER_CHECK.test(name)
);
}

View file

@ -0,0 +1,44 @@
import { ITERATOR_CONSTRUCTOR } from '../constructors';
import { SYM_ITERATOR } from '../symbols';
export interface Sequence {
v: unknown[];
t: number;
d: number;
}
export function iteratorToSequence<T>(source: Iterable<T>): Sequence {
const values: unknown[] = [];
let throwsAt = -1;
let doneAt = -1;
const iterator = source[SYM_ITERATOR]();
while (true) {
try {
const value = iterator.next();
values.push(value.value);
if (value.done) {
doneAt = values.length - 1;
break;
}
} catch (error) {
throwsAt = values.length;
values.push(error);
}
}
return {
v: values,
t: throwsAt,
d: doneAt,
};
}
const createIterator = ITERATOR_CONSTRUCTOR(SYM_ITERATOR);
export function sequenceToIterator<T>(
sequence: Sequence,
): () => IterableIterator<T> {
return createIterator(sequence) as unknown as () => IterableIterator<T>;
}

View file

@ -0,0 +1,9 @@
export default async function promiseToResult(
current: Promise<unknown>,
): Promise<[0 | 1, unknown]> {
try {
return [1, await current];
} catch (e) {
return [0, e];
}
}

View file

@ -0,0 +1,56 @@
import { SerovalUnknownTypedArrayError } from '../errors';
type TypedArrayConstructor =
| Int8ArrayConstructor
| Int16ArrayConstructor
| Int32ArrayConstructor
| Uint8ArrayConstructor
| Uint16ArrayConstructor
| Uint32ArrayConstructor
| Uint8ClampedArrayConstructor
| Float32ArrayConstructor
| Float64ArrayConstructor
| BigInt64ArrayConstructor
| BigUint64ArrayConstructor;
export type TypedArrayValue =
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint16Array
| Uint32Array
| Uint8ClampedArray
| Float32Array
| Float64Array;
export type BigIntTypedArrayValue = BigInt64Array | BigUint64Array;
export function getTypedArrayConstructor(name: string): TypedArrayConstructor {
switch (name) {
case 'Int8Array':
return Int8Array;
case 'Int16Array':
return Int16Array;
case 'Int32Array':
return Int32Array;
case 'Uint8Array':
return Uint8Array;
case 'Uint16Array':
return Uint16Array;
case 'Uint32Array':
return Uint32Array;
case 'Uint8ClampedArray':
return Uint8ClampedArray;
case 'Float32Array':
return Float32Array;
case 'Float64Array':
return Float64Array;
case 'BigInt64Array':
return BigInt64Array;
case 'BigUint64Array':
return BigUint64Array;
default:
throw new SerovalUnknownTypedArrayError(name);
}
}

36
Frontend-Learner/node_modules/seroval/src/index.ts generated vendored Normal file
View file

@ -0,0 +1,36 @@
export { Feature } from './core/compat';
export type {
AsyncParsePluginContext,
AsyncParserContextOptions,
} from './core/context/async-parser';
export type {
BaseDeserializerContextOptions,
CrossDeserializerContextOptions,
DeserializePluginContext,
VanillaDeserializerContextOptions,
} from './core/context/deserializer';
export type { BaseParserContextOptions } from './core/context/parser';
export type {
BaseSerializerContextOptions,
CrossContextOptions,
CrossSerializerContextOptions,
SerializePluginContext,
VanillaSerializerContextOptions,
} from './core/context/serializer';
export type {
StreamParsePluginContext,
StreamParserContextOptions,
SyncParsePluginContext,
SyncParserContextOptions,
} from './core/context/sync-parser';
export * from './core/cross';
export * from './core/errors';
export { getCrossReferenceHeader } from './core/keys';
export { OpaqueReference } from './core/opaque-reference';
export * from './core/plugin';
export { createReference } from './core/reference';
export { default as Serializer } from './core/Serializer';
export { createStream } from './core/stream';
export type { Stream, StreamListener } from './core/stream';
export * from './core/tree';
export type { SerovalNode } from './core/types';