UNPKG

devalue

Version:

Gets the job done when JSON.stringify can't

453 lines (411 loc) 16.4 kB
export type StringValueTag = | 'URL' | 'URLSearchParams' | 'Temporal.Duration' | 'Temporal.Instant' | 'Temporal.PlainDate' | 'Temporal.PlainTime' | 'Temporal.PlainDateTime' | 'Temporal.PlainMonthDay' | 'Temporal.PlainYearMonth' | 'Temporal.ZonedDateTime'; export type ViewTag = | 'Int8Array' | 'Uint8Array' | 'Uint8ClampedArray' | 'Int16Array' | 'Uint16Array' | 'Float16Array' | 'Int32Array' | 'Uint32Array' | 'Float32Array' | 'Float64Array' | 'BigInt64Array' | 'BigUint64Array' | 'DataView'; export type TypedArray = | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Float16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; /** * The introspection/extraction operations `stringify` performs on the value * being serialized. Every dynamic operation — property reads, prototype * method calls, iteration, type classification — goes through this * interface, so overriding members lets you control exactly how values are * inspected. * * Use cases: * - **Side-effect-free serialization**: replace operations that can execute * user code (getters, proxy traps, patched prototypes, `Symbol.toStringTag` * accessors) with implementations based on captured intrinsics, internal * slots, or property descriptors. * - **Foreign-runtime serialization**: serialize values that live in another * JavaScript runtime (a `node:vm` context, a WASM-hosted engine, a remote * process) by implementing the operations over handle objects. The * `stringify` algorithm never touches the value directly, so "value" can * be any opaque token as long as the operations agree on what it means. * * All members are optional when passed to `stringify` — omitted members fall * back to the defaults (native behavior, exported as * `defaultStringifyOperations`). * * Members are named by what they do with the value: * - `isXxx`/`hasXxx` — predicates returning booleans * - `toXxx` — conversions whose whole result crosses into host JavaScript * (`toPrimitive`, `toISOString`) or into a native container (`toPromise`) * - `xxxOf` — queries returning host data *about* the value (`typeOf`, * `tagOf`, `lengthOf`) or its constituents, which remain in value space * (`valuesOf`, `entriesOf`) * - `xxxInfo` — multi-field descriptors mixing host data and constituent * values (`viewInfo`, `regExpInfo`) * - bare verbs (`get`, `unbox`, `identify`) — accessors whose results remain * in value space * * (`toStringValue` and `unbox` deliberately avoid the names `toString` and * `valueOf`, which would shadow `Object.prototype` methods on the operations * object.) */ export interface StringifyOperations { /** * Returns the key used for deduplication and cycle detection (compared * with `Map` key semantics). Two values that represent the same logical * object must return the same key. Default: the value itself. * * Override this when serializing through handles, where two distinct * handle objects may refer to the same underlying value. * * Keys are compared across *every* value in the payload, including * primitives, so an implementation that derives keys for objects must * make sure they cannot collide with a primitive that appears in the * same payload — returning e.g. the string `'42'` as an object's key * would alias it to the string `'42'` elsewhere in the payload and emit * a wrong back-reference. Prefer keys that are unforgeable, such as the * underlying object itself, a symbol, or a wrapper object. */ identify(value: any): unknown; /** * Classifies a value. Same contract as the `typeof` operator, except * `null` must be reported as `'null'` (not `'object'`). */ typeOf(value: any): | 'undefined' | 'null' | 'boolean' | 'number' | 'bigint' | 'string' | 'symbol' | 'function' | 'object'; /** * Extracts the host-JavaScript primitive from a value whose `typeOf` is * `'null'`, `'boolean'`, `'number'`, `'bigint'` or `'string'`. * Default: the value itself (it already is the primitive). */ toPrimitive(value: any): undefined | null | boolean | number | bigint | string; /** * Returns the brand of an object value — the strings produced by * `Object.prototype.toString` without the wrapping (`'Date'`, `'Array'`, * `'Map'`, `'Object'`, `'Temporal.Instant'`, …). This decides which * serialization strategy is used, so hardened implementations should use * engine-level brand checks rather than (spoofable, getter-invoking) * `Symbol.toStringTag` lookups. */ tagOf(value: any): string; /** Returns true if the object value should be treated as a thenable. */ isThenable(value: any): boolean; /** * Converts a thenable into a native promise, whose settled value is then * serialized. The returned promise may reject, in which case * `stringifyAsync` rejects. Only called from `stringifyAsync`, for values * where `isThenable` returned true. */ toPromise(thenable: any): Promise<any>; /** * Extracts the inner value of a boxed primitive (`Number`, `String`, * `Boolean`, `BigInt` objects). Equivalent to `boxed.valueOf()`. The * result is serialized recursively, so it may be a foreign value/handle. */ unbox(boxed: any): any; /** * Returns the ISO string for a `Date` value, or `''` for an invalid * date. Equivalent to `date.toISOString()`. */ toISOString(date: any): string; /** * Returns the string form of a `URL`, `URLSearchParams` or `Temporal.*` * value. Equivalent to `value.toString()`. */ toStringValue(value: any): string; /** Returns the source and flags of a `RegExp` value. */ regExpInfo(regexp: any): { source: string; flags: string }; /** * Returns an iterable over the elements of a `Set` value. The iterable * is consumed on the host; elements may be foreign values/handles. */ valuesOf(set: any): Iterable<any>; /** * Returns an iterable over the `[key, value]` entries of a `Map` value. * The iterable is consumed on the host; keys/values may be foreign * values/handles. */ entriesOf(map: any): Iterable<[any, any]>; /** * Returns the view metadata of a typed array or `DataView` value. * `length` is only meaningful for typed arrays. `buffer` is serialized * recursively, so it may be a foreign value/handle. */ viewInfo(view: any): { buffer: any; byteOffset: number; byteLength: number; length?: number; bufferByteLength: number; }; /** * Returns a host `ArrayBuffer` with the bytes of an `ArrayBuffer` value. * Default: the value itself. Foreign-runtime implementations should copy * the bytes into a host buffer. */ toArrayBuffer(buffer: any): ArrayBuffer; /** Returns the length of an `Array` value. */ lengthOf(array: any): number; /** * Returns true if a value has an own property at `key`. Same contract as * `Object.hasOwn(value, key)`. */ hasOwn(value: any, key: string | number): boolean; /** * Returns the populated indices of a (sparse) `Array` value as strings, * in ascending order. * * Implementations that already have the value's own enumerable string * keys — as a foreign-runtime implementation typically does — should pass * them through the exported `filterArrayIndices` helper rather than * reimplementing the filtering, which encodes the sparse-array heuristic. * * Equivalent to `Object.keys(array)` filtered to * valid array indices. */ indicesOf(array: any): string[]; /** * Classifies a plain-object candidate: * - `{ kind: 'plain' | 'null-proto', keys }` — a serializable POJO and * its own enumerable string keys * - `{ kind: 'not-plain' }` — a non-POJO (stringify throws) * - `{ kind: 'symbol-keys' }` — a POJO with enumerable symbol keys * (stringify throws) */ shapeOf( value: any ): | { kind: 'plain' | 'null-proto'; keys: string[] } | { kind: 'not-plain' } | { kind: 'symbol-keys' }; /** * Reads a property from an `Array` or plain-object value. Equivalent to * `value[key]`. Hardened implementations can read through property * descriptors to control what happens for accessor properties. */ get(value: any, key: string | number): any; } /** The native JavaScript implementation exported as `defaultStringifyOperations`. */ export interface DefaultStringifyOperations extends StringifyOperations { identify(value: any): any; toPrimitive( value: undefined | null | boolean | number | bigint | string ): undefined | null | boolean | number | bigint | string; toISOString(date: Date): string; regExpInfo(regexp: RegExp): { source: string; flags: string }; valuesOf(set: Set<any>): Set<any>; entriesOf(map: Map<any, any>): Map<any, any>; viewInfo(view: any): { buffer: ArrayBufferLike; byteOffset: number; byteLength: number; length?: number; bufferByteLength: number; }; toArrayBuffer(buffer: ArrayBuffer): ArrayBuffer; lengthOf(array: any[]): number; indicesOf(array: any[]): string[]; } /** Options for `stringify` and `stringifyAsync`. */ export interface StringifyOptions { /** * Overrides for the introspection/extraction operations used while * serializing. Omitted members fall back to `defaultStringifyOperations`. */ operations?: Partial<StringifyOperations>; } /** * The construction operations `parse` and `unflatten` perform while reviving * a value. Every value the algorithm creates — primitives, built-in * instances, containers — and every mutation it performs to populate those * containers goes through this interface, so overriding members lets you * control exactly what gets built. * * Use cases: * - **Cross-realm revival**: construct values from the intrinsics of a * different realm (e.g. a `node:vm` context) so that the result passes * `instanceof` checks inside that realm. * - **Foreign-runtime revival**: build values inside another JavaScript * runtime (a WASM-hosted engine, a remote process) by implementing the * operations over handle objects. The algorithm never inspects the values * it creates — it only passes them back into other operations — so * "value" can be any opaque token. * * The naming follows the same scheme as `StringifyOperations`, with the * host/value-space boundary running the other way: * * - `fromXxx` — conversions whose input is entirely host data and whose * result crosses into value space; each is the inverse of the * corresponding `toXxx` (`fromPrimitive` / `toPrimitive`, * `fromISOString` / `toISOString`, `fromStringValue` / `toStringValue`, * `fromArrayBuffer` / `toArrayBuffer`). * - `fromXxxInfo` — construction from a multi-field descriptor, the inverse * of the corresponding `xxxInfo` (`fromRegExpInfo` / `regExpInfo`, * `fromViewInfo` / `viewInfo`). * - `createXxx` — empty value-space containers, populated afterwards by the * mutators. That ordering is what makes cyclic values possible: the empty * container is cached before its contents are revived. * - bare verbs — value-space operations whose operands and results stay in * value space (`box` inverts `unbox`, `set` inverts `get`, `addValue` * inverts `valuesOf`, `addEntry` inverts `entriesOf`). * * All members are optional when passed to `parse`/`unflatten` — omitted * members fall back to the defaults (native behavior, exported as * `defaultParseOperations`). */ export interface ParseOperations { /** * Wraps a host primitive (`string`, `number`, `boolean`, `bigint`, * `null`, `undefined`, and the special values `NaN`, `±Infinity`, `-0`) * into the representation the other operations expect. The inverse of * `toPrimitive`. Default: the value itself. */ fromPrimitive( primitive: string | number | boolean | bigint | null | undefined ): any; /** * Creates a `Date` from an ISO string. The inverse of `toISOString`. * An empty string represents an invalid date (as produced for * `new Date(NaN)`). */ fromISOString(iso: string): any; /** * Creates a `URL`, `URLSearchParams` or `Temporal.*` value from its * string form — the same tags `toStringValue` serializes, and its * inverse. `tag` distinguishes them (e.g. `'URL'`, * `'Temporal.Instant'`). */ fromStringValue(tag: StringValueTag, text: string): any; /** * Creates an `ArrayBuffer` from a host `ArrayBuffer` holding the decoded * bytes. The inverse of `toArrayBuffer`. Default: the buffer itself. * Foreign-runtime implementations should copy the bytes into the target * runtime. */ fromArrayBuffer(buffer: ArrayBuffer): any; /** * Creates a `RegExp` from its source and flags. The inverse of * `regExpInfo`. `flags` is `undefined` when the pattern had no flags. */ fromRegExpInfo(source: string, flags: string | undefined): any; /** * Creates a typed array or `DataView` over an already-revived buffer. * The inverse of `viewInfo`. `tag` is the constructor name (e.g. * `'Uint8Array'`, `'DataView'`). `byteOffset` and `length` are * `undefined` when the view spans the whole buffer; otherwise `length` * is the element count for typed arrays and the byte length for * `DataView`, matching the constructor signatures. */ fromViewInfo( tag: ViewTag, buffer: any, byteOffset: number | undefined, length: number | undefined ): any; /** * Creates a boxed primitive object (`Number`, `String`, `Boolean`, * `BigInt` wrapper) around an already-revived inner primitive. The * inverse of `unbox`. Equivalent to `Object(value)`. */ box(value: any): any; /** * Creates an array of the given length, to be populated with `set`. * The length is bounded by the size of the input, so it is safe to * allocate eagerly. Indices that are never set must remain holes. */ createArray(length: number): any; /** * Creates a sparse array of the given length, to be populated with * `set`. Unlike `createArray`, the length comes from the input rather * than being bounded by it, so implementations must not allocate * storage proportional to it. */ createSparseArray(length: number): any; /** Creates an empty object, to be populated with `set`. */ createObject(): any; /** * Creates an empty null-prototype object, to be populated with `set`. * Equivalent to `Object.create(null)`. */ createNullPrototypeObject(): any; /** Creates an empty `Set`, to be populated with `addValue`. */ createSet(): any; /** Creates an empty `Map`, to be populated with `addEntry`. */ createMap(): any; /** * Sets an element or property on a value created by `createArray`, * `createSparseArray`, `createObject` or `createNullPrototypeObject`. * The inverse of `get`, which likewise serves both arrays and objects. */ set(target: any, key: string | number, value: any): void; /** Adds a value to a `Set` created by `createSet`. The inverse of `valuesOf`. */ addValue(set: any, value: any): void; /** Adds an entry to a `Map` created by `createMap`. The inverse of `entriesOf`. */ addEntry(map: any, key: any, value: any): void; } /** The native JavaScript implementation exported as `defaultParseOperations`. */ export interface DefaultParseOperations extends ParseOperations { fromPrimitive( primitive: string | number | boolean | bigint | null | undefined ): string | number | boolean | bigint | null | undefined; fromISOString(iso: string): Date; fromStringValue(tag: StringValueTag, text: string): URL | URLSearchParams | object; fromArrayBuffer(buffer: ArrayBuffer): ArrayBuffer; fromRegExpInfo(source: string, flags: string | undefined): RegExp; fromViewInfo( tag: ViewTag, buffer: ArrayBufferLike, byteOffset: number | undefined, length: number | undefined ): TypedArray | DataView; box(value: any): object; createArray(length: number): any[]; createSparseArray(length: number): any[]; createObject(): Record<string, any>; createNullPrototypeObject(): Record<string, any>; createSet(): Set<any>; createMap(): Map<any, any>; addValue(set: Set<any>, value: any): void; addEntry(map: Map<any, any>, key: any, value: any): void; } /** Options for `parse` and `unflatten`. */ export interface ParseOptions { /** * Overrides for the construction operations used while reviving. * Omitted members fall back to `defaultParseOperations`. */ operations?: Partial<ParseOperations>; }