npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

structured-clone-preflight

v0.1.0

Published

Explain exactly why a JavaScript value cannot cross a structured-clone boundary.

Readme

structured-clone-preflight

npm version CI license: MIT

Explain exactly why a JavaScript value cannot cross a structured-clone boundary.

import { preflight } from "structured-clone-preflight";

const payload = {
  jobs: [{ id: 1 }, { id: 2, callback: () => "done" }],
};

const report = preflight(payload, { target: "worker" });

report.ok;
// false

report.errors;
// [{
//   code: "function-not-cloneable",
//   path: "$.jobs[1].callback",
//   message: "Functions cannot be structured-cloned.",
//   valueType: "Function"
// }]

Native structuredClone(), postMessage(), Workers, IndexedDB, and history APIs can fail with an unhelpful DataCloneError. This package reports the failing path, warns about values that silently change meaning, and prepares a safe transfer list without detaching anything.

Install

npm install structured-clone-preflight
  • Zero runtime dependencies
  • ESM and CommonJS
  • TypeScript declarations included
  • Node.js 18+, modern browsers, Bun, Deno, and Workers
  • Iterative, cycle-safe, and resource-bounded

Transfer planning

Some values must be transferred rather than copied. Others, such as ArrayBuffer, can be copied or transferred.

const bytes = new Uint8Array(1024 * 1024);

const report = preflight(
  { bytes },
  {
    target: "worker",
    transfer: "all",
  },
);

if (report.ok) {
  worker.postMessage({ bytes }, report.transferables);
}

Preflight itself never detaches a buffer or moves a port. Detachment occurs only if the caller later passes report.transferables to a transfer operation.

Transfer lists are deduplicated. Multiple typed-array views backed by the same ArrayBuffer produce one entry.

Node.js Buffers

Buffer requires special care:

const report = preflight(Buffer.from("secret"), {
  target: "worker",
  transfer: "all",
});

report.transferables;
// []

The package deliberately refuses to auto-transfer a Buffer's backing store. Pooled buffers can share a larger allocation containing unrelated bytes. It also warns that a cloned Buffer arrives as a plain Uint8Array.

Silent semantic losses

A value can clone successfully and still arrive differently:

class User {
  constructor(readonly name: string) {}
  greet() {
    return `Hello ${this.name}`;
  }
}

const report = preflight(new User("Ada"));

report.ok;
// true

report.warnings;
// [{ code: "prototype-dropped", path: "$", ... }]

Preflight warns about:

  • Custom prototypes and class identity being dropped
  • Enumerable symbol-keyed properties being dropped
  • Non-enumerable properties being dropped
  • Accessors becoming plain writable data properties
  • RegExp.lastIndex being reset
  • Node.js Buffer becoming Uint8Array
  • Runtime-dependent Error fields
  • Custom properties attached to built-in values being dropped
  • Shared memory remaining shared
  • Values that will be detached by a returned transfer plan

Warnings do not make report.ok false. They describe successful but potentially surprising transfers.

Targets

structured-clone

The default. Models structuredClone(value, { transfer }) in the current runtime.

preflight(value);

worker

Enforces Worker and postMessage transfer requirements and creates a transfer plan.

preflight(value, { target: "worker", transfer: "required" });

storage

Models structured serialization for persistent storage and history state. Transfer-only values and SharedArrayBuffer are rejected.

preflight(value, { target: "storage" });

Individual storage APIs may impose additional quotas or platform-object restrictions. Preflight covers structured-serialization compatibility, not database quotas or transaction state.

Transfer modes

| Mode | Behavior | | ---------- | ------------------------------------------------------------------------ | | none | Reject values that require a transfer list | | required | Include only transfer-only values such as MessagePort; this is default | | all | Also include safely discoverable optional values such as ArrayBuffer |

all is an explicit performance decision. It changes ownership: transferred values become detached or otherwise unusable on the sending side.

APIs

preflight(value, options?)

