@zeix/cause-effect 0.18.5 → 1.0.0
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 +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/bench/reactivity.bench.ts +18 -7
- package/biome.json +1 -1
- package/index.dev.js +11 -5
- package/index.js +1 -1
- package/index.ts +1 -1
- package/package.json +3 -4
- package/skills/cause-effect-dev/SKILL.md +114 -0
- package/skills/cause-effect-dev/agents/openai.yaml +4 -0
- package/src/graph.ts +6 -4
- package/src/nodes/collection.ts +4 -2
- package/src/nodes/list.ts +5 -2
- package/test/benchmark.test.ts +25 -11
- package/test/collection.test.ts +6 -3
- package/test/effect.test.ts +2 -1
- package/test/list.test.ts +8 -4
- package/test/regression.test.ts +4 -2
- package/test/store.test.ts +8 -4
- package/test/util/dependency-graph.ts +12 -6
- package/tsconfig.json +5 -1
- package/types/index.d.ts +1 -1
- package/types/src/graph.d.ts +2 -2
- package/OWNERSHIP_BUG.md +0 -95
package/ARCHITECTURE.md
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Stricter TypeScript configuration**: Enabled `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `useUnknownInCatchVariables`, `noUncheckedSideEffectImports`, and `noFallthroughCasesInSwitch` in `tsconfig.json`. All internal array and indexed object accesses have been updated to satisfy these checks. Runtime behaviour is unchanged.
|
|
8
|
+
- **`stop` on node types now typed as `Cleanup | undefined`**: The `stop` property in `SourceFields` (and by extension `StateNode`, `MemoNode`, `TaskNode`) is now declared `stop?: Cleanup | undefined` rather than `stop?: Cleanup`. Under `exactOptionalPropertyTypes`, this is required to allow clearing the property by assignment (`= undefined`) rather than deletion — preserving V8 hidden-class stability on hot-path nodes. Consumers reading `stop` from a node should already be handling `undefined` since the property is optional, but TypeScript will now surface this requirement explicitly.
|
|
9
|
+
- **`guard` on options types now requires explicit presence**: Under `exactOptionalPropertyTypes`, passing `{ guard: undefined }` to `SignalOptions`, `ComputedOptions`, or `SensorOptions` is now a type error. Omit the property entirely to leave it unset.
|
|
10
|
+
|
|
3
11
|
## 0.18.5
|
|
4
12
|
|
|
5
13
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cause & Effect
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 1.0.0
|
|
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
|
|
|
@@ -122,7 +122,8 @@ function setupMux(fw: ReactiveFramework) {
|
|
|
122
122
|
Object.fromEntries(heads.map(h => h.read()).entries()),
|
|
123
123
|
)
|
|
124
124
|
const splited = heads
|
|
125
|
-
|
|
125
|
+
// biome-ignore lint/style/noNonNullAssertion: fixed-size array
|
|
126
|
+
.map((_, index) => fw.computed(() => mux.read()[index]!))
|
|
126
127
|
.map(x => fw.computed(() => x.read() + 1))
|
|
127
128
|
for (const x of splited) {
|
|
128
129
|
fw.effect(() => {
|
|
@@ -133,7 +134,8 @@ function setupMux(fw: ReactiveFramework) {
|
|
|
133
134
|
return () => {
|
|
134
135
|
const idx = i % heads.length
|
|
135
136
|
fw.withBatch(() => {
|
|
136
|
-
|
|
137
|
+
// biome-ignore lint/style/noNonNullAssertion: fixed-size array
|
|
138
|
+
heads[idx]!.write(++i)
|
|
137
139
|
})
|
|
138
140
|
}
|
|
139
141
|
}
|
|
@@ -211,10 +213,16 @@ function setupCellx(fw: ReactiveFramework, layers: number) {
|
|
|
211
213
|
prop3: fw.signal(3),
|
|
212
214
|
prop4: fw.signal(4),
|
|
213
215
|
}
|
|
214
|
-
|
|
216
|
+
type CellxLayer = {
|
|
217
|
+
prop1: { read(): number }
|
|
218
|
+
prop2: { read(): number }
|
|
219
|
+
prop3: { read(): number }
|
|
220
|
+
prop4: { read(): number }
|
|
221
|
+
}
|
|
222
|
+
let layer: CellxLayer = start
|
|
215
223
|
|
|
216
224
|
for (let i = layers; i > 0; i--) {
|
|
217
|
-
const m = layer
|
|
225
|
+
const m: CellxLayer = layer
|
|
218
226
|
const s = {
|
|
219
227
|
prop1: fw.computed(() => m.prop2.read()),
|
|
220
228
|
prop2: fw.computed(() => m.prop1.read() - m.prop3.read()),
|
|
@@ -283,10 +291,13 @@ function setupMolWire(fw: ReactiveFramework) {
|
|
|
283
291
|
const D = fw.computed(() =>
|
|
284
292
|
numbers.map(i => ({ x: i + (A.read() % 2) - (B.read() % 2) })),
|
|
285
293
|
)
|
|
286
|
-
|
|
287
|
-
const
|
|
294
|
+
// biome-ignore lint/style/noNonNullAssertion: fixed-size array
|
|
295
|
+
const E = fw.computed(() => hard(C.read() + A.read() + D.read()[0]!.x, 'E'))
|
|
296
|
+
// biome-ignore lint/style/noNonNullAssertion: fixed-size array
|
|
297
|
+
const F = fw.computed(() => hard(D.read()[2]!.x || B.read(), 'F'))
|
|
288
298
|
const G = fw.computed(
|
|
289
|
-
|
|
299
|
+
// biome-ignore lint/style/noNonNullAssertion: fixed-size array
|
|
300
|
+
() => C.read() + (C.read() || E.read() % 2) + D.read()[4]!.x + F.read(),
|
|
290
301
|
)
|
|
291
302
|
fw.effect(() => {
|
|
292
303
|
hard(G.read(), 'H')
|
package/biome.json
CHANGED
package/index.dev.js
CHANGED
|
@@ -514,8 +514,9 @@ function diffArrays(prev, next, prevKeys, generateKey, contentBased) {
|
|
|
514
514
|
const prevByKey = new Map;
|
|
515
515
|
for (let i = 0;i < prev.length; i++) {
|
|
516
516
|
const key = prevKeys[i];
|
|
517
|
-
|
|
518
|
-
|
|
517
|
+
const item = prev[i];
|
|
518
|
+
if (key && item !== undefined)
|
|
519
|
+
prevByKey.set(key, item);
|
|
519
520
|
}
|
|
520
521
|
const seenKeys = new Set;
|
|
521
522
|
for (let i = 0;i < next.length; i++) {
|
|
@@ -679,7 +680,8 @@ function createList(value, options) {
|
|
|
679
680
|
list.set(fn(list.get()));
|
|
680
681
|
},
|
|
681
682
|
at(index) {
|
|
682
|
-
|
|
683
|
+
const key = keys[index];
|
|
684
|
+
return key !== undefined ? signals.get(key) : undefined;
|
|
683
685
|
},
|
|
684
686
|
keys() {
|
|
685
687
|
subscribe();
|
|
@@ -711,6 +713,8 @@ function createList(value, options) {
|
|
|
711
713
|
},
|
|
712
714
|
remove(keyOrIndex) {
|
|
713
715
|
const key = typeof keyOrIndex === "number" ? keys[keyOrIndex] : keyOrIndex;
|
|
716
|
+
if (key === undefined)
|
|
717
|
+
return;
|
|
714
718
|
const ok = signals.delete(key);
|
|
715
719
|
if (ok) {
|
|
716
720
|
const index = typeof keyOrIndex === "number" ? keyOrIndex : keys.indexOf(key);
|
|
@@ -1012,7 +1016,8 @@ function deriveCollection(source, callback) {
|
|
|
1012
1016
|
return node.value;
|
|
1013
1017
|
},
|
|
1014
1018
|
at(index) {
|
|
1015
|
-
|
|
1019
|
+
const key = keys[index];
|
|
1020
|
+
return key !== undefined ? signals.get(key) : undefined;
|
|
1016
1021
|
},
|
|
1017
1022
|
byKey(key) {
|
|
1018
1023
|
return signals.get(key);
|
|
@@ -1167,7 +1172,8 @@ function createCollection(watched, options) {
|
|
|
1167
1172
|
return node.value;
|
|
1168
1173
|
},
|
|
1169
1174
|
at(index) {
|
|
1170
|
-
|
|
1175
|
+
const key = keys[index];
|
|
1176
|
+
return key !== undefined ? signals.get(key) : undefined;
|
|
1171
1177
|
},
|
|
1172
1178
|
byKey(key) {
|
|
1173
1179
|
return signals.get(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,m=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 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)C$.push($)}}function m$($,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=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|=G}$.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(W){$.controller=void 0,$.error=W instanceof Error?W:Error(String(W));return}finally{m=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=p}function T$($){O$($);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 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=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 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(m)R(z,m);return z.value},set(X){O(c,X,z.guard),m$(z,X)},update(X){E(c,X);let W=X(z.value);O(c,W,z.guard),m$(z,W)}}}function N$($){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(T($)&&T(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 t$($,J,z,X,W){let Q=new WeakSet,D={},M={},C={},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(S,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))C[q]=null,H=!0;if(!H&&!K$(z,U))H=!0;return{add:D,change:M,remove:C,newKeys:U,changed:H}}function Q$($,J){O(S,$,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},C=(Z)=>{let B={};for(let N=0;N<Z.length;N++){let V=Z[N];if(V===void 0)continue;let K=X[N];if(!K)K=W(V),X[N]=K;B[K]=V}return B},U=(Z)=>{let B=!1;for(let N in Z.add){let V=Z.add[N];O(`${S} item for key "${N}"`,V),z.set(N,y(V)),B=!0}if(Object.keys(Z.change).length)z$(()=>{for(let N in Z.change){let V=Z.change[N];O(`${S} item for key "${N}"`,V);let K=z.get(N);if(K)K.set(V)}});for(let N in Z.remove){z.delete(N);let V=X.indexOf(N);if(V!==-1)X.splice(V,1);B=!0}if(B)M.flags|=b;return Z.changed},H=J?.watched,j=H?()=>{if(m){if(!M.sinks)M.stop=H();R(M,m)}}:()=>{if(m)R(M,m)},P=C($);for(let Z in P){let B=P[Z];O(`${S} item for key "${Z}"`,B),z.set(Z,y(B))}M.value=$,M.flags=0;let q={[Symbol.toStringTag]:S,[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,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 B=M.flags&G?D():M.value,N=t$(B,Z,X,W,Q);if(N.changed){X=N.newKeys,U(N),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){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(S,B,Z);if(!X.includes(B))X.push(B);O(`${S} item for key "${B}"`,Z),z.set(B,y(Z)),M.flags|=G|b;for(let N=M.sinks;N;N=N.nextSink)F(N.sink);if(x===0)I();return B},remove(Z){let B=typeof Z==="number"?X[Z]:Z;if(z.delete(B)){let V=typeof Z==="number"?Z:X.indexOf(B);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 N=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,N)){X=N,M.flags|=G;for(let V=M.sinks;V;V=V.nextSink)F(V.sink);if(x===0)I()}},splice(Z,B,...N){let V=X.length,K=Z<0?Math.max(0,V+Z):Math.min(Z,V),w=Math.max(0,Math.min(B??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 N){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(m){if(!z.sinks)z.stop=X(()=>{if(F(z),x===0)I()});R(z,m)}}:()=>{if(m)R(z,m)};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(m){if(!z.sinks)z.stop=X(()=>{if(F(z),x===0)I()});R(z,m)}}:()=>{if(m)R(z,m)};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=[],Q=(q)=>{let Z=z?q$(async(B,N)=>{let V=$.byKey(q)?.get();if(V==null)return B;return J(V,N)}):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 N of W)if(!B.has(N))X.delete(N);for(let N of q)if(!Z.has(N))Q(N);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 i))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,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)Q(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(m)R(U,m);return H(),W.length},keys(){if(m)R(U,m);return H(),W.values()},get(){if(m)R(U,m);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 s$($,J){let z=J?.value??[];if(z.length)O(s,z,Array.isArray);E(s,$,X$);let X=new Map,W=[],Q=new Map,[D,M]=E$(J?.keyConfig),C=(Z)=>Q.get(Z)??(M?D(Z):void 0),U=J?.createItem??y;function H(){let Z=[];for(let B of W)try{let N=X.get(B)?.get();if(N!=null)Z.push(N)}catch(N){if(!(N instanceof i))throw N}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(m){if(!j.sinks)j.stop=$((Z)=>{let{add:B,change:N,remove:V}=Z;if(!B?.length&&!N?.length&&!V?.length)return;let K=!1;z$(()=>{if(B)for(let w of B){let A=D(w);if(X.set(A,U(w)),Q.set(w,A),!W.includes(A))W.push(A);K=!0}if(N)for(let w of N){let A=C(w);if(!A)continue;let L=X.get(A);if(L&&N$(L))Q.delete(L.get()),L.set(w),Q.set(w,A)}if(V)for(let w of V){let A=C(w);if(!A)continue;Q.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,m)}}let q={[Symbol.toStringTag]:s,[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,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){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($,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,Q,D=!1,M=Array($.length);for(let U=0;U<$.length;U++)try{M[U]=$[U].get()}catch(H){if(H instanceof i){D=!0;continue}if(!Q)Q=[];Q.push(H instanceof Error?H:Error(String(H)))}let C;try{if(D)C=W?.();else if(Q)C=X(Q);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=_,H=new AbortController;J$(U,()=>H.abort()),C.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(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(m){if(!z.sinks)z.stop=$((X)=>{O(t,X,z.guard),m$(z,X)});R(z,m)}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,Q={},D={},M={},C=!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],C=!0}else Q[j]=J[j],C=!0;for(let j of U)if(!(j in J))M[j]=void 0,C=!0;return{add:Q,change:D,remove:M,changed:C}}function V$($,J){O(l,$,T);let z=new Map,X=(H,j)=>{if(O(`${l} for key "${H}"`,j),Array.isArray(j))z.set(H,Q$(j));else if(T(j))z.set(H,V$(j));else z.set(H,y(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];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 H.remove)z.delete(P),j=!0;if(j)Q.flags|=b;return H.changed},M=J?.watched,C=M?()=>{if(m){if(!Q.sinks)Q.stop=M();R(Q,m)}}:()=>{if(m)R(Q,m)};for(let H of Object.keys($))X(H,$[H]);let U={[Symbol.toStringTag]:l,[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 C(),z.keys()},byKey(H){return z.get(H)},get(){if(C(),Q.sources){if(Q.flags){let H=Q.flags&b;if(Q.value=v(W),H){if(Q.flags=G,f(Q),Q.error)throw Q.error}else Q.flags=p}}else if(f(Q),Q.error)throw Q.error;return Q.value},set(H){let j=Q.flags&G?W():Q.value,P=a$(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(l,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($,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 N$($)||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},Q=()=>{if(m)R(W,m);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:Q,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,N$ 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 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};
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeix/cause-effect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"author": "Esther Brunner",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"module": "index.ts",
|
|
8
8
|
"types": "types/index.d.ts",
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@biomejs/biome": "2.
|
|
10
|
+
"@biomejs/biome": "2.4.6",
|
|
11
11
|
"@types/bun": "latest",
|
|
12
12
|
"mitata": "^1.0.34",
|
|
13
13
|
"random": "^5.4.1"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"typescript": "^5.
|
|
16
|
+
"typescript": "^5.9.3"
|
|
17
17
|
},
|
|
18
18
|
"description": "Cause & Effect - reactive state management primitives library for TypeScript.",
|
|
19
19
|
"license": "MIT",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "bunx tsc --project tsconfig.build.json && bun build index.ts --outdir ./ --minify && bun build index.ts --outfile index.dev.js",
|
|
31
|
-
"build:next": "bunx tsc --project tsconfig.build.next.json && bun build next.ts --outdir ./ --minify && bun build next.ts --outfile next.dev.js",
|
|
32
31
|
"bench": "bun run bench/reactivity.bench.ts",
|
|
33
32
|
"test": "bun test",
|
|
34
33
|
"lint": "bunx biome lint --write"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cause-effect-dev
|
|
3
|
+
description: >
|
|
4
|
+
Expert developer for the @zeix/cause-effect reactive signals library. Use when
|
|
5
|
+
implementing features, fixing bugs, writing tests, or answering questions about
|
|
6
|
+
the library's internals, public API, or design decisions.
|
|
7
|
+
user_invocable: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Cause & Effect — Developer Skill
|
|
11
|
+
|
|
12
|
+
You are an expert developer on the **@zeix/cause-effect** reactive state management primitives library (v1.0.0). Work only from authoritative sources listed below. Never guess API shapes or behaviors — read the source.
|
|
13
|
+
|
|
14
|
+
## Authoritative Sources
|
|
15
|
+
|
|
16
|
+
| What you need | Where to look |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Vision, audience, constraints, non-goals | `REQUIREMENTS.md` |
|
|
19
|
+
| Mental model, non-obvious behaviors, TS constraints | `CLAUDE.md` |
|
|
20
|
+
| Full API reference with examples | `README.md` |
|
|
21
|
+
| Mapping from React/Vue/Angular patterns; when to use each signal type | `GUIDE.md` |
|
|
22
|
+
| Graph engine architecture, node shapes, propagation | `ARCHITECTURE.md` |
|
|
23
|
+
| Public API surface (all exports, types) | `index.ts` |
|
|
24
|
+
| Core graph engine (flags, propagation, flush, ownership) | `src/graph.ts` |
|
|
25
|
+
| Error classes | `src/errors.ts` |
|
|
26
|
+
| Signal base types and type guards | `src/signal.ts` |
|
|
27
|
+
| Shared utilities | `src/util.ts` |
|
|
28
|
+
|
|
29
|
+
## Source File Map
|
|
30
|
+
|
|
31
|
+
Each signal type lives in its own file:
|
|
32
|
+
|
|
33
|
+
| Signal | File | Create | Type guard |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| State | `src/nodes/state.ts` | `createState()` | `isState()` |
|
|
36
|
+
| Sensor | `src/nodes/sensor.ts` | `createSensor()` | `isSensor()` |
|
|
37
|
+
| Memo | `src/nodes/memo.ts` | `createMemo()` | `isMemo()` |
|
|
38
|
+
| Task | `src/nodes/task.ts` | `createTask()` | `isTask()` |
|
|
39
|
+
| Effect | `src/nodes/effect.ts` | `createEffect()` | — |
|
|
40
|
+
| Slot | `src/nodes/slot.ts` | `createSlot()` | `isSlot()` |
|
|
41
|
+
| Store | `src/nodes/store.ts` | `createStore()` | `isStore()` |
|
|
42
|
+
| List | `src/nodes/list.ts` | `createList()` | `isList()` |
|
|
43
|
+
| Collection | `src/nodes/collection.ts` | `createCollection()` / `deriveCollection()` | `isCollection()` |
|
|
44
|
+
|
|
45
|
+
`match()` and `MatchHandlers` live in `src/nodes/effect.ts` alongside `createEffect`.
|
|
46
|
+
|
|
47
|
+
## Internal Node Shapes
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
StateNode<T> — source only
|
|
51
|
+
MemoNode<T> — source + sink (also used by Slot, Store, List, Collection internals)
|
|
52
|
+
TaskNode<T> — source + sink + AbortController
|
|
53
|
+
EffectNode — sink + owner
|
|
54
|
+
Scope — owner only
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Two independent global pointers:
|
|
58
|
+
- `activeSink` — tracked for dependency edges (nulled by `untrack()`)
|
|
59
|
+
- `activeOwner` — tracked for cleanup registration (nulled by `unown()`)
|
|
60
|
+
|
|
61
|
+
## Key API Facts
|
|
62
|
+
|
|
63
|
+
- **`T extends {}`** — all signal generics exclude `null` and `undefined`. Use wrapper types or sentinel values to represent absence.
|
|
64
|
+
- **`createScope(fn)`** — returns a single `Cleanup` function. `fn` receives no arguments and returns an optional cleanup.
|
|
65
|
+
- **`createEffect(fn)`** — returns a `Cleanup`. Must be called inside an owner (effect or scope).
|
|
66
|
+
- **`batch(fn)`** — defers flush until `fn` returns; multiple state writes coalesce into one propagation.
|
|
67
|
+
- **`untrack(fn)`** — runs `fn` without recording dependency edges (nulls `activeSink`).
|
|
68
|
+
- **`unown(fn)`** — runs `fn` without registering cleanup in the current owner (nulls `activeOwner`). Use in `connectedCallback` for DOM-owned lifecycles.
|
|
69
|
+
- **`SKIP_EQUALITY`** — sentinel for `options.equals`; forces propagation on every update (use with mutable-reference sensors).
|
|
70
|
+
- **Memo/Task callbacks receive `prev`** — the previous value as first argument, enabling reducer patterns without external state.
|
|
71
|
+
- **`Slot` is a property descriptor** — has `get`, `set`, `configurable`, `enumerable`; can be passed directly to `Object.defineProperty()`.
|
|
72
|
+
|
|
73
|
+
## Non-Obvious Behaviors
|
|
74
|
+
|
|
75
|
+
**`byKey()`, `at()`, `keyAt()`, `indexOfKey()` do not create graph edges.** They are direct lookups. To react to structural changes (key added/removed), read `get()`, `keys()`, or `length`.
|
|
76
|
+
|
|
77
|
+
**Conditional reads delay `watched` activation.** Read signals eagerly before conditional logic to ensure `watched` fires immediately:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Good — both signals tracked on every run
|
|
81
|
+
createEffect(() => {
|
|
82
|
+
match([task, derived], { ok: ([r, v]) => render(v, r), nil: () => showSpinner() })
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Bad — derived only tracked after task resolves
|
|
86
|
+
createEffect(() => {
|
|
87
|
+
match([task], { ok: ([r]) => render(derived.get(), r), nil: () => showSpinner() })
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**`equals` suppresses entire subtrees.** When a Memo recomputes to the same value, downstream nodes receive `FLAG_CHECK` and are skipped without running. A custom `equals` on an intermediate Memo can suppress whole subgraphs.
|
|
92
|
+
|
|
93
|
+
**`watched` stays stable through mutations.** Structural mutations on a List/Collection source do not restart the `watched` callback; it stays active as long as any downstream effect is subscribed.
|
|
94
|
+
|
|
95
|
+
## Error Classes
|
|
96
|
+
|
|
97
|
+
| Class | When thrown |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `NullishSignalValueError` | Signal value is `null` or `undefined` |
|
|
100
|
+
| `InvalidSignalValueError` | Value fails the `guard` check |
|
|
101
|
+
| `InvalidCallbackError` | A required callback argument is not a function |
|
|
102
|
+
| `DuplicateKeyError` | List/Collection key collision |
|
|
103
|
+
| `UnsetSignalValueError` | Reading a Sensor/Task before it has produced a value |
|
|
104
|
+
| `ReadonlySignalError` | Writing to a read-only signal |
|
|
105
|
+
| `RequiredOwnerError` | `createEffect` called outside an owner |
|
|
106
|
+
| `CircularDependencyError` | Cycle detected in the graph |
|
|
107
|
+
|
|
108
|
+
## Workflow
|
|
109
|
+
|
|
110
|
+
1. **Read before writing.** Always read the relevant source file(s) before proposing or making changes.
|
|
111
|
+
2. **Check `ARCHITECTURE.md`** for graph-level questions (propagation, ownership, flag semantics).
|
|
112
|
+
3. **Check `README.md`** for public API usage patterns and option signatures.
|
|
113
|
+
4. **Check `REQUIREMENTS.md`** before adding anything new — the signal type set is complete and new types are explicitly out of scope.
|
|
114
|
+
5. **Run `bun test`** after changes to verify correctness.
|
package/src/graph.ts
CHANGED
|
@@ -6,12 +6,12 @@ type SourceFields<T extends {}> = {
|
|
|
6
6
|
value: T
|
|
7
7
|
sinks: Edge | null
|
|
8
8
|
sinksTail: Edge | null
|
|
9
|
-
stop?: Cleanup
|
|
9
|
+
stop?: Cleanup | undefined
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
type OptionsFields<T extends {}> = {
|
|
13
13
|
equals: (a: T, b: T) => boolean
|
|
14
|
-
guard?: Guard<T>
|
|
14
|
+
guard?: Guard<T> | undefined
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
type SinkFields = {
|
|
@@ -323,7 +323,8 @@ function runCleanup(owner: OwnerNode): void {
|
|
|
323
323
|
if (!owner.cleanup) return
|
|
324
324
|
|
|
325
325
|
if (Array.isArray(owner.cleanup))
|
|
326
|
-
|
|
326
|
+
// biome-ignore lint/style/noNonNullAssertion: index is always within bounds of a populated Cleanup[]
|
|
327
|
+
for (let i = 0; i < owner.cleanup.length; i++) owner.cleanup[i]!()
|
|
327
328
|
else owner.cleanup()
|
|
328
329
|
owner.cleanup = null
|
|
329
330
|
}
|
|
@@ -471,7 +472,8 @@ function flush(): void {
|
|
|
471
472
|
flushing = true
|
|
472
473
|
try {
|
|
473
474
|
for (let i = 0; i < queuedEffects.length; i++) {
|
|
474
|
-
|
|
475
|
+
// biome-ignore lint/style/noNonNullAssertion: index is always within bounds of a populated EffectNode[]
|
|
476
|
+
const effect = queuedEffects[i]!
|
|
475
477
|
if (effect.flags & (FLAG_DIRTY | FLAG_CHECK)) refresh(effect)
|
|
476
478
|
}
|
|
477
479
|
queuedEffects.length = 0
|
package/src/nodes/collection.ts
CHANGED
|
@@ -251,7 +251,8 @@ function deriveCollection<T extends {}, U extends {}>(
|
|
|
251
251
|
},
|
|
252
252
|
|
|
253
253
|
at(index: number) {
|
|
254
|
-
|
|
254
|
+
const key = keys[index]
|
|
255
|
+
return key !== undefined ? signals.get(key) : undefined
|
|
255
256
|
},
|
|
256
257
|
|
|
257
258
|
byKey(key: string) {
|
|
@@ -452,7 +453,8 @@ function createCollection<T extends {}>(
|
|
|
452
453
|
},
|
|
453
454
|
|
|
454
455
|
at(index: number) {
|
|
455
|
-
|
|
456
|
+
const key = keys[index]
|
|
457
|
+
return key !== undefined ? signals.get(key) : undefined
|
|
456
458
|
},
|
|
457
459
|
|
|
458
460
|
byKey(key: string) {
|
package/src/nodes/list.ts
CHANGED
|
@@ -189,7 +189,8 @@ function diffArrays<T>(
|
|
|
189
189
|
const prevByKey = new Map<string, T>()
|
|
190
190
|
for (let i = 0; i < prev.length; i++) {
|
|
191
191
|
const key = prevKeys[i]
|
|
192
|
-
|
|
192
|
+
const item = prev[i]
|
|
193
|
+
if (key && item !== undefined) prevByKey.set(key, item)
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
// Track which old keys we've seen
|
|
@@ -422,7 +423,8 @@ function createList<T extends {}>(
|
|
|
422
423
|
},
|
|
423
424
|
|
|
424
425
|
at(index: number) {
|
|
425
|
-
|
|
426
|
+
const key = keys[index]
|
|
427
|
+
return key !== undefined ? signals.get(key) : undefined
|
|
426
428
|
},
|
|
427
429
|
|
|
428
430
|
keys() {
|
|
@@ -458,6 +460,7 @@ function createList<T extends {}>(
|
|
|
458
460
|
remove(keyOrIndex: string | number) {
|
|
459
461
|
const key =
|
|
460
462
|
typeof keyOrIndex === 'number' ? keys[keyOrIndex] : keyOrIndex
|
|
463
|
+
if (key === undefined) return
|
|
461
464
|
const ok = signals.delete(key)
|
|
462
465
|
if (ok) {
|
|
463
466
|
const index =
|
package/test/benchmark.test.ts
CHANGED
|
@@ -298,7 +298,8 @@ for (const framework of [v18]) {
|
|
|
298
298
|
Object.fromEntries(heads.map(h => h.read()).entries()),
|
|
299
299
|
)
|
|
300
300
|
const splited = heads
|
|
301
|
-
|
|
301
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
302
|
+
.map((_, index) => framework.computed(() => mux.read()[index]!))
|
|
302
303
|
.map(x => framework.computed(() => x.read() + 1))
|
|
303
304
|
|
|
304
305
|
for (const x of splited) {
|
|
@@ -310,15 +311,19 @@ for (const framework of [v18]) {
|
|
|
310
311
|
return () => {
|
|
311
312
|
for (let i = 0; i < 10; i++) {
|
|
312
313
|
framework.withBatch(() => {
|
|
313
|
-
|
|
314
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
315
|
+
heads[i]!.write(i)
|
|
314
316
|
})
|
|
315
|
-
|
|
317
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
318
|
+
expect(splited[i]!.read()).toBe(i + 1)
|
|
316
319
|
}
|
|
317
320
|
for (let i = 0; i < 10; i++) {
|
|
318
321
|
framework.withBatch(() => {
|
|
319
|
-
|
|
322
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
323
|
+
heads[i]!.write(i * 2)
|
|
320
324
|
})
|
|
321
|
-
|
|
325
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
326
|
+
expect(splited[i]!.read()).toBe(i * 2 + 1)
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
})
|
|
@@ -455,16 +460,19 @@ for (const framework of [v18]) {
|
|
|
455
460
|
})),
|
|
456
461
|
)
|
|
457
462
|
const E = framework.computed(() =>
|
|
458
|
-
|
|
463
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
464
|
+
hard(C.read() + A.read() + D.read()[0]!.x, 'E'),
|
|
459
465
|
)
|
|
460
466
|
const F = framework.computed(() =>
|
|
461
|
-
|
|
467
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
468
|
+
hard(D.read()[2]!.x || B.read(), 'F'),
|
|
462
469
|
)
|
|
463
470
|
const G = framework.computed(
|
|
464
471
|
() =>
|
|
465
472
|
C.read() +
|
|
466
473
|
(C.read() || E.read() % 2) +
|
|
467
|
-
|
|
474
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
475
|
+
D.read()[4]!.x +
|
|
468
476
|
F.read(),
|
|
469
477
|
)
|
|
470
478
|
framework.effect(() => {
|
|
@@ -527,10 +535,16 @@ for (const framework of [v18]) {
|
|
|
527
535
|
prop3: framework.signal(3),
|
|
528
536
|
prop4: framework.signal(4),
|
|
529
537
|
}
|
|
530
|
-
|
|
538
|
+
type CellxLayer = {
|
|
539
|
+
prop1: Computed<number>
|
|
540
|
+
prop2: Computed<number>
|
|
541
|
+
prop3: Computed<number>
|
|
542
|
+
prop4: Computed<number>
|
|
543
|
+
}
|
|
544
|
+
let layer: CellxLayer = start
|
|
531
545
|
|
|
532
546
|
for (let i = layers; i > 0; i--) {
|
|
533
|
-
const m = layer
|
|
547
|
+
const m: CellxLayer = layer
|
|
534
548
|
const s = {
|
|
535
549
|
prop1: framework.computed(() => m.prop2.read()),
|
|
536
550
|
prop2: framework.computed(
|
|
@@ -598,7 +612,7 @@ for (const framework of [v18]) {
|
|
|
598
612
|
end.prop4.read(),
|
|
599
613
|
]
|
|
600
614
|
|
|
601
|
-
return [before, after]
|
|
615
|
+
return [before, after] as [number[], number[]]
|
|
602
616
|
}
|
|
603
617
|
|
|
604
618
|
for (const layers in expected) {
|
package/test/collection.test.ts
CHANGED
|
@@ -498,9 +498,12 @@ describe('Collection', () => {
|
|
|
498
498
|
|
|
499
499
|
const signals = [...doubled]
|
|
500
500
|
expect(signals).toHaveLength(3)
|
|
501
|
-
|
|
502
|
-
expect(signals[
|
|
503
|
-
|
|
501
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
502
|
+
expect(signals[0]!.get()).toBe(2)
|
|
503
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
504
|
+
expect(signals[1]!.get()).toBe(4)
|
|
505
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
506
|
+
expect(signals[2]!.get()).toBe(6)
|
|
504
507
|
})
|
|
505
508
|
|
|
506
509
|
test('should react to source additions', () => {
|
package/test/effect.test.ts
CHANGED
package/test/list.test.ts
CHANGED
|
@@ -323,7 +323,8 @@ describe('List', () => {
|
|
|
323
323
|
const list = createList([10, 20, 30])
|
|
324
324
|
const allKeys = [...list.keys()]
|
|
325
325
|
expect(allKeys).toHaveLength(3)
|
|
326
|
-
|
|
326
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
327
|
+
expect(list.byKey(allKeys[0]!)?.get()).toBe(10)
|
|
327
328
|
})
|
|
328
329
|
})
|
|
329
330
|
|
|
@@ -353,9 +354,12 @@ describe('List', () => {
|
|
|
353
354
|
const list = createList([10, 20, 30])
|
|
354
355
|
const signals = [...list]
|
|
355
356
|
expect(signals).toHaveLength(3)
|
|
356
|
-
|
|
357
|
-
expect(signals[
|
|
358
|
-
|
|
357
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
358
|
+
expect(signals[0]!.get()).toBe(10)
|
|
359
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
360
|
+
expect(signals[1]!.get()).toBe(20)
|
|
361
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
362
|
+
expect(signals[2]!.get()).toBe(30)
|
|
359
363
|
})
|
|
360
364
|
})
|
|
361
365
|
|
package/test/regression.test.ts
CHANGED
|
@@ -66,7 +66,8 @@ describe('Bundle size', () => {
|
|
|
66
66
|
entrypoints: ['./index.ts'],
|
|
67
67
|
minify: true,
|
|
68
68
|
})
|
|
69
|
-
|
|
69
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
70
|
+
const bytes = await result.outputs[0]!.arrayBuffer()
|
|
70
71
|
check('bundleMinified', bytes.byteLength, BUNDLE_MARGIN, 'B')
|
|
71
72
|
})
|
|
72
73
|
|
|
@@ -75,7 +76,8 @@ describe('Bundle size', () => {
|
|
|
75
76
|
entrypoints: ['./index.ts'],
|
|
76
77
|
minify: true,
|
|
77
78
|
})
|
|
78
|
-
|
|
79
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
80
|
+
const bytes = await result.outputs[0]!.arrayBuffer()
|
|
79
81
|
const gzipped = gzipSync(new Uint8Array(bytes)).byteLength
|
|
80
82
|
check('bundleGzipped', gzipped, BUNDLE_MARGIN, 'B')
|
|
81
83
|
})
|
package/test/store.test.ts
CHANGED
|
@@ -322,10 +322,14 @@ describe('Store', () => {
|
|
|
322
322
|
const user = createStore({ name: 'John', age: 25 })
|
|
323
323
|
const entries = [...user]
|
|
324
324
|
expect(entries).toHaveLength(2)
|
|
325
|
-
|
|
326
|
-
expect(entries[0][
|
|
327
|
-
|
|
328
|
-
expect(entries[
|
|
325
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
326
|
+
expect(entries[0]![0]).toBe('name')
|
|
327
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
328
|
+
expect(entries[0]![1].get()).toBe('John')
|
|
329
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
330
|
+
expect(entries[1]![0]).toBe('age')
|
|
331
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
332
|
+
expect(entries[1]![1].get()).toBe(25)
|
|
329
333
|
})
|
|
330
334
|
|
|
331
335
|
test('should maintain property key ordering', () => {
|
|
@@ -53,7 +53,8 @@ export function runGraph(
|
|
|
53
53
|
): number {
|
|
54
54
|
const rand = new Random('seed')
|
|
55
55
|
const { sources, layers } = graph
|
|
56
|
-
|
|
56
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
57
|
+
const leaves = layers[layers.length - 1]!
|
|
57
58
|
const skipCount = Math.round(leaves.length * (1 - readFraction))
|
|
58
59
|
const readLeaves = removeElems(leaves, skipCount, rand)
|
|
59
60
|
const frameworkName = framework.name.toLowerCase()
|
|
@@ -65,7 +66,8 @@ export function runGraph(
|
|
|
65
66
|
for (let i = 0; i < iterations; i++) {
|
|
66
67
|
framework.withBatch(() => {
|
|
67
68
|
const sourceDex = i % sources.length
|
|
68
|
-
|
|
69
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
70
|
+
sources[sourceDex]!.write(i + sourceDex)
|
|
69
71
|
})
|
|
70
72
|
|
|
71
73
|
for (const leaf of readLeaves) {
|
|
@@ -87,7 +89,8 @@ export function runGraph(
|
|
|
87
89
|
} */
|
|
88
90
|
|
|
89
91
|
const sourceDex = i % sources.length
|
|
90
|
-
|
|
92
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
93
|
+
sources[sourceDex]!.write(i + sourceDex)
|
|
91
94
|
|
|
92
95
|
for (const leaf of readLeaves) {
|
|
93
96
|
leaf.read()
|
|
@@ -153,7 +156,8 @@ function makeRow(
|
|
|
153
156
|
return sources.map((_, myDex) => {
|
|
154
157
|
const mySources: Computed<number>[] = []
|
|
155
158
|
for (let sourceDex = 0; sourceDex < nSources; sourceDex++) {
|
|
156
|
-
|
|
159
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
160
|
+
mySources.push(sources[(myDex + sourceDex) % sources.length]!)
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
const staticNode = random.float() < staticFraction
|
|
@@ -170,7 +174,8 @@ function makeRow(
|
|
|
170
174
|
})
|
|
171
175
|
} else {
|
|
172
176
|
// dynamic node, drops one of the sources depending on the value of the first element
|
|
173
|
-
|
|
177
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
178
|
+
const first = mySources[0]!
|
|
174
179
|
const tail = mySources.slice(1)
|
|
175
180
|
const node = framework.computed(() => {
|
|
176
181
|
counter.count++
|
|
@@ -180,7 +185,8 @@ function makeRow(
|
|
|
180
185
|
|
|
181
186
|
for (let i = 0; i < tail.length; i++) {
|
|
182
187
|
if (shouldDrop && i === dropDex) continue
|
|
183
|
-
|
|
188
|
+
// biome-ignore lint/style/noNonNullAssertion: test
|
|
189
|
+
sum += tail[i]!.read()
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
return sum
|
package/tsconfig.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
// Best practices
|
|
20
20
|
"strict": true,
|
|
21
21
|
"skipLibCheck": true,
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"exactOptionalPropertyTypes": true,
|
|
24
|
+
"useUnknownInCatchVariables": true,
|
|
25
|
+
"noUncheckedSideEffectImports": true,
|
|
22
26
|
"noFallthroughCasesInSwitch": true,
|
|
23
27
|
|
|
24
28
|
// Some stricter flags (disabled by default)
|
|
@@ -27,5 +31,5 @@
|
|
|
27
31
|
"noPropertyAccessFromIndexSignature": false,
|
|
28
32
|
},
|
|
29
33
|
"include": ["./**/*.ts"],
|
|
30
|
-
"exclude": ["node_modules", "types"],
|
|
34
|
+
"exclude": ["node_modules", "types", "index.js"],
|
|
31
35
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 1.0.0
|
|
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
|
@@ -3,11 +3,11 @@ type SourceFields<T extends {}> = {
|
|
|
3
3
|
value: T;
|
|
4
4
|
sinks: Edge | null;
|
|
5
5
|
sinksTail: Edge | null;
|
|
6
|
-
stop?: Cleanup;
|
|
6
|
+
stop?: Cleanup | undefined;
|
|
7
7
|
};
|
|
8
8
|
type OptionsFields<T extends {}> = {
|
|
9
9
|
equals: (a: T, b: T) => boolean;
|
|
10
|
-
guard?: Guard<T
|
|
10
|
+
guard?: Guard<T> | undefined;
|
|
11
11
|
};
|
|
12
12
|
type SinkFields = {
|
|
13
13
|
fn: unknown;
|
package/OWNERSHIP_BUG.md
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Ownership Bug: Component Scope Disposed by Parent Effect
|
|
2
|
-
|
|
3
|
-
## Symptom
|
|
4
|
-
|
|
5
|
-
In `module-todo`, `form-checkbox` elements wired via `checkboxes: pass(...)` lose their
|
|
6
|
-
reactive effects after the initial render — `setProperty('checked')` stops updating
|
|
7
|
-
`input.checked`, and the `on('change')` event listener is silently removed. Reading
|
|
8
|
-
`fc.checked` (a pull) still works correctly, but reactive push is gone.
|
|
9
|
-
|
|
10
|
-
## Root Cause
|
|
11
|
-
|
|
12
|
-
`createScope` registers its `dispose` on `prevOwner` — the `activeOwner` at the time the
|
|
13
|
-
scope is created. This is the right behavior for *hierarchical component trees* where a
|
|
14
|
-
parent component logically owns its children. But custom elements have a different ownership
|
|
15
|
-
model: **the DOM owns them**, via `connectedCallback` / `disconnectedCallback`.
|
|
16
|
-
|
|
17
|
-
The problem arises when a custom element's `connectedCallback` fires *inside* a
|
|
18
|
-
re-runnable reactive effect:
|
|
19
|
-
|
|
20
|
-
1. `module-todo`'s list sync effect runs inside `flush()` with `activeOwner = listSyncEffect`.
|
|
21
|
-
2. `list.append(li)` connects the `<li>`, which connects the `<form-checkbox>` inside it.
|
|
22
|
-
3. `form-checkbox.connectedCallback()` calls `runEffects(ui, setup(ui))`, which calls
|
|
23
|
-
`createScope`. `prevOwner = listSyncEffect`, so `dispose` is **registered on
|
|
24
|
-
`listSyncEffect`**.
|
|
25
|
-
4. Later, the `items = all('li[data-key]')` MutationObserver fires (the DOM mutation from
|
|
26
|
-
step 2 is detected) and re-queues `listSyncEffect`.
|
|
27
|
-
5. `runEffect(listSyncEffect)` calls `runCleanup(listSyncEffect)`, which calls all
|
|
28
|
-
registered cleanups — including `form-checkbox`'s `dispose`.
|
|
29
|
-
6. `dispose()` runs `runCleanup(fc1Scope)`, which removes the `on('change')` event
|
|
30
|
-
listener and trims the `setProperty` effect's reactive subscriptions.
|
|
31
|
-
7. The `<form-checkbox>` elements are still in the DOM, but their effects are permanently
|
|
32
|
-
gone. `connectedCallback` does not re-fire on already-connected elements.
|
|
33
|
-
|
|
34
|
-
The same problem recurs whenever `listSyncEffect` re-runs for any reason (e.g. a new todo
|
|
35
|
-
is added), disposing the scopes of all existing `<form-checkbox>` elements.
|
|
36
|
-
|
|
37
|
-
## Why `unown` Is the Correct Fix
|
|
38
|
-
|
|
39
|
-
`createScope`'s "register on `prevOwner`" semantics model one ownership relationship:
|
|
40
|
-
*parent reactive scope owns child*. Custom elements model a different one: *the DOM owns
|
|
41
|
-
the component*. `disconnectedCallback` is the authoritative cleanup trigger, not the
|
|
42
|
-
reactive graph.
|
|
43
|
-
|
|
44
|
-
`unown` is the explicit handshake that says "this scope is DOM-owned". It prevents
|
|
45
|
-
`createScope` from registering `dispose` on whatever reactive effect happens to be running
|
|
46
|
-
when `connectedCallback` fires, while leaving `this.#cleanup` + `disconnectedCallback` as
|
|
47
|
-
the sole lifecycle authority.
|
|
48
|
-
|
|
49
|
-
A `createScope`-only approach (without `unown`) has two failure modes:
|
|
50
|
-
|
|
51
|
-
| Scenario | Problem |
|
|
52
|
-
|---|---|
|
|
53
|
-
| Connects in static DOM (`activeOwner = null`) | `dispose` is discarded; effects never cleaned up on disconnect — memory leak |
|
|
54
|
-
| Connects inside a re-runnable effect | Same disposal bug as described above |
|
|
55
|
-
|
|
56
|
-
Per-item scopes (manually tracking a `Map<key, Cleanup>`) could also fix the disposal
|
|
57
|
-
problem but require significant restructuring of the list sync effect and still need
|
|
58
|
-
`unown` to prevent re-registration on each effect re-run.
|
|
59
|
-
|
|
60
|
-
## Required Changes
|
|
61
|
-
|
|
62
|
-
### `@zeix/cause-effect`
|
|
63
|
-
|
|
64
|
-
**`src/graph.ts`** — Add `unown` next to `untrack`:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
/**
|
|
68
|
-
* Runs a callback without any active owner.
|
|
69
|
-
* Any scopes or effects created inside the callback will not be registered as
|
|
70
|
-
* children of the current active owner (e.g. a re-runnable effect). Use this
|
|
71
|
-
* when a component or resource manages its own lifecycle independently of the
|
|
72
|
-
* reactive graph.
|
|
73
|
-
*
|
|
74
|
-
* @since 0.18.5
|
|
75
|
-
*/
|
|
76
|
-
function unown<T>(fn: () => T): T {
|
|
77
|
-
const prev = activeOwner
|
|
78
|
-
activeOwner = null
|
|
79
|
-
try {
|
|
80
|
-
return fn()
|
|
81
|
-
} finally {
|
|
82
|
-
activeOwner = prev
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Export it from the internal graph exports and from **`index.ts`**:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
export {
|
|
91
|
-
// ...existing exports...
|
|
92
|
-
unown,
|
|
93
|
-
untrack,
|
|
94
|
-
} from './src/graph'
|
|
95
|
-
```
|