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

@aedge-io/typed-clone

v1.0.0

Published

Type-safe, performant and extensible clone implementation

Readme

typed-clone

codecov NPM Version JSR Version

Type-safe, performant, and extensible clone implementation.


Motivation

This library was initially developed to provide a type-safe alternative to the naive structuredClone-based implementation in grugway.

  • Type-safe: Clearly encodes types that can and cannot be meaningfully cloned through the type system. Not-cloneable types get returned as Ref<T> explicitly.
  • Performant: Fast enough for the 20% of data types that make up 80% of real-world usage. structuredClone fallback for the rest (e.g. typed arrays).
  • Extensible: Simple, symbol-based clone protocol for custom types.

Use Cases

This library particularly shines when referential transparency and infallibility are desired, or when dealing with heterogeneous and complex data that usually requires hand-rolled copy/clone implementations. typed-clone offers a good baseline implementation in those cases.

The custom clone protocol also allows for a seamless interaction of standard data types with your custom types or domain model.


Quick Start

Runtime Requirements

  • Bun: ≥1.0.0
  • Deno: ≥1.14
  • Node.js: ≥17.0.0
  • Browsers: Support structuredClone

Installation

Node.js / Bun:

(bun | (p)npm) add @aedge-io/typed-clone

Deno:

deno add jsr:@aedge-io/typed-clone

Usage

Simple

import { clone } from "@aedge-io/typed-clone";

const clonedRec = clone({ msg: "hello there!" }); // { msg: string }
const clonedFn = clone(() => "hello there again!"); // Ref<() => string>

Complex

import { Clone, clone, Cloneable, CloneOptions } from "@aedge-io/typed-clone";

class NotCloneable {
  constructor(readonly name: string, private age: number) {}
  greet() {
    return `Hi, I am ${this.name} and ${this.age} years old.`;
  }
}

class Point { // implements Cloneable<Point>
  constructor(private x: number, private y: number) {}
  [Clone](opts?: CloneOptions) {
    return new Point(this.x, this.y);
  }
}

const randInt = () => Math.floor(Math.random() * 100);

const uintArray = new Uint8Array(new ArrayBuffer(4));
uintArray.set([0, 1, 2, 3]);

const meta = {
  createdAt: new Date(),
};

const original = {
  metadata: meta,
  handlers: new Map([["rand", randInt]]),
  primitives: ["string", 42, true, BigInt(9001), Symbol("foo")] as const,
  ref: new NotCloneable("Bob", 71),
  points: {
    unique: new Set([new Point(0, 1), new Point(1, 2)]),
    metadata: meta,
  },
  buf: uintArray,
  circularRef: {},
};
original.circularRef = original;

const cloned = clone(original, { transfer: [original.buf.buffer] });

// cloned = {
//   metadata: {
//     createdAt: Date;
//   };
//   handlers: Map<string, Ref<() => number>>;
//   primitives: readonly [string, number, boolean, bigInt, Ref<unique symbol>];
//   ref: Ref<NotCloneable>;
//   points: {
//     unique: Set<Point>; /* `Point` supports clone protocol */
//     metadata: {
//         createdAt: Date;
//     };
//   };
//   buf: Uint8Array<ArrayBuffer>;
//   circularRef: { ... };
// };

console.log("deep clone:", cloned !== original);
console.log("metadata cloned:", cloned.metadata !== original.metadata);
console.log("date cloned:", +cloned.metadata.createdAt === +meta.createdAt);
console.log("map cloned:", cloned.handlers !== original.handlers);
console.log("fn is ref:", cloned.handlers.get("rand") === randInt);
console.log("array cloned:", cloned.primitives !== original.primitives);
console.log("symbol is ref:", cloned.primitives[4] === original.primitives[4]);
console.log("class is ref:", cloned.ref === original.ref);
console.log("set cloned:", cloned.points.unique !== original.points.unique);
console.log("buf transferred:", original.buf.buffer.byteLength === 0);
console.log("circular ref preserved:", cloned.circularRef === cloned);
console.log(
  "shared refs preserved:",
  cloned.metadata === cloned.points.metadata,
);

Performance

clone = clone(value) (default, shared-ref cache)

clone (nc) = clone(value, { preserveRefs: false })

| Benchmark | clone | ops/s | clone (nc) | ops/s | | ------------------------------------- | -------: | --------: | ---------: | --------: | | Plain record (8 keys) | 257.1 ns | 3,890,000 | 203.8 ns | 4,908,000 | | Plain record (64 keys) | 2.3 µs | 440,400 | 2.2 µs | 463,400 | | Plain record (256 keys) | 23.0 µs | 43,520 | 21.7 µs | 46,020 | | Nested records (d=4, 16 leaves) | 4.3 µs | 232,000 | 2.8 µs | 351,900 | | Nested records (d=8, 256 leaves) | 77.3 µs | 12,940 | 47.6 µs | 21,030 | | Nested records (d=12, 4096 leaves) | 1.3 ms | 745 | 790.3 µs | 1,265 | | Array<primitive> (n=256) | 317.2 ns | 3,152,000 | 333.8 ns | 2,996,000 | | Array<primitive> (n=8192) | 9.0 µs | 111,300 | 8.9 µs | 112,900 | | Array<record> (n=256) | 39.4 µs | 25,370 | 24.5 µs | 40,830 | | Array<record> (n=8192) | 1.4 ms | 720 | 757.0 µs | 1,321 | | Map<string,record> (n=256) | 47.4 µs | 21,100 | 35.4 µs | 28,290 | | Set<record> (n=256) | 47.1 µs | 21,230 | 35.2 µs | 28,380 | | Real: Frontend state slice | 1.3 µs | 758,900 | 941.4 ns | 1,062,000 | | Real: JSON Schema (32 props) | 19.5 µs | 51,230 | 12.7 µs | 78,500 | | Real: API collection (32 items) | 96.4 µs | 10,370 | 75.4 µs | 13,260 | | Real: Agent session (32 turns) | 50.2 µs | 19,930 | 33.7 µs | 29,690 | | Real: Normalized store (256 entities) | 111.2 µs | 8,992 | 103.0 µs | 9,708 | | Real: Dashboard data (8K rows) | 349.9 µs | 2,858 | 322.3 µs | 3,103 |

By default, typed-clone keeps track of object references to support shared and circular references. The overhead is most pronounced for small data structures. By disabling it, clone operations can be up to ~50% faster.

For a comprehensive write-up including memory overhead and comparison to rfdc and structuredClone, see docs.

Your mileage may vary though! Run the full benchmark suite with deno bench.

Security

Unlike similar packages, typed-clone guards against primitive prototype poisoning. However, this protection does not extend to prototype pollution in general, since the mitigations are quite runtime-dependent.

Caveats

Given the structural nature of TypeScript's type system, certain edge-case subclasses currently don't get inferred correctly. Check out the docs for a comprehensive overview.


License

MIT License — see LICENSE.md

Resources