Returns an immutable report and does not throw for payload problems.

interface PreflightReport {
  readonly ok: boolean;
  readonly target: "structured-clone" | "worker" | "storage";
  readonly transferMode: "none" | "required" | "all";
  readonly errors: readonly PreflightError[];
  readonly warnings: readonly PreflightWarning[];
  readonly transferables: readonly object[];
  readonly stats: {
    readonly nodesVisited: number;
    readonly deepestPath: number;
    readonly cycles: number;
    readonly nativeProbes: number;
    readonly truncated: boolean;
  };
}

Invalid library options throw PreflightOptionError. Untrusted payloads are represented in the report.

isCloneable(value, options?)

Boolean convenience API:

if (isCloneable(message, { target: "worker" })) {
  // The graph passed preflight.
}

Use preflight when transfer lists or diagnostics matter.

assertCloneable(value, options?)

Returns the successful report or throws StructuredClonePreflightError. The error exposes its immutable report property.

const report = assertCloneable(message);

Options

preflight(value, {
  target: "structured-clone",
  transfer: "required",
  maxDepth: 100,
  maxNodes: 10_000,
  maxIssues: 100,
  nativeOracle: true,
  maxNativeProbes: 64,
});

Resource budgets

Object graphs are walked iteratively, so deep input does not consume the JavaScript call stack. The default budgets bound CPU, memory, diagnostics, and native probes.

Exceeding any traversal budget is an error and sets stats.truncated to true. An incomplete inspection never returns a false green.

Native oracle

JavaScript cannot portably inspect every host object's internal slots or detect a Proxy. When available, the current runtime's native structuredClone is used as a final bounded oracle. If it rejects an otherwise valid-looking graph, preflight probes bounded subvalues to locate the opaque failure.

The oracle clones the graph for validation and can therefore read enumerable accessors again. Set nativeOracle: false when a single inspection pass is required.

Disable this only when deterministic static inspection is more important than detecting runtime-specific objects:

preflight(value, { nativeOracle: false });

Error codes

| Code | Meaning | | ------------------------------- | ------------------------------------------------------ | | function-not-cloneable | A function was found | | symbol-not-cloneable | A Symbol value was found | | weak-collection-not-cloneable | WeakMap, WeakSet, WeakRef, or FinalizationRegistry | | promise-not-cloneable | A Promise was found | | property-access-threw | An enumerable getter or Error cause threw | | introspection-threw | A Proxy or exotic object rejected safe inspection | | native-clone-failed | The current runtime rejected an opaque value | | transfer-required | Transfer mode forbids a required transferable | | storage-unsupported | A value cannot use persistent structured serialization | | max-depth-exceeded | The depth budget was exceeded | | max-nodes-exceeded | The node budget was exceeded | | max-issues-exceeded | The diagnostics budget was exceeded |

Codes are stable API. Human-readable messages may become clearer in minor releases.

Property getters and proxies

The structured-clone algorithm reads enumerable properties, so author-defined getters can execute during a real clone. Preflight follows the same observable boundary and captures thrown getter errors. Do not preflight an object if merely reading its enumerable properties is unsafe.

Proxy traps can also execute during inspection. Every trap interaction is contained; payload failures become report errors rather than escaping from preflight.

What this package does not do

  • It does not clone, serialize, sanitize, or repair values.
  • It does not make functions, Promises, DOM nodes, or weak collections transferable.
  • It does not guarantee that a destination runtime exposes the same platform interfaces.
  • It does not measure IndexedDB quotas, browser history size limits, or Worker availability.
  • It does not transfer anything automatically.

The report answers one question: can this graph cross the selected structured-clone boundary, what will change, and which values must move?

Security

Resource limits are part of the public contract. Keep them bounded for untrusted graphs. See SECURITY.md for the supported versions and private reporting process.

Contributing

Bug reports should include the runtime and version, the smallest reproducible value, the selected target, and the native error when available. See CONTRIBUTING.md.

License

MIT