@zeix/cause-effect 0.18.3 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -106,14 +106,14 @@ The first four flags (`CLEAN`/`CHECK`/`DIRTY`/`RUNNING`) are used by the core gr
106
106
 
107
107
  `FLAG_RELINK` is used exclusively by composite signal types (Store, List, Collection, deriveCollection) that manage their own child signals. When a structural mutation adds or removes child signals, the node is flagged `FLAG_DIRTY | FLAG_RELINK`. On the next `get()`, the composite signal's fast path reads the flag: if `FLAG_RELINK` is set, it forces a tracked `refresh()` after rebuilding the value so that `recomputeMemo()` can call `link()` for new child signals and `trimSources()` for removed ones. This avoids the previous approach of nulling `node.sources`/`node.sourcesTail`, which orphaned edges in upstream sink lists. `FLAG_RELINK` is always cleared by `recomputeMemo()`, which assigns `node.flags = FLAG_RUNNING` (clearing all bits) at the start of recomputation.
108
108
 
109
- ### The `propagate(node)` Function
109
+ ### The `propagate(node, newFlag?)` Function
110
110
 
111
- When a source value changes, `propagate()` walks its sink list:
111
+ When a source value changes, `propagate()` walks its sink list. The `newFlag` parameter defaults to `FLAG_DIRTY` but callers may pass `FLAG_CHECK` for speculative invalidation (e.g., watched callbacks where the source value may not have actually changed).
112
112
 
