Sign In

devalue

Package Overview
Dependencies
Maintainers
2
Versions
48
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

devalue - npm Package Compare versions

Comparing version
5.8.2
to
5.9.0
+193
src/operations.js
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,

@@ -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 @@

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 @@ }

@@ -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 @@ }

@@ -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>;
}

@@ -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));
}
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 {};

@@ -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": []
}