@zeix/cause-effect 0.15.1 → 0.16.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.
Files changed (48) hide show
  1. package/.ai-context.md +254 -0
  2. package/.cursorrules +54 -0
  3. package/.github/copilot-instructions.md +132 -0
  4. package/CLAUDE.md +319 -0
  5. package/README.md +167 -159
  6. package/eslint.config.js +1 -1
  7. package/index.dev.js +528 -407
  8. package/index.js +1 -1
  9. package/index.ts +36 -25
  10. package/package.json +1 -1
  11. package/src/computed.ts +41 -30
  12. package/src/diff.ts +57 -44
  13. package/src/effect.ts +15 -16
  14. package/src/errors.ts +64 -0
  15. package/src/match.ts +2 -2
  16. package/src/resolve.ts +2 -2
  17. package/src/signal.ts +27 -49
  18. package/src/state.ts +27 -19
  19. package/src/store.ts +410 -209
  20. package/src/system.ts +122 -0
  21. package/src/util.ts +45 -6
  22. package/test/batch.test.ts +18 -11
  23. package/test/benchmark.test.ts +4 -4
  24. package/test/computed.test.ts +508 -72
  25. package/test/diff.test.ts +321 -4
  26. package/test/effect.test.ts +61 -61
  27. package/test/match.test.ts +38 -28
  28. package/test/resolve.test.ts +16 -16
  29. package/test/signal.test.ts +19 -147
  30. package/test/state.test.ts +212 -25
  31. package/test/store.test.ts +1370 -134
  32. package/test/util/dependency-graph.ts +1 -1
  33. package/types/index.d.ts +10 -9
  34. package/types/src/collection.d.ts +26 -0
  35. package/types/src/computed.d.ts +9 -9
  36. package/types/src/diff.d.ts +5 -3
  37. package/types/src/effect.d.ts +3 -3
  38. package/types/src/errors.d.ts +22 -0
  39. package/types/src/match.d.ts +1 -1
  40. package/types/src/resolve.d.ts +1 -1
  41. package/types/src/signal.d.ts +12 -19
  42. package/types/src/state.d.ts +5 -5
  43. package/types/src/store.d.ts +40 -36
  44. package/types/src/system.d.ts +44 -0
  45. package/types/src/util.d.ts +7 -5
  46. package/index.d.ts +0 -36
  47. package/src/scheduler.ts +0 -172
  48. package/types/test-new-effect.d.ts +0 -1
