{"_id":"@aedge-io/typed-clone","_rev":"3-76ff74ae28f6272b932e843ac98a5a43","name":"@aedge-io/typed-clone","dist-tags":{"latest":"1.1.0"},"versions":{"0.0.0":{"name":"@aedge-io/typed-clone","version":"0.0.0","keywords":["clanker-friendly","clone","copy","deep","deep-clone","deep-copy","deepclone","deepcopy","extensible","protocol","recursive","safe","structured","type-safe","typed","types","typesafe"],"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","_id":"@aedge-io/typed-clone@0.0.0","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"homepage":"https://github.com/aedge-io/typed-clone#readme","bugs":{"url":"https://github.com/aedge-io/typed-clone/issues"},"dist":{"shasum":"35c230046eb65b2f76112f352654b3a8c898aa67","tarball":"https://registry.npmjs.org/@aedge-io/typed-clone/-/typed-clone-0.0.0.tgz","fileCount":13,"integrity":"sha512-AUcZ43IHWtK5JVLrHrA+hxrWknkNR51RSmdH7nb6u78nSljl67bU+R/ix7WmngtQkSMXi54HAHzZsDbTyWGRfA==","signatures":[{"sig":"MEUCIQDDYCISTPKExXHmU+V+oSoUYKM21NCKAcrXbHyqmC2wawIgGJ7wK5W1/gp8y5sqqrfomC2VKFmzJ5ifZhCyNUYLVGI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":26603},"types":"./types/mod.d.ts","module":"./esm/mod.js","engines":{"node":">=17.0.0"},"exports":{".":{"import":{"types":"./types/mod.d.ts","default":"./esm/mod.js"}}},"gitHead":"e77711c83eec0ee6793675bcdc6ace2a08219f01","scripts":{},"_npmUser":{"name":"raphael-paier","email":"os@aedge.io"},"repository":{"url":"git+https://github.com/aedge-io/typed-clone.git","type":"git"},"_npmVersion":"10.9.2","description":"Type-safe, performant and extensible clone implementation","directories":{},"_nodeVersion":"24.1.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/typed-clone_0.0.0_1775057455081_0.33847436345533155","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@aedge-io/typed-clone","version":"1.0.0","keywords":["clanker-friendly","clone","copy","deep","deep-clone","deep-copy","deepclone","deepcopy","extensible","protocol","recursive","safe","structured","type-safe","typed","types","typesafe"],"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","_id":"@aedge-io/typed-clone@1.0.0","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"homepage":"https://github.com/aedge-io/typed-clone#readme","bugs":{"url":"https://github.com/aedge-io/typed-clone/issues"},"dist":{"shasum":"77af83d28b0ef749c6448817e6041729ed131a95","tarball":"https://registry.npmjs.org/@aedge-io/typed-clone/-/typed-clone-1.0.0.tgz","fileCount":7,"integrity":"sha512-/GQ1x9iZSV9eQYqVWmGrneJ/XHuPNEnr5R1WNUCmlI3MaXYi/0oFfn+an/tkRU3pRsaYRf0NiMGOG2oH5Q98yg==","signatures":[{"sig":"MEYCIQDwhISHLXlmTJLmtl93bxfELk5J94LaJe5BbJt7nUHxKQIhAOkCyXiqhBi6CENW1+ID3W9d/bgUHDcgN4rlBq6uYRTI","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aedge-io%2ftyped-clone@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":27272},"types":"./types/clone.d.ts","module":"./esm/clone.js","engines":{"node":">=17.0.0"},"exports":{".":{"import":{"types":"./types/clone.d.ts","default":"./esm/clone.js"}}},"gitHead":"369b2a6ccbf15f8c2e69372b8ffc2d08f438e1ab","scripts":{},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0aab9ee1-1a16-47d2-9403-d03a061d7976"}},"repository":{"url":"git+https://github.com/aedge-io/typed-clone.git","type":"git"},"_npmVersion":"11.11.0","description":"Type-safe, performant and extensible clone implementation","directories":{},"_nodeVersion":"24.14.1","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/typed-clone_1.0.0_1775184848430_0.9251256596586626","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@aedge-io/typed-clone","version":"1.1.0","description":"Type-safe, performant and extensible clone implementation","keywords":["agent-skill","clanker-friendly","clone","copy","deep","deep-clone","deep-copy","deepclone","deepcopy","extensible","pi-package","protocol","recursive","safe","structured","type-safe","typed","types","typesafe"],"author":{"name":"aedge-io","email":"os@aedge.io"},"repository":{"type":"git","url":"git+https://github.com/aedge-io/typed-clone.git"},"license":"MIT","bugs":{"url":"https://github.com/aedge-io/typed-clone/issues"},"module":"./esm/clone.js","types":"./types/clone.d.ts","exports":{".":{"import":{"types":"./types/clone.d.ts","default":"./esm/clone.js"}}},"scripts":{},"engines":{"node":">=17.0.0"},"publishConfig":{"access":"public","provenance":true},"pi":{"skills":["./skills"]},"gitHead":"c330bfccda5ae89408d26abe99f6fbaab86a3200","_id":"@aedge-io/typed-clone@1.1.0","homepage":"https://github.com/aedge-io/typed-clone#readme","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-Dl09I6HIHY71MJuF8+9lgKi2sfpaKIyWPaZee/ZqbxLVqCnufxoQXgPELt5Iixogqto67COLkaMxJkeRWPav1w==","shasum":"9de3f17fd687252c2848f6fa4a67e67ea367c8c5","tarball":"https://registry.npmjs.org/@aedge-io/typed-clone/-/typed-clone-1.1.0.tgz","fileCount":8,"unpackedSize":35125,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aedge-io%2ftyped-clone@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDhusgi3WJDc8ck47SycJmxj1BfaODL2+FF9JJ41So/0QIgU5EZ7tpqNGZP6/xr+kxc15x6hkVNOuEqGr09sR4b1Yw="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0aab9ee1-1a16-47d2-9403-d03a061d7976"}},"directories":{},"maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/typed-clone_1.1.0_1775575469938_0.49279157657569606"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-01T15:30:54.929Z","modified":"2026-04-07T15:24:30.345Z","0.0.0":"2026-04-01T15:30:55.213Z","1.0.0":"2026-04-03T02:54:08.575Z","1.1.0":"2026-04-07T15:24:30.068Z"},"bugs":{"url":"https://github.com/aedge-io/typed-clone/issues"},"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","homepage":"https://github.com/aedge-io/typed-clone#readme","keywords":["agent-skill","clanker-friendly","clone","copy","deep","deep-clone","deep-copy","deepclone","deepcopy","extensible","pi-package","protocol","recursive","safe","structured","type-safe","typed","types","typesafe"],"repository":{"type":"git","url":"git+https://github.com/aedge-io/typed-clone.git"},"description":"Type-safe, performant and extensible clone implementation","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"readme":"# typed-clone\n\n[![codecov](https://codecov.io/github/aedge-io/typed-clone/graph/badge.svg?token=S4AQB5UAJO)](https://codecov.io/github/aedge-io/typed-clone)\n![NPM Version](https://img.shields.io/npm/v/%40aedge-io%2Ftyped-clone)\n![JSR Version](https://img.shields.io/jsr/v/%40aedge-io/typed-clone)\n\n> Type-safe, performant, and extensible clone implementation.\n\n---\n\n### Motivation\n\nThis library was initially developed to provide a type-safe alternative to the naive `structuredClone`-based implementation in [grugway](https://github.com/aedge-io/grugway).\n\n- **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.\n- **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).\n- **Extensible**: Simple, symbol-based [clone protocol](https://github.com/aedge-io/typed-clone/tree/main/docs/clone_protocol.md) for custom types.\n\n### Use Cases\n\nThis 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.\n\nThe custom clone protocol also allows for a seamless interaction of standard data types with your custom types or domain model.\n\n---\n\n### Quick Start\n\n#### Runtime Requirements\n\n- **Bun:** ≥1.0.0\n- **Deno:** ≥1.14\n- **Node.js:** ≥17.0.0\n- **Browsers:** Support `structuredClone`\n\n#### Installation\n\n**Node.js / Bun:**\n\n```bash\n(bun | (p)npm) add @aedge-io/typed-clone\n```\n\n**Deno:**\n\n```bash\ndeno add jsr:@aedge-io/typed-clone\n```\n\n---\n\n### Usage\n\n#### Simple\n\n```typescript\nimport { clone } from \"@aedge-io/typed-clone\";\n\nconst clonedRec = clone({ msg: \"hello there!\" }); // { msg: string }\nconst clonedFn = clone(() => \"hello there again!\"); // Ref<() => string>\n```\n\n#### Complex\n\n```typescript\nimport { Clone, clone, Cloneable, CloneOptions } from \"@aedge-io/typed-clone\";\n\nclass NotCloneable {\n  constructor(readonly name: string, private age: number) {}\n  greet() {\n    return `Hi, I am ${this.name} and ${this.age} years old.`;\n  }\n}\n\nclass Point { // implements Cloneable<Point>\n  constructor(private x: number, private y: number) {}\n  [Clone](opts?: CloneOptions) {\n    return new Point(this.x, this.y);\n  }\n}\n\nconst randInt = () => Math.floor(Math.random() * 100);\n\nconst uintArray = new Uint8Array(new ArrayBuffer(4));\nuintArray.set([0, 1, 2, 3]);\n\nconst meta = {\n  createdAt: new Date(),\n};\n\nconst original = {\n  metadata: meta,\n  handlers: new Map([[\"rand\", randInt]]),\n  primitives: [\"string\", 42, true, BigInt(9001), Symbol(\"foo\")] as const,\n  ref: new NotCloneable(\"Bob\", 71),\n  points: {\n    unique: new Set([new Point(0, 1), new Point(1, 2)]),\n    metadata: meta,\n  },\n  buf: uintArray,\n  circularRef: {},\n};\noriginal.circularRef = original;\n\nconst cloned = clone(original, { transfer: [original.buf.buffer] });\n\n// cloned = {\n//   metadata: {\n//     createdAt: Date;\n//   };\n//   handlers: Map<string, Ref<() => number>>;\n//   primitives: readonly [string, number, boolean, bigInt, Ref<unique symbol>];\n//   ref: Ref<NotCloneable>;\n//   points: {\n//     unique: Set<Point>; /* `Point` supports clone protocol */\n//     metadata: {\n//         createdAt: Date;\n//     };\n//   };\n//   buf: Uint8Array<ArrayBuffer>;\n//   circularRef: { ... };\n// };\n\nconsole.log(\"deep clone:\", cloned !== original);\nconsole.log(\"metadata cloned:\", cloned.metadata !== original.metadata);\nconsole.log(\"date cloned:\", +cloned.metadata.createdAt === +meta.createdAt);\nconsole.log(\"map cloned:\", cloned.handlers !== original.handlers);\nconsole.log(\"fn is ref:\", cloned.handlers.get(\"rand\") === randInt);\nconsole.log(\"array cloned:\", cloned.primitives !== original.primitives);\nconsole.log(\"symbol is ref:\", cloned.primitives[4] === original.primitives[4]);\nconsole.log(\"class is ref:\", cloned.ref === original.ref);\nconsole.log(\"set cloned:\", cloned.points.unique !== original.points.unique);\nconsole.log(\"buf transferred:\", original.buf.buffer.byteLength === 0);\nconsole.log(\"circular ref preserved:\", cloned.circularRef === cloned);\nconsole.log(\n  \"shared refs preserved:\",\n  cloned.metadata === cloned.points.metadata,\n);\n```\n\n---\n\n### Performance\n\n> **clone** = `clone(value)` (default, shared-ref cache)\n\n> **clone (nc)** = `clone(value, { preserveRefs: false })`\n\n| Benchmark                             |    clone |     ops/s | clone (nc) |     ops/s |\n| ------------------------------------- | -------: | --------: | ---------: | --------: |\n| Plain record (8 keys)                 | 257.1 ns | 3,890,000 |   203.8 ns | 4,908,000 |\n| Plain record (64 keys)                |   2.3 µs |   440,400 |     2.2 µs |   463,400 |\n| Plain record (256 keys)               |  23.0 µs |    43,520 |    21.7 µs |    46,020 |\n| Nested records (d=4, 16 leaves)       |   4.3 µs |   232,000 |     2.8 µs |   351,900 |\n| Nested records (d=8, 256 leaves)      |  77.3 µs |    12,940 |    47.6 µs |    21,030 |\n| Nested records (d=12, 4096 leaves)    |   1.3 ms |       745 |   790.3 µs |     1,265 |\n| Array\\<primitive\\> (n=256)            | 317.2 ns | 3,152,000 |   333.8 ns | 2,996,000 |\n| Array\\<primitive\\> (n=8192)           |   9.0 µs |   111,300 |     8.9 µs |   112,900 |\n| Array\\<record\\> (n=256)               |  39.4 µs |    25,370 |    24.5 µs |    40,830 |\n| Array\\<record\\> (n=8192)              |   1.4 ms |       720 |   757.0 µs |     1,321 |\n| Map\\<string,record\\> (n=256)          |  47.4 µs |    21,100 |    35.4 µs |    28,290 |\n| Set\\<record\\> (n=256)                 |  47.1 µs |    21,230 |    35.2 µs |    28,380 |\n| Real: Frontend state slice            |   1.3 µs |   758,900 |   941.4 ns | 1,062,000 |\n| Real: JSON Schema (32 props)          |  19.5 µs |    51,230 |    12.7 µs |    78,500 |\n| Real: API collection (32 items)       |  96.4 µs |    10,370 |    75.4 µs |    13,260 |\n| Real: Agent session (32 turns)        |  50.2 µs |    19,930 |    33.7 µs |    29,690 |\n| Real: Normalized store (256 entities) | 111.2 µs |     8,992 |   103.0 µs |     9,708 |\n| Real: Dashboard data (8K rows)        | 349.9 µs |     2,858 |   322.3 µs |     3,103 |\n\nBy 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.\n\nFor a comprehensive write-up including memory overhead and comparison to [rfdc](https://github.com/davidmarkclements/rfdc) and `structuredClone`, see [docs](https://github.com/aedge-io/typed-clone/tree/main/docs/performance.md).\n\n**Your mileage may vary though!** Run the full benchmark suite with `deno bench`.\n\n### Security\n\nUnlike 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.\n\n- [Secure JSON.parse](https://github.com/fastify/secure-json-parse)\n- [MDN prototype pollution](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution)\n\n### Caveats\n\nGiven the structural nature of TypeScript's type system, certain edge-case subclasses currently don't get inferred correctly. Check out the [docs](https://github.com/aedge-io/typed-clone/tree/main/docs/type_safety.md) for a comprehensive overview.\n\n---\n\n### License\n\nMIT License — see [LICENSE.md](./LICENSE.md)\n\n### Resources\n\n- [MDN structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)\n- [V8 Memory Model](https://www.dashlane.com/blog/how-is-data-stored-in-v8-js-engine-memory)\n","readmeFilename":"README.md"}