Skip to content

Prototype path-aware immutable nested writes - #1

Draft
hatim-s wants to merge 1 commit into
mainfrom
prototype/path-aware-immutable-writer
Draft

Prototype path-aware immutable nested writes#1
hatim-s wants to merge 1 commit into
mainfrom
prototype/path-aware-immutable-writer

Conversation

@hatim-s

@hatim-s hatim-s commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Issue

Bolt nested writes currently go through Mutative. That keeps immutable snapshots, but it is expensive for hot leaf-write paths where Bolt already knows the exact path being replaced.

Cause

The nested write path pays general-purpose proxy/draft overhead even though the operation is always a single path replacement. A direct in-place mutation prototype was fast for primitive leaf subscribers, but it is not React-safe as a default because root and parent path subscribers rely on changed object references from useSyncExternalStore.

Implementation

  • Replace the nested Mutative write path with a small path-aware immutable writer.
  • Keep root replacement as direct set("", nextState) assignment.
  • Preserve public APIs: createBolt, createBoltStore, useStore, useSet, get, getState, subscribe, and typed paths.
  • Clone only the root and object/array ancestors on the changed path.
  • Skip state replacement and notifications when Object.is(previousValue, nextValue).
  • Preserve sibling references and old getState() snapshots after later nested writes.
  • Create missing object/array branches from upcoming path segments, matching the existing lodash-style behavior for numeric path segments.
  • Remove Mutative from the runtime dependency path.
  • Keep the benchmark routes and Legend/Zustand comparison harness available for before/after measurement.
nested set("a.b.c", next)
old root
  a
    b
      c

new root clone
  a clone
    b clone
      c = next

untouched siblings keep old references

Validation

  • bun test src/bolt/bolt.test.ts
  • bun run build:lib
  • bun run build

Focused tests cover:

  • exact leaf subscribers update on leaf writes
  • root subscribers update on nested writes
  • parent path subscribers update on descendant writes
  • sibling path subscribers do not update on unrelated descendant writes
  • root replacement notifies all subscribed paths
  • no-op writes do not notify or replace state
  • updater callbacks receive the current path value
  • dynamic array paths work
  • missing object/array branches are created correctly
  • old state references remain immutable snapshots after later nested writes
  • object path snapshots receive changed references when descendants change

Benchmarks

Protocol: production build + vite preview, default settings, 3 measured passes per route, median reported. Browser automation used headless Google Chrome against http://127.0.0.1:4173.

Route Workload Engine Dispatch before Dispatch after Paint before Paint after
/stress-test 512 cells, 10,000 leaf writes Zustand 367.0 ms 377.7 ms 483.4 ms 491.5 ms
/stress-test 512 cells, 10,000 leaf writes Legend State 31.7 ms 31.2 ms 45.4 ms 43.4 ms
/stress-test 512 cells, 10,000 leaf writes Bolt 367.2 ms 13.2 ms 383.2 ms 25.0 ms
/stress-test-2 512 cells, 100 root snapshots Legend State 91.3 ms 88.6 ms 100.4 ms 100.0 ms
/stress-test-2 512 cells, 100 root snapshots Bolt 11.0 ms 11.7 ms 24.8 ms 25.0 ms
/stress-test-3 512 cells, 1,000 mixed steps Legend State 16.2 ms 16.9 ms 24.9 ms 26.2 ms
/stress-test-3 512 cells, 1,000 mixed steps Bolt 33.1 ms 6.9 ms 42.5 ms 16.6 ms

Known Limitations

  • The writer intentionally clones the changed path only; it is not a general object producer API.
  • Updater callbacks should return the next value. They are not meant to mutate the received value in place.
  • Benchmark results are local/headless medians and should be rechecked on target hardware before drawing product-level conclusions.

Co-authored-by: GPT 5.5 <codex@openai.com>
Co-authored-by: Zed <zed@zed.dev>
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