package/index.js CHANGED
@@ -1 +1 @@
1
- var q,f=new Set,p=0,h=new Map,k,r=()=>{k=void 0;let $=Array.from(h.values());h.clear();for(let B of $)B()},F$=()=>{if(k)cancelAnimationFrame(k);k=requestAnimationFrame(r)};queueMicrotask(r);var E=($)=>{let B=new Set,W=$;return W.off=(F)=>{B.add(F)},W.cleanup=()=>{for(let F of B)F();B.clear()},W},O=($)=>{if(q&&!$.has(q)){let B=q;$.add(B),q.off(()=>{$.delete(B)})}},C=($)=>{for(let B of $)if(p)f.add(B);else B()},w=()=>{while(f.size){let $=Array.from(f);f.clear();for(let B of $)B()}},v=($)=>{p++;try{$()}finally{w(),p--}},T=($,B)=>{let W=q;q=B;try{$()}finally{q=W}},G$=($,B)=>new Promise((W,F)=>{h.set(B||Symbol(),()=>{try{W($())}catch(Q){F(Q)}}),F$()});var a=($)=>typeof $==="string",e=($)=>typeof $==="number",D=($)=>typeof $==="function",y=($)=>D($)&&$.constructor.name==="AsyncFunction",P=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,R=($)=>P($,"Object"),$$=($)=>{if(!$.length)return null;let B=$.map((W)=>a(W)?parseInt(W,10):e(W)?W:NaN);return B.every((W)=>Number.isFinite(W)&&W>=0)?B.sort((W,F)=>W-F):null},B$=($,B)=>(B in $)&&D($[B]),V=($)=>$ instanceof DOMException&&$.name==="AbortError",Y=($)=>$ instanceof Error?$:Error(String($));class I extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var d="State",S=($)=>{let B=new Set,W=$,F={[Symbol.toStringTag]:d,get:()=>{return O(B),W},set:(Q)=>{if(K(W,Q))return;if(W=Q,C(B),z===W)B.clear()},update:(Q)=>{F.set(Q(W))}};return F},b=($)=>P($,d);var o=($)=>{let B=y($),W=!1,F,Q=E(()=>T(()=>{if(W)throw new I("effect");W=!0,F?.abort(),F=void 0;let Z;try{if(B){F=new AbortController;let A=F;$(F.signal).then((H)=>{if(D(H)&&F===A)Q.off(H)}).catch((H)=>{if(!V(H))console.error("Async effect error:",H)})}else if(Z=$(),D(Z))Q.off(Z)}catch(A){if(!V(A))console.error("Effect callback error:",A)}W=!1},Q));return Q(),()=>{F?.abort(),Q.cleanup()}};var c="Store",m=($)=>{let B=new Set,W=new EventTarget,F=new Map,Q=new Map,Z=S(0),A=()=>{let M=Array.from(F.keys()),L=$$(M);if(L)return L.map((J)=>F.get(String(J))?.get());let G={};for(let[J,X]of F)G[J]=X.get();return G},H=(M,L)=>W.dispatchEvent(new CustomEvent(M,{detail:L})),x=(M,L)=>{let G=String(M),J=n(L);F.set(G,J);let X=o(()=>{let j=J.get();if(j!=null)H("store-change",{[M]:j})});Q.set(G,X)},U=(M)=>{let L=String(M);F.delete(L);let G=Q.get(L);if(G)G();Q.delete(L)},N=(M,L)=>{let G=u(M,L);return v(()=>{if(Object.keys(G.add).length){for(let J in G.add){let X=G.add[J];if(X!=null)x(J,X)}H("store-add",G.add)}if(Object.keys(G.change).length){for(let J in G.change){let X=F.get(J),j=G.change[J];if(X&&j!=null&&B$(X,"set"))X.set(j)}H("store-change",G.change)}if(Object.keys(G.remove).length){for(let J in G.remove)U(J);H("store-remove",G.remove)}Z.set(F.size)}),G.changed};N({},$),setTimeout(()=>{let M=new CustomEvent("store-add",{detail:$});W.dispatchEvent(M)},0);let _=["add","get","remove","set","update","addEventListener","removeEventListener","dispatchEvent","size"];return new Proxy({},{get(M,L){switch(L){case"add":return(G,J)=>{if(!F.has(G))x(G,J),C(B),H("store-add",{[G]:J}),Z.set(F.size)};case"get":return()=>{return O(B),A()};case"remove":return(G)=>{if(F.has(G))U(G),C(B),H("store-remove",{[G]:z}),Z.set(F.size)};case"set":return(G)=>{if(N(A(),G)){if(C(B),z===G)B.clear()}};case"update":return(G)=>{let J=A(),X=G(J);if(N(J,X)){if(C(B),z===X)B.clear()}};case"addEventListener":return W.addEventListener.bind(W);case"removeEventListener":return W.removeEventListener.bind(W);case"dispatchEvent":return W.dispatchEvent.bind(W);case"size":return Z}if(L===Symbol.toStringTag)return c;if(L===Symbol.iterator)return function*(){for(let[G,J]of F)yield[G,J]};return F.get(String(L))},has(M,L){let G=String(L);return F.has(G)||_.includes(G)||L===Symbol.toStringTag||L===Symbol.iterator},ownKeys(){return Array.from(F.keys()).map((M)=>String(M))},getOwnPropertyDescriptor(M,L){let G=F.get(String(L));return G?{enumerable:!0,configurable:!0,writable:!0,value:G}:void 0}})},g=($)=>P($,c);var z=Symbol(),W$=($)=>b($)||t($)||g($);function J$($){if(W$($))return $;if(s($))return i($);if(Array.isArray($))return m($);if(Array.isArray($)||R($))return m($);return S($)}function n($){if(b($)||g($))return $;if(Array.isArray($))return m($);if(R($))return m($);return S($)}var K=($,B,W)=>{if(Object.is($,B))return!0;if(typeof $!==typeof B)return!1;if(typeof $!=="object"||$===null||B===null)return!1;if(!W)W=new WeakSet;if(W.has($)||W.has(B))throw new I("isEqual");W.add($),W.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let F=0;F<$.length;F++)if(!K($[F],B[F],W))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(R($)&&R(B)){let F=Object.keys($),Q=Object.keys(B);if(F.length!==Q.length)return!1;for(let Z of F){if(!(Z in B))return!1;if(!K($[Z],B[Z],W))return!1}return!0}return!1}finally{W.delete($),W.delete(B)}},u=($,B)=>{let W=new WeakSet;return((Q,Z)=>{let A={},H={},x={},U=Object.keys(Q),N=Object.keys(Z),_=new Set([...U,...N]);for(let L of _){let G=L in Q,J=L in Z;if(!G&&J){A[L]=Z[L];continue}else if(G&&!J){x[L]=z;continue}let X=Q[L],j=Z[L];if(!K(X,j,W))H[L]=j}return{changed:Object.keys(A).length>0||Object.keys(H).length>0||Object.keys(x).length>0,add:A,change:H,remove:x}})($,B)};var l="Computed",i=($)=>{let B=new Set,W=z,F,Q,Z=!0,A=!1,H=!1,x=(J)=>{if(!K(J,W))W=J,A=!0;F=void 0,Z=!1},U=()=>{A=z!==W,W=z,F=void 0},N=(J)=>{let X=Y(J);A=!F||X.name!==F.name||X.message!==F.message,W=z,F=X},_=(J)=>(X)=>{if(H=!1,Q=void 0,J(X),A)C(B)},M=E(()=>{if(Z=!0,Q?.abort(),B.size)C(B);else M.cleanup()});M.off(()=>{Q?.abort()});let L=()=>T(()=>{if(H)throw new I("computed");if(A=!1,y($)){if(Q)return W;Q=new AbortController,Q.signal.addEventListener("abort",()=>{H=!1,Q=void 0,L()},{once:!0})}let J;H=!0;try{J=Q?$(Q.signal):$()}catch(X){if(V(X))U();else N(X);H=!1;return}if(J instanceof Promise)J.then(_(x),_(N));else if(J==null||z===J)U();else x(J);H=!1},M);return{[Symbol.toStringTag]:l,get:()=>{if(O(B),w(),Z)L();if(F)throw F;return W}}},t=($)=>P($,l),s=($)=>D($)&&$.length<2;function L$($,B){try{if($.pending)B.nil?.();else if($.errors)B.err?.($.errors);else B.ok?.($.values)}catch(W){if(B.err&&(!$.errors||!$.errors.includes(Y(W))))B.err($.errors?[...$.errors,Y(W)]:[Y(W)]);else throw W}}function Q$($){let B=[],W=!1,F={};for(let[Q,Z]of Object.entries($))try{let A=Z.get();if(A===z)W=!0;else F[Q]=A}catch(A){B.push(Y(A))}if(W)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:F}}export{E as watch,J$ as toSignal,n as toMutableSignal,Y as toError,O as subscribe,m as store,S as state,Q$ as resolve,T as observe,C as notify,L$ as match,a as isString,g as isStore,b as isState,W$ as isSignal,e as isNumber,D as isFunction,K as isEqual,s as isComputedCallback,t as isComputed,y as isAsyncFunction,V as isAbortError,w as flush,G$ as enqueue,o as effect,u as diff,i as computed,v as batch,z as UNSET,c as TYPE_STORE,d as TYPE_STATE,l as TYPE_COMPUTED,I as CircularDependencyError};
1
+ class W extends Error{constructor($){super(`Circular dependency detected in ${$}`);this.name="CircularDependencyError"}}class P extends TypeError{constructor($,x){super(`Invalid ${$} callback ${x}`);this.name="InvalidCallbackError"}}class n extends TypeError{constructor($,x){super(`Invalid signal value ${x} in ${$}`);this.name="InvalidSignalValueError"}}class L extends TypeError{constructor($){super(`Nullish signal values are not allowed in ${$}`);this.name="NullishSignalValueError"}}class u extends Error{constructor($,x){super(`Could not add store key "${$}" with value ${x} because it already exists`);this.name="StoreKeyExistsError"}}class c extends RangeError{constructor($){super(`Could not remove store index ${String($)} because it is out of range`);this.name="StoreKeyRangeError"}}class t extends Error{constructor($,x){super(`Could not set store key "${$}" to ${x} because it is readonly`);this.name="StoreKeyReadonlyError"}}var K=Symbol(),x$=($)=>typeof $==="string",H$=($)=>typeof $==="number",p=($)=>typeof $==="symbol",Y=($)=>typeof $==="function",d=($)=>Y($)&&$.constructor.name==="AsyncFunction",E=($,x)=>Object.prototype.toString.call($)===`[object ${x}]`,A=($)=>E($,"Object"),s=($)=>A($)||Array.isArray($),K$=($)=>{if(!$.length)return null;let x=$.map((G)=>x$(G)?parseInt(G,10):H$(G)?G:NaN);return x.every((G)=>Number.isFinite(G)&&G>=0)?x.sort((G,J)=>G-J):null};var g=($)=>$ instanceof DOMException&&$.name==="AbortError",_=($)=>$ instanceof Error?$:Error(String($));var i=($)=>{let x=K$(Object.keys($));if(x===null)return $;let G=[];for(let J of x)G.push($[String(J)]);return G},U=($)=>x$($)?`"${$}"`:!!$&&typeof $==="object"?JSON.stringify($):String($);var f=($,x,G)=>{if(Object.is($,x))return!0;if(typeof $!==typeof x)return!1;if(typeof $!=="object"||$===null||x===null)return!1;if(!G)G=new WeakSet;if(G.has($)||G.has(x))throw new W("isEqual");G.add($),G.add(x);try{if(Array.isArray($)&&Array.isArray(x)){if($.length!==x.length)return!1;for(let J=0;J<$.length;J++)if(!f($[J],x[J],G))return!1;return!0}if(Array.isArray($)!==Array.isArray(x))return!1;if(A($)&&A(x)){let J=Object.keys($),Q=Object.keys(x);if(J.length!==Q.length)return!1;for(let M of J){if(!(M in x))return!1;if(!f($[M],x[M],G))return!1}return!0}return!1}finally{G.delete($),G.delete(x)}},R$=($,x)=>{let G=s($),J=s(x);if(!G||!J){let q=!Object.is($,x);return{changed:q,add:q&&J?x:{},change:{},remove:q&&G?$:{}}}let Q=new WeakSet,M={},I={},C={},D=Object.keys($),j=Object.keys(x),O=new Set([...D,...j]);for(let q of O){let N=q in $,T=q in x;if(!N&&T){M[q]=x[q];continue}else if(N&&!T){C[q]=K;continue}let B=$[q],R=x[q];if(!f(B,R,Q))I[q]=R}return{changed:Object.keys(M).length>0||Object.keys(I).length>0||Object.keys(C).length>0,add:M,change:I,remove:C}};var w,r=new Set,B$=0,V=($)=>{let x=new Set,G=$;return G.unwatch=(J)=>{x.add(J)},G.cleanup=()=>{for(let J of x)J();x.clear()},G},m=($)=>{if(w&&!$.has(w)){let x=w;x.unwatch(()=>{$.delete(x)}),$.add(x)}},F=($)=>{for(let x of $)if(B$)r.add(x);else x()},l=()=>{while(r.size){let $=Array.from(r);r.clear();for(let x of $)x()}},J$=($)=>{B$++;try{$()}finally{l(),B$--}},y=($,x)=>{let G=w;w=x;try{$()}finally{w=G}};var G$="Computed",M$=($,x=K)=>{if(!a($))throw new P("computed",U($));if(x==null)throw new L("computed");let G=new Set,J=x,Q,M,I=!0,C=!1,D=!1,j=(B)=>{if(!f(B,J))J=B,C=!0;Q=void 0,I=!1},O=()=>{C=K!==J,J=K,Q=void 0},S=(B)=>{let R=_(B);C=!Q||R.name!==Q.name||R.message!==Q.message,J=K,Q=R},q=(B)=>(R)=>{if(D=!1,M=void 0,B(R),C)F(G)},N=V(()=>{if(I=!0,M?.abort(),G.size)F(G);else N.cleanup()});N.unwatch(()=>{M?.abort()});let T=()=>y(()=>{if(D)throw new W("computed");if(C=!1,d($)){if(M)return J;M=new AbortController,M.signal.addEventListener("abort",()=>{D=!1,M=void 0,T()},{once:!0})}let B;D=!0;try{B=M?$(J,M.signal):$(J)}catch(R){if(g(R))O();else S(R);D=!1;return}if(B instanceof Promise)B.then(q(j),q(S));else if(B==null||K===B)O();else j(B);D=!1},N);return{[Symbol.toStringTag]:G$,get:()=>{if(m(G),l(),I)T();if(Q)throw Q;return J}}},o=($)=>E($,G$),a=($)=>Y($)&&$.length<3;var q$=($)=>{if(!Y($)||$.length>1)throw new P("effect",U($));let x=d($),G=!1,J,Q=V(()=>y(()=>{if(G)throw new W("effect");G=!0,J?.abort(),J=void 0;let M;try{if(x){J=new AbortController;let I=J;$(J.signal).then((C)=>{if(Y(C)&&J===I)Q.unwatch(C)}).catch((C)=>{if(!g(C))console.error("Async effect error:",C)})}else if(M=$(),Y(M))Q.unwatch(M)}catch(I){if(!g(I))console.error("Effect callback error:",I)}G=!1},Q));return Q(),()=>{J?.abort(),Q.cleanup()}};function D$($,x){try{if($.pending)x.nil?.();else if($.errors)x.err?.($.errors);else if($.ok)x.ok($.values)}catch(G){if(x.err&&(!$.errors||!$.errors.includes(_(G))))x.err($.errors?[...$.errors,_(G)]:[_(G)]);else throw G}}function z$($){let x=[],G=!1,J={};for(let[Q,M]of Object.entries($))try{let I=M.get();if(I===K)G=!0;else J[Q]=I}catch(I){x.push(_(I))}if(G)return{ok:!1,pending:!0};if(x.length>0)return{ok:!1,errors:x};return{ok:!0,values:J}}var Q$="State",b=($)=>{if($==null)throw new L("state");let x=new Set,G=$,J={[Symbol.toStringTag]:Q$,get:()=>{return m(x),G},set:(Q)=>{if(Q==null)throw new L("state");if(f(G,Q))return;if(G=Q,F(x),K===G)x.clear()},update:(Q)=>{if(!Y(Q))throw new P("state update",U(Q));J.set(Q(G))}};return J},k=($)=>E($,Q$);var e="Store",$$=($)=>{if($==null)throw new L("store");let x=new Set,G={add:new Set,change:new Set,remove:new Set,sort:new Set},J=new Map,Q=new Map,M=Array.isArray($),I=b(0),C=()=>{let B={};for(let[R,X]of J)B[R]=X.get();return B},D=(B,R)=>{Object.freeze(R);for(let X of G[B])X(R)},j=()=>Array.from(J.keys()).map((B)=>Number(B)).filter((B)=>Number.isInteger(B)).sort((B,R)=>B-R),O=(B,R)=>{if(R==null)throw new L(`store for key "${B}"`);if(R===K)return!0;if(p(R)||Y(R)||o(R))throw new n(`store for key "${B}"`,U(R));return!0},S=(B,R,X=!1)=>{if(!O(B,R))return!1;let Z=k(R)||h(R)?R:A(R)||Array.isArray(R)?$$(R):b(R);J.set(B,Z);let H=V(()=>y(()=>{D("change",{[B]:Z.get()})},H));if(H(),Q.set(B,H),X)I.set(J.size),F(x),D("add",{[B]:R});return!0},q=(B,R=!1)=>{let X=J.delete(B);if(X){let Z=Q.get(B);if(Z)Z.cleanup();Q.delete(B)}if(R)I.set(J.size),F(x),D("remove",{[B]:K});return X},N=(B,R,X)=>{let Z=R$(B,R);return J$(()=>{if(Object.keys(Z.add).length){for(let H in Z.add){let z=Z.add[H]??K;S(H,z)}if(X)setTimeout(()=>{D("add",Z.add)},0);else D("add",Z.add)}if(Object.keys(Z.change).length){for(let H in Z.change){let z=Z.change[H];if(!O(H,z))continue;let v=J.get(H);if(X$(v))v.set(z);else throw new t(H,U(z))}D("change",Z.change)}if(Object.keys(Z.remove).length){for(let H in Z.remove)q(H);D("remove",Z.remove)}I.set(J.size)}),Z.changed};N({},$,!0);let T={add:M?(B)=>{let R=J.size,X=String(R);S(X,B,!0)}:(B,R)=>{if(!J.has(B))S(B,R,!0);else throw new u(B,U(R))},get:()=>{return m(x),i(C())},remove:M?(B)=>{let R=i(C()),X=J.size;if(!Array.isArray(R)||B<=-X||B>=X)throw new c(B);let Z=[...R];if(Z.splice(B,1),N(R,Z))F(x)}:(B)=>{if(J.has(B))q(B,!0)},set:(B)=>{if(N(C(),B)){if(F(x),K===B)x.clear()}},update:(B)=>{let R=C(),X=B(i(R));if(N(R,X)){if(F(x),K===X)x.clear()}},sort:(B)=>{let R=Array.from(J.entries()).map(([H,z])=>[H,z.get()]).sort(B?(H,z)=>B(H[1],z[1]):(H,z)=>String(H[1]).localeCompare(String(z[1]))),X=R.map(([H])=>String(H)),Z=new Map;R.forEach(([H],z)=>{let v=String(H),C$=M?String(z):String(H),Z$=J.get(v);if(Z$)Z.set(C$,Z$)}),J.clear(),Z.forEach((H,z)=>J.set(z,H)),F(x),D("sort",X)},on:(B,R)=>{return G[B].add(R),()=>G[B].delete(R)},size:I};return new Proxy({},{get(B,R){if(R===Symbol.toStringTag)return e;if(R===Symbol.isConcatSpreadable)return M;if(R===Symbol.iterator)return M?function*(){let X=j();for(let Z of X){let H=J.get(String(Z));if(H)yield H}}:function*(){for(let[X,Z]of J)yield[X,Z]};if(p(R))return;if(R in T)return T[R];if(R==="length"&&M)return m(x),I.get();return J.get(R)},has(B,R){let X=String(R);return X&&J.has(X)||Object.keys(T).includes(X)||R===Symbol.toStringTag||R===Symbol.iterator||R===Symbol.isConcatSpreadable||R==="length"&&M},ownKeys(){return M?j().map((B)=>String(B)).concat(["length"]):Array.from(J.keys()).map((B)=>String(B))},getOwnPropertyDescriptor(B,R){let X=(H)=>({enumerable:!1,configurable:!0,writable:!1,value:H});if(R==="length"&&M)return{enumerable:!0,configurable:!0,writable:!1,value:I.get()};if(R===Symbol.isConcatSpreadable)return X(M);if(R===Symbol.toStringTag)return X(e);if(p(R))return;if(Object.keys(T).includes(R))return X(T[R]);let Z=J.get(R);return Z?{enumerable:!0,configurable:!0,writable:!0,value:Z}:void 0}})},h=($)=>E($,e);var I$=($)=>k($)||o($)||h($),X$=($)=>k($)||h($);function F$($){if(I$($))return $;if(a($))return M$($);if(Array.isArray($)||A($))return $$($);return b($)}export{U as valueString,F$ as toSignal,_ as toError,m as subscribe,z$ as resolve,y as observe,F as notify,D$ as match,p as isSymbol,x$ as isString,h as isStore,k as isState,I$ as isSignal,s as isRecordOrArray,A as isRecord,H$ as isNumber,X$ as isMutableSignal,Y as isFunction,f as isEqual,a as isComputedCallback,o as isComputed,d as isAsyncFunction,g as isAbortError,l as flush,R$ as diff,V as createWatcher,$$ as createStore,b as createState,q$ as createEffect,M$ as createComputed,J$ as batch,K as UNSET,e as TYPE_STORE,Q$ as TYPE_STATE,G$ as TYPE_COMPUTED,t as StoreKeyReadonlyError,c as StoreKeyRangeError,u as StoreKeyExistsError,L as NullishSignalValueError,n as InvalidSignalValueError,P as InvalidCallbackError,W as CircularDependencyError};
package/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.15.1
3
+ * @version 0.16.0
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
7
7
  export {
8
8
  type Computed,
9
9
  type ComputedCallback,
10
- computed,
10
+ createComputed,
11
11
  isComputed,
12
12
  isComputedCallback,
13
13
  TYPE_COMPUTED,
@@ -16,51 +16,62 @@ export {
16
16
  type DiffResult,
17
17
  diff,
18
18
  isEqual,
19
+ type UnknownArray,
19
20
  type UnknownRecord,
20
21
  type UnknownRecordOrArray,
21
22
  } from './src/diff'
22
- export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
23
+ export {
24
+ createEffect,
25
+ type EffectCallback,
26
+ type MaybeCleanup,
27
+ } from './src/effect'
28
+ export {
29
+ CircularDependencyError,
30
+ InvalidCallbackError,
31
+ InvalidSignalValueError,
32
+ NullishSignalValueError,
33
+ StoreKeyExistsError,
34
+ StoreKeyRangeError,
35
+ StoreKeyReadonlyError,
36
+ } from './src/errors'
23
37
  export { type MatchHandlers, match } from './src/match'
24
38
  export { type ResolveResult, resolve } from './src/resolve'
25
39
  export {
26
- batch,
27
- type Cleanup,
28
- enqueue,
29
- flush,
30
- notify,
31
- observe,
32
- subscribe,
33
- type Updater,
34
- type Watcher,
35
- watch,
36
- } from './src/scheduler'
37
- export {
40
+ isMutableSignal,
38
41
  isSignal,
39
- type MaybeSignal,
40
42
  type Signal,
41
43
  type SignalValues,
42
- toMutableSignal,
43
44
  toSignal,
44
- UNSET,
45
45
  type UnknownSignalRecord,
46
46
  } from './src/signal'
47
- export { isState, type State, state, TYPE_STATE } from './src/state'
47
+ export { createState, isState, type State, TYPE_STATE } from './src/state'
48
48
  export {
49
+ createStore,
49
50
  isStore,
50
51
  type Store,
51
- type StoreAddEvent,
52
- type StoreChangeEvent,
53
- type StoreEventMap,
54
- type StoreRemoveEvent,
55
- store,
52
+ type StoreChanges,
56
53
  TYPE_STORE,
57
54
  } from './src/store'
58
55
  export {
59
- CircularDependencyError,
56
+ batch,
57
+ type Cleanup,
58
+ createWatcher,
59
+ flush,
60
+ notify,
61
+ observe,
62
+ subscribe,
63
+ type Watcher,
64
+ } from './src/system'
65
+ export {
60
66
  isAbortError,
61
67
  isAsyncFunction,
62
68
  isFunction,
63
69
  isNumber,
70
+ isRecord,
71
+ isRecordOrArray,
64
72
  isString,
73
+ isSymbol,
65
74
  toError,
75
+ UNSET,
76
+ valueString,
66
77
  } from './src/util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
package/src/computed.ts CHANGED
@@ -1,31 +1,36 @@
1
1
  import { isEqual } from './diff'
2
2
  import {
3
+ CircularDependencyError,
4
+ InvalidCallbackError,
5
+ NullishSignalValueError,
6
+ } from './errors'
7
+ import {
8
+ createWatcher,
3
9
  flush,
4
10
  notify,
5
11
  observe,
6
12
  subscribe,
7
13
  type Watcher,
8
- watch,
9
- } from './scheduler'
10
- import { UNSET } from './signal'
14
+ } from './system'
11
15
  import {
12
- CircularDependencyError,
13
16
  isAbortError,
14
17
  isAsyncFunction,
15
18
  isFunction,
16
19
  isObjectOfType,
17
20
  toError,
21
+ UNSET,
22
+ valueString,
18
23
  } from './util'
