Repository navigation
perf(state): drop the hooks slot on objects and the frozen hooks cache - #187
Merged
Merged
Conversation
The blind-spots group names creation of a target that takes a second symbol as a gap, but no case timed it. Add one shaped like the hookless creation case with a single no-op onRead hook built once at module level, so the next change can compare hooked creation against this baseline.
A hooked non-array target kept its StateHooks under the HOOKS symbol, and a hooked non-extensible target was written into frozenHooksCache, but nothing read either: every trap reaches the hooks through its handler closure. Only an array target needs the slot, for the DirectArrayContext its direct push and pop read through the Proxy receiver. hooksSlotFor now returns undefined for a non-array target, so the symbol is defined exactly on extensible arrays, and the cache with its writer is gone. The HooksSlot type says so. No consumer-visible change: hooks fire as before on every kind of target, direct push and pop on hooked arrays behave as before, and a non-extensible target still gets its Proxy registered in proxyCache. Closes HOOKS_TODO.md item C1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Every
state(obj, hooks)call on a non-array target ran oneObject.definePropertyfor the module-privateHOOKSsymbol, and nothing read it. A hooked non-extensible target was also written into a WeakMap that had no reader. Hooks reach every trap through the handler closures, so both writes were dead. This removes them and closesHOOKS_TODO.mditem C1.hooksSlotFor, which now returnsundefinedfor a non-array target. The array branch,definePropertyOnTargetand the internal-symbol guard are unchanged.catcharm ofdefineProxyPropertiesstays. An extensible target whosedefinePropertytrap rejects symbols still reaches it, andtests/property-frozen-sealed.test.tscovers that path.proxyCache.Changes Made
src/index.ts:hooksSlotForreturnsundefinedfor non-array targets,HooksSlotnarrows toDirectArrayContext | undefined, andfrozenHooksCachewithstoreHooksForFrozenTargetis deleted.scripts/benchmark.ts: a newstate creation (hooks)case in the blind-spots group. It is its own commit, so the baseline below comes from the unchanged library.tests/state-hooks.test.ts: one test asserts that a hooked plain object owns one own symbol, a hooked array owns two, and a hookless object owns one. The object case fails on trunk.src/AGENTS.md,src/hooks/AGENTS.md,tests/AGENTS.md, two test comments, andHOOKS_TODO.md, where C1 moves toshipped.Performance Impact
state creation (hooks)median went from 21.49ms to between 13.24ms and 13.39ms across five runs on Node v24.21.0, Apple M4 Pro.classic loop,state no subs,state + derive + 2 effects,effect triggers,derive chain depth 10,sealed write 1 sub,state no subs after seal,state write equal value,array push/pop (undefined)). The two baseline runs of identical code differed by more than 1% on 8 cases, so the suite cannot resolve 1% on them.state creation(+1.4%, +1.1%) andarray push/pop (hooks)(+2.0%, +1.1%). Neither path is in the diff. A targeted probe with 8 alternating processes per commit, 15 samples each, measured them at -0.59% and -0.37%, while the baseline's own processes spread 2.6%. The full tables are in the ticket reports underopenspec/changes/drop-unread-hooks-slot-on-objects/tickets/.Test Coverage
npm testpasses 308 tests.npm run test:coveragereports 100% branches, 100% functions, 100% lines.npm run checkandnpm run buildpass.state(raw, { onRead() {} })and checkObject.getOwnPropertySymbols(raw).length. It is 1 for a plain object and 2 for an array.Related Issues
None. Tracked as
HOOKS_TODO.mditem C1 and decided in.do/inbox/2026-10-25-drop-unread-hooks-slot-on-objects.md(Q80 a).Checklist