113
- - **Memo/Task sinks** (have `sinks` field): Flagged `DIRTY`. Their own sinks are recursively flagged `CHECK`. If the node has an in-flight `AbortController`, it is aborted immediately.
114
- - **Effect sinks** (no `sinks` field): Flagged `DIRTY` and pushed onto the `queuedEffects` array for later execution.
113
+ - **Memo/Task sinks** (have `sinks` field): Flagged with `newFlag` (typically `DIRTY`). Their own sinks are recursively flagged `CHECK`. If the node has an in-flight `AbortController`, it is aborted immediately. Short-circuits if the node already carries an equal or higher flag.
114
+ - **Effect sinks** (no `sinks` field): Flagged with `newFlag` and pushed onto the `queuedEffects` array. An effect is only enqueued once — subsequent propagations escalate the flag (e.g., `CHECK` → `DIRTY`) without re-enqueuing. The flag is assigned (not OR'd) to clear `FLAG_RUNNING`, preserving the existing pattern where a state update inside a running effect triggers a re-run.
115
115
 
116
- The two-level flagging (`DIRTY` for direct dependents, `CHECK` for transitive) avoids unnecessary recomputation. A `CHECK` node only recomputes if, upon inspection during `refresh()`, one of its sources turns out to have actually changed.
116
+ The two-level flagging (`DIRTY` for direct dependents, `CHECK` for transitive) avoids unnecessary recomputation. A `CHECK` node only recomputes if, upon inspection during `refresh()`, one of its sources turns out to have actually changed. This applies equally to memo, task, and effect nodes.
117
117
 
118
118
  ### The `refresh(node)` Function
119
119
 
@@ -141,7 +141,7 @@ If `FLAG_RUNNING` is encountered, a `CircularDependencyError` is thrown.
141
141
 
142
142
  ### The `flush()` Function
143
143
 
144
- `flush()` iterates over `queuedEffects`, calling `refresh()` on each effect that is still `DIRTY`. A `flushing` guard prevents re-entrant flushes. Effects that were enqueued during the flush (due to async resolution or nested state changes) are processed in the same pass, since `flush()` reads the array length dynamically.
144
+ `flush()` iterates over `queuedEffects`, calling `refresh()` on each effect that is still `DIRTY` or `CHECK`. A `flushing` guard prevents re-entrant flushes. Effects that were enqueued during the flush (due to async resolution or nested state changes) are processed in the same pass, since `flush()` reads the array length dynamically. Effects with only `FLAG_CHECK` enter `refresh()`, which walks their sources — if no source value actually changed, the effect is cleaned without running.
145
145
 
146
146
  ### Effect Lifecycle
147
147
 
@@ -209,7 +209,7 @@ The `error` field preserves thrown errors: if `fn` throws, the error is stored a
209
209
 
210
210
  **Reducer pattern**: The `prev` parameter enables state accumulation across recomputations without writable state.
211
211
 
212
- **Watched lifecycle**: An optional `watched` callback in options provides lazy external invalidation. The callback receives an `invalidate` function and is invoked on first sink attachment. Calling `invalidate()` marks the node `FLAG_DIRTY`, propagates to sinks, and flushes triggering re-evaluation of the memo's `fn` without changing any tracked dependency. The returned cleanup is stored as `node.stop` and called when the last sink detaches. This enables patterns like DOM observation (MutationObserver) where the memo re-derives its value in response to external events.
212
+ **Watched lifecycle**: An optional `watched` callback in options provides lazy external invalidation. The callback receives an `invalidate` function and is invoked on first sink attachment. Calling `invalidate()` calls `propagate(node)` on the memo itself, which marks it `FLAG_DIRTY` and propagates `FLAG_CHECK` to downstream sinks, then flushes. During flush, downstream effects verify the memo via `refresh()` — if the memo's `equals` function determines the recomputed value is unchanged, the effect is cleaned without running. The returned cleanup is stored as `node.stop` and called when the last sink detaches. This enables patterns like DOM observation (MutationObserver) where a memo re-derives its value in response to external events, with the `equals` check respected at every level of the graph.
213
213
 
214
214
  ### Task (`src/nodes/task.ts`)
215
215
 
@@ -221,7 +221,7 @@ During dependency tracking, only the synchronous preamble of `fn` is tracked (be
221
221
 
222
222
  `isPending()` returns `true` while a computation is in flight. `abort()` cancels the current computation manually. Errors are preserved like Memo, but old values are retained on errors (the last successful result remains accessible).
223
223
 
224
- **Watched lifecycle**: Same pattern as Memo — an optional `watched` callback receives `invalidate` and is invoked on first sink attachment. Calling `invalidate()` marks the node dirty and triggers re-execution, which aborts any in-flight computation via the existing `AbortController` mechanism before starting a new one.
224
+ **Watched lifecycle**: Same pattern as Memo — an optional `watched` callback receives `invalidate` and is invoked on first sink attachment. Calling `invalidate()` calls `propagate(node)` on the task itself, which marks it dirty, aborts any in-flight computation eagerly via the `AbortController`, and propagates `FLAG_CHECK` to downstream sinks. Effects only re-run if the task's resolved value actually changes.
225
225
 
226
226
  ### Effect (`src/nodes/effect.ts`)
227
227
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.4
4
+
5
+ ### Fixed
6
+
7
+ - **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.
8
+
9
+ ### Changed
10
+
11
+ - **`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., `CHECK` → `DIRTY`) without re-enqueuing.
12
+ - **`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.
13
+ - **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.
14
+
3
15
  ## 0.18.3
4
16
 
5
17
  ### Added
package/CLAUDE.md CHANGED
@@ -126,7 +126,7 @@ The generic constraint `T extends {}` is crucial - it excludes `null` and `undef
126
126
 
127
127
  Computed signals implement smart memoization:
128
128
  - **Dependency Tracking**: Automatically tracks which signals are accessed during computation via `link()`
129
- - **Stale Detection**: Flag-based dirty checking (CLEAN, CHECK, DIRTY) — only recalculates when dependencies actually change
129
+ - **Stale Detection**: Flag-based dirty checking (CLEAN, CHECK, DIRTY) — only recalculates when dependencies actually change. The `equals` check is respected at every graph level: when a memo recomputes to the same value, downstream memos *and* effects are cleaned without running.
130
130
  - **Async Support**: `createTask` handles Promise-based computations with automatic AbortController cancellation
131
131
  - **Error Handling**: Preserves error states and prevents cascade failures
132
132
  - **Reducer Capabilities**: Access to previous value enables state accumulation and transitions
@@ -274,7 +274,7 @@ const processed = todoList
274
274
  **Memo (`createMemo`)**:
275
275
  - Synchronous derived computations with memoization
276
276
  - Reducer pattern with previous value access
277
- - Optional `watched(invalidate)` callback for lazy external invalidation (e.g., MutationObserver)
277
+ - Optional `watched(invalidate)` callback for lazy external invalidation (e.g., MutationObserver). Calling `invalidate()` marks the memo dirty and propagates `FLAG_CHECK` to sinks — effects only re-run if the memo's `equals` check determines the value actually changed.
278
278
 
279
279
  ```typescript
280
280
  const doubled = createMemo(() => count.get() * 2)
@@ -295,7 +295,7 @@ const runningTotal = createMemo(prev => prev + currentValue.get(), { value: 0 })
295
295
 
296
296
  **Task (`createTask`)**:
297
297
  - Async computations with automatic cancellation
298
- - Optional `watched(invalidate)` callback for lazy external invalidation
298
+ - Optional `watched(invalidate)` callback for lazy external invalidation. Calling `invalidate()` eagerly aborts any in-flight computation and propagates `FLAG_CHECK` to sinks.
299
299
 
300
300
  ```typescript
301
301
  const userData = createTask(async (prev, abort) => {
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cause & Effect
2
2
 
3
- Version 0.18.3
3
+ Version 0.18.4
4
4
 
5
5
  **Cause & Effect** is a reactive state management primitives library for TypeScript. It provides the foundational building blocks for managing complex, dynamic, composite, and asynchronous state — correctly and performantly — in a unified signal graph.
6
6
 
package/index.dev.js CHANGED
@@ -199,10 +199,12 @@ function propagate(node, newFlag = FLAG_DIRTY) {
199
199
  for (let e = node.sinks;e; e = e.nextSink)
200
200
  propagate(e.sink, FLAG_CHECK);
201
201
  } else {
202
- if (flags & FLAG_DIRTY)
202
+ if ((flags & (FLAG_DIRTY | FLAG_CHECK)) >= newFlag)
203
203
  return;
204
- node.flags = FLAG_DIRTY;
205
- queuedEffects.push(node);
204
+ const wasQueued = flags & (FLAG_DIRTY | FLAG_CHECK);
205
+ node.flags = newFlag;
206
+ if (!wasQueued)
207
+ queuedEffects.push(node);
206
208
  }
207
209
  }
208
210
  function setState(node, next) {
@@ -354,7 +356,7 @@ function flush() {
354
356
  try {
355
357
  for (let i = 0;i < queuedEffects.length; i++) {
356
358
  const effect = queuedEffects[i];
357
- if (effect.flags & FLAG_DIRTY)
359
+ if (effect.flags & (FLAG_DIRTY | FLAG_CHECK))
358
360
  refresh(effect);
359
361
  }
360
362
  queuedEffects.length = 0;
@@ -797,9 +799,7 @@ function createMemo(fn, options) {
797
799
  if (activeSink) {
798
800
  if (!node.sinks)
799
801
  node.stop = watched(() => {
800
- node.flags |= FLAG_DIRTY;
801
- for (let e = node.sinks;e; e = e.nextSink)
802
- propagate(e.sink);
802
+ propagate(node);
803
803
  if (batchDepth === 0)
804
804
  flush();
805
805
  });
@@ -848,9 +848,7 @@ function createTask(fn, options) {
848
848
  if (activeSink) {
849
849
  if (!node.sinks)
850
850
  node.stop = watched(() => {
851
- node.flags |= FLAG_DIRTY;
852
- for (let e = node.sinks;e; e = e.nextSink)
853
- propagate(e.sink);
851
+ propagate(node);
854
852
  if (batchDepth === 0)
855
853
  flush();
856
854
  });
package/index.js CHANGED
@@ -1 +1 @@
1
- function g($){return typeof $==="function"}function i($){return g($)&&$.constructor.name==="AsyncFunction"}function z$($){return g($)&&$.constructor.name!=="AsyncFunction"}function A($,J){return Object.prototype.toString.call($)===`[object ${J}]`}function L($){return A($,"Object")}function A$($,J=(z)=>z!=null){return Array.isArray($)&&$.every(J)}function m$($){return typeof $==="string"?`"${$}"`:!!$&&typeof $==="object"?JSON.stringify($):String($)}class X$ extends Error{constructor($){super(`[${$}] Circular dependency detected`);this.name="CircularDependencyError"}}class Y$ extends TypeError{constructor($){super(`[${$}] Signal value cannot be null or undefined`);this.name="NullishSignalValueError"}}class o extends Error{constructor($){super(`[${$}] Signal value is unset`);this.name="UnsetSignalValueError"}}class Z$ extends TypeError{constructor($,J){super(`[${$}] Signal value ${m$(J)} is invalid`);this.name="InvalidSignalValueError"}}class b$ extends TypeError{constructor($,J){super(`[${$}] Callback ${m$(J)} is invalid`);this.name="InvalidCallbackError"}}class P$ extends Error{constructor($){super(`[${$}] Signal is read-only`);this.name="ReadonlySignalError"}}class D$ extends Error{constructor($){super(`[${$}] Active owner is required`);this.name="RequiredOwnerError"}}class e extends Error{constructor($,J,z){super(`[${$}] Could not add key "${J}"${z?` with value ${JSON.stringify(z)}`:""} because it already exists`);this.name="DuplicateKeyError"}}function K($,J,z){if(J==null)throw new Y$($);if(z&&!z(J))throw new Z$($,J)}function j$($,J){if(J==null)throw new o($)}function T($,J,z=g){if(!z(J))throw new b$($,J)}var c="State",u="Memo",d="Task",t="Sensor",E="List",l="Collection",r="Store",s="Slot",S=0,C$=1,D=2,M$=4,b=8,N=null,y=null,G$=[],R=0,_$=!1,k=($,J)=>$===J,L$=($,J)=>!1;function g$($,J){let z=J.sourcesTail;if(z){let X=J.sources;while(X){if(X===$)return!0;if(X===z)break;X=X.nextSource}}return!1}function F($,J){let z=J.sourcesTail;if(z?.source===$)return;let X=null,W=J.flags&M$;if(W){if(X=z?z.nextSource:J.sources,X?.source===$){J.sourcesTail=X;return}}let H=$.sinksTail;if(H?.sink===J&&(!W||g$(H,J)))return;let P={source:$,sink:J,nextSource:X,prevSink:H,nextSink:null};if(J.sourcesTail=$.sinksTail=P,z)z.nextSource=P;else J.sources=P;if(H)H.nextSink=P;else $.sinks=P}function k$($){let{source:J,nextSource:z,nextSink:X,prevSink:W}=$;if(X)X.prevSink=W;else J.sinksTail=W;if(W)W.nextSink=X;else J.sinks=X;if(!J.sinks){if(J.stop)J.stop(),J.stop=void 0;if("sources"in J&&J.sources){let H=J;H.sourcesTail=null,W$(H)}}return z}function W$($){let J=$.sourcesTail,z=J?J.nextSource:$.sources;while(z)z=k$(z);if(J)J.nextSource=null;else $.sources=null}function x($,J=D){let z=$.flags;if("sinks"in $){if((z&(D|C$))>=J)return;if($.flags=z|J,"controller"in $&&$.controller)$.controller.abort(),$.controller=void 0;for(let X=$.sinks;X;X=X.nextSink)x(X.sink,C$)}else{if(z&D)return;$.flags=D,G$.push($)}}function U$($,J){if($.equals($.value,J))return;$.value=J;for(let z=$.sinks;z;z=z.nextSink)x(z.sink);if(R===0)w()}function $$($,J){if(!$.cleanup)$.cleanup=J;else if(Array.isArray($.cleanup))$.cleanup.push(J);else $.cleanup=[$.cleanup,J]}function K$($){if(!$.cleanup)return;if(Array.isArray($.cleanup))for(let J=0;J<$.cleanup.length;J++)$.cleanup[J]();else $.cleanup();$.cleanup=null}function v$($){let J=N;N=$,$.sourcesTail=null,$.flags=M$;let z=!1;try{let X=$.fn($.value);if($.error||!$.equals(X,$.value))$.value=X,$.error=void 0,z=!0}catch(X){z=!0,$.error=X instanceof Error?X:Error(String(X))}finally{N=J,W$($)}if(z){for(let X=$.sinks;X;X=X.nextSink)if(X.sink.flags&C$)X.sink.flags|=D}$.flags=S}function c$($){$.controller?.abort();let J=new AbortController;$.controller=J,$.error=void 0;let z=N;N=$,$.sourcesTail=null,$.flags=M$;let X;try{X=$.fn($.value,J.signal)}catch(W){$.controller=void 0,$.error=W instanceof Error?W:Error(String(W));return}finally{N=z,W$($)}X.then((W)=>{if(J.signal.aborted)return;if($.controller=void 0,$.error||!$.equals(W,$.value)){$.value=W,$.error=void 0;for(let H=$.sinks;H;H=H.nextSink)x(H.sink);if(R===0)w()}},(W)=>{if(J.signal.aborted)return;$.controller=void 0;let H=W instanceof Error?W:Error(String(W));if(!$.error||H.name!==$.error.name||H.message!==$.error.message){$.error=H;for(let P=$.sinks;P;P=P.nextSink)x(P.sink);if(R===0)w()}}),$.flags=S}function T$($){K$($);let J=N,z=y;N=y=$,$.sourcesTail=null,$.flags=M$;try{let X=$.fn();if(typeof X==="function")$$($,X)}finally{N=J,y=z,W$($)}$.flags=S}function I($){if($.flags&C$)for(let J=$.sources;J;J=J.nextSource){if("fn"in J.source)I(J.source);if($.flags&D)break}if($.flags&M$)throw new X$("controller"in $?d:("value"in $)?u:"Effect");if($.flags&D)if("controller"in $)c$($);else if("value"in $)v$($);else T$($);else $.flags=S}function w(){if(_$)return;_$=!0;try{for(let $=0;$<G$.length;$++){let J=G$[$];if(J.flags&D)I(J)}G$.length=0}finally{_$=!1}}function J$($){R++;try{$()}finally{if(R--,R===0)w()}}function v($){let J=N;N=null;try{return $()}finally{N=J}}function u$($){let J=y,z={cleanup:null};y=z;try{let X=$();if(typeof X==="function")$$(z,X);let W=()=>K$(z);if(J)$$(J,W);return W}finally{y=J}}function p($,J){K(c,$,J?.guard);let z={value:$,sinks:null,sinksTail:null,equals:J?.equals??k,guard:J?.guard};return{[Symbol.toStringTag]:c,get(){if(N)F(z,N);return z.value},set(X){K(c,X,z.guard),U$(z,X)},update(X){T(c,X);let W=X(z.value);K(c,W,z.guard),U$(z,W)}}}function N$($){return A($,c)}function n($,J,z){if(Object.is($,J))return!0;if(typeof $!==typeof J)return!1;if($==null||typeof $!=="object"||J==null||typeof J!=="object")return!1;if(!z)z=new WeakSet;if(z.has($)||z.has(J))throw new X$("isEqual");z.add($),z.add(J);try{let X=Array.isArray($);if(X!==Array.isArray(J))return!1;if(X){let W=$,H=J;if(W.length!==H.length)return!1;for(let P=0;P<W.length;P++)if(!n(W[P],H[P],z))return!1;return!0}if(L($)&&L(J)){let W=Object.keys($),H=Object.keys(J);if(W.length!==H.length)return!1;for(let P of W){if(!(P in J))return!1;if(!n($[P],J[P],z))return!1}return!0}return!1}finally{z.delete($),z.delete(J)}}function O$($,J){if($.length!==J.length)return!1;for(let z=0;z<$.length;z++)if($[z]!==J[z])return!1;return!0}function E$($){let J=0,z=typeof $==="function";return[typeof $==="string"?()=>`${$}${J++}`:z?(X)=>$(X)||String(J++):()=>String(J++),z]}function d$($,J,z,X,W){let H=new WeakSet,P={},M={},C={},U=[],Q=!1,j=new Map;for(let q=0;q<$.length;q++){let Z=z[q];if(Z&&$[q])j.set(Z,$[q])}let G=new Set;for(let q=0;q<J.length;q++){let Z=J[q];if(Z===void 0)continue;let B=W?X(Z):z[q]??X(Z);if(G.has(B))throw new e(E,B,Z);if(U.push(B),G.add(B),!j.has(B))P[B]=Z,Q=!0;else if(!n(j.get(B),Z,H))M[B]=Z,Q=!0}for(let[q]of j)if(!G.has(q))C[q]=null,Q=!0;if(!Q&&!O$(z,U))Q=!0;return{add:P,change:M,remove:C,newKeys:U,changed:Q}}function H$($,J){K(E,$,Array.isArray);let z=new Map,X=[],[W,H]=E$(J?.keyConfig),P=()=>X.map((Z)=>z.get(Z)?.get()).filter((Z)=>Z!==void 0),M={fn:P,value:$,flags:D,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},C=(Z)=>{let B={};for(let V=0;V<Z.length;V++){let m=Z[V];if(m===void 0)continue;let O=X[V];if(!O)O=W(m),X[V]=O;B[O]=m}return B},U=(Z)=>{let B=!1;for(let V in Z.add){let m=Z.add[V];K(`${E} item for key "${V}"`,m),z.set(V,p(m)),B=!0}if(Object.keys(Z.change).length)J$(()=>{for(let V in Z.change){let m=Z.change[V];K(`${E} item for key "${V}"`,m);let O=z.get(V);if(O)O.set(m)}});for(let V in Z.remove){z.delete(V);let m=X.indexOf(V);if(m!==-1)X.splice(m,1);B=!0}if(B)M.flags|=b;return Z.changed},Q=J?.watched,j=Q?()=>{if(N){if(!M.sinks)M.stop=Q();F(M,N)}}:()=>{if(N)F(M,N)},G=C($);for(let Z in G){let B=G[Z];K(`${E} item for key "${Z}"`,B),z.set(Z,p(B))}M.value=$,M.flags=0;let q={[Symbol.toStringTag]:E,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of X){let B=z.get(Z);if(B)yield B}},get length(){return j(),X.length},get(){if(j(),M.sources){if(M.flags){let Z=M.flags&b;if(M.value=v(P),Z){if(M.flags=D,I(M),M.error)throw M.error}else M.flags=S}}else if(I(M),M.error)throw M.error;return M.value},set(Z){let B=M.flags&D?P():M.value,V=d$(B,Z,X,W,H);if(V.changed){X=V.newKeys,U(V),M.flags|=D;for(let m=M.sinks;m;m=m.nextSink)x(m.sink);if(R===0)w()}},update(Z){q.set(Z(q.get()))},at(Z){return z.get(X[Z])},keys(){return j(),X.values()},byKey(Z){return z.get(Z)},keyAt(Z){return X[Z]},indexOfKey(Z){return X.indexOf(Z)},add(Z){let B=W(Z);if(z.has(B))throw new e(E,B,Z);if(!X.includes(B))X.push(B);K(`${E} item for key "${B}"`,Z),z.set(B,p(Z)),M.flags|=D|b;for(let V=M.sinks;V;V=V.nextSink)x(V.sink);if(R===0)w();return B},remove(Z){let B=typeof Z==="number"?X[Z]:Z;if(z.delete(B)){let m=typeof Z==="number"?Z:X.indexOf(B);if(m>=0)X.splice(m,1);M.flags|=D|b;for(let O=M.sinks;O;O=O.nextSink)x(O.sink);if(R===0)w()}},sort(Z){let V=X.map((m)=>[m,z.get(m)?.get()]).sort(g(Z)?(m,O)=>Z(m[1],O[1]):(m,O)=>String(m[1]).localeCompare(String(O[1]))).map(([m])=>m);if(!O$(X,V)){X=V,M.flags|=D;for(let m=M.sinks;m;m=m.nextSink)x(m.sink);if(R===0)w()}},splice(Z,B,...V){let m=X.length,O=Z<0?Math.max(0,m+Z):Math.min(Z,m),f=Math.max(0,Math.min(B??Math.max(0,m-Math.max(0,O)),m-O)),Y={},_={};for(let h=0;h<f;h++){let a=O+h,f$=X[a];if(f$){let y$=z.get(f$);if(y$)_[f$]=y$.get()}}let I$=X.slice(0,O);for(let h of V){let a=W(h);if(z.has(a)&&!(a in _))throw new e(E,a,h);I$.push(a),Y[a]=h}I$.push(...X.slice(O+f));let p$=!!(Object.keys(Y).length||Object.keys(_).length);if(p$){U({add:Y,change:{},remove:_,changed:p$}),X=I$,M.flags|=D;for(let h=M.sinks;h;h=h.nextSink)x(h.sink);if(R===0)w()}return Object.values(_)},deriveCollection(Z){return R$(q,Z)}};return q}function F$($){return A($,E)}function Q$($,J){if(T(u,$,z$),J?.value!==void 0)K(u,J.value,J?.guard);let z={fn:$,value:J?.value,flags:D,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:J?.equals??k,error:void 0,stop:void 0},X=J?.watched,W=X?()=>{if(N){if(!z.sinks)z.stop=X(()=>{z.flags|=D;for(let H=z.sinks;H;H=H.nextSink)x(H.sink);if(R===0)w()});F(z,N)}}:()=>{if(N)F(z,N)};return{[Symbol.toStringTag]:u,get(){if(W(),I(z),z.error)throw z.error;return j$(u,z.value),z.value}}}function S$($){return A($,u)}function B$($,J){if(T(d,$,i),J?.value!==void 0)K(d,J.value,J?.guard);let z={fn:$,value:J?.value,sources:null,sourcesTail:null,sinks:null,sinksTail:null,flags:D,equals:J?.equals??k,controller:void 0,error:void 0,stop:void 0},X=J?.watched,W=X?()=>{if(N){if(!z.sinks)z.stop=X(()=>{z.flags|=D;for(let H=z.sinks;H;H=H.nextSink)x(H.sink);if(R===0)w()});F(z,N)}}:()=>{if(N)F(z,N)};return{[Symbol.toStringTag]:d,get(){if(W(),I(z),z.error)throw z.error;return j$(d,z.value),z.value},isPending(){return!!z.controller},abort(){z.controller?.abort(),z.controller=void 0}}}function h$($){return A($,d)}function R$($,J){T(l,J);let z=i(J),X=new Map,W=[],H=(q)=>{let Z=z?B$(async(B,V)=>{let m=$.byKey(q)?.get();if(m==null)return B;return J(m,V)}):Q$(()=>{let B=$.byKey(q)?.get();if(B==null)return;return J(B)});X.set(q,Z)};function P(q){if(!O$(W,q)){let Z=new Set(W),B=new Set(q);for(let V of W)if(!B.has(V))X.delete(V);for(let V of q)if(!Z.has(V))H(V);W=q,U.flags|=b}}function M(){P(Array.from($.keys()));let q=[];for(let Z of W)try{let B=X.get(Z)?.get();if(B!=null)q.push(B)}catch(B){if(!(B instanceof o))throw B}return q}let U={fn:M,value:[],flags:D,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:(q,Z)=>{if(q.length!==Z.length)return!1;for(let B=0;B<q.length;B++)if(q[B]!==Z[B])return!1;return!0},error:void 0};function Q(){if(U.sources){if(U.flags)if(U.value=v(M),U.flags&b){if(U.flags=D,I(U),U.error)throw U.error}else U.flags=S}else if(U.sinks){if(I(U),U.error)throw U.error}else U.value=v(M)}let j=Array.from(v(()=>$.keys()));for(let q of j)H(q);W=j;let G={[Symbol.toStringTag]:l,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let q of W){let Z=X.get(q);if(Z)yield Z}},get length(){if(N)F(U,N);return Q(),W.length},keys(){if(N)F(U,N);return Q(),W.values()},get(){if(N)F(U,N);return Q(),U.value},at(q){return X.get(W[q])},byKey(q){return X.get(q)},keyAt(q){return W[q]},indexOfKey(q){return W.indexOf(q)},deriveCollection(q){return R$(G,q)}};return G}function t$($,J){let z=J?.value??[];if(z.length)K(l,z,Array.isArray);T(l,$,z$);let X=new Map,W=[],H=new Map,[P,M]=E$(J?.keyConfig),C=(Z)=>H.get(Z)??(M?P(Z):void 0),U=J?.createItem??p;function Q(){let Z=[];for(let B of W)try{let V=X.get(B)?.get();if(V!=null)Z.push(V)}catch(V){if(!(V instanceof o))throw V}return Z}let j={fn:Q,value:z,flags:D,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:L$,error:void 0};for(let Z of z){let B=P(Z);X.set(B,U(Z)),H.set(Z,B),W.push(B)}j.value=z,j.flags=D;function G(){if(N){if(!j.sinks)j.stop=$((Z)=>{let{add:B,change:V,remove:m}=Z;if(!B?.length&&!V?.length&&!m?.length)return;let O=!1;J$(()=>{if(B)for(let f of B){let Y=P(f);if(X.set(Y,U(f)),H.set(f,Y),!W.includes(Y))W.push(Y);O=!0}if(V)for(let f of V){let Y=C(f);if(!Y)continue;let _=X.get(Y);if(_&&N$(_))H.delete(_.get()),_.set(f),H.set(f,Y)}if(m)for(let f of m){let Y=C(f);if(!Y)continue;H.delete(f),X.delete(Y);let _=W.indexOf(Y);if(_!==-1)W.splice(_,1);O=!0}j.flags=D|(O?b:0);for(let f=j.sinks;f;f=f.nextSink)x(f.sink)})});F(j,N)}}let q={[Symbol.toStringTag]:l,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of W){let B=X.get(Z);if(B)yield B}},get length(){return G(),W.length},keys(){return G(),W.values()},get(){if(G(),j.sources){if(j.flags){let Z=j.flags&b;if(j.value=v(Q),Z){if(j.flags=D,I(j),j.error)throw j.error}else j.flags=S}}else if(I(j),j.error)throw j.error;return j.value},at(Z){return X.get(W[Z])},byKey(Z){return X.get(Z)},keyAt(Z){return W[Z]},indexOfKey(Z){return W.indexOf(Z)},deriveCollection(Z){return R$(q,Z)}};return q}function l$($){return A($,l)}function r$($){T("Effect",$);let J={fn:$,flags:D,sources:null,sourcesTail:null,cleanup:null},z=()=>{K$(J),J.fn=void 0,J.flags=S,J.sourcesTail=null,W$(J)};if(y)$$(y,z);return T$(J),z}function s$($,J){if(!y)throw new D$("match");let{ok:z,err:X=console.error,nil:W}=J,H,P=!1,M=Array($.length);for(let U=0;U<$.length;U++)try{M[U]=$[U].get()}catch(Q){if(Q instanceof o){P=!0;continue}if(!H)H=[];H.push(Q instanceof Error?Q:Error(String(Q)))}let C;try{if(P)C=W?.();else if(H)C=X(H);else C=z(M)}catch(U){X([U instanceof Error?U:Error(String(U))])}if(typeof C==="function")return C;if(C instanceof Promise){let U=y,Q=new AbortController;$$(U,()=>Q.abort()),C.then((j)=>{if(!Q.signal.aborted&&typeof j==="function")$$(U,j)}).catch((j)=>{X([j instanceof Error?j:Error(String(j))])})}}function i$($,J){if(T(t,$,z$),J?.value!==void 0)K(t,J.value,J?.guard);let z={value:J?.value,sinks:null,sinksTail:null,equals:J?.equals??k,guard:J?.guard,stop:void 0};return{[Symbol.toStringTag]:t,get(){if(N){if(!z.sinks)z.stop=$((X)=>{K(t,X,z.guard),U$(z,X)});F(z,N)}return j$(t,z.value),z.value}}}function o$($){return A($,t)}function n$($,J){let z=L($)||Array.isArray($),X=L(J)||Array.isArray(J);if(!z||!X){let j=!Object.is($,J);return{changed:j,add:j&&X?J:{},change:{},remove:j&&z?$:{}}}let W=new WeakSet,H={},P={},M={},C=!1,U=Object.keys($),Q=Object.keys(J);for(let j of Q)if(j in $){if(!n($[j],J[j],W))P[j]=J[j],C=!0}else H[j]=J[j],C=!0;for(let j of U)if(!(j in J))M[j]=void 0,C=!0;return{add:H,change:P,remove:M,changed:C}}function V$($,J){K(r,$,L);let z=new Map,X=(Q,j)=>{if(K(`${r} for key "${Q}"`,j),Array.isArray(j))z.set(Q,H$(j));else if(L(j))z.set(Q,V$(j));else z.set(Q,p(j))},W=()=>{let Q={};return z.forEach((j,G)=>{Q[G]=j.get()}),Q},H={fn:W,value:$,flags:D,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},P=(Q)=>{let j=!1;for(let G in Q.add)X(G,Q.add[G]),j=!0;if(Object.keys(Q.change).length)J$(()=>{for(let G in Q.change){let q=Q.change[G];K(`${r} for key "${G}"`,q);let Z=z.get(G);if(Z)if(L(q)!==x$(Z))X(G,q),j=!0;else Z.set(q)}});for(let G in Q.remove)z.delete(G),j=!0;if(j)H.flags|=b;return Q.changed},M=J?.watched,C=M?()=>{if(N){if(!H.sinks)H.stop=M();F(H,N)}}:()=>{if(N)F(H,N)};for(let Q of Object.keys($))X(Q,$[Q]);let U={[Symbol.toStringTag]:r,[Symbol.isConcatSpreadable]:!1,*[Symbol.iterator](){for(let Q of Array.from(z.keys())){let j=z.get(Q);if(j)yield[Q,j]}},keys(){return C(),z.keys()},byKey(Q){return z.get(Q)},get(){if(C(),H.sources){if(H.flags){let Q=H.flags&b;if(H.value=v(W),Q){if(H.flags=D,I(H),H.error)throw H.error}else H.flags=S}}else if(I(H),H.error)throw H.error;return H.value},set(Q){let j=H.flags&D?W():H.value,G=n$(j,Q);if(P(G)){H.flags|=D;for(let q=H.sinks;q;q=q.nextSink)x(q.sink);if(R===0)w()}},update(Q){U.set(Q(U.get()))},add(Q,j){if(z.has(Q))throw new e(r,Q,j);X(Q,j),H.flags|=D|b;for(let G=H.sinks;G;G=G.nextSink)x(G.sink);if(R===0)w();return Q},remove(Q){if(z.delete(Q)){H.flags|=D|b;for(let G=H.sinks;G;G=G.nextSink)x(G.sink);if(R===0)w()}}};return new Proxy(U,{get(Q,j){if(j in Q)return Reflect.get(Q,j);if(typeof j!=="symbol")return Q.byKey(j)},has(Q,j){if(j in Q)return!0;return Q.byKey(String(j))!==void 0},ownKeys(Q){return Array.from(Q.keys())},getOwnPropertyDescriptor(Q,j){if(j in Q)return Reflect.getOwnPropertyDescriptor(Q,j);if(typeof j==="symbol")return;let G=Q.byKey(String(j));return G?{enumerable:!0,configurable:!0,writable:!0,value:G}:void 0}})}function x$($){return A($,r)}function a$($,J){return i($)?B$($,J):Q$($,J)}function e$($){if(q$($))return $;if($==null)throw new Z$("createSignal",$);if(i($))return B$($);if(g($))return Q$($);if(A$($))return H$($);if(L($))return V$($);return p($)}function $J($){if(w$($))return $;if($==null||g($)||q$($))throw new Z$("createMutableSignal",$);if(A$($))return H$($);if(L($))return V$($);return p($)}function JJ($){return S$($)||h$($)}function q$($){let J=[c,u,d,t,s,E,l,r],z=Object.prototype.toString.call($).slice(8,-1);return J.includes(z)}function w$($){return N$($)||x$($)||F$($)}function zJ($,J){K(s,$,q$);let z=$,X=J?.guard,W={fn:()=>z.get(),value:void 0,flags:D,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:J?.equals??k,error:void 0},H=()=>{if(N)F(W,N);if(I(W),W.error)throw W.error;return W.value},P=(C)=>{if(!w$(z))throw new P$(s);K(s,C,X),z.set(C)},M=(C)=>{K(s,C,q$),z=C,W.flags|=D;for(let U=W.sinks;U;U=U.nextSink)x(U.sink);if(R===0)w()};return{[Symbol.toStringTag]:s,configurable:!0,enumerable:!0,get:H,set:P,replace:M,current:()=>z}}function XJ($){return A($,s)}export{m$ as valueString,v as untrack,s$ as match,h$ as isTask,x$ as isStore,N$ as isState,XJ as isSlot,q$ as isSignal,o$ as isSensor,L as isRecord,A as isObjectOfType,w$ as isMutableSignal,S$ as isMemo,F$ as isList,g as isFunction,n as isEqual,JJ as isComputed,l$ as isCollection,i as isAsyncFunction,B$ as createTask,V$ as createStore,p as createState,zJ as createSlot,e$ as createSignal,i$ as createSensor,u$ as createScope,$J as createMutableSignal,Q$ as createMemo,H$ as createList,r$ as createEffect,a$ as createComputed,t$ as createCollection,J$ as batch,o as UnsetSignalValueError,L$ as SKIP_EQUALITY,D$ as RequiredOwnerError,P$ as ReadonlySignalError,Y$ as NullishSignalValueError,Z$ as InvalidSignalValueError,b$ as InvalidCallbackError,X$ as CircularDependencyError};
1
+ function g($){return typeof $==="function"}function i($){return g($)&&$.constructor.name==="AsyncFunction"}function X$($){return g($)&&$.constructor.name!=="AsyncFunction"}function Y($,J){return Object.prototype.toString.call($)===`[object ${J}]`}function L($){return Y($,"Object")}function Y$($,J=(z)=>z!=null){return Array.isArray($)&&$.every(J)}function D$($){return typeof $==="string"?`"${$}"`:!!$&&typeof $==="object"?JSON.stringify($):String($)}class Z$ extends Error{constructor($){super(`[${$}] Circular dependency detected`);this.name="CircularDependencyError"}}class A$ extends TypeError{constructor($){super(`[${$}] Signal value cannot be null or undefined`);this.name="NullishSignalValueError"}}class o extends Error{constructor($){super(`[${$}] Signal value is unset`);this.name="UnsetSignalValueError"}}class j$ extends TypeError{constructor($,J){super(`[${$}] Signal value ${D$(J)} is invalid`);this.name="InvalidSignalValueError"}}class b$ extends TypeError{constructor($,J){super(`[${$}] Callback ${D$(J)} is invalid`);this.name="InvalidCallbackError"}}class P$ extends Error{constructor($){super(`[${$}] Signal is read-only`);this.name="ReadonlySignalError"}}class G$ extends Error{constructor($){super(`[${$}] Active owner is required`);this.name="RequiredOwnerError"}}class e extends Error{constructor($,J,z){super(`[${$}] Could not add key "${J}"${z?` with value ${JSON.stringify(z)}`:""} because it already exists`);this.name="DuplicateKeyError"}}function C($,J,z){if(J==null)throw new A$($);if(z&&!z(J))throw new j$($,J)}function W$($,J){if(J==null)throw new o($)}function T($,J,z=g){if(!z(J))throw new b$($,J)}var c="State",u="Memo",d="Task",t="Sensor",E="List",l="Collection",r="Store",s="Slot",S=0,$$=1,G=2,U$=4,b=8,N=null,y=null,O$=[],x=0,_$=!1,k=($,J)=>$===J,L$=($,J)=>!1;function g$($,J){let z=J.sourcesTail;if(z){let X=J.sources;while(X){if(X===$)return!0;if(X===z)break;X=X.nextSource}}return!1}function R($,J){let z=J.sourcesTail;if(z?.source===$)return;let X=null,W=J.flags&U$;if(W){if(X=z?z.nextSource:J.sources,X?.source===$){J.sourcesTail=X;return}}let Q=$.sinksTail;if(Q?.sink===J&&(!W||g$(Q,J)))return;let D={source:$,sink:J,nextSource:X,prevSink:Q,nextSink:null};if(J.sourcesTail=$.sinksTail=D,z)z.nextSource=D;else J.sources=D;if(Q)Q.nextSink=D;else $.sinks=D}function k$($){let{source:J,nextSource:z,nextSink:X,prevSink:W}=$;if(X)X.prevSink=W;else J.sinksTail=W;if(W)W.nextSink=X;else J.sinks=X;if(!J.sinks){if(J.stop)J.stop(),J.stop=void 0;if("sources"in J&&J.sources){let Q=J;Q.sourcesTail=null,H$(Q)}}return z}function H$($){let J=$.sourcesTail,z=J?J.nextSource:$.sources;while(z)z=k$(z);if(J)J.nextSource=null;else $.sources=null}function F($,J=G){let z=$.flags;if("sinks"in $){if((z&(G|$$))>=J)return;if($.flags=z|J,"controller"in $&&$.controller)$.controller.abort(),$.controller=void 0;for(let X=$.sinks;X;X=X.nextSink)F(X.sink,$$)}else{if((z&(G|$$))>=J)return;let X=z&(G|$$);if($.flags=J,!X)O$.push($)}}function N$($,J){if($.equals($.value,J))return;$.value=J;for(let z=$.sinks;z;z=z.nextSink)F(z.sink);if(x===0)I()}function J$($,J){if(!$.cleanup)$.cleanup=J;else if(Array.isArray($.cleanup))$.cleanup.push(J);else $.cleanup=[$.cleanup,J]}function C$($){if(!$.cleanup)return;if(Array.isArray($.cleanup))for(let J=0;J<$.cleanup.length;J++)$.cleanup[J]();else $.cleanup();$.cleanup=null}function v$($){let J=N;N=$,$.sourcesTail=null,$.flags=U$;let z=!1;try{let X=$.fn($.value);if($.error||!$.equals(X,$.value))$.value=X,$.error=void 0,z=!0}catch(X){z=!0,$.error=X instanceof Error?X:Error(String(X))}finally{N=J,H$($)}if(z){for(let X=$.sinks;X;X=X.nextSink)if(X.sink.flags&$$)X.sink.flags|=G}$.flags=S}function c$($){$.controller?.abort();let J=new AbortController;$.controller=J,$.error=void 0;let z=N;N=$,$.sourcesTail=null,$.flags=U$;let X;try{X=$.fn($.value,J.signal)}catch(W){$.controller=void 0,$.error=W instanceof Error?W:Error(String(W));return}finally{N=z,H$($)}X.then((W)=>{if(J.signal.aborted)return;if($.controller=void 0,$.error||!$.equals(W,$.value)){$.value=W,$.error=void 0;for(let Q=$.sinks;Q;Q=Q.nextSink)F(Q.sink);if(x===0)I()}},(W)=>{if(J.signal.aborted)return;$.controller=void 0;let Q=W instanceof Error?W:Error(String(W));if(!$.error||Q.name!==$.error.name||Q.message!==$.error.message){$.error=Q;for(let D=$.sinks;D;D=D.nextSink)F(D.sink);if(x===0)I()}}),$.flags=S}function T$($){C$($);let J=N,z=y;N=y=$,$.sourcesTail=null,$.flags=U$;try{let X=$.fn();if(typeof X==="function")J$($,X)}finally{N=J,y=z,H$($)}$.flags=S}function w($){if($.flags&$$)for(let J=$.sources;J;J=J.nextSource){if("fn"in J.source)w(J.source);if($.flags&G)break}if($.flags&U$)throw new Z$("controller"in $?d:("value"in $)?u:"Effect");if($.flags&G)if("controller"in $)c$($);else if("value"in $)v$($);else T$($);else $.flags=S}function I(){if(_$)return;_$=!0;try{for(let $=0;$<O$.length;$++){let J=O$[$];if(J.flags&(G|$$))w(J)}O$.length=0}finally{_$=!1}}function z$($){x++;try{$()}finally{if(x--,x===0)I()}}function v($){let J=N;N=null;try{return $()}finally{N=J}}function u$($){let J=y,z={cleanup:null};y=z;try{let X=$();if(typeof X==="function")J$(z,X);let W=()=>C$(z);if(J)J$(J,W);return W}finally{y=J}}function p($,J){C(c,$,J?.guard);let z={value:$,sinks:null,sinksTail:null,equals:J?.equals??k,guard:J?.guard};return{[Symbol.toStringTag]:c,get(){if(N)R(z,N);return z.value},set(X){C(c,X,z.guard),N$(z,X)},update(X){T(c,X);let W=X(z.value);C(c,W,z.guard),N$(z,W)}}}function V$($){return Y($,c)}function n($,J,z){if(Object.is($,J))return!0;if(typeof $!==typeof J)return!1;if($==null||typeof $!=="object"||J==null||typeof J!=="object")return!1;if(!z)z=new WeakSet;if(z.has($)||z.has(J))throw new Z$("isEqual");z.add($),z.add(J);try{let X=Array.isArray($);if(X!==Array.isArray(J))return!1;if(X){let W=$,Q=J;if(W.length!==Q.length)return!1;for(let D=0;D<W.length;D++)if(!n(W[D],Q[D],z))return!1;return!0}if(L($)&&L(J)){let W=Object.keys($),Q=Object.keys(J);if(W.length!==Q.length)return!1;for(let D of W){if(!(D in J))return!1;if(!n($[D],J[D],z))return!1}return!0}return!1}finally{z.delete($),z.delete(J)}}function K$($,J){if($.length!==J.length)return!1;for(let z=0;z<$.length;z++)if($[z]!==J[z])return!1;return!0}function E$($){let J=0,z=typeof $==="function";return[typeof $==="string"?()=>`${$}${J++}`:z?(X)=>$(X)||String(J++):()=>String(J++),z]}function d$($,J,z,X,W){let Q=new WeakSet,D={},M={},O={},U=[],H=!1,j=new Map;for(let q=0;q<$.length;q++){let Z=z[q];if(Z&&$[q])j.set(Z,$[q])}let P=new Set;for(let q=0;q<J.length;q++){let Z=J[q];if(Z===void 0)continue;let B=W?X(Z):z[q]??X(Z);if(P.has(B))throw new e(E,B,Z);if(U.push(B),P.add(B),!j.has(B))D[B]=Z,H=!0;else if(!n(j.get(B),Z,Q))M[B]=Z,H=!0}for(let[q]of j)if(!P.has(q))O[q]=null,H=!0;if(!H&&!K$(z,U))H=!0;return{add:D,change:M,remove:O,newKeys:U,changed:H}}function Q$($,J){C(E,$,Array.isArray);let z=new Map,X=[],[W,Q]=E$(J?.keyConfig),D=()=>X.map((Z)=>z.get(Z)?.get()).filter((Z)=>Z!==void 0),M={fn:D,value:$,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},O=(Z)=>{let B={};for(let V=0;V<Z.length;V++){let m=Z[V];if(m===void 0)continue;let K=X[V];if(!K)K=W(m),X[V]=K;B[K]=m}return B},U=(Z)=>{let B=!1;for(let V in Z.add){let m=Z.add[V];C(`${E} item for key "${V}"`,m),z.set(V,p(m)),B=!0}if(Object.keys(Z.change).length)z$(()=>{for(let V in Z.change){let m=Z.change[V];C(`${E} item for key "${V}"`,m);let K=z.get(V);if(K)K.set(m)}});for(let V in Z.remove){z.delete(V);let m=X.indexOf(V);if(m!==-1)X.splice(m,1);B=!0}if(B)M.flags|=b;return Z.changed},H=J?.watched,j=H?()=>{if(N){if(!M.sinks)M.stop=H();R(M,N)}}:()=>{if(N)R(M,N)},P=O($);for(let Z in P){let B=P[Z];C(`${E} item for key "${Z}"`,B),z.set(Z,p(B))}M.value=$,M.flags=0;let q={[Symbol.toStringTag]:E,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of X){let B=z.get(Z);if(B)yield B}},get length(){return j(),X.length},get(){if(j(),M.sources){if(M.flags){let Z=M.flags&b;if(M.value=v(D),Z){if(M.flags=G,w(M),M.error)throw M.error}else M.flags=S}}else if(w(M),M.error)throw M.error;return M.value},set(Z){let B=M.flags&G?D():M.value,V=d$(B,Z,X,W,Q);if(V.changed){X=V.newKeys,U(V),M.flags|=G;for(let m=M.sinks;m;m=m.nextSink)F(m.sink);if(x===0)I()}},update(Z){q.set(Z(q.get()))},at(Z){return z.get(X[Z])},keys(){return j(),X.values()},byKey(Z){return z.get(Z)},keyAt(Z){return X[Z]},indexOfKey(Z){return X.indexOf(Z)},add(Z){let B=W(Z);if(z.has(B))throw new e(E,B,Z);if(!X.includes(B))X.push(B);C(`${E} item for key "${B}"`,Z),z.set(B,p(Z)),M.flags|=G|b;for(let V=M.sinks;V;V=V.nextSink)F(V.sink);if(x===0)I();return B},remove(Z){let B=typeof Z==="number"?X[Z]:Z;if(z.delete(B)){let m=typeof Z==="number"?Z:X.indexOf(B);if(m>=0)X.splice(m,1);M.flags|=G|b;for(let K=M.sinks;K;K=K.nextSink)F(K.sink);if(x===0)I()}},sort(Z){let V=X.map((m)=>[m,z.get(m)?.get()]).sort(g(Z)?(m,K)=>Z(m[1],K[1]):(m,K)=>String(m[1]).localeCompare(String(K[1]))).map(([m])=>m);if(!K$(X,V)){X=V,M.flags|=G;for(let m=M.sinks;m;m=m.nextSink)F(m.sink);if(x===0)I()}},splice(Z,B,...V){let m=X.length,K=Z<0?Math.max(0,m+Z):Math.min(Z,m),f=Math.max(0,Math.min(B??Math.max(0,m-Math.max(0,K)),m-K)),A={},_={};for(let h=0;h<f;h++){let a=K+h,f$=X[a];if(f$){let y$=z.get(f$);if(y$)_[f$]=y$.get()}}let w$=X.slice(0,K);for(let h of V){let a=W(h);if(z.has(a)&&!(a in _))throw new e(E,a,h);w$.push(a),A[a]=h}w$.push(...X.slice(K+f));let p$=!!(Object.keys(A).length||Object.keys(_).length);if(p$){U({add:A,change:{},remove:_,changed:p$}),X=w$,M.flags|=G;for(let h=M.sinks;h;h=h.nextSink)F(h.sink);if(x===0)I()}return Object.values(_)},deriveCollection(Z){return x$(q,Z)}};return q}function R$($){return Y($,E)}function B$($,J){if(T(u,$,X$),J?.value!==void 0)C(u,J.value,J?.guard);let z={fn:$,value:J?.value,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:J?.equals??k,error:void 0,stop:void 0},X=J?.watched,W=X?()=>{if(N){if(!z.sinks)z.stop=X(()=>{if(F(z),x===0)I()});R(z,N)}}:()=>{if(N)R(z,N)};return{[Symbol.toStringTag]:u,get(){if(W(),w(z),z.error)throw z.error;return W$(u,z.value),z.value}}}function S$($){return Y($,u)}function q$($,J){if(T(d,$,i),J?.value!==void 0)C(d,J.value,J?.guard);let z={fn:$,value:J?.value,sources:null,sourcesTail:null,sinks:null,sinksTail:null,flags:G,equals:J?.equals??k,controller:void 0,error:void 0,stop:void 0},X=J?.watched,W=X?()=>{if(N){if(!z.sinks)z.stop=X(()=>{if(F(z),x===0)I()});R(z,N)}}:()=>{if(N)R(z,N)};return{[Symbol.toStringTag]:d,get(){if(W(),w(z),z.error)throw z.error;return W$(d,z.value),z.value},isPending(){return!!z.controller},abort(){z.controller?.abort(),z.controller=void 0}}}function h$($){return Y($,d)}function x$($,J){T(l,J);let z=i(J),X=new Map,W=[],Q=(q)=>{let Z=z?q$(async(B,V)=>{let m=$.byKey(q)?.get();if(m==null)return B;return J(m,V)}):B$(()=>{let B=$.byKey(q)?.get();if(B==null)return;return J(B)});X.set(q,Z)};function D(q){if(!K$(W,q)){let Z=new Set(W),B=new Set(q);for(let V of W)if(!B.has(V))X.delete(V);for(let V of q)if(!Z.has(V))Q(V);W=q,U.flags|=b}}function M(){D(Array.from($.keys()));let q=[];for(let Z of W)try{let B=X.get(Z)?.get();if(B!=null)q.push(B)}catch(B){if(!(B instanceof o))throw B}return q}let U={fn:M,value:[],flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:(q,Z)=>{if(q.length!==Z.length)return!1;for(let B=0;B<q.length;B++)if(q[B]!==Z[B])return!1;return!0},error:void 0};function H(){if(U.sources){if(U.flags)if(U.value=v(M),U.flags&b){if(U.flags=G,w(U),U.error)throw U.error}else U.flags=S}else if(U.sinks){if(w(U),U.error)throw U.error}else U.value=v(M)}let j=Array.from(v(()=>$.keys()));for(let q of j)Q(q);W=j;let P={[Symbol.toStringTag]:l,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let q of W){let Z=X.get(q);if(Z)yield Z}},get length(){if(N)R(U,N);return H(),W.length},keys(){if(N)R(U,N);return H(),W.values()},get(){if(N)R(U,N);return H(),U.value},at(q){return X.get(W[q])},byKey(q){return X.get(q)},keyAt(q){return W[q]},indexOfKey(q){return W.indexOf(q)},deriveCollection(q){return x$(P,q)}};return P}function t$($,J){let z=J?.value??[];if(z.length)C(l,z,Array.isArray);T(l,$,X$);let X=new Map,W=[],Q=new Map,[D,M]=E$(J?.keyConfig),O=(Z)=>Q.get(Z)??(M?D(Z):void 0),U=J?.createItem??p;function H(){let Z=[];for(let B of W)try{let V=X.get(B)?.get();if(V!=null)Z.push(V)}catch(V){if(!(V instanceof o))throw V}return Z}let j={fn:H,value:z,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:L$,error:void 0};for(let Z of z){let B=D(Z);X.set(B,U(Z)),Q.set(Z,B),W.push(B)}j.value=z,j.flags=G;function P(){if(N){if(!j.sinks)j.stop=$((Z)=>{let{add:B,change:V,remove:m}=Z;if(!B?.length&&!V?.length&&!m?.length)return;let K=!1;z$(()=>{if(B)for(let f of B){let A=D(f);if(X.set(A,U(f)),Q.set(f,A),!W.includes(A))W.push(A);K=!0}if(V)for(let f of V){let A=O(f);if(!A)continue;let _=X.get(A);if(_&&V$(_))Q.delete(_.get()),_.set(f),Q.set(f,A)}if(m)for(let f of m){let A=O(f);if(!A)continue;Q.delete(f),X.delete(A);let _=W.indexOf(A);if(_!==-1)W.splice(_,1);K=!0}j.flags=G|(K?b:0);for(let f=j.sinks;f;f=f.nextSink)F(f.sink)})});R(j,N)}}let q={[Symbol.toStringTag]:l,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of W){let B=X.get(Z);if(B)yield B}},get length(){return P(),W.length},keys(){return P(),W.values()},get(){if(P(),j.sources){if(j.flags){let Z=j.flags&b;if(j.value=v(H),Z){if(j.flags=G,w(j),j.error)throw j.error}else j.flags=S}}else if(w(j),j.error)throw j.error;return j.value},at(Z){return X.get(W[Z])},byKey(Z){return X.get(Z)},keyAt(Z){return W[Z]},indexOfKey(Z){return W.indexOf(Z)},deriveCollection(Z){return x$(q,Z)}};return q}function l$($){return Y($,l)}function r$($){T("Effect",$);let J={fn:$,flags:G,sources:null,sourcesTail:null,cleanup:null},z=()=>{C$(J),J.fn=void 0,J.flags=S,J.sourcesTail=null,H$(J)};if(y)J$(y,z);return T$(J),z}function s$($,J){if(!y)throw new G$("match");let{ok:z,err:X=console.error,nil:W}=J,Q,D=!1,M=Array($.length);for(let U=0;U<$.length;U++)try{M[U]=$[U].get()}catch(H){if(H instanceof o){D=!0;continue}if(!Q)Q=[];Q.push(H instanceof Error?H:Error(String(H)))}let O;try{if(D)O=W?.();else if(Q)O=X(Q);else O=z(M)}catch(U){X([U instanceof Error?U:Error(String(U))])}if(typeof O==="function")return O;if(O instanceof Promise){let U=y,H=new AbortController;J$(U,()=>H.abort()),O.then((j)=>{if(!H.signal.aborted&&typeof j==="function")J$(U,j)}).catch((j)=>{X([j instanceof Error?j:Error(String(j))])})}}function i$($,J){if(T(t,$,X$),J?.value!==void 0)C(t,J.value,J?.guard);let z={value:J?.value,sinks:null,sinksTail:null,equals:J?.equals??k,guard:J?.guard,stop:void 0};return{[Symbol.toStringTag]:t,get(){if(N){if(!z.sinks)z.stop=$((X)=>{C(t,X,z.guard),N$(z,X)});R(z,N)}return W$(t,z.value),z.value}}}function o$($){return Y($,t)}function n$($,J){let z=L($)||Array.isArray($),X=L(J)||Array.isArray(J);if(!z||!X){let j=!Object.is($,J);return{changed:j,add:j&&X?J:{},change:{},remove:j&&z?$:{}}}let W=new WeakSet,Q={},D={},M={},O=!1,U=Object.keys($),H=Object.keys(J);for(let j of H)if(j in $){if(!n($[j],J[j],W))D[j]=J[j],O=!0}else Q[j]=J[j],O=!0;for(let j of U)if(!(j in J))M[j]=void 0,O=!0;return{add:Q,change:D,remove:M,changed:O}}function m$($,J){C(r,$,L);let z=new Map,X=(H,j)=>{if(C(`${r} for key "${H}"`,j),Array.isArray(j))z.set(H,Q$(j));else if(L(j))z.set(H,m$(j));else z.set(H,p(j))},W=()=>{let H={};return z.forEach((j,P)=>{H[P]=j.get()}),H},Q={fn:W,value:$,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},D=(H)=>{let j=!1;for(let P in H.add)X(P,H.add[P]),j=!0;if(Object.keys(H.change).length)z$(()=>{for(let P in H.change){let q=H.change[P];C(`${r} for key "${P}"`,q);let Z=z.get(P);if(Z)if(L(q)!==F$(Z))X(P,q),j=!0;else Z.set(q)}});for(let P in H.remove)z.delete(P),j=!0;if(j)Q.flags|=b;return H.changed},M=J?.watched,O=M?()=>{if(N){if(!Q.sinks)Q.stop=M();R(Q,N)}}:()=>{if(N)R(Q,N)};for(let H of Object.keys($))X(H,$[H]);let U={[Symbol.toStringTag]:r,[Symbol.isConcatSpreadable]:!1,*[Symbol.iterator](){for(let H of Array.from(z.keys())){let j=z.get(H);if(j)yield[H,j]}},keys(){return O(),z.keys()},byKey(H){return z.get(H)},get(){if(O(),Q.sources){if(Q.flags){let H=Q.flags&b;if(Q.value=v(W),H){if(Q.flags=G,w(Q),Q.error)throw Q.error}else Q.flags=S}}else if(w(Q),Q.error)throw Q.error;return Q.value},set(H){let j=Q.flags&G?W():Q.value,P=n$(j,H);if(D(P)){Q.flags|=G;for(let q=Q.sinks;q;q=q.nextSink)F(q.sink);if(x===0)I()}},update(H){U.set(H(U.get()))},add(H,j){if(z.has(H))throw new e(r,H,j);X(H,j),Q.flags|=G|b;for(let P=Q.sinks;P;P=P.nextSink)F(P.sink);if(x===0)I();return H},remove(H){if(z.delete(H)){Q.flags|=G|b;for(let P=Q.sinks;P;P=P.nextSink)F(P.sink);if(x===0)I()}}};return new Proxy(U,{get(H,j){if(j in H)return Reflect.get(H,j);if(typeof j!=="symbol")return H.byKey(j)},has(H,j){if(j in H)return!0;return H.byKey(String(j))!==void 0},ownKeys(H){return Array.from(H.keys())},getOwnPropertyDescriptor(H,j){if(j in H)return Reflect.getOwnPropertyDescriptor(H,j);if(typeof j==="symbol")return;let P=H.byKey(String(j));return P?{enumerable:!0,configurable:!0,writable:!0,value:P}:void 0}})}function F$($){return Y($,r)}function a$($,J){return i($)?q$($,J):B$($,J)}function e$($){if(M$($))return $;if($==null)throw new j$("createSignal",$);if(i($))return q$($);if(g($))return B$($);if(Y$($))return Q$($);if(L($))return m$($);return p($)}function $J($){if(I$($))return $;if($==null||g($)||M$($))throw new j$("createMutableSignal",$);if(Y$($))return Q$($);if(L($))return m$($);return p($)}function JJ($){return S$($)||h$($)}function M$($){let J=[c,u,d,t,s,E,l,r],z=Object.prototype.toString.call($).slice(8,-1);return J.includes(z)}function I$($){return V$($)||F$($)||R$($)}function zJ($,J){C(s,$,M$);let z=$,X=J?.guard,W={fn:()=>z.get(),value:void 0,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:J?.equals??k,error:void 0},Q=()=>{if(N)R(W,N);if(w(W),W.error)throw W.error;return W.value},D=(O)=>{if(!I$(z))throw new P$(s);C(s,O,X),z.set(O)},M=(O)=>{C(s,O,M$),z=O,W.flags|=G;for(let U=W.sinks;U;U=U.nextSink)F(U.sink);if(x===0)I()};return{[Symbol.toStringTag]:s,configurable:!0,enumerable:!0,get:Q,set:D,replace:M,current:()=>z}}function XJ($){return Y($,s)}export{D$ as valueString,v as untrack,s$ as match,h$ as isTask,F$ as isStore,V$ as isState,XJ as isSlot,M$ as isSignal,o$ as isSensor,L as isRecord,Y as isObjectOfType,I$ as isMutableSignal,S$ as isMemo,R$ as isList,g as isFunction,n as isEqual,JJ as isComputed,l$ as isCollection,i as isAsyncFunction,q$ as createTask,m$ as createStore,p as createState,zJ as createSlot,e$ as createSignal,i$ as createSensor,u$ as createScope,$J as createMutableSignal,B$ as createMemo,Q$ as createList,r$ as createEffect,a$ as createComputed,t$ as createCollection,z$ as batch,o as UnsetSignalValueError,L$ as SKIP_EQUALITY,G$ as RequiredOwnerError,P$ as ReadonlySignalError,A$ as NullishSignalValueError,j$ as InvalidSignalValueError,b$ as InvalidCallbackError,Z$ as CircularDependencyError};
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.18.3
3
+ * @version 0.18.4
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "author": "Esther Brunner",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/graph.ts CHANGED
@@ -292,11 +292,12 @@ function propagate(node: SinkNode, newFlag = FLAG_DIRTY): void {
292
292
  for (let e = node.sinks; e; e = e.nextSink)
293
293
  propagate(e.sink, FLAG_CHECK)
294
294
  } else {
295
- if (flags & FLAG_DIRTY) return
295
+ if ((flags & (FLAG_DIRTY | FLAG_CHECK)) >= newFlag) return
296
296
 
297
297
  // Enqueue effect for later execution
298
- node.flags = FLAG_DIRTY
299
- queuedEffects.push(node as EffectNode)
298
+ const wasQueued = flags & (FLAG_DIRTY | FLAG_CHECK)
299
+ node.flags = newFlag
300
+ if (!wasQueued) queuedEffects.push(node as EffectNode)
300
301
  }
301
302
  }
302
303
 
@@ -471,7 +472,7 @@ function flush(): void {
471
472
  try {
472
473
  for (let i = 0; i < queuedEffects.length; i++) {
473
474
  const effect = queuedEffects[i]
474
- if (effect.flags & FLAG_DIRTY) refresh(effect)
475
+ if (effect.flags & (FLAG_DIRTY | FLAG_CHECK)) refresh(effect)
475
476
  }
476
477
  queuedEffects.length = 0
477
478
  } finally {
@@ -601,6 +602,7 @@ export {
601
602
  createScope,
602
603
  DEFAULT_EQUALITY,
603
604
  SKIP_EQUALITY,
605
+ FLAG_CHECK,
604
606
  FLAG_CLEAN,
605
607
  FLAG_DIRTY,
606
608
  FLAG_RELINK,
package/src/nodes/memo.ts CHANGED
@@ -110,9 +110,7 @@ function createMemo<T extends {}>(
110
110
  if (activeSink) {
111
111
  if (!node.sinks)
112
112
  node.stop = watched(() => {
113
- node.flags |= FLAG_DIRTY
114
- for (let e = node.sinks; e; e = e.nextSink)
115
- propagate(e.sink)
113
+ propagate(node as unknown as SinkNode)
116
114
  if (batchDepth === 0) flush()
117
115
  })
118
116
  link(node, activeSink)
package/src/nodes/task.ts CHANGED
@@ -129,9 +129,7 @@ function createTask<T extends {}>(
129
129
  if (activeSink) {
130
130
  if (!node.sinks)
131
131
  node.stop = watched(() => {
132
- node.flags |= FLAG_DIRTY
133
- for (let e = node.sinks; e; e = e.nextSink)
134
- propagate(e.sink)
132
+ propagate(node as unknown as SinkNode)
135
133
  if (batchDepth === 0) flush()
136
134
  })
137
135
  link(node, activeSink)
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, mock, test } from 'bun:test'
2
2
  import {
3
+ batch,
3
4
  createEffect,
4
5
  createMemo,
5
6
  createScope,
@@ -140,6 +141,209 @@ describe('createEffect', () => {
140
141
  })
141
142
  })
142
143
 
144
+ describe('Watched memo equality', () => {
145
+ test('should skip effect re-run when watched memo recomputes to same value', () => {
146
+ let invalidate!: () => void
147
+ let effectCount = 0
148
+
149
+ // Memo whose computed value does not change on invalidation
150
+ const memo = createMemo(() => 42, {
151
+ value: 42,
152
+ watched: inv => {
153
+ invalidate = inv
154
+ return () => {}
155
+ },
156
+ })
157
+
158
+ const dispose = createScope(() => {
159
+ createEffect(() => {
160
+ void memo.get()
161
+ effectCount++
162
+ })
163
+ })
164
+
165
+ expect(effectCount).toBe(1)
166
+
167
+ // Invalidate — memo recomputes but returns same value (42)
168
+ invalidate()
169
+
170
+ // Because equals(42, 42) is true, the effect should NOT re-run
171
+ expect(effectCount).toBe(1)
172
+
173
+ dispose()
174
+ })
175
+
176
+ test('should re-run effect when watched memo recomputes to different value', () => {
177
+ let invalidate!: () => void
178
+ let effectCount = 0
179
+ let externalValue = 1
180
+
181
+ const memo = createMemo(() => externalValue, {
182
+ value: 0,
183
+ watched: inv => {
184
+ invalidate = inv
185
+ return () => {}
186
+ },
187
+ })
188
+
189
+ let observed = 0
190
+ const dispose = createScope(() => {
191
+ createEffect(() => {
192
+ observed = memo.get()
193
+ effectCount++
194
+ })
195
+ })
196
+
197
+ expect(effectCount).toBe(1)
198
+ expect(observed).toBe(1)
199
+
200
+ // Change external value and invalidate — memo returns a new value
201
+ externalValue = 99
202
+ invalidate()
203
+
204
+ expect(effectCount).toBe(2)
205
+ expect(observed).toBe(99)
206
+
207
+ dispose()
208
+ })
209
+
210
+ test('should respect custom equals to skip effect re-run', () => {
211
+ let invalidate!: () => void
212
+ let effectCount = 0
213
+ let externalValue = 3
214
+
215
+ // Custom equals: treat values as equal when they round to the same integer
216
+ const memo = createMemo(() => externalValue, {
217
+ value: 0,
218
+ equals: (a, b) => Math.floor(a) === Math.floor(b),
219
+ watched: inv => {
220
+ invalidate = inv
221
+ return () => {}
222
+ },
223
+ })
224
+
225
+ const dispose = createScope(() => {
226
+ createEffect(() => {
227
+ void memo.get()
228
+ effectCount++
229
+ })
230
+ })
231
+
232
+ expect(effectCount).toBe(1)
233
+
234
+ // External value changes slightly but rounds to same integer
235
+ externalValue = 3.7
236
+ invalidate()
237
+ expect(effectCount).toBe(1) // equals says same → effect skipped
238
+
239
+ // External value changes to a different integer
240
+ externalValue = 4.1
241
+ invalidate()
242
+ expect(effectCount).toBe(2) // equals says different → effect runs
243
+
244
+ dispose()
245
+ })
246
+
247
+ test('should skip effect re-run through memo chain when watched memo value unchanged', () => {
248
+ let invalidate!: () => void
249
+ let effectCount = 0
250
+
251
+ const watchedMemo = createMemo(() => 42, {
252
+ value: 42,
253
+ watched: inv => {
254
+ invalidate = inv
255
+ return () => {}
256
+ },
257
+ })
258
+
259
+ // Downstream memo that doubles the watched memo value
260
+ const doubled = createMemo(() => watchedMemo.get() * 2)
261
+
262
+ const dispose = createScope(() => {
263
+ createEffect(() => {
264
+ void doubled.get()
265
+ effectCount++
266
+ })
267
+ })
268
+
269
+ expect(effectCount).toBe(1)
270
+
271
+ // Invalidate — watchedMemo recomputes to same value, so doubled
272
+ // should also remain unchanged, and the effect should not re-run
273
+ invalidate()
274
+ expect(effectCount).toBe(1)
275
+
276
+ dispose()
277
+ })
278
+
279
+ test('should skip effect when invalidate is called inside batch and value unchanged', () => {
280
+ let invalidate!: () => void
281
+ let effectCount = 0
282
+
283
+ const memo = createMemo(() => 42, {
284
+ value: 42,
285
+ watched: inv => {
286
+ invalidate = inv
287
+ return () => {}
288
+ },
289
+ })
290
+
291
+ const dispose = createScope(() => {
292
+ createEffect(() => {
293
+ void memo.get()
294
+ effectCount++
295
+ })
296
+ })
297
+
298
+ expect(effectCount).toBe(1)
299
+
300
+ batch(() => {
301
+ invalidate()
302
+ })
303
+
304
+ // Value didn't change so effect should still be at 1
305
+ expect(effectCount).toBe(1)
306
+
307
+ dispose()
308
+ })
309
+
310
+ test('should still run effect for dirty state even when watched memo unchanged', () => {
311
+ let invalidate!: () => void
312
+ let effectCount = 0
313
+ const state = createState(1)
314
+
315
+ const memo = createMemo(() => 42, {
316
+ value: 42,
317
+ watched: inv => {
318
+ invalidate = inv
319
+ return () => {}
320
+ },
321
+ })
322
+
323
+ let observedState = 0
324
+ const dispose = createScope(() => {
325
+ createEffect(() => {
326
+ observedState = state.get()
327
+ void memo.get()
328
+ effectCount++
329
+ })
330
+ })
331
+
332
+ expect(effectCount).toBe(1)
333
+
334
+ // Change the state AND invalidate — effect must run because state changed
335
+ batch(() => {
336
+ state.set(2)
337
+ invalidate()
338
+ })
339
+
340
+ expect(effectCount).toBe(2)
341
+ expect(observedState).toBe(2)
342
+
343
+ dispose()
344
+ })
345
+ })
346
+
143
347
  describe('Input Validation', () => {
144
348
  test('should throw InvalidCallbackError for non-function', () => {
145
349
  // @ts-expect-error - Testing invalid input
@@ -119,6 +119,7 @@ declare const TYPE_COLLECTION = "Collection";
119
119
  declare const TYPE_STORE = "Store";
120
120
  declare const TYPE_SLOT = "Slot";
121
121
  declare const FLAG_CLEAN = 0;
122
+ declare const FLAG_CHECK: number;
122
123
  declare const FLAG_DIRTY: number;
123
124
  declare const FLAG_RELINK: number;
124
125
  declare let activeSink: SinkNode | null;
@@ -217,4 +218,4 @@ declare function untrack<T>(fn: () => T): T;
217
218
  * ```
218
219
  */
219
220
  declare function createScope(fn: () => MaybeCleanup): Cleanup;
220
- export { type Cleanup, type ComputedOptions, type EffectCallback, type EffectNode, type MaybeCleanup, type MemoCallback, type MemoNode, type Scope, type Signal, type SignalOptions, type SinkNode, type StateNode, type TaskCallback, type TaskNode, activeOwner, activeSink, batch, batchDepth, createScope, DEFAULT_EQUALITY, SKIP_EQUALITY, FLAG_CLEAN, FLAG_DIRTY, FLAG_RELINK, flush, link, propagate, refresh, registerCleanup, runCleanup, runEffect, setState, trimSources, TYPE_COLLECTION, TYPE_LIST, TYPE_MEMO, TYPE_SENSOR, TYPE_STATE, TYPE_SLOT, TYPE_STORE, TYPE_TASK, unlink, untrack, };
221
+ export { type Cleanup, type ComputedOptions, type EffectCallback, type EffectNode, type MaybeCleanup, type MemoCallback, type MemoNode, type Scope, type Signal, type SignalOptions, type SinkNode, type StateNode, type TaskCallback, type TaskNode, activeOwner, activeSink, batch, batchDepth, createScope, DEFAULT_EQUALITY, SKIP_EQUALITY, FLAG_CHECK, FLAG_CLEAN, FLAG_DIRTY, FLAG_RELINK, flush, link, propagate, refresh, registerCleanup, runCleanup, runEffect, setState, trimSources, TYPE_COLLECTION, TYPE_LIST, TYPE_MEMO, TYPE_SENSOR, TYPE_STATE, TYPE_SLOT, TYPE_STORE, TYPE_TASK, unlink, untrack, };