19
24
 
20
25
  /* === Types === */
21
26
 
22
27
  type Computed<T extends {}> = {
23
- [Symbol.toStringTag]: 'Computed'
28
+ readonly [Symbol.toStringTag]: 'Computed'
24
29
  get(): T
25
30
  }
26
31
  type ComputedCallback<T extends {} & { then?: undefined }> =
27
- | ((abort: AbortSignal) => Promise<T>)
28
- | (() => T)
32
+ | ((oldValue: T, abort: AbortSignal) => Promise<T>)
33
+ | ((oldValue: T) => T)
29
34
 
30
35
  /* === Constants === */
31
36
 
@@ -37,14 +42,21 @@ const TYPE_COMPUTED = 'Computed'
37
42
  * Create a derived signal from existing signals
38
43
  *
39
44
  * @since 0.9.0
40
- * @param {ComputedCallback<T>} fn - computation callback function
45
+ * @param {ComputedCallback<T>} callback - Computation callback function
41
46
  * @returns {Computed<T>} - Computed signal
42
47
  */
43
- const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
48
+ const createComputed = <T extends {}>(
49
+ callback: ComputedCallback<T>,
50
+ initialValue: T = UNSET,
51
+ ): Computed<T> => {
52
+ if (!isComputedCallback(callback))
53
+ throw new InvalidCallbackError('computed', valueString(callback))
54
+ if (initialValue == null) throw new NullishSignalValueError('computed')
55
+
44
56
  const watchers: Set<Watcher> = new Set()
45
57
 
46
58
  // Internal state
47
- let value: T = UNSET
59
+ let value: T = initialValue
48
60
  let error: Error | undefined
49
61
  let controller: AbortController | undefined
50
62
  let dirty = true
@@ -75,22 +87,22 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
75
87
  error = newError
76
88
  }
