Skip to content

Releases: zeixcom/cause-effect

Version 1.0.0

09 Mar 08:09
d069cb3

Choose a tag to compare

What's Changed

Changed

  • Stricter TypeScript configuration: Enabled noUncheckedIndexedAccess, exactOptionalPropertyTypes, useUnknownInCatchVariables, noUncheckedSideEffectImports, and noFallthroughCasesInSwitch in tsconfig.json. All internal array and indexed object accesses have been updated to satisfy these checks. Runtime behaviour is unchanged.
  • stop on node types now typed as Cleanup | undefined: The stop property in SourceFields (and by extension StateNode, MemoNode, TaskNode) is now declared stop?: Cleanup | undefined rather than stop?: Cleanup. Under exactOptionalPropertyTypes, this is required to allow clearing the property by assignment (= undefined) rather than deletion — preserving V8 hidden-class stability on hot-path nodes. Consumers reading stop from a node should already be handling undefined since the property is optional, but TypeScript will now surface this requirement explicitly.
  • guard on options types now requires explicit presence: Under exactOptionalPropertyTypes, passing { guard: undefined } to SignalOptions, ComputedOptions, or SensorOptions is now a type error. Omit the property entirely to leave it unset.

Full Changelog: v0.18.5...v1.0.0

Version 0.18.5

01 Mar 12:56
5ffa134

Choose a tag to compare

Version 0.18.5 Pre-release
Pre-release

What's Changed

Added

  • unown(fn) — escape hatch for DOM-owned component lifecycles: Runs a callback with activeOwner set to null, preventing any createScope or createEffect calls inside from being registered as children of the current active owner. Use this in connectedCallback (or any external lifecycle hook) when a component manages its own cleanup independently via disconnectedCallback rather than through the reactive ownership tree.

