epoch-2: Proxy-based API, hooks instrumentation, docs overhaul#33
Draft
nerdalytics wants to merge 101 commits intotrunkfrom
Draft
epoch-2: Proxy-based API, hooks instrumentation, docs overhaul#33nerdalytics wants to merge 101 commits intotrunkfrom
nerdalytics wants to merge 101 commits intotrunkfrom
Conversation
Replace function-based state with Proxy-based objects for more natural
property access syntax. Removes select, lens, readonlyState, and
protectedState APIs in favor of direct property mutation tracking.
BREAKING CHANGE: v2000.0.0 API overhaul
- state() now returns reactive Proxy object
- derive() returns {value, dispose, [Symbol.dispose]}
- Removed: select, lens, readonlyState, protectedState
Delete tests for removed APIs (select, lens, readonlyState, protectedState) and legacy function-based state tests. Replaced by new reorganized test suite in follow-up commit.
Introduce new test organization with clear separation of concerns: - Core tests for each primitive (state, derive, effect, batch) - Integration tests (state-derive, state-effect, batch-integration) - Updated cleanup, cyclic-dependency, and infinite-loop tests Includes test style guide and organization documentation.
Remove beacon-logo.png and beacon-logo@2.png in favor of new beacon-logo-v2.svg for better scalability and smaller file size.
- Update README with new usage examples and API reference - Refresh TECHNICAL_DETAILS with Proxy implementation details - Add docs/ folder with modular documentation per feature - Remove references to deprecated APIs (select, lens, etc.)
- Simplify GitHub Actions workflows - Update mise.toml configuration - Remove benchmark.ts, strip-comments.ts, update-performance-docs.ts - Enhance naiv-benchmark.ts with consolidated functionality
Add documentation for Beacon's hooks system: - HOOKS.md: Overview and usage guide - HOOKS_API.md: API reference - HOOKS_CATALOG.md: Available hooks catalog - HOOKS_TODO.md: Future development roadmap
… not derive chain consistency Derive chains propagate consistently for a single source mutation without batch — effects run in Set insertion order, which matches creation order, which matches dependency order. Batch collapses multiple source mutations into one notification cycle. Updated docs that implied batch was needed for derive chain consistency.
Whitelist .md files in docs/, src/, tests/, scripts/ in .gitignore to allow progressive disclosure documentation. Add AGENTS.md and CLAUDE.md index files at root and per-directory level for codebase navigation and domain-specific instructions.
…ncyAdd, onSchedule
…Error, onDependencyChange
Remove unused HooksObject type, replace non-null assertions with optional chaining, fix import ordering, add explicit parameter types to hook callbacks, apply Biome formatting.
Add src/hooks/AGENTS.md documenting the hooks public API, composition utility, interfaces, and design constraints. Update tests/AGENTS.md with hooks test category and per-file coverage breakdown. Update root and src indexes to reflect hooks infrastructure now implemented.
Skip dirtyTargets bookkeeping in handleBatchFastPath when target has no subscribers. Inline getArrayLengthBeforeMutation to eliminate function call overhead for non-array targets. Add specialized lean get/set handlers for the common no-hooks path, avoiding unnecessary function calls per property access.
Add 9 targeted benchmarks measuring isolated hot-path operations: state creation, read, write with subscribers, effect triggers, many dependencies, derive chains, and batch comparisons.
Add forceGC + heap delta measurement to runBench so epoch-2 benchmarks report memory pressure identical to trunk. Pass --expose-gc in npm script. Remove unused bun from mise.toml.
Run N complete cycles (default 10, -R flag to override) to reduce run-to-run variance. Aggregates all samples before computing stats.
- addPendingEffect: replace .has()+.add() with .add()+size check - scheduleSubscriberWithProp: cache __hooks lookup, skip callHookSafe when no hooks - runPendingEffectBatch: fast path for single pending effect (skip array copy)
…ss fast path - Remove Array.isArray + length tracking from handleBatchFastPath hot loop - Move array length notification to flushDirtyTargets (once per target, not per write) - Add batch() fast path when no hooks passed (skip composeHook/callHookSafe)
On effect re-run, capture the ordered sequence of (target, prop) reads into a flat array (__readList). On subsequent re-runs, compare reads by index instead of rebuilding Map/Set temp collections and iterating to compare. When all reads match — zero allocations, zero collection operations, just pointer comparisons. If a mismatch is detected mid-run (deps changed), bail: replay matched reads into temp collections, continue with standard tracking, and invalidate the readList for rebuild on next stable re-run. 100 states individual: 4,445ms → 1,150ms (-74%) state + derive + 2 effects: 30,956ms → 20,112ms (-35%) state write 100 subs: 7,468ms → 4,361ms (-42%) Full suite total: 67.3s → 43.1s (-36%)
Adds fast-check (v4.5.3) and implements model-based property tests that verify reactive arrays behave identically to plain Arrays under arbitrary mutation sequences (push, pop, shift, unshift, splice, sort, reverse). Three properties tested (300 runs each): - Content equivalence after arbitrary mutation sequences - Return value equivalence for value-returning methods - Batch deduplication (at most 1 effect per batch) Includes PROPERTY_BASED_TESTING.md documenting 10 PBT opportunities. https://claude.ai/code/session_0159wAJYHfr72KhPx4kxNu86
Verifies Object.is semantics across the full primitive type space using fast-check with weighted arbitraries that bias toward edge cases (NaN, -0, +0, Infinity, null, undefined). Five properties tested: - Same value write never triggers effects - Effect fires iff Object.is(old, new) is false - N repeated same-value writes produce 0 triggers - Same-value writes inside batch produce 0 triggers - Derive skips downstream notification when output is unchanged Updates PROPERTY_BASED_TESTING.md to mark #1 and #2 as done. https://claude.ai/code/session_0159wAJYHfr72KhPx4kxNu86
Verifies batch deduplication invariants under arbitrary write sequences using fast-check: single-property writes, multi-property writes on one state, multi-state updates, and nested batch depth. Four properties tested (300 runs each): - Single-property: at most 1 effect per batch + correct final value - Multi-property: at most 1 effect + correct final value per key - Multi-state: at most 1 effect when updating N states in a batch - Nested batches: effects run 0 times mid-batch, exactly 1 after Updates PROPERTY_BASED_TESTING.md to mark #3 as done. https://claude.ai/code/session_0159wAJYHfr72KhPx4kxNu86
…vity Verify WeakMap fallback paths (proxyCacheSubs, frozenMethodCache) used when Object.isExtensible(target) is false. Tests cover proxy identity, read tracking, cross-effect write notifications, disposal cleanup, direct value writes, and sealed array method caching. https://claude.ai/code/session_0159wAJYHfr72KhPx4kxNu86
Add 7 properties modeling the actual usage pattern: frozen/sealed objects as children of extensible parents that get replaced, not mutated. Tests cover child replacement triggering effects, read-through correctness, proxy identity stability, derive tracking, batch deduplication, primitive property readability, and same-reference no-op via Object.is. https://claude.ai/code/session_0159wAJYHfr72KhPx4kxNu86
…uplication Apply Strunk's rules across both READMEs: omit needless words, use active voice, prefer specific language over vague puffery. Remove duplicate Architecture subsections already covered under Advanced Features. Fix factually wrong browser FAQ.
Absorb 3 trunk commits: tree-shaking decomposition (cb74ce1), OIDC npm publishing (77d9c67), multi-cycle benchmark (244179c). Conflict resolution: keep epoch-2 on all 7 files. Epoch-2 already has decomposed function exports (Proxy-based API). Cherry-picked sideEffects: false from trunk into package.json. CI OIDC fix auto-merged.
Add esbuild as dev dependency for tree-shaking verification and potential uglify-js replacement. Lower Biome maxAllowedComplexity from 20 to 10.
…rtyTypes Wrap return types with NonNullable<> on all five Proxy handler factories. They always return a function, never undefined — the previous ProxyHandler indexed-access return type was overly permissive and broke under exactOptionalPropertyTypes.
…ests Add type guards for array index access across five PBT files. tsconfig.lts.json enables noUncheckedIndexedAccess, so arr[i] returns T | undefined. Each fix extracts the access into a local variable and narrows with an if-throw guard.
Replace uglify-js with esbuild for postbuild minification. Fix check:fix to run biome check (not biome format). Tighten cognitive complexity threshold from 15 to 10.
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.
Summary
state.valueinstead ofstate())state,effect,derive,batch)sideEffects: false, CI OIDC publishing, multi-cycle benchmark)exactOptionalPropertyTypesandnoUncheckedIndexedAccesstype errors for strict LTS buildChanges
Core (
src/index.ts)state()with five handler traps (get, set, deleteProperty, has, ownKeys)derive()returns{ value, reactive }— disposal viareactive = falsetoggle, notdispose()methodsrc/types.ts, composition utility insrc/hooks/NonNullable<>forexactOptionalPropertyTypescompliancePerformance Optimizations
getSubscriberseffectDependencies,effectStateReads,parentEffect,childEffects,subscriberCache) with direct EffectFunction properties (__deps,__reads,__parent,__children); change inner reads tracking fromWeakMaptoMap; simplifypromoteTempToGlobalto 2 assignments; removesubscriberCachelayerperformWrite/checkInfiniteLoopwhen!currentEffect(~28% improvement on subscriberless writes)HOOKLESS_HANDLERat module level avoids 5 factory calls + 1 object allocation per state (~33% improvement on state creation)Benchmark Results
Worktree-based comparison, 1M iterations, 10 cycles x 7 samples = 70 total per benchmark. Medians shown.
Interpretation: Epoch-2's Proxy-based architecture pays a fixed cost on bare reads/writes/creation (no subscribers involved). Where it matters — effect re-runs, subscriber notification, derive chains — epoch-2 is 15-64% faster due to per-property tracking, stable-dep skip, readList fast path, and inlined hot paths.
Documentation (
docs/README*.md,README.md,.github/README.md)dispose()/Symbol.dispose/usingreferences withreactivetoggle inREADME.derive.mdbatchDirtyTargetswith actualpendingEffects+deferredEffectCreationsmechanism inREADME.batch.mdREADME.core.mdREADME.debugging.md— remove fictional env-var debug system (BEACON_DEBUG,NODE_ENV,devLogRead/Write/Assert), replace with hooks-based debuggingREADME.hooks.mdcovering all 16 hook callbacks, composition, error isolationv2000.0.0version references from proseRefactoring
composeHookfrom hooks module instead of inliningTests
{primitive}-core.test.ts,{primitive}-hooks.test.ts, integration, behaviornoUncheckedIndexedAccesstype errors across all property-based test filesstate: array mutations, same-value optimization, proxy identity, deep reactivity, frozen/sealed objects, frozen children of reactive stateeffect: cleanup completeness, infinite loop detection boundary, dynamic dependency trackingbatch: effect deduplication, error recoveryderive: consistency invariantsInfrastructure
sideEffects: false, CI OIDC npm publishing, multi-cycle benchmarkcheck:fixnow runsbiome check --write(wasbiome format --fix).github/README.md, keep root README as install + quick start onlyTest plan
npm test— 193/193 tests pass (129 unit + 64 property-based, 27 suites)npm run test:coverage— meets coverage targets (100% branches, 100% functions, 90% lines)npm run build— builds successfully (esbuild minification)tsc -p tsconfig.lts.json— zero type errors under strict modenpm run check— Biome lint + format + assists cleannpm run benchmark— confirms performance improvementstate-only bundle eliminatesderiveandbatchcodeid-token: write, noNODE_AUTH_TOKENgrep -r "dispose()" docs/shows only effect disposalsgrep -rE "BEACON_DEBUG|devLog|batchDirtyTargets|v2000\.0\.0" docs/returns empty