77
89
  const settle =
78
- <T>(settleFn: (arg: T) => void) =>
90
+ <T>(fn: (arg: T) => void) =>
79
91
  (arg: T) => {
80
92
  computing = false
81
93
  controller = undefined
82
- settleFn(arg)
94
+ fn(arg)
83
95
  if (changed) notify(watchers)
84
96
  }
85
97
 
86
98
  // Own watcher: called when notified from sources (push)
87
- const mark = watch(() => {
99
+ const watcher = createWatcher(() => {
88
100
  dirty = true
89
101
  controller?.abort()
90
102
  if (watchers.size) notify(watchers)
91
- else mark.cleanup()
103
+ else watcher.cleanup()
92
104
  })
93
- mark.off(() => {
105
+ watcher.unwatch(() => {
94
106
  controller?.abort()
95
107
  })
96
108
 
@@ -99,7 +111,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
99
111
  observe(() => {
100
112
  if (computing) throw new CircularDependencyError('computed')
101
113
  changed = false
102
- if (isAsyncFunction(fn)) {
114
+ if (isAsyncFunction(callback)) {
103
115
  // Return current value until promise resolves
104
116
  if (controller) return value
105
117
  controller = new AbortController()
@@ -108,9 +120,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
108
120
  () => {
109
121
  computing = false
110
122
  controller = undefined
111
-
112
- // Retry computation with updated state
113
- compute()
123
+ compute() // Retry computation with updated state
114
124
  },
115
125
  {
116
126
  once: true,
@@ -120,7 +130,9 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
120
130
  let result: T | Promise<T>
121
131
  computing = true
122
132
  try {
123
- result = controller ? fn(controller.signal) : (fn as () => T)()
133
+ result = controller
134
+ ? callback(value, controller.signal)
135
+ : (callback as (oldValue: T) => T)(value)
124
136
  } catch (e) {
125
137
  if (isAbortError(e)) nil()
126
138
  else err(e)
@@ -131,16 +143,16 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
131
143
  else if (null == result || UNSET === result) nil()
132
144
  else ok(result)
133
145
  computing = false
134
- }, mark)
146
+ }, watcher)
135
147
 
136
- const c: Computed<T> = {
148
+ return {
137
149
  [Symbol.toStringTag]: TYPE_COMPUTED,
138
150
 
139
151
  /**
140
152
  * Get the current value of the computed
141
153
  *
142
154
  * @since 0.9.0
143
- * @returns {T} - current value of the computed
155
+ * @returns {T} - Current value of the computed
144
156
  */
145
157
  get: (): T => {
146
158
  subscribe(watchers)
@@ -150,15 +162,14 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
150
162
  return value
151
163
  },
152
164
  }
153
- return c
154
165
  }
155
166
 
156
167
  /**
157
- * Check if a value is a computed state
168
+ * Check if a value is a computed signal
158
169
  *
159
170
  * @since 0.9.0
160
- * @param {unknown} value - value to check
161
- * @returns {boolean} - true if value is a computed state, false otherwise
171
+ * @param {unknown} value - Value to check
172
+ * @returns {boolean} - true if value is a computed signal, false otherwise
162
173
  */
163
174
  const isComputed = /*#__PURE__*/ <T extends {}>(
164
175
  value: unknown,
@@ -168,18 +179,18 @@ const isComputed = /*#__PURE__*/ <T extends {}>(
168
179
  * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
169
180
  *
170
181
  * @since 0.12.0
171
- * @param {unknown} value - value to check
182
+ * @param {unknown} value - Value to check
172
183
  * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
173
184
  */
174
185
  const isComputedCallback = /*#__PURE__*/ <T extends {}>(
175
186
  value: unknown,
176
- ): value is ComputedCallback<T> => isFunction(value) && value.length < 2
187
+ ): value is ComputedCallback<T> => isFunction(value) && value.length < 3
177
188
 
178
189
  /* === Exports === */
179
190
 
180
191
  export {
181
192
  TYPE_COMPUTED,
182
- computed,
193
+ createComputed,
183
194
  isComputed,
184
195
  isComputedCallback,
185
196
  type Computed,
package/src/diff.ts CHANGED
@@ -1,12 +1,14 @@
1
- import { UNSET } from './signal'
2
- import { CircularDependencyError, isRecord } from './util'
1
+ import { CircularDependencyError } from './errors'
2
+ import { isRecord, isRecordOrArray, UNSET } from './util'
3
3
 
4
4
  /* === Types === */
5
5
 
6
6
  type UnknownRecord = Record<string, unknown & {}>
7
- type UnknownRecordOrArray = {
8
- [x: string | number]: unknown & {}
7
+ type UnknownArray = ReadonlyArray<unknown & {}>
8
+ type ArrayToRecord<T extends UnknownArray> = {
9
+ [key: string]: T extends Array<infer U extends {}> ? U : never
9
10
  }
11
+ type UnknownRecordOrArray = UnknownRecord | ArrayToRecord<UnknownArray>
10
12
 
11
13
  type DiffResult<T extends UnknownRecordOrArray = UnknownRecord> = {
12
14
  changed: boolean
@@ -69,6 +71,8 @@ const isEqual = <T>(a: T, b: T, visited?: WeakSet<object>): boolean => {
69
71
  return true
70
72
  }
71
73
 
74
+ // For non-records/non-arrays, they are only equal if they are the same reference
75
+ // (which would have been caught by Object.is at the beginning)
72
76
  return false
73
77
  } finally {
74
78
  visited.delete(a as object)
@@ -88,61 +92,70 @@ const diff = <T extends UnknownRecordOrArray>(
88
92
  oldObj: T,
89
93
  newObj: T,
90
94
  ): DiffResult<T> => {
91
- const visited = new WeakSet<object>()
92
-
93
- const diffRecords = (
94
- oldRecord: Record<string, unknown>,
95
- newRecord: Record<string, unknown>,
96
- ): DiffResult<T> => {
97
- const add: Partial<T> = {}
98
- const change: Partial<T> = {}
99
- const remove: Partial<T> = {}
100
-
101
- const oldKeys = Object.keys(oldRecord)
102
- const newKeys = Object.keys(newRecord)
103
- const allKeys = new Set([...oldKeys, ...newKeys])
104
-
105
- for (const key of allKeys) {
106
- const oldHas = key in oldRecord
107
- const newHas = key in newRecord
108
-
109
- if (!oldHas && newHas) {
110
- add[key as keyof T] = newRecord[key] as T[keyof T]
111
- continue
112
- } else if (oldHas && !newHas) {
113
- remove[key as keyof T] = UNSET
114
- continue
115
- }
95
+ // Guard against non-objects that can't be diffed properly with Object.keys and 'in' operator
96
+ const oldValid = isRecordOrArray(oldObj)
97
+ const newValid = isRecordOrArray(newObj)
98
+ if (!oldValid || !newValid) {
99
+ // For non-objects or non-plain objects, treat as complete change if different
100
+ const changed = !Object.is(oldObj, newObj)
101
+ return {
102
+ changed,
103
+ add: changed && newValid ? newObj : {},
104
+ change: {},
105
+ remove: changed && oldValid ? oldObj : {},
106
+ }
107
+ }
116
108
 
117
- const oldValue = oldRecord[key] as T[keyof T]
118
- const newValue = newRecord[key] as T[keyof T]
109
+ const visited = new WeakSet()
119
110
 
120
- if (!isEqual(oldValue, newValue, visited))
121
- change[key as keyof T] = newValue
122
- }
111
+ const add: Partial<T> = {}
112
+ const change: Partial<T> = {}
113
+ const remove: Partial<T> = {}
123
114
 
124
- const changed =
125
- Object.keys(add).length > 0 ||
126
- Object.keys(change).length > 0 ||
127
- Object.keys(remove).length > 0
115
+ const oldKeys = Object.keys(oldObj)
116
+ const newKeys = Object.keys(newObj)
117
+ const allKeys = new Set([...oldKeys, ...newKeys])
128
118
 
129
- return {
130
- changed,
131
- add,
132
- change,
133
- remove,
119
+ for (const key of allKeys) {
120
+ const oldHas = key in oldObj
121
+ const newHas = key in newObj
122
+
123
+ if (!oldHas && newHas) {
124
+ add[key as keyof T] = newObj[key] as T[keyof T]
125
+ continue
126
+ } else if (oldHas && !newHas) {
127
+ remove[key as keyof T] = UNSET
128
+ continue
134
129
  }
130
+
131
+ const oldValue = oldObj[key] as T[keyof T]
132
+ const newValue = newObj[key] as T[keyof T]
133
+
134
+ if (!isEqual(oldValue, newValue, visited))
135
+ change[key as keyof T] = newValue
135
136
  }
136
137
 
137
- return diffRecords(oldObj, newObj)
138
+ const changed =
139
+ Object.keys(add).length > 0 ||
140
+ Object.keys(change).length > 0 ||
141
+ Object.keys(remove).length > 0
142
+
143
+ return {
144
+ changed,
145
+ add,
146
+ change,
147
+ remove,
148
+ }
138
149
  }
139
150
 
140
151
  /* === Exports === */
141
152
 
142
153
  export {
154
+ type ArrayToRecord,
143
155
  type DiffResult,
144
156
  diff,
145
157
  isEqual,
146
158
  type UnknownRecord,
159
+ type UnknownArray,
147
160
  type UnknownRecordOrArray,
148
161
  }
package/src/effect.ts CHANGED
@@ -1,10 +1,6 @@
1
- import { type Cleanup, observe, watch } from './scheduler'
2
- import {
3
- CircularDependencyError,
4
- isAbortError,
5
- isAsyncFunction,
6
- isFunction,
7
- } from './util'
1
+ import { CircularDependencyError, InvalidCallbackError } from './errors'
2
+ import { type Cleanup, createWatcher, observe } from './system'
3
+ import { isAbortError, isAsyncFunction, isFunction, valueString } from './util'
8
4
 
9
5
  /* === Types === */
10
6
 
@@ -28,12 +24,15 @@ type EffectCallback =
28
24
  * @param {EffectCallback} callback - Synchronous or asynchronous effect callback
29
25
  * @returns {Cleanup} - Cleanup function for the effect
30
26
  */
31
- const effect = (callback: EffectCallback): Cleanup => {
32
- const isAsync = isAsyncFunction<MaybeCleanup>(callback)
27
+ const createEffect = (callback: EffectCallback): Cleanup => {
28
+ if (!isFunction(callback) || callback.length > 1)
29
+ throw new InvalidCallbackError('effect', valueString(callback))
30
+
31
+ const isAsync = isAsyncFunction(callback)
33
32
  let running = false
34
33
  let controller: AbortController | undefined
35
34
 
36
- const run = watch(() =>
35
+ const watcher = createWatcher(() =>
37
36
  observe(() => {
38
37
  if (running) throw new CircularDependencyError('effect')
39
38
  running = true
@@ -56,7 +55,7 @@ const effect = (callback: EffectCallback): Cleanup => {
56
55
  isFunction(cleanup) &&
57
56
  controller === currentController
58
57
  )
59
- run.off(cleanup)
58
+ watcher.unwatch(cleanup)
60
59
  })
61
60
  .catch(error => {
62
61
  if (!isAbortError(error))
@@ -64,7 +63,7 @@ const effect = (callback: EffectCallback): Cleanup => {
64
63
  })
65
64
  } else {
66
65
  cleanup = (callback as () => MaybeCleanup)()
67
- if (isFunction(cleanup)) run.off(cleanup)
66
+ if (isFunction(cleanup)) watcher.unwatch(cleanup)
68
67
  }
69
68
  } catch (error) {
70
69
  if (!isAbortError(error))
@@ -72,16 +71,16 @@ const effect = (callback: EffectCallback): Cleanup => {
72
71
  }
73
72
 
74
73
  running = false
75
- }, run),
74
+ }, watcher),
76
75
  )
77
76
 
78
- run()
77
+ watcher()
79
78
  return () => {
80
79
  controller?.abort()
81
- run.cleanup()
80
+ watcher.cleanup()
82
81
  }
83
82
  }
84
83
 
85
84
  /* === Exports === */
86
85
 
87
- export { type MaybeCleanup, type EffectCallback, effect }
86
+ export { type MaybeCleanup, type EffectCallback, createEffect }
package/src/errors.ts ADDED
@@ -0,0 +1,64 @@
1
+ class CircularDependencyError extends Error {
2
+ constructor(where: string) {
3
+ super(`Circular dependency detected in ${where}`)
4
+ this.name = 'CircularDependencyError'
5
+ }
6
+ }
7
+
8
+ class InvalidCallbackError extends TypeError {
9
+ constructor(where: string, value: string) {
10
+ super(`Invalid ${where} callback ${value}`)
11
+ this.name = 'InvalidCallbackError'
12
+ }
13
+ }
14
+
15
+ class InvalidSignalValueError extends TypeError {
16
+ constructor(where: string, value: string) {
17
+ super(`Invalid signal value ${value} in ${where}`)
18
+ this.name = 'InvalidSignalValueError'
19
+ }
20
+ }
21
+
22
+ class NullishSignalValueError extends TypeError {
23
+ constructor(where: string) {
24
+ super(`Nullish signal values are not allowed in ${where}`)
25
+ this.name = 'NullishSignalValueError'
26
+ }
27
+ }
28
+
29
+ class StoreKeyExistsError extends Error {
30
+ constructor(key: string, value: string) {
31
+ super(
32
+ `Could not add store key "${key}" with value ${value} because it already exists`,
33
+ )
34
+ this.name = 'StoreKeyExistsError'
35
+ }
36
+ }
37
+
38
+ class StoreKeyRangeError extends RangeError {
39
+ constructor(index: number) {
40
+ super(
41
+ `Could not remove store index ${String(index)} because it is out of range`,
42
+ )
43
+ this.name = 'StoreKeyRangeError'
44
+ }
45
+ }
46
+
47
+ class StoreKeyReadonlyError extends Error {
48
+ constructor(key: string, value: string) {
49
+ super(
50
+ `Could not set store key "${key}" to ${value} because it is readonly`,
51
+ )
52
+ this.name = 'StoreKeyReadonlyError'
53
+ }
54
+ }
55
+
56
+ export {
57
+ CircularDependencyError,
58
+ InvalidCallbackError,
59
+ InvalidSignalValueError,
60
+ NullishSignalValueError,
61
+ StoreKeyExistsError,
62
+ StoreKeyRangeError,
63
+ StoreKeyReadonlyError,
64
+ }
package/src/match.ts CHANGED
@@ -5,7 +5,7 @@ import { toError } from './util'
5
5
  /* === Types === */
6
6
 
7
7
  type MatchHandlers<S extends UnknownSignalRecord> = {
8
- ok?: (values: SignalValues<S>) => void
8
+ ok: (values: SignalValues<S>) => void
9
9
  err?: (errors: readonly Error[]) => void
10
10
  nil?: () => void
11
11
  }
@@ -31,7 +31,7 @@ function match<S extends UnknownSignalRecord>(
31
31
  try {
32
32
  if (result.pending) handlers.nil?.()
33
33
  else if (result.errors) handlers.err?.(result.errors)
34
- else handlers.ok?.(result.values as SignalValues<S>)
34
+ else if (result.ok) handlers.ok(result.values)
35
35
  } catch (error) {
36
36
  // If handler throws, try error handler, otherwise rethrow
37
37
  if (
package/src/resolve.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { UnknownRecord } from './diff'
2
- import { type SignalValues, UNSET, type UnknownSignalRecord } from './signal'
3
- import { toError } from './util'
2
+ import type { SignalValues, UnknownSignalRecord } from './signal'
3
+ import { toError, UNSET } from './util'
4
4
 
5
5
  /* === Types === */
6
6