Fixed

  • Scope disposal bug when connectedCallback fires inside a re-runnable effect: Previously, calling createScope inside a reactive effect (e.g. a list sync effect) registered the scope's dispose on that effect's cleanup list. When the effect re-ran — for example, because a MutationObserver fired — it called runCleanup, 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 the connectedCallback body in unown(() => 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

18 Feb 17:09
2dc2acf

Choose a tag to compare

Version 0.18.4 Pre-release
Pre-release

What's Changed

Fixed

  • Watched invalidate() now respects equals at every graph level: Previously, calling invalidate() from a Memo or Task watched callback propagated FLAG_DIRTY directly to effect sinks, causing unconditional re-runs even when the recomputed value was unchanged. Now invalidate() delegates to propagate(node), which marks the node itself FLAG_DIRTY and propagates FLAG_CHECK to downstream sinks. During flush, effects verify their sources via refresh() — if the memo's equals function 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() supports FLAG_CHECK for effect nodes: The effect branch of propagate() now respects the newFlag parameter instead of unconditionally setting FLAG_DIRTY. Effects are enqueued only on first notification; subsequent propagations escalate the flag (e.g., CHECKDIRTY) without re-enqueuing.
  • flush() processes FLAG_CHECK effects: The flush loop now calls refresh() on effects with either FLAG_DIRTY or FLAG_CHECK, enabling the check-sources-first path for effects.
  • Task invalidate() aborts eagerly: Task watched callbacks now abort in-flight computations immediately during propagate() rather than deferring to recomputeTask(), consistent with the normal dependency-change path.

Full Changelog: v0.18.3...v0.18.4

Version 0.18.2

15 Feb 12:30
1e62979

Choose a tag to compare

Version 0.18.2 Pre-release
Pre-release

What's Changed

Fixed

  • watched propagation through deriveCollection() chains: When an effect reads a derived collection, the watched callback on the source List, Store, or Collection now activates correctly — even through multiple levels of .deriveCollection() chaining. Previously, deriveCollection did not propagate sink subscriptions back to the source's watched lifecycle.
  • Stable watched lifecycle during mutations: Adding, removing, or sorting items on a List (or Store/Collection) consumed through deriveCollection() no longer tears down and restarts the watched callback. 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 watched cleanup function.

Changed

  • FLAG_RELINK replaces source-nulling in composite signals: Store, List, Collection, and deriveCollection no longer null out node.sources/node.sourcesTail on structural mutations. Instead, a new FLAG_RELINK bitmap flag triggers a tracked refresh() on the next .get() call, re-establishing edges cleanly via link()/trimSources() without orphaning them.
  • Cascading trimSources() in unlink(): When a MemoNode loses all sinks, its own sources are now trimmed recursively, ensuring upstream watched cleanup propagates correctly through intermediate nodes.
  • Three-path ensureFresh() in deriveCollection: 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 premature watched activation during initialization.

Full Changelog: v0.18.1...v0.18.2

Version 0.18.1

14 Feb 11:33
8538112

Choose a tag to compare

Version 0.18.1 Pre-release
Pre-release

What's Changed

Added

  • Memo watched(invalidate) option: createMemo(fn, { watched }) accepts a lazy lifecycle callback that receives an invalidate function. Calling invalidate() 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. Calling invalidate() aborts any in-flight computation and triggers re-execution.
  • CollectionChanges<T> type: New typed interface for collection mutations with add?: T[], change?: T[], remove?: T[] arrays. Replaces the untyped DiffResult records previously used by CollectionCallback.
  • SensorOptions<T> type: Dedicated options type for createSensor, extending SignalOptions<T> with optional value.
  • CollectionChanges export from public API (index.ts).
  • SensorOptions export from public API (index.ts).

Changed

  • createSensor parameter renamed: startwatched for consistency with Store/List lifecycle terminology.
  • createSensor options type: ComputedOptions<T>SensorOptions<T>. This decouples Sensor options from ComputedOptions, which now carries the watched(invalidate) field for Memo/Task.
  • createCollection parameter renamed: startwatched for consistency.
  • CollectionCallback is now generic: CollectionCallbackCollectionCallback<T>. The applyChanges parameter accepts CollectionChanges<T> instead of DiffResult.
  • CollectionOptions.createItem signature: (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 return string | undefined. Returning undefined falls back to synthetic key generation.

Removed

  • DiffResult removed from public API: No longer re-exported from index.ts. The type remains available from src/nodes/list.ts for internal use but is superseded by CollectionChanges<T> for collection mutations.

Full Changelog: v0.18.0...v0.18.1

Version 0.18.0

11 Feb 13:34
a7d58e2

Choose a tag to compare

Version 0.18.0 Pre-release
Pre-release

What's Changed

Full Changelog: v0.17.3...v0.18.0

Version 0.17.3

08 Jan 15:12
f0263c0

Choose a tag to compare

Version 0.17.3 Pre-release
Pre-release

What's Changed

Breaking Changes

  • New way to instantiate signals:
    • createState() -> new State()
    • createComputed() -> new Memo() (sync) or new 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 Collection signal type for read-only derived reactive lists
  • new Ref signal 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

03 Dec 15:00
fd47c5d

Choose a tag to compare

Version 0.16.1 Pre-release
Pre-release

What's Changed

Bugfixes

  • Stores used to expose a State<number> for the .size property. 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 .length property anyway to be recognized as array-like by JavaScript functions. As accessing .length in array-like stores establishes a reactive subscription as well, .size would become a mere duplicate of the same feature with a more complicated API. So we decided to drop .size altogether and expose .length as a read-only property for record-like stores as well.
  • Methods, symbol properties on all signals and the length property 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

01 Dec 15:24
cb64fbb

Choose a tag to compare

Version 0.16.0 Pre-release
Pre-release

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() and watcher.off() -> watcher.unwatch()
  • Changed store events from CustomEvent to 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 AbortSignal for async callback to second parameter
    • Optional second parameter for initial value

New Features

  • Add reducer-like capabilities to createComputed for 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 README to document accumulative computed signals and reflect API changes
  • New AI-targeted documentation files:
    • .ai-context.md
    • .cursorrules
    • CLAUDE.md
    • .github/copilot-instructions.md

Full Changelog: v0.15.2...v0.16.0

Version 0.15.2

08 Nov 23:07
5a4ab87

Choose a tag to compare

Version 0.15.2 Pre-release
Pre-release

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 .length property to array-like stores and expose Symbol.isConcatSpreadable to 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