| import { MAX_ARRAY_INDEX } from './constants.js'; | ||
| import { | ||
| enumerable_symbols, | ||
| get_type, | ||
| is_plain_object, | ||
| valid_array_indices | ||
| } from './utils.js'; | ||
| /** | ||
| * Merges caller-provided operation overrides over the defaults. Iterating the | ||
| * default keys (rather than the override's own keys) means nullish members | ||
| * fall back to the default, and inherited members — e.g. from a class | ||
| * instance — are picked up. | ||
| * | ||
| * @template {Record<string, any>} T | ||
| * @param {T} defaults | ||
| * @param {Partial<T> | undefined} overrides | ||
| * @returns {T} | ||
| */ | ||
| export function merge_operations(defaults, overrides) { | ||
| if (!overrides) return defaults; | ||
| const merged = /** @type {T} */ ({}); | ||
| for (const key of /** @type {(keyof T)[]} */ (Object.keys(defaults))) { | ||
| merged[key] = overrides[key] ?? defaults[key]; | ||
| } | ||
| return merged; | ||
| } | ||
| /** @type {{ kind: 'not-plain' }} */ | ||
| const NOT_PLAIN = Object.freeze({ kind: 'not-plain' }); | ||
| /** @type {{ kind: 'symbol-keys' }} */ | ||
| const SYMBOL_KEYS = Object.freeze({ kind: 'symbol-keys' }); | ||
| /** | ||
| * The default implementations of every introspection/extraction operation | ||
| * `stringify` performs on the value being serialized. Each one uses native | ||
| * JavaScript semantics (property access, iteration, prototype methods, etc). | ||
| * | ||
| * Pass overrides via the `operations` option of `stringify`/`stringifyAsync` | ||
| * to customize how values are inspected — e.g. to serialize values without | ||
| * triggering getters, proxy traps, or patched prototype methods, or to | ||
| * serialize values that live in a different JavaScript runtime (a `node:vm` | ||
| * context, a WASM-hosted engine, a remote process) through handle objects. | ||
| * | ||
| * The object is frozen — it is shared by every `stringify` call that does | ||
| * not override a given operation. | ||
| * | ||
| */ | ||
| /** @type {import('./types.js').DefaultStringifyOperations} */ | ||
| const stringify_operations = { | ||
| identify: (value) => value, | ||
| typeOf: (value) => (value === null ? 'null' : typeof value), | ||
| toPrimitive: (value) => value, | ||
| tagOf: (value) => get_type(value), | ||
| isThenable: (value) => typeof value.then === 'function', | ||
| toPromise: (thenable) => Promise.resolve(thenable), | ||
| unbox: (boxed) => boxed.valueOf(), | ||
| toISOString: (date) => (isNaN(date.getDate()) ? '' : date.toISOString()), | ||
| toStringValue: (value) => value.toString(), | ||
| regExpInfo: (regexp) => ({ source: regexp.source, flags: regexp.flags }), | ||
| valuesOf: (set) => set, | ||
| entriesOf: (map) => map, | ||
| viewInfo: (view) => ({ | ||
| buffer: view.buffer, | ||
| byteOffset: view.byteOffset, | ||
| byteLength: view.byteLength, | ||
| length: view.length, | ||
| bufferByteLength: view.buffer.byteLength | ||
| }), | ||
| toArrayBuffer: (buffer) => buffer, | ||
| lengthOf: (array) => array.length, | ||
| hasOwn: (value, key) => Object.hasOwn(value, key), | ||
| indicesOf: (array) => valid_array_indices(array), | ||
| shapeOf: (value) => { | ||
| if (!is_plain_object(value)) return NOT_PLAIN; | ||
| if (enumerable_symbols(value).length > 0) return SYMBOL_KEYS; | ||
| return { | ||
| kind: Object.getPrototypeOf(value) === null ? 'null-proto' : 'plain', | ||
| keys: Object.keys(value) | ||
| }; | ||
| }, | ||
| get: (value, key) => value[key] | ||
| }; | ||
| export const default_stringify_operations = Object.freeze(stringify_operations); | ||
| /** | ||
| * The default implementations of every construction operation `parse` and | ||
| * `unflatten` perform while reviving a value. Each one uses native | ||
| * JavaScript semantics (built-in constructors, property assignment, etc). | ||
| * | ||
| * Pass overrides via the `operations` option of `parse`/`unflatten` to | ||
| * customize how values are built — e.g. to construct them from the | ||
| * intrinsics of a different realm (a `node:vm` context), or to build up | ||
| * values inside another JavaScript runtime (a WASM-hosted engine, a remote | ||
| * process) through handle objects. | ||
| * | ||
| * The object is frozen — it is shared by every `parse` call that does not | ||
| * override a given operation. | ||
| * | ||
| */ | ||
| /** @type {import('./types.js').DefaultParseOperations} */ | ||
| const parse_operations = { | ||
| fromPrimitive: (primitive) => primitive, | ||
| fromISOString: (iso) => new Date(iso), | ||
| fromStringValue: (tag, text) => { | ||
| if (tag === 'URL') return new URL(text); | ||
| if (tag === 'URLSearchParams') return new URLSearchParams(text); | ||
| // 'Temporal.Instant', 'Temporal.PlainDate', ... | ||
| // @ts-expect-error TS doesn't know about Temporal yet | ||
| return Temporal[tag.slice(9)].from(text); | ||
| }, | ||
| fromArrayBuffer: (buffer) => buffer, | ||
| fromRegExpInfo: (source, flags) => new RegExp(source, flags), | ||
| fromViewInfo: (tag, buffer, byteOffset, length) => { | ||
| const Constructor = /** @type {any} */ (globalThis)[tag]; | ||
| return byteOffset !== undefined | ||
| ? new Constructor(buffer, byteOffset, length) | ||
| : new Constructor(buffer); | ||
| }, | ||
| box: (value) => Object(value), | ||
| createArray: (length) => new Array(length), | ||
| createSparseArray: (length) => { | ||
| /** @type {any[]} */ | ||
| const array = []; | ||
| // Setting `array.length = length` (or equivalently calling | ||
| // `new Array(length)`) on an untrusted length is a DoS vector: V8 | ||
| // eagerly allocates a contiguous backing store for array lengths below | ||
| // ~10^8, so a small payload with a huge declared length can force | ||
| // arbitrary memory allocation. Touching the largest-possible index | ||
| // first forces V8 into dictionary-elements mode, where `length` is | ||
| // just a number and no contiguous allocation occurs. | ||
| array[MAX_ARRAY_INDEX] = undefined; | ||
| delete array[MAX_ARRAY_INDEX]; | ||
| array.length = length; | ||
| return array; | ||
| }, | ||
| createObject: () => ({}), | ||
| createNullPrototypeObject: () => Object.create(null), | ||
| createSet: () => new Set(), | ||
| createMap: () => new Map(), | ||
| set: (target, key, value) => { | ||
| target[key] = value; | ||
| }, | ||
| addValue: (set, value) => { | ||
| set.add(value); | ||
| }, | ||
| addEntry: (map, key, value) => { | ||
| map.set(key, value); | ||
| } | ||
| }; | ||
| export const default_parse_operations = Object.freeze(parse_operations); |
+14
-1
| export { uneval } from './src/uneval.js'; | ||
| export { parse, unflatten } from './src/parse.js'; | ||
| export { stringify, stringifyAsync } from './src/stringify.js'; | ||
| export { DevalueError } from './src/utils.js'; | ||
| export { | ||
| default_stringify_operations as defaultStringifyOperations, | ||
| default_parse_operations as defaultParseOperations | ||
| } from './src/operations.js'; | ||
| export { DevalueError, filter_array_indices as filterArrayIndices } from './src/utils.js'; | ||
| /** @typedef {import('./src/types.js').StringValueTag} StringValueTag */ | ||
| /** @typedef {import('./src/types.js').ViewTag} ViewTag */ | ||
| /** @typedef {import('./src/types.js').StringifyOperations} StringifyOperations */ | ||
| /** @typedef {import('./src/types.js').DefaultStringifyOperations} DefaultStringifyOperations */ | ||
| /** @typedef {import('./src/types.js').StringifyOptions} StringifyOptions */ | ||
| /** @typedef {import('./src/types.js').ParseOperations} ParseOperations */ | ||
| /** @typedef {import('./src/types.js').DefaultParseOperations} DefaultParseOperations */ | ||
| /** @typedef {import('./src/types.js').ParseOptions} ParseOptions */ |
+1
-1
| { | ||
| "name": "devalue", | ||
| "description": "Gets the job done when JSON.stringify can't", | ||
| "version": "5.8.2", | ||
| "version": "5.9.0", | ||
| "repository": "sveltejs/devalue", | ||
@@ -6,0 +6,0 @@ "sideEffects": false, |
+80
-0
@@ -148,2 +148,82 @@ # devalue | ||
| ## Custom operations | ||
| Every introspection `stringify` performs on the value being serialized — property reads, prototype method calls, iteration, type classification — goes through an operations interface that you can override via the `operations` option. Omitted members fall back to the defaults (exported as `defaultStringifyOperations`), which behave exactly as devalue always has. | ||
| This is useful in two situations: | ||
| **Side-effect-free serialization.** By default, serializing a value can execute user code: getters and proxy traps fire during property reads, `Object.prototype.toString` consults (potentially getter-defined) `Symbol.toStringTag`, and patched prototype methods like `Date.prototype.toISOString` or `Map.prototype[Symbol.iterator]` are invoked. Deterministic or sandboxed runtimes can replace these operations with implementations based on captured intrinsics and property descriptors: | ||
| ```js | ||
| const originalToISOString = Date.prototype.toISOString; | ||
| const stringified = devalue.stringify(value, undefined, { | ||
| operations: { | ||
| // use a captured intrinsic instead of a (possibly patched) prototype method | ||
| toISOString: (date) => originalToISOString.call(date), | ||
| // read through descriptors so getters are never invoked | ||
| get: (object, key) => { | ||
| const descriptor = Object.getOwnPropertyDescriptor(object, key); | ||
| if (descriptor?.get) throw new Error(`refusing to invoke getter for "${key}"`); | ||
| return descriptor?.value; | ||
| } | ||
| } | ||
| }); | ||
| ``` | ||
| **Foreign-runtime serialization.** The `stringify` algorithm never touches the value directly, so "value" can be an opaque handle to something living in another JavaScript runtime — a `node:vm` context, a WASM-hosted engine, a remote process — as long as the operations know how to inspect it. Implement `typeOf`/`tagOf` for classification, `toPrimitive`/`get`/`entriesOf`/etc. for extraction, and `identify` to key deduplication and cycle detection on the underlying value's identity rather than the handle's: | ||
| ```js | ||
| const stringified = devalue.stringify(rootHandle, undefined, { | ||
| operations: { | ||
| identify: (handle) => handle.pointer, | ||
| typeOf: (handle) => handle.typeOf(), | ||
| get: (handle, key) => handle.getProperty(key) | ||
| // ... see StringifyOperations for the full interface | ||
| } | ||
| }); | ||
| ``` | ||
| Some operations have a non-obvious contract that is easy to get subtly wrong. Where the work is not specific to your values, devalue exports the pieces so you don't have to reimplement them — `filterArrayIndices` does the array-index filtering that `indicesOf` needs, given keys you already have: | ||
| ```js | ||
| indicesOf: (handle) => devalue.filterArrayIndices(handle.ownEnumerableStringKeys()) | ||
| ``` | ||
| Reducers compose with custom operations: they receive the raw value/handle, and whatever they return is serialized through the same operations. | ||
| ### Customizing `parse` | ||
| The mirror image: `parse` and `unflatten` build every value through construction operations (`ParseOperations`, defaults exported as `defaultParseOperations`), so you can control what gets created. The members mirror `StringifyOperations` with the host/value-space boundary running the other way: each `fromXxx` inverts the corresponding `toXxx`, `fromXxxInfo` inverts `xxxInfo`, and the bare-verb mutators invert the bare-verb accessors (`set`/`get`, `addValue`/`valuesOf`, `addEntry`/`entriesOf`, `box`/`unbox`). | ||
| **Cross-realm revival.** By default the revived value is built from the intrinsics of whichever realm devalue is running in, so `instanceof` checks fail elsewhere. Constructing from a target realm's intrinsics fixes that: | ||
| ```js | ||
| const revived = devalue.parse(serialized, undefined, { | ||
| operations: { | ||
| fromISOString: (iso) => new sandbox.Date(iso), | ||
| createMap: () => new sandbox.Map(), | ||
| createObject: () => sandbox.makeObject() | ||
| } | ||
| }); | ||
| ``` | ||
| **Foreign-runtime revival.** `parse` never inspects the values it creates — it only passes them back into other operations — so the operations can build values inside another runtime and return opaque handles: | ||
| ```js | ||
| const rootHandle = devalue.parse(serialized, undefined, { | ||
| operations: { | ||
| fromPrimitive: (primitive) => vm.toHandle(primitive), | ||
| createObject: () => vm.newObject(), | ||
| set: (handle, key, value) => handle.setProp(key, value) | ||
| // ... see ParseOperations for the full interface | ||
| } | ||
| }); | ||
| ``` | ||
| Containers are created empty and populated afterwards (`createMap` then `addEntry`, `createObject` then `set`, and so on) — that ordering is what allows cyclic values to be revived, since the empty container is cached before its contents are built. | ||
| Revivers compose the same way reducers do: they receive whatever the operations built, and their return value is used as-is. | ||
| ## Error handling | ||
@@ -150,0 +230,0 @@ |
+40
-63
| import { decode64 } from './base64.js'; | ||
| import { | ||
| HOLE, | ||
| MAX_ARRAY_INDEX, | ||
| NAN, | ||
@@ -12,2 +11,3 @@ NEGATIVE_INFINITY, | ||
| } from './constants.js'; | ||
| import { default_parse_operations, merge_operations } from './operations.js'; | ||
| import { is_valid_array_index, is_valid_array_len } from './utils.js'; | ||
@@ -19,5 +19,6 @@ | ||
| * @param {Record<string, (value: any) => any>} [revivers] | ||
| * @param {import('./types.js').ParseOptions} [options] | ||
| */ | ||
| export function parse(serialized, revivers) { | ||
| return unflatten(JSON.parse(serialized), revivers); | ||
| export function parse(serialized, revivers, options) { | ||
| return unflatten(JSON.parse(serialized), revivers, options); | ||
| } | ||
@@ -29,4 +30,8 @@ | ||
| * @param {Record<string, (value: any) => any>} [revivers] | ||
| * @param {import('./types.js').ParseOptions} [options] | ||
| */ | ||
| export function unflatten(parsed, revivers) { | ||
| export function unflatten(parsed, revivers, options) { | ||
| /** @type {import('./types.js').ParseOperations} */ | ||
| const ops = merge_operations(default_parse_operations, options?.operations); | ||
| if (typeof parsed === 'number') return hydrate(parsed, true); | ||
@@ -54,7 +59,7 @@ | ||
| function hydrate(index, standalone = false) { | ||
| if (index === UNDEFINED) return undefined; | ||
| if (index === NAN) return NaN; | ||
| if (index === POSITIVE_INFINITY) return Infinity; | ||
| if (index === NEGATIVE_INFINITY) return -Infinity; | ||
| if (index === NEGATIVE_ZERO) return -0; | ||
| if (index === UNDEFINED) return ops.fromPrimitive(undefined); | ||
| if (index === NAN) return ops.fromPrimitive(NaN); | ||
| if (index === POSITIVE_INFINITY) return ops.fromPrimitive(Infinity); | ||
| if (index === NEGATIVE_INFINITY) return ops.fromPrimitive(-Infinity); | ||
| if (index === NEGATIVE_ZERO) return ops.fromPrimitive(-0); | ||
@@ -70,3 +75,3 @@ if (standalone || typeof index !== 'number') { | ||
| if (!value || typeof value !== 'object') { | ||
| hydrated[index] = value; | ||
| hydrated[index] = ops.fromPrimitive(value); | ||
| } else if (Array.isArray(value)) { | ||
@@ -111,10 +116,10 @@ if (typeof value[0] === 'string') { | ||
| case 'Date': | ||
| hydrated[index] = new Date(value[1]); | ||
| hydrated[index] = ops.fromISOString(value[1]); | ||
| break; | ||
| case 'Set': | ||
| const set = new Set(); | ||
| const set = ops.createSet(); | ||
| hydrated[index] = set; | ||
| for (let i = 1; i < value.length; i += 1) { | ||
| set.add(hydrate(value[i])); | ||
| ops.addValue(set, hydrate(value[i])); | ||
| } | ||
@@ -124,6 +129,6 @@ break; | ||
| case 'Map': | ||
| const map = new Map(); | ||
| const map = ops.createMap(); | ||
| hydrated[index] = map; | ||
| for (let i = 1; i < value.length; i += 2) { | ||
| map.set(hydrate(value[i]), hydrate(value[i + 1])); | ||
| ops.addEntry(map, hydrate(value[i]), hydrate(value[i + 1])); | ||
| } | ||
@@ -133,3 +138,3 @@ break; | ||
| case 'RegExp': | ||
| hydrated[index] = new RegExp(value[1], value[2]); | ||
| hydrated[index] = ops.fromRegExpInfo(value[1], value[2]); | ||
| break; | ||
@@ -148,3 +153,3 @@ | ||
| hydrated[index] = Object(hydrate(wrapped_index)); | ||
| hydrated[index] = ops.box(hydrate(wrapped_index)); | ||
| break; | ||
@@ -154,7 +159,7 @@ } | ||
| case 'BigInt': | ||
| hydrated[index] = BigInt(value[1]); | ||
| hydrated[index] = ops.fromPrimitive(BigInt(value[1])); | ||
| break; | ||
| case 'null': | ||
| const obj = Object.create(null); | ||
| const obj = ops.createNullPrototypeObject(); | ||
| hydrated[index] = obj; | ||
@@ -166,3 +171,3 @@ for (let i = 1; i < value.length; i += 2) { | ||
| obj[value[i]] = hydrate(value[i + 1]); | ||
| ops.set(obj, value[i], hydrate(value[i + 1])); | ||
| } | ||
@@ -191,9 +196,5 @@ break; | ||
| const TypedArrayConstructor = globalThis[type]; | ||
| const buffer = hydrate(value[1]); | ||
| hydrated[index] = | ||
| value[2] !== undefined | ||
| ? new TypedArrayConstructor(buffer, value[2], value[3]) | ||
| : new TypedArrayConstructor(buffer); | ||
| hydrated[index] = ops.fromViewInfo(type, buffer, value[2], value[3]); | ||
@@ -208,7 +209,8 @@ break; | ||
| } | ||
| const arraybuffer = decode64(base64); | ||
| hydrated[index] = arraybuffer; | ||
| hydrated[index] = ops.fromArrayBuffer(decode64(base64)); | ||
| break; | ||
| } | ||
| case 'URL': | ||
| case 'URLSearchParams': | ||
| case 'Temporal.Duration': | ||
@@ -222,20 +224,7 @@ case 'Temporal.Instant': | ||
| case 'Temporal.ZonedDateTime': { | ||
| const temporalName = type.slice(9); | ||
| // @ts-expect-error TS doesn't know about Temporal yet | ||
| hydrated[index] = Temporal[temporalName].from(value[1]); | ||
| // the same tags `toStringValue` serializes on the stringify side | ||
| hydrated[index] = ops.fromStringValue(type, value[1]); | ||
| break; | ||
| } | ||
| case 'URL': { | ||
| const url = new URL(value[1]); | ||
| hydrated[index] = url; | ||
| break; | ||
| } | ||
| case 'URLSearchParams': { | ||
| const url = new URLSearchParams(value[1]); | ||
| hydrated[index] = url; | ||
| break; | ||
| } | ||
| default: | ||
@@ -252,16 +241,8 @@ throw new Error(`Unknown type ${type}`); | ||
| /** @type {any[]} */ | ||
| const array = []; | ||
| // `len` comes from the input rather than being bounded by it, so | ||
| // `createSparseArray` is responsible for not allocating storage | ||
| // proportional to it. | ||
| const array = ops.createSparseArray(len); | ||
| hydrated[index] = array; | ||
| // Setting `array.length = len` (or equivalently calling `new Array(len)`) | ||
| // on an untrusted `len` is a DoS vector: V8 eagerly allocates a | ||
| // contiguous backing store for array lengths below ~10^8, so a | ||
| // small payload with a huge declared length can force arbitrary | ||
| // memory allocation. Touching the largest-possible index first | ||
| // forces V8 into dictionary-elements mode, where `length` is | ||
| // just a number and no contiguous allocation occurs. | ||
| array[MAX_ARRAY_INDEX] = undefined; | ||
| delete array[MAX_ARRAY_INDEX]; | ||
| for (let i = 2; i < value.length; i += 2) { | ||
@@ -274,8 +255,6 @@ const idx = value[i]; | ||
| array[idx] = hydrate(value[i + 1]); | ||
| ops.set(array, idx, hydrate(value[i + 1])); | ||
| } | ||
| array.length = len; | ||
| } else { | ||
| const array = new Array(value.length); | ||
| const array = ops.createArray(value.length); | ||
| hydrated[index] = array; | ||
@@ -287,8 +266,7 @@ | ||
| array[i] = hydrate(n); | ||
| ops.set(array, i, hydrate(n)); | ||
| } | ||
| } | ||
| } else { | ||
| /** @type {Record<string, any>} */ | ||
| const object = {}; | ||
| const object = ops.createObject(); | ||
| hydrated[index] = object; | ||
@@ -301,4 +279,3 @@ | ||
| const n = value[key]; | ||
| object[key] = hydrate(n); | ||
| ops.set(object, key, hydrate(value[key])); | ||
| } | ||
@@ -305,0 +282,0 @@ } |
+85
-69
@@ -0,12 +1,3 @@ | ||
| import { DevalueError, stringify_key, stringify_string } from './utils.js'; | ||
| import { | ||
| DevalueError, | ||
| enumerable_symbols, | ||
| get_type, | ||
| is_plain_object, | ||
| is_primitive, | ||
| stringify_key, | ||
| stringify_string, | ||
| valid_array_indices | ||
| } from './utils.js'; | ||
| import { | ||
| HOLE, | ||
@@ -21,2 +12,3 @@ NAN, | ||
| import { encode64 } from './base64.js'; | ||
| import { default_stringify_operations, merge_operations } from './operations.js'; | ||
@@ -27,5 +19,6 @@ /** | ||
| * @param {Record<string, (value: any) => any>} [reducers] | ||
| * @param {import('./types.js').StringifyOptions} [options] | ||
| */ | ||
| export function stringify(value, reducers) { | ||
| const stringified = run(false, value, reducers); | ||
| export function stringify(value, reducers, options) { | ||
| const stringified = run(false, value, reducers, options); | ||
| return typeof stringified === 'string' ? stringified : `[${stringified.join(',')}]`; | ||
@@ -38,5 +31,6 @@ } | ||
| * @param {Record<string, (value: any) => any>} [reducers] | ||
| * @param {import('./types.js').StringifyOptions} [options] | ||
| */ | ||
| export async function stringifyAsync(value, reducers) { | ||
| const stringified = run(true, value, reducers); | ||
| export async function stringifyAsync(value, reducers, options) { | ||
| const stringified = run(true, value, reducers, options); | ||
@@ -77,4 +71,7 @@ if (typeof stringified === 'string') { | ||
| * @param {Record<string, (value: any) => any>} [reducers] | ||
| * @param {import('./types.js').StringifyOptions} [options] | ||
| */ | ||
| function run(async, value, reducers) { | ||
| function run(async, value, reducers, options) { | ||
| const ops = merge_operations(default_stringify_operations, options?.operations); | ||
| /** @type {any[]} */ | ||
@@ -104,12 +101,26 @@ const stringified = []; | ||
| function flatten(thing, index) { | ||
| if (thing === undefined) return UNDEFINED; | ||
| if (Number.isNaN(thing)) return NAN; | ||
| if (thing === Infinity) return POSITIVE_INFINITY; | ||
| if (thing === -Infinity) return NEGATIVE_INFINITY; | ||
| if (thing === 0 && 1 / thing < 0) return NEGATIVE_ZERO; | ||
| const type = ops.typeOf(thing); | ||
| if (indexes.has(thing)) return /** @type {number} */ (indexes.get(thing)); | ||
| if (type === 'undefined') return UNDEFINED; | ||
| /** @type {number | undefined} */ | ||
| let number; | ||
| // `ops.toPrimitive` is the boundary between the value being serialized and | ||
| // plain host JavaScript: everything below operates on the extracted host | ||
| // primitive, so native comparisons and arithmetic are correct there. | ||
| if (type === 'number') { | ||
| number = /** @type {number} */ (ops.toPrimitive(thing)); | ||
| if (Number.isNaN(number)) return NAN; | ||
| if (number === Infinity) return POSITIVE_INFINITY; | ||
| if (number === -Infinity) return NEGATIVE_INFINITY; | ||
| if (number === 0 && 1 / number < 0) return NEGATIVE_ZERO; | ||
| } | ||
| const id = ops.identify(thing); | ||
| if (indexes.has(id)) return /** @type {number} */ (indexes.get(id)); | ||
| index ??= p++; | ||
| indexes.set(thing, index); | ||
| indexes.set(id, index); | ||
@@ -124,5 +135,5 @@ for (const { key, fn } of custom) { | ||
| if (typeof thing === 'function') { | ||
| if (type === 'function') { | ||
| throw new DevalueError(`Cannot stringify a function`, keys, thing, value); | ||
| } else if (typeof thing === 'symbol') { | ||
| } else if (type === 'symbol') { | ||
| throw new DevalueError(`Cannot stringify a Symbol primitive`, keys, thing, value); | ||
@@ -134,5 +145,5 @@ } | ||
| if (is_primitive(thing)) { | ||
| str = stringify_primitive(thing); | ||
| } else if (typeof thing.then === 'function') { | ||
| if (type !== 'object') { | ||
| str = stringify_primitive(type === 'number' ? number : ops.toPrimitive(thing)); | ||
| } else if (ops.isThenable(thing)) { | ||
| if (!async) { | ||
@@ -147,3 +158,3 @@ throw new DevalueError( | ||
| str = Promise.resolve(thing).then((value) => { | ||
| str = ops.toPromise(thing).then((value) => { | ||
| const i = flatten(value, index); | ||
@@ -153,5 +164,5 @@ if (i < 0) stringified[index] = i; | ||
| } else { | ||
| const type = get_type(thing); | ||
| const tag = ops.tagOf(thing); | ||
| switch (type) { | ||
| switch (tag) { | ||
| case 'Number': | ||
@@ -161,20 +172,19 @@ case 'String': | ||
| case 'BigInt': | ||
| str = `["Object",${flatten(thing.valueOf())}]`; | ||
| str = `["Object",${flatten(ops.unbox(thing))}]`; | ||
| break; | ||
| case 'Date': | ||
| const valid = !isNaN(thing.getDate()); | ||
| str = `["Date","${valid ? thing.toISOString() : ''}"]`; | ||
| str = `["Date","${ops.toISOString(thing)}"]`; | ||
| break; | ||
| case 'URL': | ||
| str = `["URL",${stringify_string(thing.toString())}]`; | ||
| str = `["URL",${stringify_string(ops.toStringValue(thing))}]`; | ||
| break; | ||
| case 'URLSearchParams': | ||
| str = `["URLSearchParams",${stringify_string(thing.toString())}]`; | ||
| str = `["URLSearchParams",${stringify_string(ops.toStringValue(thing))}]`; | ||
| break; | ||
| case 'RegExp': | ||
| const { source, flags } = thing; | ||
| const { source, flags } = ops.regExpInfo(thing); | ||
| str = flags | ||
@@ -195,10 +205,12 @@ ? `["RegExp",${stringify_string(source)},"${flags}"]` | ||
| const length = ops.lengthOf(thing); | ||
| str = '['; | ||
| for (let i = 0; i < thing.length; i += 1) { | ||
| for (let i = 0; i < length; i += 1) { | ||
| if (i > 0) str += ','; | ||
| if (Object.hasOwn(thing, i)) { | ||
| if (ops.hasOwn(thing, i)) { | ||
| keys.push(`[${i}]`); | ||
| str += flatten(thing[i]); | ||
| str += flatten(ops.get(thing, i)); | ||
| keys.pop(); | ||
@@ -242,15 +254,15 @@ } else if (mostly_dense) { | ||
| // (4 + d) + P * (d + 1) < (L - P) * 3 | ||
| const populated_keys = valid_array_indices(/** @type {any[]} */ (thing)); | ||
| const populated_keys = ops.indicesOf(thing); | ||
| const population = populated_keys.length; | ||
| const d = String(thing.length).length; | ||
| const d = String(length).length; | ||
| const hole_cost = (thing.length - population) * 3; | ||
| const hole_cost = (length - population) * 3; | ||
| const sparse_cost = 4 + d + population * (d + 1); | ||
| if (hole_cost > sparse_cost) { | ||
| str = '[' + SPARSE + ',' + thing.length; | ||
| str = '[' + SPARSE + ',' + length; | ||
| for (let j = 0; j < populated_keys.length; j++) { | ||
| const key = populated_keys[j]; | ||
| keys.push(`[${key}]`); | ||
| str += ',' + key + ',' + flatten(thing[key]); | ||
| str += ',' + key + ',' + flatten(ops.get(thing, key)); | ||
| keys.pop(); | ||
@@ -274,3 +286,3 @@ } | ||
| for (const value of thing) { | ||
| for (const value of ops.valuesOf(thing)) { | ||
| str += `,${flatten(value)}`; | ||
@@ -285,4 +297,9 @@ } | ||
| for (const [key, value] of thing) { | ||
| keys.push(`.get(${is_primitive(key) ? stringify_primitive(key) : '...'})`); | ||
| for (const [key, value] of ops.entriesOf(thing)) { | ||
| const key_type = ops.typeOf(key); | ||
| const key_is_primitive = | ||
| key_type !== 'object' && key_type !== 'function' && key_type !== 'symbol'; | ||
| keys.push( | ||
| `.get(${key_is_primitive ? stringify_primitive(ops.toPrimitive(key)) : '...'})` | ||
| ); | ||
| str += `,${flatten(key)},${flatten(value)}`; | ||
@@ -307,9 +324,8 @@ keys.pop(); | ||
| case 'BigUint64Array': { | ||
| /** @type {import("./types.js").TypedArray} */ | ||
| const typedArray = thing; | ||
| str = '["' + type + '",' + flatten(typedArray.buffer); | ||
| const info = ops.viewInfo(thing); | ||
| str = '["' + tag + '",' + flatten(info.buffer); | ||
| // handle subarrays | ||
| if (typedArray.byteLength !== typedArray.buffer.byteLength) { | ||
| str += `,${typedArray.byteOffset},${typedArray.length}`; | ||
| if (info.byteLength !== info.bufferByteLength) { | ||
| str += `,${info.byteOffset},${info.length}`; | ||
| } | ||
@@ -322,8 +338,7 @@ | ||
| case 'DataView': { | ||
| /** @type {DataView} */ | ||
| const view = thing; | ||
| str = '["' + type + '",' + flatten(view.buffer); | ||
| const info = ops.viewInfo(thing); | ||
| str = '["' + tag + '",' + flatten(info.buffer); | ||
| if (view.byteLength !== view.buffer.byteLength) { | ||
| str += `,${view.byteOffset},${view.byteLength}`; | ||
| if (info.byteLength !== info.bufferByteLength) { | ||
| str += `,${info.byteOffset},${info.byteLength}`; | ||
| } | ||
@@ -336,5 +351,3 @@ | ||
| case 'ArrayBuffer': { | ||
| /** @type {ArrayBuffer} */ | ||
| const arraybuffer = thing; | ||
| const base64 = encode64(arraybuffer); | ||
| const base64 = encode64(ops.toArrayBuffer(thing)); | ||
@@ -353,17 +366,19 @@ str = `["ArrayBuffer","${base64}"]`; | ||
| case 'Temporal.ZonedDateTime': | ||
| str = `["${type}",${stringify_string(thing.toString())}]`; | ||
| str = `["${tag}",${stringify_string(ops.toStringValue(thing))}]`; | ||
| break; | ||
| default: | ||
| if (!is_plain_object(thing)) { | ||
| default: { | ||
| const shape = ops.shapeOf(thing); | ||
| if (shape.kind === 'not-plain') { | ||
| throw new DevalueError(`Cannot stringify arbitrary non-POJOs`, keys, thing, value); | ||
| } | ||
| if (enumerable_symbols(thing).length > 0) { | ||
| if (shape.kind === 'symbol-keys') { | ||
| throw new DevalueError(`Cannot stringify POJOs with symbolic keys`, keys, thing, value); | ||
| } | ||
| if (Object.getPrototypeOf(thing) === null) { | ||
| if (shape.kind === 'null-proto') { | ||
| str = '["null"'; | ||
| for (const key of Object.keys(thing)) { | ||
| for (const key of shape.keys) { | ||
| if (key === '__proto__') { | ||
@@ -379,3 +394,3 @@ throw new DevalueError( | ||
| keys.push(stringify_key(key)); | ||
| str += `,${stringify_string(key)},${flatten(thing[key])}`; | ||
| str += `,${stringify_string(key)},${flatten(ops.get(thing, key))}`; | ||
| keys.pop(); | ||
@@ -387,3 +402,3 @@ } | ||
| let started = false; | ||
| for (const key of Object.keys(thing)) { | ||
| for (const key of shape.keys) { | ||
| if (key === '__proto__') { | ||
@@ -401,3 +416,3 @@ throw new DevalueError( | ||
| keys.push(stringify_key(key)); | ||
| str += `${stringify_string(key)}:${flatten(thing[key])}`; | ||
| str += `${stringify_string(key)}:${flatten(ops.get(thing, key))}`; | ||
| keys.pop(); | ||
@@ -407,2 +422,3 @@ } | ||
| } | ||
| } | ||
| } | ||
@@ -409,0 +425,0 @@ } |
+439
-0
@@ -0,1 +1,28 @@ | ||
| 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 = | ||
@@ -14,1 +41,413 @@ | Int8Array | ||
| | 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>; | ||
| } |
+30
-5
@@ -148,7 +148,6 @@ import { MAX_ARRAY_INDEX, MAX_ARRAY_LEN } from './constants.js'; | ||
| /** | ||
| * Finds the populated indices of an array. | ||
| * @param {unknown[]} array | ||
| * Returns the length of the leading run of valid array indices in `keys`. | ||
| * @param {readonly string[]} keys | ||
| */ | ||
| export function valid_array_indices(array) { | ||
| const keys = Object.keys(array); | ||
| function array_index_cut(keys) { | ||
| for (var i = keys.length - 1; i >= 0; i--) { | ||
@@ -159,4 +158,30 @@ if (is_valid_array_index_string(keys[i])) { | ||
| } | ||
| keys.length = i + 1; | ||
| return i + 1; | ||
| } | ||
| /** | ||
| * Finds the populated indices of an array. | ||
| * @param {unknown[]} array | ||
| */ | ||
| export function valid_array_indices(array) { | ||
| const keys = Object.keys(array); | ||
| keys.length = array_index_cut(keys); | ||
| return keys; | ||
| } | ||
| /** | ||
| * Given the own enumerable string keys of an array-like value, in property | ||
| * order, returns the leading run of them that are valid array indices. | ||
| * | ||
| * This is the filtering half of the `indicesOf` stringify operation, | ||
| * exposed so that custom operations — which typically already have the keys | ||
| * in hand, e.g. from a foreign runtime — don't have to reimplement it. | ||
| * | ||
| * Does not modify `keys`. | ||
| * | ||
| * @param {readonly string[]} keys | ||
| * @returns {string[]} | ||
| */ | ||
| export function filter_array_indices(keys) { | ||
| return keys.slice(0, array_index_cut(keys)); | ||
| } |
+485
-11
| declare module 'devalue' { | ||
| export type StringValueTag = StringValueTag_1; | ||
| export type ViewTag = ViewTag_1; | ||
| export type StringifyOperations = StringifyOperations_1; | ||
| export type DefaultStringifyOperations = DefaultStringifyOperations_1; | ||
| export type StringifyOptions = StringifyOptions_1; | ||
| export type ParseOperations = ParseOperations_1; | ||
| export type DefaultParseOperations = DefaultParseOperations_1; | ||
| export type ParseOptions = ParseOptions_1; | ||
| /** | ||
@@ -7,17 +15,459 @@ * Turn a value into the JavaScript that creates an equivalent value | ||
| export function uneval(value: any, replacer?: (value: any, uneval: (value: any) => string) => string | void): string; | ||
| export class DevalueError extends Error { | ||
| type StringValueTag_1 = | ||
| | 'URL' | ||
| | 'URLSearchParams' | ||
| | 'Temporal.Duration' | ||
| | 'Temporal.Instant' | ||
| | 'Temporal.PlainDate' | ||
| | 'Temporal.PlainTime' | ||
| | 'Temporal.PlainDateTime' | ||
| | 'Temporal.PlainMonthDay' | ||
| | 'Temporal.PlainYearMonth' | ||
| | 'Temporal.ZonedDateTime'; | ||
| type ViewTag_1 = | ||
| | 'Int8Array' | ||
| | 'Uint8Array' | ||
| | 'Uint8ClampedArray' | ||
| | 'Int16Array' | ||
| | 'Uint16Array' | ||
| | 'Float16Array' | ||
| | 'Int32Array' | ||
| | 'Uint32Array' | ||
| | 'Float32Array' | ||
| | 'Float64Array' | ||
| | 'BigInt64Array' | ||
| | 'BigUint64Array' | ||
| | 'DataView'; | ||
| 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.) | ||
| */ | ||
| interface StringifyOperations_1 { | ||
| /** | ||
| * @param value - The value that failed to be serialized | ||
| * @param root - The root value being serialized | ||
| * 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. | ||
| */ | ||
| constructor(message: string, keys: string[], value?: any, root?: any); | ||
| path: string; | ||
| value: any; | ||
| root: any; | ||
| 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`. */ | ||
| interface DefaultStringifyOperations_1 extends StringifyOperations_1 { | ||
| 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`. */ | ||
| interface StringifyOptions_1 { | ||
| /** | ||
| * Overrides for the introspection/extraction operations used while | ||
| * serializing. Omitted members fall back to `defaultStringifyOperations`. | ||
| */ | ||
| operations?: Partial<StringifyOperations_1>; | ||
| } | ||
| /** | ||
| * 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`). | ||
| */ | ||
| interface ParseOperations_1 { | ||
| /** | ||
| * 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_1, 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_1, | ||
| 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`. */ | ||
| interface DefaultParseOperations_1 extends ParseOperations_1 { | ||
| fromPrimitive( | ||
| primitive: string | number | boolean | bigint | null | undefined | ||
| ): string | number | boolean | bigint | null | undefined; | ||
| fromISOString(iso: string): Date; | ||
| fromStringValue(tag: StringValueTag_1, text: string): URL | URLSearchParams | object; | ||
| fromArrayBuffer(buffer: ArrayBuffer): ArrayBuffer; | ||
| fromRegExpInfo(source: string, flags: string | undefined): RegExp; | ||
| fromViewInfo( | ||
| tag: ViewTag_1, | ||
| 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`. */ | ||
| interface ParseOptions_1 { | ||
| /** | ||
| * Overrides for the construction operations used while reviving. | ||
| * Omitted members fall back to `defaultParseOperations`. | ||
| */ | ||
| operations?: Partial<ParseOperations_1>; | ||
| } | ||
| /** | ||
| * Revive a value serialized with `devalue.stringify` | ||
| * | ||
| */ | ||
| export function parse(serialized: string, revivers?: Record<string, (value: any) => any>): any; | ||
| export function parse(serialized: string, revivers?: Record<string, (value: any) => any>, options?: ParseOptions_1): any; | ||
| /** | ||
@@ -27,3 +477,3 @@ * Revive a value flattened with `devalue.stringify` | ||
| */ | ||
| export function unflatten(parsed: number | any[], revivers?: Record<string, (value: any) => any>): any; | ||
| export function unflatten(parsed: number | any[], revivers?: Record<string, (value: any) => any>, options?: ParseOptions_1): any; | ||
| /** | ||
@@ -33,3 +483,3 @@ * Turn a value into a JSON string that can be parsed with `devalue.parse` | ||
| */ | ||
| export function stringify(value: any, reducers?: Record<string, (value: any) => any>): string; | ||
| export function stringify(value: any, reducers?: Record<string, (value: any) => any>, options?: StringifyOptions_1): string; | ||
| /** | ||
@@ -39,3 +489,27 @@ * Turn a value into a JSON string that can be parsed with `devalue.parse` | ||
| */ | ||
| export function stringifyAsync(value: any, reducers?: Record<string, (value: any) => any>): Promise<string>; | ||
| export function stringifyAsync(value: any, reducers?: Record<string, (value: any) => any>, options?: StringifyOptions_1): Promise<string>; | ||
| export const defaultStringifyOperations: Readonly<DefaultStringifyOperations_1>; | ||
| export const defaultParseOperations: Readonly<DefaultParseOperations_1>; | ||
| /** | ||
| * Given the own enumerable string keys of an array-like value, in property | ||
| * order, returns the leading run of them that are valid array indices. | ||
| * | ||
| * This is the filtering half of the `indicesOf` stringify operation, | ||
| * exposed so that custom operations — which typically already have the keys | ||
| * in hand, e.g. from a foreign runtime — don't have to reimplement it. | ||
| * | ||
| * Does not modify `keys`. | ||
| * | ||
| * */ | ||
| export function filterArrayIndices(keys: readonly string[]): string[]; | ||
| export class DevalueError extends Error { | ||
| /** | ||
| * @param value - The value that failed to be serialized | ||
| * @param root - The root value being serialized | ||
| */ | ||
| constructor(message: string, keys: string[], value?: any, root?: any); | ||
| path: string; | ||
| value: any; | ||
| root: any; | ||
| } | ||
@@ -42,0 +516,0 @@ export {}; |
+16
-5
@@ -5,14 +5,24 @@ { | ||
| "names": [ | ||
| "StringValueTag", | ||
| "ViewTag", | ||
| "StringifyOperations", | ||
| "DefaultStringifyOperations", | ||
| "StringifyOptions", | ||
| "ParseOperations", | ||
| "DefaultParseOperations", | ||
| "ParseOptions", | ||
| "uneval", | ||
| "DevalueError", | ||
| "TypedArray", | ||
| "parse", | ||
| "unflatten", | ||
| "stringify", | ||
| "stringifyAsync" | ||
| "stringifyAsync", | ||
| "DevalueError" | ||
| ], | ||
| "sources": [ | ||
| "../src/types.d.ts", | ||
| "../src/uneval.js", | ||
| "../src/utils.js", | ||
| "../src/parse.js", | ||
| "../src/stringify.js" | ||
| "../src/stringify.js", | ||
| "../src/utils.js" | ||
| ], | ||
@@ -23,6 +33,7 @@ "sourcesContent": [ | ||
| null, | ||
| null, | ||
| null | ||
| ], | ||
| "mappings": ";;;;;iBAsBgBA,MAAMA;cCPTC,YAAYA;;;;;;;;;;;;;;iBCGTC,KAAKA;;;;;iBASLC,SAASA;;;;;iBCDTC,SAASA;;;;;iBAUHC,cAAcA", | ||
| "mappings": ";aAAYA,cAAcA;aAYdC,OAAOA;aAmEFC,mBAAmBA;aAoKnBC,0BAA0BA;aAsB1BC,gBAAgBA;aA+ChBC,eAAeA;aA2GfC,sBAAsBA;aA0BtBC,YAAYA;;;;;iBCvabC,MAAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;MDKVC,UAAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBERNC,KAAKA;;;;;iBAULC,SAASA;;;;;iBCVTC,SAASA;;;;;iBAWHC,cAAcA;;;;;;;;;;;;;;;cCfvBC,YAAYA", | ||
| "ignoreList": [] | ||
| } |
101975
87.11%16
6.67%2425
70.41%329
32.13%