@zeix/cause-effect 1.0.1 → 1.0.2
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/.github/copilot-instructions.md +3 -0
- package/ARCHITECTURE.md +26 -0
- package/CHANGELOG.md +6 -0
- package/CLAUDE.md +1 -1
- package/GUIDE.md +2 -5
- package/README.md +4 -3
- package/index.dev.js +14 -0
- package/index.js +1 -1
- package/index.ts +1 -1
- package/package.json +1 -1
- package/skills/cause-effect/references/non-obvious-behaviors.md +22 -0
- package/skills/cause-effect/references/signal-types.md +9 -5
- package/skills/cause-effect-dev/references/non-obvious-behaviors.md +16 -0
- package/src/nodes/list.ts +18 -0
- package/test/list.test.ts +121 -0
- package/types/index.d.ts +1 -1
- package/types/src/graph.d.ts +2 -0
- package/types/src/nodes/collection.d.ts +38 -0
- package/types/src/nodes/effect.d.ts +13 -1
- package/types/src/nodes/list.d.ts +30 -2
- package/types/src/nodes/memo.d.ts +0 -1
- package/types/src/nodes/sensor.d.ts +10 -4
- package/types/src/nodes/store.d.ts +11 -0
- package/types/src/signal.d.ts +6 -0
|
@@ -120,6 +120,9 @@ const users = createList(
|
|
|
120
120
|
[{ id: 'alice', name: 'Alice' }],
|
|
121
121
|
{ keyConfig: u => u.id }
|
|
122
122
|
)
|
|
123
|
+
const key = users.add({ id: 'bob', name: 'Bob' })
|
|
124
|
+
users.replace(key, { id: 'bob', name: 'Bobby' }) // update item, propagates to all subscribers
|
|
125
|
+
users.remove(key)
|
|
123
126
|
|
|
124
127
|
// Memo for synchronous derived values
|
|
125
128
|
const doubled = createMemo(() => count.get() * 2)
|
package/ARCHITECTURE.md
CHANGED
|
@@ -253,6 +253,8 @@ A reactive object where each property is its own signal. Properties are automati
|
|
|
253
253
|
|
|
254
254
|
**Two-path access with `FLAG_RELINK`**: On first `get()`, `refresh()` executes `buildValue()` with `activeSink = storeNode`, establishing edges from each child signal to the store. Subsequent reads use a fast path: `untrack(buildValue)` rebuilds the value without re-establishing edges. Structural mutations (`add`/`remove`/`set` with additions or removals) set `FLAG_DIRTY | FLAG_RELINK` on the node. The next `get()` detects `FLAG_RELINK` and forces a tracked `refresh()` after rebuilding the value, so `recomputeMemo()` links new child signals and trims removed ones without orphaning edges.
|
|
255
255
|
|
|
256
|
+
**Same two-path propagation asymmetry as List**: `store.name.set(v)` (via proxy, equivalent to `byKey('name').set(v)`) propagates only if `childSignal → storeNode` edges exist — which requires `store.get()` to have been called at least once. Effects that subscribe only via `store.keys()` or the iterator, without ever calling `store.get()`, will not be notified of child signal mutations. In practice this is uncommon for Store because typical consumers either call `store.get()` (whole object) or `store.name.get()` (single property) — both of which establish the necessary edges. Effects that use `store.set({...})` rather than `store.name.set(v)` are always safe: `set()` explicitly propagates through `node.sinks` after `applyChanges()`.
|
|
257
|
+
|
|
256
258
|
**Diff-based updates**: `store.set(newObj)` diffs the new object against the current state, applying only the granular changes to child signals. This preserves identity of unchanged child signals and their downstream edges.
|
|
257
259
|
|
|
258
260
|
**Watched lifecycle**: An optional `watched` callback in options provides lazy resource allocation, following the same pattern as Sensor — activated on first sink, cleaned up when the last sink detaches.
|
|
@@ -269,6 +271,30 @@ A reactive array with stable keys and per-item reactivity. Each item becomes a `
|
|
|
269
271
|
|
|
270
272
|
**Diff-based updates**: `list.set(newArray)` uses `diffArrays()` to compute granular additions, changes, and removals. Changed items update their existing `State` signals; structural changes (add/remove) set `FLAG_DIRTY | FLAG_RELINK`.
|
|
271
273
|
|
|
274
|
+
**Two propagation paths — and the asymmetry between them**
|
|
275
|
+
|
|
276
|
+
The List node has two distinct propagation paths. Understanding when each applies is essential for correct usage and for implementing `replace()`:
|
|
277
|
+
|
|
278
|
+
| Path | Trigger | How it works | When it fires |
|
|
279
|
+
|------|---------|-------------|---------------|
|
|
280
|
+
| Structural | `add()`, `remove()`, `sort()`, `splice()`, `set()` | Calls `propagate(e.sink)` directly on `node.sinks` | Always, as soon as any consumer has subscribed via `keys()`, `length`, `get()`, or iterator |
|
|
281
|
+
| Value | `byKey(k).set(v)` on a raw item signal | `setState(itemNode)` → propagates to `itemNode.sinks` → listNode (if linked) → downstream | Only if `recomputeMemo(listNode)` has previously run with that item, establishing the `itemSignal → listNode` edge |
|
|
282
|
+
|
|
283
|
+
The value path has a prerequisite: `list.get()` must have been called at least once after the item was present, so that `buildValue()` executed with `activeSink = listNode` and called `link(itemSignal, listNode)` for each item. This happens automatically for any consumer that reads `list.get()` directly. It does **not** happen for consumers that subscribe only via structural methods (`list.keys()`, `list.length`, the `[Symbol.iterator]`): those call `subscribe()` which links `listNode → effectNode`, but `buildValue()` is never run with `activeSink = listNode`, so no `itemSignal → listNode` edges are established.
|
|
284
|
+
|
|
285
|
+
Consequence: code that calls `byKey(k).set(v)` to update an item will silently fail to notify effects that subscribe via structural accessors. The item signal propagates to nothing because `listNode` is absent from its sinks.
|
|
286
|
+
|
|
287
|
+
**`list.replace(key, value)` — the correct API for item mutation with guaranteed propagation**
|
|
288
|
+
|
|
289
|
+
To close this gap, `List` should expose a `replace(key, value)` method that combines both paths:
|
|
290
|
+
|
|
291
|
+
1. Updates the item signal: `signals.get(key)?.set(value)` — this propagates through item signal edges to any consumers that subscribed directly (e.g. effects reading `byKey(k).get()`).
|
|
292
|
+
2. Explicitly marks the list dirty and propagates through `node.sinks`: `node.flags |= FLAG_DIRTY; for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)` — this notifies structural consumers regardless of edge state.
|
|
293
|
+
|
|
294
|
+
This mirrors what `list.set(newArray)` already does internally: `applyChanges()` calls `signal.set(val)` on changed items, then `list.set()` calls `propagate(e.sink)` on `node.sinks` unconditionally. `replace(key, value)` is a single-item version of the same invariant.
|
|
295
|
+
|
|
296
|
+
`byKey(key).set(value)` remains valid for effects that subscribe directly to the item signal. It is **not** a safe pattern for effects that subscribe to the list via structural accessors. This asymmetry should be documented in the public API.
|
|
297
|
+
|
|
272
298
|
### Collection (`src/nodes/collection.ts`)
|
|
273
299
|
|
|
274
300
|
Collection implements two creation patterns that share the same `Collection<T>` interface:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`List.replace(key, value)` — guaranteed item mutation**: Updates the value of an existing item in place, propagating to all subscribers regardless of how they subscribed. `byKey(key).set(value)` only propagates through `itemSignal → listNode` edges, which are established lazily when `list.get()` is called; effects that subscribed via `list.keys()`, `list.length`, or the iterator never trigger that path and receive no notification. `replace()` closes this gap by also walking `node.sinks` directly — the same structural propagation path used by `add()`, `remove()`, and `sort()`. Signal identity is preserved: the `State<T>` returned by `byKey(key)` is the same object before and after. No-op if the key does not exist or the value is reference-equal to the current value.
|
|
8
|
+
|
|
3
9
|
## 1.0.1
|
|
4
10
|
|
|
5
11
|
### Added
|
package/CLAUDE.md
CHANGED
|
@@ -30,7 +30,7 @@ Scope — owner only (createScope)
|
|
|
30
30
|
|
|
31
31
|
**`T extends {}` excludes `null` and `undefined` at the type level.** Every signal generic uses this constraint. Signals cannot hold nullish values — use a wrapper type or a union with a sentinel if you need to represent absence.
|
|
32
32
|
|
|
33
|
-
**`byKey()`, `at()`, `keyAt()`, and `indexOfKey()` do NOT create graph edges.** They are direct lookups. An effect that only calls `collection.byKey('x')?.get()` will react to value changes of key `'x'` but will *not* re-run if that key is added or removed. To track structural changes, read `get()`, `keys()`, or `length`.
|
|
33
|
+
**`byKey()`, `at()`, `keyAt()`, and `indexOfKey()` do NOT create graph edges.** They are direct lookups. An effect that only calls `collection.byKey('x')?.get()` will react to value changes of key `'x'` but will *not* re-run if that key is added or removed. To track structural changes, read `get()`, `keys()`, or `length`. When updating an existing item's value, use `list.replace(key, value)` rather than `byKey(key).set(value)`. `replace()` guarantees propagation to all subscribers regardless of graph edge state. `byKey(key).set(value)` is only safe for effects that directly call `byKey(key).get()` inside their body.
|
|
34
34
|
|
|
35
35
|
**Conditional reads delay `watched` activation.** Dependencies are tracked only for `.get()` calls that actually execute during a given effect run. If a signal read is inside a branch that doesn't execute (e.g. the `ok` arm of `match()` while a Task is still pending), no edge is created and `watched` does not fire. Read signals eagerly before conditional logic when lifecycle activation matters:
|
|
36
36
|
|
package/GUIDE.md
CHANGED
|
@@ -224,14 +224,11 @@ const todos = createList([
|
|
|
224
224
|
{ id: 't2', text: 'Build app', done: false }
|
|
225
225
|
], { keyConfig: todo => todo.id })
|
|
226
226
|
|
|
227
|
-
// Get a stable reference to a specific item
|
|
228
|
-
const first = todos.byKey('t1')
|
|
229
|
-
|
|
230
227
|
todos.sort((a, b) => a.text.localeCompare(b.text))
|
|
231
|
-
//
|
|
228
|
+
// 'Learn signals' is still at key 't1', regardless of position
|
|
232
229
|
|
|
233
230
|
// Update a single item without replacing the array
|
|
234
|
-
|
|
231
|
+
todos.replace('t1', { id: 't1', text: 'Learn signals', done: true })
|
|
235
232
|
```
|
|
236
233
|
|
|
237
234
|
Each item is its own signal. Sorting reorders keys without destroying signals or their downstream dependencies. Adding and removing items is granular — unaffected items and their effects don't re-run.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cause & Effect
|
|
2
2
|
|
|
3
|
-
Version 1.0.
|
|
3
|
+
Version 1.0.2
|
|
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
|
|
|
@@ -242,7 +242,7 @@ items.splice(1, 1, 'orange')
|
|
|
242
242
|
items.sort()
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
Access items by key using `.byKey()` or by index using `.at()`. `.indexOfKey()` returns the current index of an item in the list, while `.keyAt()` returns the key of an item at a given position.
|
|
245
|
+
Access items by key using `.byKey()` or by index using `.at()`. `.indexOfKey()` returns the current index of an item in the list, while `.keyAt()` returns the key of an item at a given position. To update an existing item, use `.replace(key, value)` — this propagates to all subscribers regardless of how they subscribed to the list.
|
|
246
246
|
|
|
247
247
|
Keys are stable across reordering. Use `keyConfig` in options to control key generation:
|
|
248
248
|
|
|
@@ -260,10 +260,11 @@ const users = createList(
|
|
|
260
260
|
const key = items.add('orange')
|
|
261
261
|
items.sort()
|
|
262
262
|
console.log(items.byKey(key)?.get()) // 'orange'
|
|
263
|
+
items.replace(key, 'ORANGE') // update in place
|
|
263
264
|
console.log(items.indexOfKey(key)) // current index
|
|
264
265
|
```
|
|
265
266
|
|
|
266
|
-
Lists have `.keys()`, `.add()`, and `.remove()` methods like stores. Additionally, they have `.sort()`, `.splice()`, and a reactive `.length` property. But unlike stores, deeply nested properties in items are not converted to individual signals.
|
|
267
|
+
Lists have `.keys()`, `.add()`, and `.remove()` methods like stores. Additionally, they have `.replace()`, `.sort()`, `.splice()`, and a reactive `.length` property. But unlike stores, deeply nested properties in items are not converted to individual signals.
|
|
267
268
|
|
|
268
269
|
### Collection
|
|
269
270
|
|
package/index.dev.js
CHANGED
|
@@ -727,6 +727,20 @@ function createList(value, options) {
|
|
|
727
727
|
flush();
|
|
728
728
|
}
|
|
729
729
|
},
|
|
730
|
+
replace(key, value2) {
|
|
731
|
+
const signal = signals.get(key);
|
|
732
|
+
if (!signal)
|
|
733
|
+
return;
|
|
734
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value2);
|
|
735
|
+
if (signal.get() === value2)
|
|
736
|
+
return;
|
|
737
|
+
signal.set(value2);
|
|
738
|
+
node.flags |= FLAG_DIRTY;
|
|
739
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
740
|
+
propagate(e.sink);
|
|
741
|
+
if (batchDepth === 0)
|
|
742
|
+
flush();
|
|
743
|
+
},
|
|
730
744
|
sort(compareFn) {
|
|
731
745
|
const entries = keys.map((key) => [key, signals.get(key)?.get()]).sort(isFunction(compareFn) ? (a, b) => compareFn(a[1], b[1]) : (a, b) => String(a[1]).localeCompare(String(b[1])));
|
|
732
746
|
const newOrder = entries.map(([key]) => key);
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function g($){return typeof $==="function"}function o($){return g($)&&$.constructor.name==="AsyncFunction"}function X$($){return g($)&&$.constructor.name!=="AsyncFunction"}function Y($,J){return Object.prototype.toString.call($)===`[object ${J}]`}function T($){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 i 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 O($,J,z){if(J==null)throw new A$($);if(z&&!z(J))throw new j$($,J)}function W$($,J){if(J==null)throw new i($)}function E($,J,z=g){if(!z(J))throw new b$($,J)}var c="State",u="Memo",d="Task",t="Sensor",S="List",s="Collection",l="Store",r="Slot",p=0,$$=1,G=2,U$=4,b=8,N=null,_=null,C$=[],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 B=$.sinksTail;if(B?.sink===J&&(!W||g$(B,J)))return;let D={source:$,sink:J,nextSource:X,prevSink:B,nextSink:null};if(J.sourcesTail=$.sinksTail=D,z)z.nextSource=D;else J.sources=D;if(B)B.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 B=J;B.sourcesTail=null,H$(B)}}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)C$.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 O$($){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=p}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 B=$.sinks;B;B=B.nextSink)F(B.sink);if(x===0)I()}},(W)=>{if(J.signal.aborted)return;$.controller=void 0;let B=W instanceof Error?W:Error(String(W));if(!$.error||B.name!==$.error.name||B.message!==$.error.message){$.error=B;for(let D=$.sinks;D;D=D.nextSink)F(D.sink);if(x===0)I()}}),$.flags=p}function T$($){O$($);let J=N,z=_;N=_=$,$.sourcesTail=null,$.flags=U$;try{let X=$.fn();if(typeof X==="function")J$($,X)}finally{N=J,_=z,H$($)}$.flags=p}function f($){if($.flags&$$)for(let J=$.sources;J;J=J.nextSource){if("fn"in J.source)f(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=p}function I(){if(_$)return;_$=!0;try{for(let $=0;$<C$.length;$++){let J=C$[$];if(J.flags&(G|$$))f(J)}C$.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=_,z={cleanup:null};_=z;try{let X=$();if(typeof X==="function")J$(z,X);let W=()=>O$(z);if(J)J$(J,W);return W}finally{_=J}}function d$($){let J=_;_=null;try{return $()}finally{_=J}}function y($,J){O(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){O(c,X,z.guard),N$(z,X)},update(X){E(c,X);let W=X(z.value);O(c,W,z.guard),N$(z,W)}}}function m$($){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=$,B=J;if(W.length!==B.length)return!1;for(let D=0;D<W.length;D++)if(!n(W[D],B[D],z))return!1;return!0}if(T($)&&T(J)){let W=Object.keys($),B=Object.keys(J);if(W.length!==B.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 t$($,J,z,X,W){let B=new WeakSet,D={},M={},C={},U=[],Q=!1,j=new Map;for(let q=0;q<$.length;q++){let Z=z[q],H=$[q];if(Z&&H!==void 0)j.set(Z,H)}let P=new Set;for(let q=0;q<J.length;q++){let Z=J[q];if(Z===void 0)continue;let H=W?X(Z):z[q]??X(Z);if(P.has(H))throw new e(S,H,Z);if(U.push(H),P.add(H),!j.has(H))D[H]=Z,Q=!0;else if(!n(j.get(H),Z,B))M[H]=Z,Q=!0}for(let[q]of j)if(!P.has(q))C[q]=null,Q=!0;if(!Q&&!K$(z,U))Q=!0;return{add:D,change:M,remove:C,newKeys:U,changed:Q}}function Q$($,J){O(S,$,Array.isArray);let z=new Map,X=[],[W,B]=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},C=(Z)=>{let H={};for(let m=0;m<Z.length;m++){let V=Z[m];if(V===void 0)continue;let K=X[m];if(!K)K=W(V),X[m]=K;H[K]=V}return H},U=(Z)=>{let H=!1;for(let m in Z.add){let V=Z.add[m];O(`${S} item for key "${m}"`,V),z.set(m,y(V)),H=!0}if(Object.keys(Z.change).length)z$(()=>{for(let m in Z.change){let V=Z.change[m];O(`${S} item for key "${m}"`,V);let K=z.get(m);if(K)K.set(V)}});for(let m in Z.remove){z.delete(m);let V=X.indexOf(m);if(V!==-1)X.splice(V,1);H=!0}if(H)M.flags|=b;return Z.changed},Q=J?.watched,j=Q?()=>{if(N){if(!M.sinks)M.stop=Q();R(M,N)}}:()=>{if(N)R(M,N)},P=C($);for(let Z in P){let H=P[Z];O(`${S} item for key "${Z}"`,H),z.set(Z,y(H))}M.value=$,M.flags=0;let q={[Symbol.toStringTag]:S,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of X){let H=z.get(Z);if(H)yield H}},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,f(M),M.error)throw M.error}else M.flags=p}}else if(f(M),M.error)throw M.error;return M.value},set(Z){let H=M.flags&G?D():M.value,m=t$(H,Z,X,W,B);if(m.changed){X=m.newKeys,U(m),M.flags|=G;for(let V=M.sinks;V;V=V.nextSink)F(V.sink);if(x===0)I()}},update(Z){q.set(Z(q.get()))},at(Z){let H=X[Z];return H!==void 0?z.get(H):void 0},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 H=W(Z);if(z.has(H))throw new e(S,H,Z);if(!X.includes(H))X.push(H);O(`${S} item for key "${H}"`,Z),z.set(H,y(Z)),M.flags|=G|b;for(let m=M.sinks;m;m=m.nextSink)F(m.sink);if(x===0)I();return H},remove(Z){let H=typeof Z==="number"?X[Z]:Z;if(H===void 0)return;if(z.delete(H)){let V=typeof Z==="number"?Z:X.indexOf(H);if(V>=0)X.splice(V,1);M.flags|=G|b;for(let K=M.sinks;K;K=K.nextSink)F(K.sink);if(x===0)I()}},sort(Z){let m=X.map((V)=>[V,z.get(V)?.get()]).sort(g(Z)?(V,K)=>Z(V[1],K[1]):(V,K)=>String(V[1]).localeCompare(String(K[1]))).map(([V])=>V);if(!K$(X,m)){X=m,M.flags|=G;for(let V=M.sinks;V;V=V.nextSink)F(V.sink);if(x===0)I()}},splice(Z,H,...m){let V=X.length,K=Z<0?Math.max(0,V+Z):Math.min(Z,V),w=Math.max(0,Math.min(H??Math.max(0,V-Math.max(0,K)),V-K)),A={},L={};for(let h=0;h<w;h++){let a=K+h,w$=X[a];if(w$){let y$=z.get(w$);if(y$)L[w$]=y$.get()}}let f$=X.slice(0,K);for(let h of m){let a=W(h);if(z.has(a)&&!(a in L))throw new e(S,a,h);f$.push(a),A[a]=h}f$.push(...X.slice(K+w));let h$=!!(Object.keys(A).length||Object.keys(L).length);if(h$){U({add:A,change:{},remove:L,changed:h$}),X=f$,M.flags|=G;for(let h=M.sinks;h;h=h.nextSink)F(h.sink);if(x===0)I()}return Object.values(L)},deriveCollection(Z){return x$(q,Z)}};return q}function R$($){return Y($,S)}function B$($,J){if(E(u,$,X$),J?.value!==void 0)O(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(),f(z),z.error)throw z.error;return W$(u,z.value),z.value}}}function S$($){return Y($,u)}function q$($,J){if(E(d,$,o),J?.value!==void 0)O(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(),f(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 p$($){return Y($,d)}function x$($,J){E(s,J);let z=o(J),X=new Map,W=[],B=(q)=>{let Z=z?q$(async(H,m)=>{let V=$.byKey(q)?.get();if(V==null)return H;return J(V,m)}):B$(()=>{let H=$.byKey(q)?.get();if(H==null)return;return J(H)});X.set(q,Z)};function D(q){if(!K$(W,q)){let Z=new Set(W),H=new Set(q);for(let m of W)if(!H.has(m))X.delete(m);for(let m of q)if(!Z.has(m))B(m);W=q,U.flags|=b}}function M(){D(Array.from($.keys()));let q=[];for(let Z of W)try{let H=X.get(Z)?.get();if(H!=null)q.push(H)}catch(H){if(!(H instanceof i))throw H}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 H=0;H<q.length;H++)if(q[H]!==Z[H])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=G,f(U),U.error)throw U.error}else U.flags=p}else if(U.sinks){if(f(U),U.error)throw U.error}else U.value=v(M)}let j=Array.from(v(()=>$.keys()));for(let q of j)B(q);W=j;let P={[Symbol.toStringTag]:s,[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 Q(),W.length},keys(){if(N)R(U,N);return Q(),W.values()},get(){if(N)R(U,N);return Q(),U.value},at(q){let Z=W[q];return Z!==void 0?X.get(Z):void 0},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 s$($,J){let z=J?.value??[];if(z.length)O(s,z,Array.isArray);E(s,$,X$);let X=new Map,W=[],B=new Map,[D,M]=E$(J?.keyConfig),C=(Z)=>B.get(Z)??(M?D(Z):void 0),U=J?.createItem??y;function Q(){let Z=[];for(let H of W)try{let m=X.get(H)?.get();if(m!=null)Z.push(m)}catch(m){if(!(m instanceof i))throw m}return Z}let j={fn:Q,value:z,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:L$,error:void 0};for(let Z of z){let H=D(Z);X.set(H,U(Z)),B.set(Z,H),W.push(H)}j.value=z,j.flags=G;function P(){if(N){if(!j.sinks)j.stop=$((Z)=>{let{add:H,change:m,remove:V}=Z;if(!H?.length&&!m?.length&&!V?.length)return;let K=!1;z$(()=>{if(H)for(let w of H){let A=D(w);if(X.set(A,U(w)),B.set(w,A),!W.includes(A))W.push(A);K=!0}if(m)for(let w of m){let A=C(w);if(!A)continue;let L=X.get(A);if(L&&m$(L))B.delete(L.get()),L.set(w),B.set(w,A)}if(V)for(let w of V){let A=C(w);if(!A)continue;B.delete(w),X.delete(A);let L=W.indexOf(A);if(L!==-1)W.splice(L,1);K=!0}j.flags=G|(K?b:0);for(let w=j.sinks;w;w=w.nextSink)F(w.sink)})});R(j,N)}}let q={[Symbol.toStringTag]:s,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of W){let H=X.get(Z);if(H)yield H}},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(Q),Z){if(j.flags=G,f(j),j.error)throw j.error}else j.flags=p}}else if(f(j),j.error)throw j.error;return j.value},at(Z){let H=W[Z];return H!==void 0?X.get(H):void 0},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($,s)}function r$($){E("Effect",$);let J={fn:$,flags:G,sources:null,sourcesTail:null,cleanup:null},z=()=>{O$(J),J.fn=void 0,J.flags=p,J.sourcesTail=null,H$(J)};if(_)J$(_,z);return T$(J),z}function o$($,J){if(!_)throw new G$("match");let{ok:z,err:X=console.error,nil:W}=J,B,D=!1,M=Array($.length);for(let U=0;U<$.length;U++)try{M[U]=$[U].get()}catch(Q){if(Q instanceof i){D=!0;continue}if(!B)B=[];B.push(Q instanceof Error?Q:Error(String(Q)))}let C;try{if(D)C=W?.();else if(B)C=X(B);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=_,Q=new AbortController;J$(U,()=>Q.abort()),C.then((j)=>{if(!Q.signal.aborted&&typeof j==="function")J$(U,j)}).catch((j)=>{X([j instanceof Error?j:Error(String(j))])})}}function i$($,J){if(E(t,$,X$),J?.value!==void 0)O(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)=>{O(t,X,z.guard),N$(z,X)});R(z,N)}return W$(t,z.value),z.value}}}function n$($){return Y($,t)}function a$($,J){let z=T($)||Array.isArray($),X=T(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,B={},D={},M={},C=!1,U=Object.keys($),Q=Object.keys(J);for(let j of Q)if(j in $){if(!n($[j],J[j],W))D[j]=J[j],C=!0}else B[j]=J[j],C=!0;for(let j of U)if(!(j in J))M[j]=void 0,C=!0;return{add:B,change:D,remove:M,changed:C}}function V$($,J){O(l,$,T);let z=new Map,X=(Q,j)=>{if(O(`${l} for key "${Q}"`,j),Array.isArray(j))z.set(Q,Q$(j));else if(T(j))z.set(Q,V$(j));else z.set(Q,y(j))},W=()=>{let Q={};return z.forEach((j,P)=>{Q[P]=j.get()}),Q},B={fn:W,value:$,flags:G,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},D=(Q)=>{let j=!1;for(let P in Q.add)X(P,Q.add[P]),j=!0;if(Object.keys(Q.change).length)z$(()=>{for(let P in Q.change){let q=Q.change[P];O(`${l} for key "${P}"`,q);let Z=z.get(P);if(Z)if(T(q)!==F$(Z))X(P,q),j=!0;else Z.set(q)}});for(let P in Q.remove)z.delete(P),j=!0;if(j)B.flags|=b;return Q.changed},M=J?.watched,C=M?()=>{if(N){if(!B.sinks)B.stop=M();R(B,N)}}:()=>{if(N)R(B,N)};for(let Q of Object.keys($))X(Q,$[Q]);let U={[Symbol.toStringTag]:l,[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(),B.sources){if(B.flags){let Q=B.flags&b;if(B.value=v(W),Q){if(B.flags=G,f(B),B.error)throw B.error}else B.flags=p}}else if(f(B),B.error)throw B.error;return B.value},set(Q){let j=B.flags&G?W():B.value,P=a$(j,Q);if(D(P)){B.flags|=G;for(let q=B.sinks;q;q=q.nextSink)F(q.sink);if(x===0)I()}},update(Q){U.set(Q(U.get()))},add(Q,j){if(z.has(Q))throw new e(l,Q,j);X(Q,j),B.flags|=G|b;for(let P=B.sinks;P;P=P.nextSink)F(P.sink);if(x===0)I();return Q},remove(Q){if(z.delete(Q)){B.flags|=G|b;for(let P=B.sinks;P;P=P.nextSink)F(P.sink);if(x===0)I()}}};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 P=Q.byKey(String(j));return P?{enumerable:!0,configurable:!0,writable:!0,value:P}:void 0}})}function F$($){return Y($,l)}function e$($,J){return o($)?q$($,J):B$($,J)}function $J($){if(M$($))return $;if($==null)throw new j$("createSignal",$);if(o($))return q$($);if(g($))return B$($);if(Y$($))return Q$($);if(T($))return V$($);return y($)}function JJ($){if(I$($))return $;if($==null||g($)||M$($))throw new j$("createMutableSignal",$);if(Y$($))return Q$($);if(T($))return V$($);return y($)}function zJ($){return S$($)||p$($)}function M$($){let J=[c,u,d,t,r,S,s,l],z=Object.prototype.toString.call($).slice(8,-1);return J.includes(z)}function I$($){return m$($)||F$($)||R$($)}function XJ($,J){O(r,$,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},B=()=>{if(N)R(W,N);if(f(W),W.error)throw W.error;return W.value},D=(C)=>{if(!I$(z))throw new P$(r);O(r,C,X),z.set(C)},M=(C)=>{O(r,C,M$),z=C,W.flags|=G;for(let U=W.sinks;U;U=U.nextSink)F(U.sink);if(x===0)I()};return{[Symbol.toStringTag]:r,configurable:!0,enumerable:!0,get:B,set:D,replace:M,current:()=>z}}function ZJ($){return Y($,r)}export{D$ as valueString,v as untrack,d$ as unown,o$ as match,p$ as isTask,F$ as isStore,m$ as isState,ZJ as isSlot,M$ as isSignal,n$ as isSensor,T as isRecord,Y as isObjectOfType,I$ as isMutableSignal,S$ as isMemo,R$ as isList,g as isFunction,n as isEqual,zJ as isComputed,l$ as isCollection,o as isAsyncFunction,q$ as createTask,V$ as createStore,y as createState,XJ as createSlot,$J as createSignal,i$ as createSensor,u$ as createScope,JJ as createMutableSignal,B$ as createMemo,Q$ as createList,r$ as createEffect,e$ as createComputed,s$ as createCollection,z$ as batch,i as UnsetSignalValueError,L$ as SKIP_EQUALITY,G$ as RequiredOwnerError,P$ as ReadonlySignalError,A$ as NullishSignalValueError,j$ as InvalidSignalValueError,b$ as InvalidCallbackError,Z$ as CircularDependencyError};
|
|
1
|
+
function g($){return typeof $==="function"}function o($){return g($)&&$.constructor.name==="AsyncFunction"}function X$($){return g($)&&$.constructor.name!=="AsyncFunction"}function f($,J){return Object.prototype.toString.call($)===`[object ${J}]`}function E($){return f($,"Object")}function f$($,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 i extends Error{constructor($){super(`[${$}] Signal value is unset`);this.name="UnsetSignalValueError"}}class W$ 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 K($,J,z){if(J==null)throw new A$($);if(z&&!z(J))throw new W$($,J)}function j$($,J){if(J==null)throw new i($)}function S($,J,z=g){if(!z(J))throw new b$($,J)}var c="State",u="Memo",d="Task",s="Sensor",L="List",t="Collection",l="Store",r="Slot",p=0,$$=1,P=2,U$=4,b=8,m=null,_=null,C$=[],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,j=J.flags&U$;if(j){if(X=z?z.nextSource:J.sources,X?.source===$){J.sourcesTail=X;return}}let B=$.sinksTail;if(B?.sink===J&&(!j||g$(B,J)))return;let D={source:$,sink:J,nextSource:X,prevSink:B,nextSink:null};if(J.sourcesTail=$.sinksTail=D,z)z.nextSource=D;else J.sources=D;if(B)B.nextSink=D;else $.sinks=D}function k$($){let{source:J,nextSource:z,nextSink:X,prevSink:j}=$;if(X)X.prevSink=j;else J.sinksTail=j;if(j)j.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 B=J;B.sourcesTail=null,H$(B)}}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 x($,J=P){let z=$.flags;if("sinks"in $){if((z&(P|$$))>=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,$$)}else{if((z&(P|$$))>=J)return;let X=z&(P|$$);if($.flags=J,!X)C$.push($)}}function N$($,J){if($.equals($.value,J))return;$.value=J;for(let z=$.sinks;z;z=z.nextSink)x(z.sink);if(R===0)I()}function J$($,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=m;m=$,$.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{m=J,H$($)}if(z){for(let X=$.sinks;X;X=X.nextSink)if(X.sink.flags&$$)X.sink.flags|=P}$.flags=p}function c$($){$.controller?.abort();let J=new AbortController;$.controller=J,$.error=void 0;let z=m;m=$,$.sourcesTail=null,$.flags=U$;let X;try{X=$.fn($.value,J.signal)}catch(j){$.controller=void 0,$.error=j instanceof Error?j:Error(String(j));return}finally{m=z,H$($)}X.then((j)=>{if(J.signal.aborted)return;if($.controller=void 0,$.error||!$.equals(j,$.value)){$.value=j,$.error=void 0;for(let B=$.sinks;B;B=B.nextSink)x(B.sink);if(R===0)I()}},(j)=>{if(J.signal.aborted)return;$.controller=void 0;let B=j instanceof Error?j:Error(String(j));if(!$.error||B.name!==$.error.name||B.message!==$.error.message){$.error=B;for(let D=$.sinks;D;D=D.nextSink)x(D.sink);if(R===0)I()}}),$.flags=p}function T$($){K$($);let J=m,z=_;m=_=$,$.sourcesTail=null,$.flags=U$;try{let X=$.fn();if(typeof X==="function")J$($,X)}finally{m=J,_=z,H$($)}$.flags=p}function w($){if($.flags&$$)for(let J=$.sources;J;J=J.nextSource){if("fn"in J.source)w(J.source);if($.flags&P)break}if($.flags&U$)throw new Z$("controller"in $?d:("value"in $)?u:"Effect");if($.flags&P)if("controller"in $)c$($);else if("value"in $)v$($);else T$($);else $.flags=p}function I(){if(_$)return;_$=!0;try{for(let $=0;$<C$.length;$++){let J=C$[$];if(J.flags&(P|$$))w(J)}C$.length=0}finally{_$=!1}}function z$($){R++;try{$()}finally{if(R--,R===0)I()}}function v($){let J=m;m=null;try{return $()}finally{m=J}}function u$($){let J=_,z={cleanup:null};_=z;try{let X=$();if(typeof X==="function")J$(z,X);let j=()=>K$(z);if(J)J$(J,j);return j}finally{_=J}}function d$($){let J=_;_=null;try{return $()}finally{_=J}}function y($,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(m)F(z,m);return z.value},set(X){K(c,X,z.guard),N$(z,X)},update(X){S(c,X);let j=X(z.value);K(c,j,z.guard),N$(z,j)}}}function V$($){return f($,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 j=$,B=J;if(j.length!==B.length)return!1;for(let D=0;D<j.length;D++)if(!n(j[D],B[D],z))return!1;return!0}if(E($)&&E(J)){let j=Object.keys($),B=Object.keys(J);if(j.length!==B.length)return!1;for(let D of j){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 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 s$($,J,z,X,j){let B=new WeakSet,D={},M={},C={},N=[],Q=!1,W=new Map;for(let q=0;q<$.length;q++){let Z=z[q],H=$[q];if(Z&&H!==void 0)W.set(Z,H)}let G=new Set;for(let q=0;q<J.length;q++){let Z=J[q];if(Z===void 0)continue;let H=j?X(Z):z[q]??X(Z);if(G.has(H))throw new e(L,H,Z);if(N.push(H),G.add(H),!W.has(H))D[H]=Z,Q=!0;else if(!n(W.get(H),Z,B))M[H]=Z,Q=!0}for(let[q]of W)if(!G.has(q))C[q]=null,Q=!0;if(!Q&&!O$(z,N))Q=!0;return{add:D,change:M,remove:C,newKeys:N,changed:Q}}function Q$($,J){K(L,$,Array.isArray);let z=new Map,X=[],[j,B]=E$(J?.keyConfig),D=()=>X.map((Z)=>z.get(Z)?.get()).filter((Z)=>Z!==void 0),M={fn:D,value:$,flags:P,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},C=(Z)=>{let H={};for(let U=0;U<Z.length;U++){let V=Z[U];if(V===void 0)continue;let O=X[U];if(!O)O=j(V),X[U]=O;H[O]=V}return H},N=(Z)=>{let H=!1;for(let U in Z.add){let V=Z.add[U];K(`${L} item for key "${U}"`,V),z.set(U,y(V)),H=!0}if(Object.keys(Z.change).length)z$(()=>{for(let U in Z.change){let V=Z.change[U];K(`${L} item for key "${U}"`,V);let O=z.get(U);if(O)O.set(V)}});for(let U in Z.remove){z.delete(U);let V=X.indexOf(U);if(V!==-1)X.splice(V,1);H=!0}if(H)M.flags|=b;return Z.changed},Q=J?.watched,W=Q?()=>{if(m){if(!M.sinks)M.stop=Q();F(M,m)}}:()=>{if(m)F(M,m)},G=C($);for(let Z in G){let H=G[Z];K(`${L} item for key "${Z}"`,H),z.set(Z,y(H))}M.value=$,M.flags=0;let q={[Symbol.toStringTag]:L,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of X){let H=z.get(Z);if(H)yield H}},get length(){return W(),X.length},get(){if(W(),M.sources){if(M.flags){let Z=M.flags&b;if(M.value=v(D),Z){if(M.flags=P,w(M),M.error)throw M.error}else M.flags=p}}else if(w(M),M.error)throw M.error;return M.value},set(Z){let H=M.flags&P?D():M.value,U=s$(H,Z,X,j,B);if(U.changed){X=U.newKeys,N(U),M.flags|=P;for(let V=M.sinks;V;V=V.nextSink)x(V.sink);if(R===0)I()}},update(Z){q.set(Z(q.get()))},at(Z){let H=X[Z];return H!==void 0?z.get(H):void 0},keys(){return W(),X.values()},byKey(Z){return z.get(Z)},keyAt(Z){return X[Z]},indexOfKey(Z){return X.indexOf(Z)},add(Z){let H=j(Z);if(z.has(H))throw new e(L,H,Z);if(!X.includes(H))X.push(H);K(`${L} item for key "${H}"`,Z),z.set(H,y(Z)),M.flags|=P|b;for(let U=M.sinks;U;U=U.nextSink)x(U.sink);if(R===0)I();return H},remove(Z){let H=typeof Z==="number"?X[Z]:Z;if(H===void 0)return;if(z.delete(H)){let V=typeof Z==="number"?Z:X.indexOf(H);if(V>=0)X.splice(V,1);M.flags|=P|b;for(let O=M.sinks;O;O=O.nextSink)x(O.sink);if(R===0)I()}},replace(Z,H){let U=z.get(Z);if(!U)return;if(K(`${L} item for key "${Z}"`,H),U.get()===H)return;U.set(H),M.flags|=P;for(let V=M.sinks;V;V=V.nextSink)x(V.sink);if(R===0)I()},sort(Z){let U=X.map((V)=>[V,z.get(V)?.get()]).sort(g(Z)?(V,O)=>Z(V[1],O[1]):(V,O)=>String(V[1]).localeCompare(String(O[1]))).map(([V])=>V);if(!O$(X,U)){X=U,M.flags|=P;for(let V=M.sinks;V;V=V.nextSink)x(V.sink);if(R===0)I()}},splice(Z,H,...U){let V=X.length,O=Z<0?Math.max(0,V+Z):Math.min(Z,V),Y=Math.max(0,Math.min(H??Math.max(0,V-Math.max(0,O)),V-O)),A={},T={};for(let h=0;h<Y;h++){let a=O+h,Y$=X[a];if(Y$){let y$=z.get(Y$);if(y$)T[Y$]=y$.get()}}let w$=X.slice(0,O);for(let h of U){let a=j(h);if(z.has(a)&&!(a in T))throw new e(L,a,h);w$.push(a),A[a]=h}w$.push(...X.slice(O+Y));let h$=!!(Object.keys(A).length||Object.keys(T).length);if(h$){N({add:A,change:{},remove:T,changed:h$}),X=w$,M.flags|=P;for(let h=M.sinks;h;h=h.nextSink)x(h.sink);if(R===0)I()}return Object.values(T)},deriveCollection(Z){return x$(q,Z)}};return q}function R$($){return f($,L)}function B$($,J){if(S(u,$,X$),J?.value!==void 0)K(u,J.value,J?.guard);let z={fn:$,value:J?.value,flags:P,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:J?.equals??k,error:void 0,stop:void 0},X=J?.watched,j=X?()=>{if(m){if(!z.sinks)z.stop=X(()=>{if(x(z),R===0)I()});F(z,m)}}:()=>{if(m)F(z,m)};return{[Symbol.toStringTag]:u,get(){if(j(),w(z),z.error)throw z.error;return j$(u,z.value),z.value}}}function S$($){return f($,u)}function q$($,J){if(S(d,$,o),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:P,equals:J?.equals??k,controller:void 0,error:void 0,stop:void 0},X=J?.watched,j=X?()=>{if(m){if(!z.sinks)z.stop=X(()=>{if(x(z),R===0)I()});F(z,m)}}:()=>{if(m)F(z,m)};return{[Symbol.toStringTag]:d,get(){if(j(),w(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 p$($){return f($,d)}function x$($,J){S(t,J);let z=o(J),X=new Map,j=[],B=(q)=>{let Z=z?q$(async(H,U)=>{let V=$.byKey(q)?.get();if(V==null)return H;return J(V,U)}):B$(()=>{let H=$.byKey(q)?.get();if(H==null)return;return J(H)});X.set(q,Z)};function D(q){if(!O$(j,q)){let Z=new Set(j),H=new Set(q);for(let U of j)if(!H.has(U))X.delete(U);for(let U of q)if(!Z.has(U))B(U);j=q,N.flags|=b}}function M(){D(Array.from($.keys()));let q=[];for(let Z of j)try{let H=X.get(Z)?.get();if(H!=null)q.push(H)}catch(H){if(!(H instanceof i))throw H}return q}let N={fn:M,value:[],flags:P,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:(q,Z)=>{if(q.length!==Z.length)return!1;for(let H=0;H<q.length;H++)if(q[H]!==Z[H])return!1;return!0},error:void 0};function Q(){if(N.sources){if(N.flags)if(N.value=v(M),N.flags&b){if(N.flags=P,w(N),N.error)throw N.error}else N.flags=p}else if(N.sinks){if(w(N),N.error)throw N.error}else N.value=v(M)}let W=Array.from(v(()=>$.keys()));for(let q of W)B(q);j=W;let G={[Symbol.toStringTag]:t,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let q of j){let Z=X.get(q);if(Z)yield Z}},get length(){if(m)F(N,m);return Q(),j.length},keys(){if(m)F(N,m);return Q(),j.values()},get(){if(m)F(N,m);return Q(),N.value},at(q){let Z=j[q];return Z!==void 0?X.get(Z):void 0},byKey(q){return X.get(q)},keyAt(q){return j[q]},indexOfKey(q){return j.indexOf(q)},deriveCollection(q){return x$(G,q)}};return G}function t$($,J){let z=J?.value??[];if(z.length)K(t,z,Array.isArray);S(t,$,X$);let X=new Map,j=[],B=new Map,[D,M]=E$(J?.keyConfig),C=(Z)=>B.get(Z)??(M?D(Z):void 0),N=J?.createItem??y;function Q(){let Z=[];for(let H of j)try{let U=X.get(H)?.get();if(U!=null)Z.push(U)}catch(U){if(!(U instanceof i))throw U}return Z}let W={fn:Q,value:z,flags:P,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:L$,error:void 0};for(let Z of z){let H=D(Z);X.set(H,N(Z)),B.set(Z,H),j.push(H)}W.value=z,W.flags=P;function G(){if(m){if(!W.sinks)W.stop=$((Z)=>{let{add:H,change:U,remove:V}=Z;if(!H?.length&&!U?.length&&!V?.length)return;let O=!1;z$(()=>{if(H)for(let Y of H){let A=D(Y);if(X.set(A,N(Y)),B.set(Y,A),!j.includes(A))j.push(A);O=!0}if(U)for(let Y of U){let A=C(Y);if(!A)continue;let T=X.get(A);if(T&&V$(T))B.delete(T.get()),T.set(Y),B.set(Y,A)}if(V)for(let Y of V){let A=C(Y);if(!A)continue;B.delete(Y),X.delete(A);let T=j.indexOf(A);if(T!==-1)j.splice(T,1);O=!0}W.flags=P|(O?b:0);for(let Y=W.sinks;Y;Y=Y.nextSink)x(Y.sink)})});F(W,m)}}let q={[Symbol.toStringTag]:t,[Symbol.isConcatSpreadable]:!0,*[Symbol.iterator](){for(let Z of j){let H=X.get(Z);if(H)yield H}},get length(){return G(),j.length},keys(){return G(),j.values()},get(){if(G(),W.sources){if(W.flags){let Z=W.flags&b;if(W.value=v(Q),Z){if(W.flags=P,w(W),W.error)throw W.error}else W.flags=p}}else if(w(W),W.error)throw W.error;return W.value},at(Z){let H=j[Z];return H!==void 0?X.get(H):void 0},byKey(Z){return X.get(Z)},keyAt(Z){return j[Z]},indexOfKey(Z){return j.indexOf(Z)},deriveCollection(Z){return x$(q,Z)}};return q}function l$($){return f($,t)}function r$($){S("Effect",$);let J={fn:$,flags:P,sources:null,sourcesTail:null,cleanup:null},z=()=>{K$(J),J.fn=void 0,J.flags=p,J.sourcesTail=null,H$(J)};if(_)J$(_,z);return T$(J),z}function o$($,J){if(!_)throw new G$("match");let{ok:z,err:X=console.error,nil:j}=J,B,D=!1,M=Array($.length);for(let N=0;N<$.length;N++)try{M[N]=$[N].get()}catch(Q){if(Q instanceof i){D=!0;continue}if(!B)B=[];B.push(Q instanceof Error?Q:Error(String(Q)))}let C;try{if(D)C=j?.();else if(B)C=X(B);else C=z(M)}catch(N){X([N instanceof Error?N:Error(String(N))])}if(typeof C==="function")return C;if(C instanceof Promise){let N=_,Q=new AbortController;J$(N,()=>Q.abort()),C.then((W)=>{if(!Q.signal.aborted&&typeof W==="function")J$(N,W)}).catch((W)=>{X([W instanceof Error?W:Error(String(W))])})}}function i$($,J){if(S(s,$,X$),J?.value!==void 0)K(s,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]:s,get(){if(m){if(!z.sinks)z.stop=$((X)=>{K(s,X,z.guard),N$(z,X)});F(z,m)}return j$(s,z.value),z.value}}}function n$($){return f($,s)}function a$($,J){let z=E($)||Array.isArray($),X=E(J)||Array.isArray(J);if(!z||!X){let W=!Object.is($,J);return{changed:W,add:W&&X?J:{},change:{},remove:W&&z?$:{}}}let j=new WeakSet,B={},D={},M={},C=!1,N=Object.keys($),Q=Object.keys(J);for(let W of Q)if(W in $){if(!n($[W],J[W],j))D[W]=J[W],C=!0}else B[W]=J[W],C=!0;for(let W of N)if(!(W in J))M[W]=void 0,C=!0;return{add:B,change:D,remove:M,changed:C}}function m$($,J){K(l,$,E);let z=new Map,X=(Q,W)=>{if(K(`${l} for key "${Q}"`,W),Array.isArray(W))z.set(Q,Q$(W));else if(E(W))z.set(Q,m$(W));else z.set(Q,y(W))},j=()=>{let Q={};return z.forEach((W,G)=>{Q[G]=W.get()}),Q},B={fn:j,value:$,flags:P,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:n,error:void 0},D=(Q)=>{let W=!1;for(let G in Q.add)X(G,Q.add[G]),W=!0;if(Object.keys(Q.change).length)z$(()=>{for(let G in Q.change){let q=Q.change[G];K(`${l} for key "${G}"`,q);let Z=z.get(G);if(Z)if(E(q)!==F$(Z))X(G,q),W=!0;else Z.set(q)}});for(let G in Q.remove)z.delete(G),W=!0;if(W)B.flags|=b;return Q.changed},M=J?.watched,C=M?()=>{if(m){if(!B.sinks)B.stop=M();F(B,m)}}:()=>{if(m)F(B,m)};for(let Q of Object.keys($))X(Q,$[Q]);let N={[Symbol.toStringTag]:l,[Symbol.isConcatSpreadable]:!1,*[Symbol.iterator](){for(let Q of Array.from(z.keys())){let W=z.get(Q);if(W)yield[Q,W]}},keys(){return C(),z.keys()},byKey(Q){return z.get(Q)},get(){if(C(),B.sources){if(B.flags){let Q=B.flags&b;if(B.value=v(j),Q){if(B.flags=P,w(B),B.error)throw B.error}else B.flags=p}}else if(w(B),B.error)throw B.error;return B.value},set(Q){let W=B.flags&P?j():B.value,G=a$(W,Q);if(D(G)){B.flags|=P;for(let q=B.sinks;q;q=q.nextSink)x(q.sink);if(R===0)I()}},update(Q){N.set(Q(N.get()))},add(Q,W){if(z.has(Q))throw new e(l,Q,W);X(Q,W),B.flags|=P|b;for(let G=B.sinks;G;G=G.nextSink)x(G.sink);if(R===0)I();return Q},remove(Q){if(z.delete(Q)){B.flags|=P|b;for(let G=B.sinks;G;G=G.nextSink)x(G.sink);if(R===0)I()}}};return new Proxy(N,{get(Q,W){if(W in Q)return Reflect.get(Q,W);if(typeof W!=="symbol")return Q.byKey(W)},has(Q,W){if(W in Q)return!0;return Q.byKey(String(W))!==void 0},ownKeys(Q){return Array.from(Q.keys())},getOwnPropertyDescriptor(Q,W){if(W in Q)return Reflect.getOwnPropertyDescriptor(Q,W);if(typeof W==="symbol")return;let G=Q.byKey(String(W));return G?{enumerable:!0,configurable:!0,writable:!0,value:G}:void 0}})}function F$($){return f($,l)}function e$($,J){return o($)?q$($,J):B$($,J)}function $J($){if(M$($))return $;if($==null)throw new W$("createSignal",$);if(o($))return q$($);if(g($))return B$($);if(f$($))return Q$($);if(E($))return m$($);return y($)}function JJ($){if(I$($))return $;if($==null||g($)||M$($))throw new W$("createMutableSignal",$);if(f$($))return Q$($);if(E($))return m$($);return y($)}function zJ($){return S$($)||p$($)}function M$($){let J=[c,u,d,s,r,L,t,l],z=Object.prototype.toString.call($).slice(8,-1);return J.includes(z)}function I$($){return V$($)||F$($)||R$($)}function XJ($,J){K(r,$,M$);let z=$,X=J?.guard,j={fn:()=>z.get(),value:void 0,flags:P,sources:null,sourcesTail:null,sinks:null,sinksTail:null,equals:J?.equals??k,error:void 0},B=()=>{if(m)F(j,m);if(w(j),j.error)throw j.error;return j.value},D=(C)=>{if(!I$(z))throw new P$(r);K(r,C,X),z.set(C)},M=(C)=>{K(r,C,M$),z=C,j.flags|=P;for(let N=j.sinks;N;N=N.nextSink)x(N.sink);if(R===0)I()};return{[Symbol.toStringTag]:r,configurable:!0,enumerable:!0,get:B,set:D,replace:M,current:()=>z}}function ZJ($){return f($,r)}export{D$ as valueString,v as untrack,d$ as unown,o$ as match,p$ as isTask,F$ as isStore,V$ as isState,ZJ as isSlot,M$ as isSignal,n$ as isSensor,E as isRecord,f as isObjectOfType,I$ as isMutableSignal,S$ as isMemo,R$ as isList,g as isFunction,n as isEqual,zJ as isComputed,l$ as isCollection,o as isAsyncFunction,q$ as createTask,m$ as createStore,y as createState,XJ as createSlot,$J as createSignal,i$ as createSensor,u$ as createScope,JJ as createMutableSignal,B$ as createMemo,Q$ as createList,r$ as createEffect,e$ as createComputed,t$ as createCollection,z$ as batch,i as UnsetSignalValueError,L$ as SKIP_EQUALITY,G$ as RequiredOwnerError,P$ as ReadonlySignalError,A$ as NullishSignalValueError,W$ as InvalidSignalValueError,b$ as InvalidCallbackError,Z$ as CircularDependencyError};
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -36,6 +36,28 @@ createEffect(() => {
|
|
|
36
36
|
```
|
|
37
37
|
</direct_lookups_do_not_track>
|
|
38
38
|
|
|
39
|
+
<bykey_set_does_not_propagate_to_structural_subscribers>
|
|
40
|
+
**`byKey(key).set(value)` does not propagate to effects that subscribed via `list.keys()`,
|
|
41
|
+
`list.length`, or the iterator.** Those effects subscribe to the list's structural node but
|
|
42
|
+
do not establish item-level edges, so a direct item signal mutation reaches them only if
|
|
43
|
+
`list.get()` has previously been called to link the item signal to the list node.
|
|
44
|
+
|
|
45
|
+
Use `list.replace(key, value)` for imperative item updates. It propagates through both paths
|
|
46
|
+
— item-level edges and the structural node — regardless of how subscribers are attached.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Wrong — silently does nothing for effects that subscribed via list.keys()
|
|
50
|
+
list.byKey(key)?.set(newValue)
|
|
51
|
+
|
|
52
|
+
// Correct — guaranteed propagation to all subscribers
|
|
53
|
+
list.replace(key, newValue)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`byKey(key).set(value)` is safe only when the consuming effect directly calls
|
|
57
|
+
`byKey(key).get()` inside its body — that creates a direct edge from the item signal to the
|
|
58
|
+
effect, bypassing the list node entirely.
|
|
59
|
+
</bykey_set_does_not_propagate_to_structural_subscribers>
|
|
60
|
+
|
|
39
61
|
<conditional_reads_delay_watched>
|
|
40
62
|
**Conditional signal reads delay `watched` activation.** The `watched` callback on a State
|
|
41
63
|
or Sensor fires when the first downstream effect subscribes. If a signal is only read inside
|
|
@@ -150,15 +150,19 @@ user.name = 'Bob' // only effects reading `user.name` re-run
|
|
|
150
150
|
- You need to react to structural changes (items added, removed, reordered) as well as value changes
|
|
151
151
|
|
|
152
152
|
**Key facts:**
|
|
153
|
-
- Items are identified by a key; keys
|
|
153
|
+
- Items are identified by a stable key; keys survive sorting and reordering
|
|
154
154
|
- `byKey()`, `at()`, `keyAt()`, and `indexOfKey()` are direct lookups — they **do not create graph edges**
|
|
155
155
|
- To react to structural changes, read `get()`, `keys()`, or `length` instead
|
|
156
|
+
- To update an existing item, use `list.replace(key, value)` — **not** `byKey(key).set(value)`. `replace()` propagates to all subscribers; `byKey().set()` silently misses effects that subscribed via `keys()`, `length`, or the iterator
|
|
156
157
|
|
|
157
158
|
```typescript
|
|
158
|
-
const todos = createList
|
|
159
|
-
{ id: '
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
const todos = createList(
|
|
160
|
+
[{ id: 't1', text: 'Buy milk', done: false }],
|
|
161
|
+
{ keyConfig: todo => todo.id }
|
|
162
|
+
)
|
|
163
|
+
todos.add({ id: 't2', text: 'Walk dog', done: false })
|
|
164
|
+
todos.replace('t1', { id: 't1', text: 'Buy milk', done: true }) // update in place
|
|
165
|
+
todos.remove('t2')
|
|
162
166
|
```
|
|
163
167
|
</List>
|
|
164
168
|
|
|
@@ -30,6 +30,22 @@ createEffect(() => {
|
|
|
30
30
|
```
|
|
31
31
|
</direct_lookups_do_not_track>
|
|
32
32
|
|
|
33
|
+
<bykey_set_does_not_propagate_to_structural_subscribers>
|
|
34
|
+
**`byKey(key).set(value)` does not propagate through `listNode.sinks` unless `itemSignal → listNode` edges exist.** Those edges are established only when `recomputeMemo(listNode)` runs — which requires `list.get()` to have been called. Effects that subscribed via `list.keys()`, `list.length`, or the iterator link to `listNode.sinks` but never trigger `recomputeMemo`, so the item-level edges are never created.
|
|
35
|
+
|
|
36
|
+
`list.replace(key, value)` is the correct API for imperative item mutations. It calls `signal.set(value)` (value path) then explicitly walks `node.sinks` (structural path), guaranteeing propagation regardless of edge state.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Wrong — misses structural subscribers if list.get() was never called
|
|
40
|
+
list.byKey(key)?.set(newValue)
|
|
41
|
+
|
|
42
|
+
// Correct — propagates through both paths unconditionally
|
|
43
|
+
list.replace(key, newValue)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`byKey(key).set(value)` is safe only when the consuming effect directly reads `byKey(key).get()` inside its body, establishing a direct `itemSignal → effectNode` edge.
|
|
47
|
+
</bykey_set_does_not_propagate_to_structural_subscribers>
|
|
48
|
+
|
|
33
49
|
<conditional_reads_delay_watched>
|
|
34
50
|
**Conditional signal reads delay `watched` activation.** The `watched` callback on a State or Sensor fires when the first downstream effect subscribes. If a signal is only read inside a branch that hasn't executed yet, `watched` does not fire until that branch runs.
|
|
35
51
|
|
package/src/nodes/list.ts
CHANGED
|
@@ -82,6 +82,13 @@ type List<T extends {}> = {
|
|
|
82
82
|
indexOfKey(key: string): number
|
|
83
83
|
add(value: T): string
|
|
84
84
|
remove(keyOrIndex: string | number): void
|
|
85
|
+
/**
|
|
86
|
+
* Updates an existing item by key, propagating to all subscribers.
|
|
87
|
+
* No-op if the key does not exist or the value is reference-equal to the current value.
|
|
88
|
+
* @param key - Stable key of the item to update
|
|
89
|
+
* @param value - New value for the item
|
|
90
|
+
*/
|
|
91
|
+
replace(key: string, value: T): void
|
|
85
92
|
sort(compareFn?: (a: T, b: T) => number): void
|
|
86
93
|
splice(start: number, deleteCount?: number, ...items: T[]): T[]
|
|
87
94
|
deriveCollection<R extends {}>(
|
|
@@ -495,6 +502,17 @@ function createList<T extends {}>(
|
|
|
495
502
|
}
|
|
496
503
|
},
|
|
497
504
|
|
|
505
|
+
replace(key: string, value: T) {
|
|
506
|
+
const signal = signals.get(key)
|
|
507
|
+
if (!signal) return
|
|
508
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value)
|
|
509
|
+
if (signal.get() === value) return
|
|
510
|
+
signal.set(value)
|
|
511
|
+
node.flags |= FLAG_DIRTY
|
|
512
|
+
for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
|
|
513
|
+
if (batchDepth === 0) flush()
|
|
514
|
+
},
|
|
515
|
+
|
|
498
516
|
sort(compareFn?: (a: T, b: T) => number) {
|
|
499
517
|
const entries = keys
|
|
500
518
|
.map(key => [key, signals.get(key)?.get()] as [string, T])
|
package/test/list.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
+
batch,
|
|
3
4
|
createEffect,
|
|
4
5
|
createList,
|
|
5
6
|
createMemo,
|
|
@@ -209,6 +210,126 @@ describe('List', () => {
|
|
|
209
210
|
})
|
|
210
211
|
})
|
|
211
212
|
|
|
213
|
+
describe('replace', () => {
|
|
214
|
+
test('should update the item signal value', () => {
|
|
215
|
+
const list = createList(['a', 'b', 'c'])
|
|
216
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
217
|
+
const key = list.keyAt(1)!
|
|
218
|
+
list.replace(key, 'B')
|
|
219
|
+
expect(list.byKey(key)?.get()).toBe('B')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('structural subscriber via keys() re-runs after replace(); byKey().set() does NOT', () => {
|
|
223
|
+
const list = createList(['x', 'y'])
|
|
224
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
225
|
+
const key = list.keyAt(0)!
|
|
226
|
+
let effectCount = 0
|
|
227
|
+
|
|
228
|
+
// This effect subscribes structurally via keys() only — no list.get() call
|
|
229
|
+
createEffect(() => {
|
|
230
|
+
void [...list.keys()]
|
|
231
|
+
effectCount++
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
expect(effectCount).toBe(1)
|
|
235
|
+
|
|
236
|
+
// replace() propagates through node.sinks — structural subscriber re-runs
|
|
237
|
+
list.replace(key, 'X')
|
|
238
|
+
expect(effectCount).toBe(2)
|
|
239
|
+
|
|
240
|
+
// byKey().set() does NOT propagate through node.sinks — structural subscriber does NOT re-run
|
|
241
|
+
list.byKey(key)?.set('XX')
|
|
242
|
+
expect(effectCount).toBe(2)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('direct subscriber via byKey().get() re-runs after replace()', () => {
|
|
246
|
+
const list = createList(['a', 'b'])
|
|
247
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
248
|
+
const key = list.keyAt(0)!
|
|
249
|
+
let lastValue = ''
|
|
250
|
+
let effectCount = 0
|
|
251
|
+
|
|
252
|
+
createEffect(() => {
|
|
253
|
+
// biome-ignore lint/style/noNonNullAssertion: key is valid
|
|
254
|
+
lastValue = list.byKey(key)!.get()
|
|
255
|
+
effectCount++
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
expect(effectCount).toBe(1)
|
|
259
|
+
expect(lastValue).toBe('a')
|
|
260
|
+
|
|
261
|
+
list.replace(key, 'A')
|
|
262
|
+
expect(effectCount).toBe(2)
|
|
263
|
+
expect(lastValue).toBe('A')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('no-op on equal value (same reference)', () => {
|
|
267
|
+
const obj = { id: 1 }
|
|
268
|
+
const list = createList([obj])
|
|
269
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
270
|
+
const key = list.keyAt(0)!
|
|
271
|
+
let effectCount = 0
|
|
272
|
+
|
|
273
|
+
createEffect(() => {
|
|
274
|
+
list.get()
|
|
275
|
+
effectCount++
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
expect(effectCount).toBe(1)
|
|
279
|
+
|
|
280
|
+
list.replace(key, obj)
|
|
281
|
+
expect(effectCount).toBe(1)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('no-op on missing key — does not throw and does not trigger effects', () => {
|
|
285
|
+
const list = createList([1, 2, 3])
|
|
286
|
+
let effectCount = 0
|
|
287
|
+
|
|
288
|
+
createEffect(() => {
|
|
289
|
+
list.get()
|
|
290
|
+
effectCount++
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
expect(effectCount).toBe(1)
|
|
294
|
+
expect(() => list.replace('nonexistent', 99)).not.toThrow()
|
|
295
|
+
expect(effectCount).toBe(1)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('batch compatibility — effects run only once inside batch()', () => {
|
|
299
|
+
const list = createList(['a', 'b', 'c'])
|
|
300
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
301
|
+
const key0 = list.keyAt(0)!
|
|
302
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
303
|
+
const key1 = list.keyAt(1)!
|
|
304
|
+
let effectCount = 0
|
|
305
|
+
|
|
306
|
+
createEffect(() => {
|
|
307
|
+
list.get()
|
|
308
|
+
effectCount++
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
expect(effectCount).toBe(1)
|
|
312
|
+
|
|
313
|
+
batch(() => {
|
|
314
|
+
list.replace(key0, 'A')
|
|
315
|
+
list.replace(key1, 'B')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
expect(effectCount).toBe(2)
|
|
319
|
+
expect(list.get()).toEqual(['A', 'B', 'c'])
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('signal identity preserved — byKey() returns same signal before and after replace()', () => {
|
|
323
|
+
const list = createList([10, 20])
|
|
324
|
+
// biome-ignore lint/style/noNonNullAssertion: index is within bounds
|
|
325
|
+
const key = list.keyAt(0)!
|
|
326
|
+
const signalBefore = list.byKey(key)
|
|
327
|
+
list.replace(key, 99)
|
|
328
|
+
const signalAfter = list.byKey(key)
|
|
329
|
+
expect(signalBefore).toBe(signalAfter)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
212
333
|
describe('sort', () => {
|
|
213
334
|
test('should sort with default string comparison', () => {
|
|
214
335
|
const list = createList([3, 1, 2])
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 1.0.
|
|
3
|
+
* @version 1.0.2
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
export { CircularDependencyError, type Guard, InvalidCallbackError, InvalidSignalValueError, NullishSignalValueError, ReadonlySignalError, RequiredOwnerError, UnsetSignalValueError, } from './src/errors';
|
package/types/src/graph.d.ts
CHANGED
|
@@ -226,6 +226,8 @@ declare function createScope(fn: () => MaybeCleanup): Cleanup;
|
|
|
226
226
|
* reactive graph.
|
|
227
227
|
*
|
|
228
228
|
* @since 0.18.5
|
|
229
|
+
* @param fn - The function to execute without an active owner
|
|
230
|
+
* @returns The return value of `fn`
|
|
229
231
|
*/
|
|
230
232
|
declare function unown<T>(fn: () => T): T;
|
|
231
233
|
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, unown, untrack, };
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { type Cleanup, type Signal } from '../graph';
|
|
2
2
|
import { type KeyConfig, type List } from './list';
|
|
3
3
|
type CollectionSource<T extends {}> = List<T> | Collection<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Transformation callback for `deriveCollection` — sync or async.
|
|
6
|
+
* Sync callbacks produce a `Memo<T>` per item; async callbacks produce a `Task<T>`
|
|
7
|
+
* with automatic cancellation when the source item changes.
|
|
8
|
+
*
|
|
9
|
+
* @template T - The type of derived items
|
|
10
|
+
* @template U - The type of source items
|
|
11
|
+
*/
|
|
4
12
|
type DeriveCollectionCallback<T extends {}, U extends {}> = ((sourceValue: U) => T) | ((sourceValue: U, abort: AbortSignal) => Promise<T>);
|
|
13
|
+
/**
|
|
14
|
+
* A read-only reactive keyed collection with per-item reactivity.
|
|
15
|
+
* Created by `createCollection` (externally driven) or via `.deriveCollection()` on a `List` or `Collection`.
|
|
16
|
+
*
|
|
17
|
+
* @template T - The type of items in the collection
|
|
18
|
+
*/
|
|
5
19
|
type Collection<T extends {}> = {
|
|
6
20
|
readonly [Symbol.toStringTag]: 'Collection';
|
|
7
21
|
readonly [Symbol.isConcatSpreadable]: true;
|
|
@@ -16,16 +30,40 @@ type Collection<T extends {}> = {
|
|
|
16
30
|
deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>): Collection<R>;
|
|
17
31
|
readonly length: number;
|
|
18
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Granular mutation descriptor passed to the `applyChanges` callback inside a `CollectionCallback`.
|
|
35
|
+
*
|
|
36
|
+
* @template T - The type of items in the collection
|
|
37
|
+
*/
|
|
19
38
|
type CollectionChanges<T> = {
|
|
39
|
+
/** Items to add. Each item is assigned a new key via the configured `keyConfig`. */
|
|
20
40
|
add?: T[];
|
|
41
|
+
/** Items whose values have changed. Matched to existing entries by key. */
|
|
21
42
|
change?: T[];
|
|
43
|
+
/** Items to remove. Matched to existing entries by key. */
|
|
22
44
|
remove?: T[];
|
|
23
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Configuration options for `createCollection`.
|
|
48
|
+
*
|
|
49
|
+
* @template T - The type of items in the collection
|
|
50
|
+
*/
|
|
24
51
|
type CollectionOptions<T extends {}> = {
|
|
52
|
+
/** Initial items. Defaults to `[]`. */
|
|
25
53
|
value?: T[];
|
|
54
|
+
/** Key generation strategy. See `KeyConfig`. Defaults to auto-increment. */
|
|
26
55
|
keyConfig?: KeyConfig<T>;
|
|
56
|
+
/** Factory for per-item signals. Defaults to `createState`. */
|
|
27
57
|
createItem?: (value: T) => Signal<T>;
|
|
28
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Setup callback for `createCollection`. Invoked when the collection gains its first downstream
|
|
61
|
+
* subscriber; receives an `applyChanges` function to push granular mutations into the graph.
|
|
62
|
+
*
|
|
63
|
+
* @template T - The type of items in the collection
|
|
64
|
+
* @param apply - Call with a `CollectionChanges` object to add, update, or remove items
|
|
65
|
+
* @returns A cleanup function invoked when the collection loses all subscribers
|
|
66
|
+
*/
|
|
29
67
|
type CollectionCallback<T extends {}> = (apply: (changes: CollectionChanges<T>) => void) => Cleanup;
|
|
30
68
|
/**
|
|
31
69
|
* Creates a derived Collection from a List or another Collection with item-level memoization.
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { type Cleanup, type EffectCallback, type MaybeCleanup, type Signal } from '../graph';
|
|
2
|
+
/** A value that is either synchronous or a `Promise` — used for handler return types in `match()`. */
|
|
2
3
|
type MaybePromise<T> = T | Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Handlers for all states of one or more signals passed to `match()`.
|
|
6
|
+
*
|
|
7
|
+
* @template T - Tuple of `Signal` types being matched
|
|
8
|
+
*/
|
|
3
9
|
type MatchHandlers<T extends readonly Signal<unknown & {}>[]> = {
|
|
10
|
+
/** Called when all signals have a value. Receives a tuple of resolved values. */
|
|
4
11
|
ok: (values: {
|
|
5
12
|
[K in keyof T]: T[K] extends Signal<infer V> ? V : never;
|
|
6
13
|
}) => MaybePromise<MaybeCleanup>;
|
|
14
|
+
/** Called when one or more signals hold an error. Defaults to `console.error`. */
|
|
7
15
|
err?: (errors: readonly Error[]) => MaybePromise<MaybeCleanup>;
|
|
16
|
+
/** Called when one or more signals are unset (pending). */
|
|
8
17
|
nil?: () => MaybePromise<MaybeCleanup>;
|
|
9
18
|
};
|
|
10
19
|
/**
|
|
@@ -38,10 +47,13 @@ type MatchHandlers<T extends readonly Signal<unknown & {}>[]> = {
|
|
|
38
47
|
*/
|
|
39
48
|
declare function createEffect(fn: EffectCallback): Cleanup;
|
|
40
49
|
/**
|
|
41
|
-
*
|
|
50
|
+
* Reads one or more signals and dispatches to the appropriate handler based on their state.
|
|
42
51
|
* Must be called within an active owner (effect or scope) so async cleanup can be registered.
|
|
43
52
|
*
|
|
44
53
|
* @since 0.15.0
|
|
54
|
+
* @param signals - Tuple of signals to read; all must have a value for `ok` to run.
|
|
55
|
+
* @param handlers - Object with an `ok` branch and optional `err` and `nil` branches.
|
|
56
|
+
* @returns An optional cleanup function if the active handler returns one.
|
|
45
57
|
* @throws RequiredOwnerError If called without an active owner.
|
|
46
58
|
*/
|
|
47
59
|
declare function match<T extends readonly Signal<unknown & {}>[]>(signals: readonly [...T], handlers: MatchHandlers<T>): MaybeCleanup;
|
|
@@ -8,11 +8,31 @@ type DiffResult = {
|
|
|
8
8
|
change: UnknownRecord;
|
|
9
9
|
remove: UnknownRecord;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Key generation strategy for `createList` items.
|
|
13
|
+
* A string value is used as a prefix for auto-incremented keys (`prefix0`, `prefix1`, …).
|
|
14
|
+
* A function receives each item and returns a stable string key, or `undefined` to fall back to auto-increment.
|
|
15
|
+
*
|
|
16
|
+
* @template T - The type of items in the list
|
|
17
|
+
*/
|
|
11
18
|
type KeyConfig<T> = string | ((item: T) => string | undefined);
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for `createList`.
|
|
21
|
+
*
|
|
22
|
+
* @template T - The type of items in the list
|
|
23
|
+
*/
|
|
12
24
|
type ListOptions<T extends {}> = {
|
|
25
|
+
/** Key generation strategy. A string prefix or a function `(item) => string | undefined`. Defaults to auto-increment. */
|
|
13
26
|
keyConfig?: KeyConfig<T>;
|
|
27
|
+
/** Lifecycle callback invoked when the list gains its first downstream subscriber. Must return a cleanup function. */
|
|
14
28
|
watched?: () => Cleanup;
|
|
15
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* A reactive ordered array with stable keys and per-item reactivity.
|
|
32
|
+
* Each item is a `State<T>` signal; structural changes (add/remove/sort) propagate reactively.
|
|
33
|
+
*
|
|
34
|
+
* @template T - The type of items in the list
|
|
35
|
+
*/
|
|
16
36
|
type List<T extends {}> = {
|
|
17
37
|
readonly [Symbol.toStringTag]: 'List';
|
|
18
38
|
readonly [Symbol.isConcatSpreadable]: true;
|
|
@@ -28,6 +48,13 @@ type List<T extends {}> = {
|
|
|
28
48
|
indexOfKey(key: string): number;
|
|
29
49
|
add(value: T): string;
|
|
30
50
|
remove(keyOrIndex: string | number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Updates an existing item by key, propagating to all subscribers.
|
|
53
|
+
* No-op if the key does not exist or the value is reference-equal to the current value.
|
|
54
|
+
* @param key - Stable key of the item to update
|
|
55
|
+
* @param value - New value for the item
|
|
56
|
+
*/
|
|
57
|
+
replace(key: string, value: T): void;
|
|
31
58
|
sort(compareFn?: (a: T, b: T) => number): void;
|
|
32
59
|
splice(start: number, deleteCount?: number, ...items: T[]): T[];
|
|
33
60
|
deriveCollection<R extends {}>(callback: (sourceValue: T) => R): Collection<R>;
|
|
@@ -51,8 +78,9 @@ declare function getKeyGenerator<T extends {}>(keyConfig?: KeyConfig<T>): [(item
|
|
|
51
78
|
*
|
|
52
79
|
* @since 0.18.0
|
|
53
80
|
* @param value - Initial array of items
|
|
54
|
-
* @param options -
|
|
55
|
-
* @
|
|
81
|
+
* @param options.keyConfig - Key generation strategy: string prefix or `(item) => string | undefined`. Defaults to auto-increment.
|
|
82
|
+
* @param options.watched - Lifecycle callback invoked on first subscriber; must return a cleanup function called on last unsubscribe.
|
|
83
|
+
* @returns A `List` signal with reactive per-item `State` signals
|
|
56
84
|
*/
|
|
57
85
|
declare function createList<T extends {}>(value: T[], options?: ListOptions<T>): List<T>;
|
|
58
86
|
/**
|
|
@@ -12,7 +12,6 @@ type Memo<T extends {}> = {
|
|
|
12
12
|
* Recomputes if dependencies have changed since last access.
|
|
13
13
|
* When called inside another reactive context, creates a dependency.
|
|
14
14
|
* @returns The computed value
|
|
15
|
-
* @throws UnsetSignalValueError If the memo value is still unset when read.
|
|
16
15
|
*/
|
|
17
16
|
get(): T;
|
|
18
17
|
};
|
|
@@ -15,11 +15,9 @@ type Sensor<T extends {}> = {
|
|
|
15
15
|
get(): T;
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Configuration options for `createSensor`.
|
|
19
19
|
*
|
|
20
|
-
* @template T - The type of value
|
|
21
|
-
* @param set - A function to set the observed value
|
|
22
|
-
* @returns A cleanup function when the sensor stops being watched
|
|
20
|
+
* @template T - The type of value produced by the sensor
|
|
23
21
|
*/
|
|
24
22
|
type SensorOptions<T extends {}> = SignalOptions<T> & {
|
|
25
23
|
/**
|
|
@@ -28,6 +26,14 @@ type SensorOptions<T extends {}> = SignalOptions<T> & {
|
|
|
28
26
|
*/
|
|
29
27
|
value?: T;
|
|
30
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* Setup callback for `createSensor`. Invoked when the sensor gains its first downstream
|
|
31
|
+
* subscriber; receives a `set` function to push new values into the graph.
|
|
32
|
+
*
|
|
33
|
+
* @template T - The type of value produced by the sensor
|
|
34
|
+
* @param set - Updates the sensor value and propagates the change to subscribers
|
|
35
|
+
* @returns A cleanup function invoked when the sensor loses all subscribers
|
|
36
|
+
*/
|
|
31
37
|
type SensorCallback<T extends {}> = (set: (next: T) => void) => Cleanup;
|
|
32
38
|
/**
|
|
33
39
|
* Creates a sensor that tracks external input and updates a state value as long as it is active.
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { type Cleanup, TYPE_STORE } from '../graph';
|
|
2
2
|
import { type List, type UnknownRecord } from './list';
|
|
3
3
|
import { type State } from './state';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for `createStore`.
|
|
6
|
+
*/
|
|
4
7
|
type StoreOptions = {
|
|
8
|
+
/** Invoked when the store gains its first downstream subscriber; returns a cleanup called when the last one unsubscribes. */
|
|
5
9
|
watched?: () => Cleanup;
|
|
6
10
|
};
|
|
7
11
|
type BaseStore<T extends UnknownRecord> = {
|
|
@@ -19,6 +23,13 @@ type BaseStore<T extends UnknownRecord> = {
|
|
|
19
23
|
add<K extends keyof T & string>(key: K, value: T[K]): K;
|
|
20
24
|
remove(key: string): void;
|
|
21
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* A reactive object with per-property reactivity.
|
|
28
|
+
* Each property is wrapped as a `State`, nested `Store`, or `List` signal, accessible directly via proxy.
|
|
29
|
+
* Updating one property only re-runs effects that read that property.
|
|
30
|
+
*
|
|
31
|
+
* @template T - The plain-object type whose properties become reactive signals
|
|
32
|
+
*/
|
|
22
33
|
type Store<T extends UnknownRecord> = BaseStore<T> & {
|
|
23
34
|
[K in keyof T]: T[K] extends readonly (infer U extends {})[] ? List<U> : T[K] extends UnknownRecord ? Store<T[K]> : T[K] extends unknown & {} ? State<T[K] & {}> : State<T[K] & {}> | undefined;
|
|
24
35
|
};
|
package/types/src/signal.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ import { type Memo } from './nodes/memo';
|
|
|
4
4
|
import { type State } from './nodes/state';
|
|
5
5
|
import { type Store } from './nodes/store';
|
|
6
6
|
import { type Task } from './nodes/task';
|
|
7
|
+
/**
|
|
8
|
+
* A readable and writable signal — the type union of `State`, `Store`, and `List`.
|
|
9
|
+
* Use as a parameter type for generic code that accepts any writable signal.
|
|
10
|
+
*
|
|
11
|
+
* @template T - The type of value held by the signal
|
|
12
|
+
*/
|
|
7
13
|
type MutableSignal<T extends {}> = {
|
|
8
14
|
get(): T;
|
|
9
15
|
set(value: T): void;
|