Releases: zeixcom/cause-effect
Releases · zeixcom/cause-effect
Version 1.0.0
What's Changed
Changed
- Stricter TypeScript configuration: Enabled
noUncheckedIndexedAccess,exactOptionalPropertyTypes,useUnknownInCatchVariables,noUncheckedSideEffectImports, andnoFallthroughCasesInSwitchintsconfig.json. All internal array and indexed object accesses have been updated to satisfy these checks. Runtime behaviour is unchanged. stopon node types now typed asCleanup | undefined: Thestopproperty inSourceFields(and by extensionStateNode,MemoNode,TaskNode) is now declaredstop?: Cleanup | undefinedrather thanstop?: Cleanup. UnderexactOptionalPropertyTypes, this is required to allow clearing the property by assignment (= undefined) rather than deletion — preserving V8 hidden-class stability on hot-path nodes. Consumers readingstopfrom a node should already be handlingundefinedsince the property is optional, but TypeScript will now surface this requirement explicitly.guardon options types now requires explicit presence: UnderexactOptionalPropertyTypes, passing{ guard: undefined }toSignalOptions,ComputedOptions, orSensorOptionsis now a type error. Omit the property entirely to leave it unset.
Full Changelog: v0.18.5...v1.0.0
Version 0.18.5
What's Changed
Added
unown(fn)— escape hatch for DOM-owned component lifecycles: Runs a callback withactiveOwnerset tonull, preventing anycreateScopeorcreateEffectcalls inside from being registered as children of the current active owner. Use this inconnectedCallback(or any external lifecycle hook) when a component manages its own cleanup independently viadisconnectedCallbackrather than through the reactive ownership tree.
Fixed
- Scope disposal bug when
connectedCallbackfires inside a re-runnable effect: Previously, callingcreateScopeinside a reactive effect (e.g. a list sync effect) registered the scope'sdisposeon that effect's cleanup list. When the effect re-ran — for example, because aMutationObserverfired — it calledrunCleanup, disposing all child scopes including those belonging to already-connected custom elements. This silently removed event listeners and reactive subscriptions from components that were still live in the DOM. Wrapping theconnectedCallbackbody inunown(() => createScope(...))detaches the scope from the effect's ownership, so effect re-runs no longer dispose it.
Full Changelog: v0.18.4...v0.18.5
Version 0.18.4
What's Changed
Fixed
- Watched
invalidate()now respectsequalsat every graph level: Previously, callinginvalidate()from a Memo or Taskwatchedcallback propagatedFLAG_DIRTYdirectly to effect sinks, causing unconditional re-runs even when the recomputed value was unchanged. Nowinvalidate()delegates topropagate(node), which marks the node itselfFLAG_DIRTYand propagatesFLAG_CHECKto downstream sinks. During flush, effects verify their sources viarefresh()— if the memo'sequalsfunction determines the value is unchanged, the effect is cleaned without running. This eliminates unnecessary effect executions for watched memos with custom equality or stable return values.
Changed
propagate()supportsFLAG_CHECKfor effect nodes: The effect branch ofpropagate()now respects thenewFlagparameter instead of unconditionally settingFLAG_DIRTY. Effects are enqueued only on first notification; subsequent propagations escalate the flag (e.g.,CHECK→DIRTY) without re-enqueuing.flush()processesFLAG_CHECKeffects: The flush loop now callsrefresh()on effects with eitherFLAG_DIRTYorFLAG_CHECK, enabling the check-sources-first path for effects.- Task
invalidate()aborts eagerly: Task watched callbacks now abort in-flight computations immediately duringpropagate()rather than deferring torecomputeTask(), consistent with the normal dependency-change path.
Full Changelog: v0.18.3...v0.18.4
Version 0.18.2
What's Changed
Fixed
watchedpropagation throughderiveCollection()chains: When an effect reads a derived collection, thewatchedcallback on the source List, Store, or Collection now activates correctly — even through multiple levels of.deriveCollection()chaining. Previously,deriveCollectiondid not propagate sink subscriptions back to the source'swatchedlifecycle.- Stable
watchedlifecycle during mutations: Adding, removing, or sorting items on a List (or Store/Collection) consumed throughderiveCollection()no longer tears down and restarts thewatchedcallback. The watcher remains active as long as at least one downstream effect is subscribed. - Cleanup cascade on disposal: When the last effect unsubscribes from a derived collection chain, cleanup now propagates upstream through all intermediate nodes to the source, correctly invoking the
watchedcleanup function.
Changed
FLAG_RELINKreplaces source-nulling in composite signals: Store, List, Collection, and deriveCollection no longer null outnode.sources/node.sourcesTailon structural mutations. Instead, a newFLAG_RELINKbitmap flag triggers a trackedrefresh()on the next.get()call, re-establishing edges cleanly vialink()/trimSources()without orphaning them.- Cascading
trimSources()inunlink(): When a MemoNode loses all sinks, its own sources are now trimmed recursively, ensuring upstreamwatchedcleanup propagates correctly through intermediate nodes. - Three-path
ensureFresh()inderiveCollection: The internal freshness check now distinguishes between fast path (has sources, clean), first subscriber (has sinks but no sources yet), and no subscriber (untracked build). This prevents prematurewatchedactivation during initialization.
Full Changelog: v0.18.1...v0.18.2
Version 0.18.1
What's Changed
Added
- Memo
watched(invalidate)option:createMemo(fn, { watched })accepts a lazy lifecycle callback that receives aninvalidatefunction. Callinginvalidate()marks the memo dirty and triggers re-evaluation. The callback is invoked on first sink attachment and cleaned up when the last sink detaches. This enables patterns like DOM observation where a memo re-derives its value in response to external events (e.g., MutationObserver) without needing a separate Sensor. - Task
watched(invalidate)option: Same pattern as Memo. Callinginvalidate()aborts any in-flight computation and triggers re-execution. CollectionChanges<T>type: New typed interface for collection mutations withadd?: T[],change?: T[],remove?: T[]arrays. Replaces the untypedDiffResultrecords previously used byCollectionCallback.SensorOptions<T>type: Dedicated options type forcreateSensor, extendingSignalOptions<T>with optionalvalue.CollectionChangesexport from public API (index.ts).SensorOptionsexport from public API (index.ts).
Changed
createSensorparameter renamed:start→watchedfor consistency with Store/List lifecycle terminology.createSensoroptions type:ComputedOptions<T>→SensorOptions<T>. This decouples Sensor options fromComputedOptions, which now carries thewatched(invalidate)field for Memo/Task.createCollectionparameter renamed:start→watchedfor consistency.CollectionCallbackis now generic:CollectionCallback→CollectionCallback<T>. TheapplyChangesparameter acceptsCollectionChanges<T>instead ofDiffResult.CollectionOptions.createItemsignature:(key: string, value: T) => Signal<T>→(value: T) => Signal<T>. Key generation is now handled internally.KeyConfig<T>return type relaxed: Key functions may now returnstring | undefined. Returningundefinedfalls back to synthetic key generation.
Removed
DiffResultremoved from public API: No longer re-exported fromindex.ts. The type remains available fromsrc/nodes/list.tsfor internal use but is superseded byCollectionChanges<T>for collection mutations.
Full Changelog: v0.18.0...v0.18.1
Version 0.18.0
What's Changed
- Linked List Based Signal Graph by @estherbrunner in #23
Full Changelog: v0.17.3...v0.18.0
Version 0.17.3
What's Changed
Breaking Changes
- New way to instantiate signals:
createState()->new State()createComputed()->new Memo()(sync) ornew Task()(createComputed()still exists as a wrapper)
- Removed StoreChanges notification listener system in favor of a unified reactivity model. Access to reactive
.keys()iterator on Store, List, or Collection in an effect to be notified about structural changes only, or the usual.get()to be notified about resolved value changes instead.
New Features
- new
Collectionsignal type for read-only derived reactive lists - new
Refsignal type for externally managed objects like DOM elements, WebSocket connections, or files on the server.
Improvements
- Massive performance gain for Store instantiation (more than double) and List instantiation (5x faster)
- Improved performance of method access in List (about 3x faster)
- Reduced memory usage for Store (less than half) and List (about 4x less) due to method sharing of classes
These huge gains convinced us to switch to a class-based architecture for signals, which made the breaking change for instantiation neccessary. We opted for not providing wrapper functions to support the deprecated API signature, since we're still in a pre-release phase.
Full Changelog: v0.16.1...v0.17.3
Version 0.16.1
What's Changed
Bugfixes
- Stores used to expose a
State<number>for the.sizeproperty. This was a mistake as mutating size from outside would have unpredictable consequences. It should have been a read-only computed signal instead. But then again, array-like Stores need a.lengthproperty anyway to be recognized as array-like by JavaScript functions. As accessing.lengthin array-like stores establishes a reactive subscription as well,.sizewould become a mere duplicate of the same feature with a more complicated API. So we decided to drop.sizealtogether and expose.lengthas a read-only property for record-like stores as well. - Methods, symbol properties on all signals and the
lengthproperty on stores non-configurable and non-enumerable, aligning with default behavior of objects and arrays in JavaScript.
Full Changelog: v0.16.0...v0.16.1
Version 0.16.0
What's Changed
Breaking Changes
- Renamed functions to more clearly express what they do and make naming conflicts with local variables less likely:
computed()->createComputed()effect()->createEffect()state()->createState()store()->createStore()watch()->createWatcher()andwatcher.off()->watcher.unwatch()
- Changed store events from
CustomEventto light-weight change notifications:store.addEventListener('store-*', handler)->store.on('*', handler)store.removeEventListener('store-*', handler)-> cleanup function returned from.on()handler- Change notification callbacks receive the former details as the only parameter, no more event involved
- Changed parameters of
createComputed()to allow for reducer-like capabilities:- Computed callback now receives old value as first parameter, pushing
AbortSignalfor async callback to second parameter - Optional second parameter for initial value
- Computed callback now receives old value as first parameter, pushing
New Features
- Add reducer-like capabilities to
createComputedfor accumulative computed signals - Basic input validation with more descriptive error messages for all signal types and effects
Tests
- Updated test cases to reflect API changes
- Tests covering accumulative computed signals, more edge cases and input validation
Documentation
- Updated
READMEto document accumulative computed signals and reflect API changes - New AI-targeted documentation files:
.ai-context.md.cursorrulesCLAUDE.md.github/copilot-instructions.md
Full Changelog: v0.15.2...v0.16.0
Version 0.15.2
What's Changed
New Features
.sort()method on stores to sort entries in place, avoiding change notifications for nested signals which only changed position- Add
.lengthproperty to array-like stores and exposeSymbol.isConcatSpreadableto make them behave (almost) like native arrays
Bugfixes
- Fix type inference bug related to
toSignal()function - Improve type inference of array-like stores
- Validate that input for
diff()function is either record or array - Fix bug with sparse arrays in
diff()
Full Changelog: v0.15.0...v0.15.2