Skip to content

3.2.1

Latest

Choose a tag to compare

@unadlib unadlib released this 30 Aug 17:39
· 130 commits to main since this release

Coaction 3.2.1

Coaction 3.2.1 is a performance fix for the object-payload form of setState(). Copying the store's current root state was a deep clone, which made every set({ ... }) scale with the size of the whole state rather than the size of what you wrote. Behavior and safety guarantees are unchanged.

All official Coaction packages are versioned together at 3.2.1.

Highlights

set({ ... }) no longer re-clones untouched state

On an object-payload commit, Coaction copied the current root state through the deep replacement sanitizer. Every value was re-cloned on every commit, including fields the update never touched, so the cost was O(total state) instead of O(payload).

Replacing one unrelated scalar in a store that merely holds a 10,000-item array, 400 operations:

Before After
set({ counter }) 1,591 ms 2 ms

The store already owns and already sanitized its current state, so re-sanitizing it on each commit was redundant. The copy is now shallow over own enumerable keys, and nested values keep their identity — the same structural sharing the Mutative draft path already relies on.

The regression entered in 2.0.0 and roughly tripled again in 2.1.0, alongside the deep public-state mutation guards. It went unnoticed because no benchmark threshold covered the path.

Guarantees are unchanged

Data handed to set({ ... }) is still deep-sanitized. Unsafe keys are stripped and aliasing with objects the caller still holds is broken, exactly as before. Only the copy of state Coaction already owned changed.

That sanitizer is why writing a large value through an object payload remains slower than a draft update, and it is a deliberate cost rather than a defect:

Update path ops/sec
set((draft) => { ... }) 43,918
set({ items }) 342

Measured on a 1,000-item cart, Apple M1 Max, Node 24.16. **Prefer set((draft) => { ... }) whenever the value you are wrt path lets Mutative report precise patches, so neither the payload walk nor the cached getter's snapshot rebuild isrequired.

Both write paths are now gated

benchmark-regression-thresholds.json had no entry covering object payloads, and the regression suite never exercised them, so pnpm benchmark:check stayed green across two major versions. Two cases now cover the path from both sides:

  • Coaction unrelated field replacement guards the fixed clone.
  • Coaction object replacement + cached getter guards the payload sanitizer so it cannot silently get worse.

Benchmark documentation refreshed

The README and the benchmarking guide previously quoted the same measurements from different runs, and the bulk-update taot that predated 2.0.0 and no longer reproduced. Every table is now regenerated from a single run, with the spread statedexplicitly: roughly ±1% on the stable read cases and up to ±19% on object replacement. Quote the ratio, not the trailing digits.

The write-path discussion is also split into the defect fixed here and the sanitizer cost that remains by design. See Zustand-focused benchmarks.

Upgrade notes

npm install coaction@3.2.1

No source changes are required. Update any framework integration packages to 3.2.1 together with the core package.

Two things worth doing while you are here:

  1. Audit set({ ... }) call sites whose target field is a sizable array, object, or lookup table, and convert them to set((draft) => { ... }). Nothing throws and tests stay green, so these are easy to miss in review.
  2. The coaction/local gzip budget moves up about 240 B to cover the new copy helper. If you track entry size in CI, exp

History compatibility

@coaction/history continues to depend on travels@^2.1.0 || ^3.0.0. Compatibility is verified in CI against the publis0 releases and against a pinned Travels 3 source candidate; no registry publication of Travels 3 is implied.

Full Changelog: v3.2.0...v3.2.1