Skip to content

feat(clone): production-grade 1.0.0#37

Merged
ob-aion merged 11 commits into
mainfrom
feat/optim
May 15, 2026
Merged

feat(clone): production-grade 1.0.0#37
ob-aion merged 11 commits into
mainfrom
feat/optim

Conversation

@ob-aion
Copy link
Copy Markdown
Collaborator

@ob-aion ob-aion commented May 15, 2026

Summary

Production-grade hardening for the 1.0.0 release of @coroboros/clone: typed CloneError, browser-safe Buffer, three granular opt-out flags (cycles, preservePrototype, copyDescriptors), a mitata bench against the field, a fast-check property suite, CI, and a README rewrite.

Changes

  • APICloneError class with code: 'UNSUPPORTED_TYPE' and Error.cause. Unsupported types throw instead of silently returning undefined. Three new CloneOptions flags, all default true (back-compat with current behavior). Fix: null-prototype objects (Object.create(null)) clone correctly instead of throwing.
  • Browser — the Buffer branch reads globalThis.Buffer at module init. The node:buffer import is type-only.
  • Performance — Plain-object hot path uses {} instead of Object.create(Object.prototype). TypedArray dispatch via one ArrayBuffer.isView call replacing a 9-entry Set lookup. Array branch swaps forEach for a classic for loop. Plain-object fast path when copyDescriptors: false.
  • Tests — 97 tests across 5 files, including a fast-check property suite and a folded cycles spec. Drops the dead is helper and the standalone cycle.test.ts (folded into clone.test.ts and freeze.test.ts).
  • Benchbench/clone.bench.mjs runs five fixture buckets vs structuredClone, lodash.cloneDeep, rfdc, and fast-copy. bench/baseline.md captures the 1.0.0 numbers and the 10 % regression budget.
  • CI.github/workflows/ci.yml calls coroboros/ci/.github/workflows/javascript-npm-packages.yml@v0. OIDC Trusted Publisher and npm provenance on tag push.
  • Docs — README rewrite with "Why this exists" intro, CloneError and three-flag docs, fast-clone composition example, CI badge. CLAUDE.md adds the bench command, fast-check, the regression budget, and the CI-owns-publish rule.

Breaking changes

  • clone(fn), clone(promise), clone(new WeakMap()), clone(new Intl.Collator()), clone(SomeConstructor) throw CloneError('UNSUPPORTED_TYPE', ...) instead of returning undefined.
  • helpers.ts no longer exports the dead is helper.

Upgrade notes

  • The previously silent undefined return for unsupported types now throws. Wrap calls in try/catch or filter inputs upstream when feeding non-clonable values.
  • The three new flags default true so existing call sites keep their current behavior.

Test plan

  • pnpm lint clean
  • pnpm typecheck clean
  • pnpm test — 97/97 green
  • pnpm build — ESM 7.50 kB / gzip 1.93 kB · CJS 7.62 kB / gzip 1.97 kB
  • pnpm bench — runs to completion across all 5 buckets

ob-aion added 11 commits May 15, 2026 12:23
Replaces silent `undefined` return with `throw new CloneError('UNSUPPORTED_TYPE', ...)`
for functions, Promises, Intl.*, WeakMap, WeakSet, and constructor functions.

BREAKING CHANGE: previously these inputs returned `undefined`. Calls that relied on
that contract must now wrap in try/catch or filter inputs upstream.
Captures `Buffer` from `globalThis` at module init via a `BufferRef` constant. The
Buffer branch only runs when the runtime exposes Buffer; otherwise the path is
inert. The `node:buffer` import is type-only — no runtime side-effect on bundlers
that strip `node:` externals.
Three new opt-out flags on `CloneOptions`, all default true so existing callers see
no behavior change. Composing all three `false` gives a fast plain-JSON clone path.

- `cycles: false` skips the WeakMap visited cache. Caller asserts no cycles.
- `preservePrototype: false` flattens custom objects to plain objects.
- `copyDescriptors: false` uses `for...in` + assign for plain objects, drops
  symbol keys and non-enumerable descriptors. Errors keep message + name only;
  boxed wrappers keep their value only.
…otype

- TypedArray dispatch now uses one `ArrayBuffer.isView` call instead of a
  9-entry Set lookup, after DataView and Buffer have already been claimed.
- Plain-object path skips `Object.create(Object.prototype)` and uses a literal,
  which V8 compiles into a faster object-creation map for the most common case.
- `getType` returns a tighter `AnyConstructor` union instead of `unknown`.
Property tests caught a real bug: clone(Object.create(null)) threw CloneError
because the constructor lookup returned undefined for null-prototype objects.
The unsupported-type check now triggers only on functions and explicitly-listed
constructors, so null-prototype objects fall through to the plain-object path
and are cloned with their null prototype preserved.

Restructure tests to match sparkline's "one spec per source module" convention:
fold cycle.test.ts into clone.test.ts and freeze.test.ts under describe('cycles')
blocks; drop cycle.test.ts.

Adds fast-check as a devDependency.
…copy

`bench/clone.bench.mjs` runs five fixture buckets — flat-10, nested-100,
large-1000, with-cycles, class-instances — across the in-package `clone`
(default and fast variants) and the four established cloners. `bench/baseline.md`
captures the Apple M1 / Node 22.22.2 numbers as the 1.0.0 baseline with a
10 percent regression budget going forward.

Adds `mitata`, `lodash.clonedeep`, `rfdc`, `fast-copy`, and the lodash types
as devDependencies; adds `pnpm bench` script.
@ob-aion ob-aion merged commit 13cc507 into main May 15, 2026
5 checks passed
@ob-aion ob-aion deleted the feat/optim branch May 15, 2026 07:22
@ob-aion ob-aion changed the title feat(clone): optim pass for 1.0.0 feat(clone): production-grade 1.0.0 May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant