@zeix/cause-effect 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1 +1 @@
1
- var q,f=new Set,p=0,h=new Map,k,l=()=>{k=void 0;let $=Array.from(h.values());h.clear();for(let B of $)B()},B$=()=>{if(k)cancelAnimationFrame(k);k=requestAnimationFrame(l)};queueMicrotask(l);var m=($)=>{let B=new Set,W=$;return W.off=(x)=>{B.add(x)},W.cleanup=()=>{for(let x of B)x();B.clear()},W},U=($)=>{if(q&&!$.has(q)){let B=q;$.add(B),q.off(()=>{$.delete(B)})}},M=($)=>{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--}},y=($,B)=>{let W=q;q=B;try{$()}finally{q=W}},W$=($,B)=>new Promise((W,x)=>{h.set(B||Symbol(),()=>{try{W($())}catch(J){x(J)}}),B$()});var A=($)=>typeof $==="function",E=($)=>A($)&&$.constructor.name==="AsyncFunction",O=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,V=($)=>O($,"Object");var d=($)=>{let B={};for(let W=0;W<$.length;W++)if(W in $)B[String(W)]=$[W];return B},a=($,B)=>(B in $)&&A($[B]),R=($)=>$ instanceof DOMException&&$.name==="AbortError",Y=($)=>$ instanceof Error?$:Error(String($));class D extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var o="State",S=($)=>{let B=new Set,W=$,x={[Symbol.toStringTag]:o,get:()=>{return U(B),W},set:(J)=>{if(P(W,J))return;if(W=J,M(B),C===W)B.clear()},update:(J)=>{x.set(J(W))}};return x},b=($)=>O($,o);var c=($)=>{let B=E($),W=!1,x,J=m(()=>y(()=>{if(W)throw new D("effect");W=!0,x?.abort(),x=void 0;let F;try{if(B){x=new AbortController;let H=x;$(x.signal).then((z)=>{if(A(z)&&x===H)J.off(z)}).catch((z)=>{if(!R(z))console.error("Async effect error:",z)})}else if(F=$(),A(F))J.off(F)}catch(H){if(!R(H))console.error("Effect callback error:",H)}W=!1},J));return J(),()=>{x?.abort(),J.cleanup()}};var n="Store",T=($)=>{let B=new Set,W=new EventTarget,x=new Map,J=new Map,F=S(0),H=()=>{let Z={};for(let[L,Q]of x)Z[L]=Q.get();return Z},z=(Z,L)=>W.dispatchEvent(new CustomEvent(Z,{detail:L})),K=(Z,L)=>{let Q=e(L);x.set(Z,Q);let G=c(()=>{let X=Q.get();if(X!=null)z("store-change",{[Z]:X})});J.set(Z,G)},N=(Z)=>{x.delete(Z);let L=J.get(Z);if(L)L();J.delete(Z)},j=(Z,L)=>{let Q=u(Z,L);return v(()=>{if(Object.keys(Q.add).length){for(let G in Q.add){let X=Q.add[G];if(X!=null)K(G,X)}z("store-add",Q.add)}if(Object.keys(Q.change).length){for(let G in Q.change){let X=x.get(G),I=Q.change[G];if(X&&I!=null&&a(X,"set"))X.set(I)}z("store-change",Q.change)}if(Object.keys(Q.remove).length){for(let G in Q.remove)N(G);z("store-remove",Q.remove)}F.set(x.size)}),Q.changed};j({},$),setTimeout(()=>{let Z=new CustomEvent("store-add",{detail:$});W.dispatchEvent(Z)},0);let _=["add","get","remove","set","update","addEventListener","removeEventListener","dispatchEvent","size"];return new Proxy({},{get(Z,L){let Q=String(L);switch(L){case"add":return(G,X)=>{if(!x.has(G))K(G,X),M(B),z("store-add",{[G]:X}),F.set(x.size)};case"get":return()=>{return U(B),H()};case"remove":return(G)=>{if(x.has(G))N(G),M(B),z("store-remove",{[G]:C}),F.set(x.size)};case"set":return(G)=>{if(j(H(),G)){if(M(B),C===G)B.clear()}};case"update":return(G)=>{let X=H(),I=G(X);if(j(X,I)){if(M(B),C===I)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 F}if(L===Symbol.toStringTag)return n;if(L===Symbol.iterator)return function*(){for(let[G,X]of x)yield[G,X]};return x.get(Q)},has(Z,L){let Q=String(L);return x.has(Q)||_.includes(Q)||L===Symbol.toStringTag||L===Symbol.iterator},ownKeys(){return Array.from(x.keys())},getOwnPropertyDescriptor(Z,L){let Q=x.get(String(L));return Q?{enumerable:!0,configurable:!0,writable:!0,value:Q}:void 0}})},g=($)=>O($,n);var C=Symbol(),$$=($)=>b($)||t($)||g($);function x$($){if($$($))return $;if(s($))return i($);if(Array.isArray($))return T(d($));if(V($))return T($);return S($)}function e($){if(b($)||g($))return $;if(Array.isArray($))return T(d($));if(V($))return T($);return S($)}var P=($,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 D("isEqual");W.add($),W.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let x=0;x<$.length;x++)if(!P($[x],B[x],W))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(V($)&&V(B)){let x=Object.keys($),J=Object.keys(B);if(x.length!==J.length)return!1;for(let F of x){if(!(F in B))return!1;if(!P($[F],B[F],W))return!1}return!0}return!1}finally{W.delete($),W.delete(B)}},u=($,B)=>{let W=new WeakSet;return((J,F)=>{let H={},z={},K={},N=Object.keys(J),j=Object.keys(F),_=new Set([...N,...j]);for(let L of _){let Q=L in J,G=L in F;if(!Q&&G){H[L]=F[L];continue}else if(Q&&!G){K[L]=C;continue}let X=J[L],I=F[L];if(!P(X,I,W))z[L]=I}return{changed:Object.keys(H).length>0||Object.keys(z).length>0||Object.keys(K).length>0,add:H,change:z,remove:K}})($,B)};var r="Computed",i=($)=>{let B=new Set,W=C,x,J,F=!0,H=!1,z=!1,K=(G)=>{if(!P(G,W))W=G,H=!0;x=void 0,F=!1},N=()=>{H=C!==W,W=C,x=void 0},j=(G)=>{let X=Y(G);H=!x||X.name!==x.name||X.message!==x.message,W=C,x=X},_=(G)=>(X)=>{if(z=!1,J=void 0,G(X),H)M(B)},Z=m(()=>{if(F=!0,J?.abort(),B.size)M(B);else Z.cleanup()});Z.off(()=>{J?.abort()});let L=()=>y(()=>{if(z)throw new D("computed");if(H=!1,E($)){if(J)return W;J=new AbortController,J.signal.addEventListener("abort",()=>{z=!1,J=void 0,L()},{once:!0})}let G;z=!0;try{G=J?$(J.signal):$()}catch(X){if(R(X))N();else j(X);z=!1;return}if(G instanceof Promise)G.then(_(K),_(j));else if(G==null||C===G)N();else K(G);z=!1},Z);return{[Symbol.toStringTag]:r,get:()=>{if(U(B),w(),F)L();if(x)throw x;return W}}},t=($)=>O($,r),s=($)=>A($)&&$.length<2;function G$($,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)))){let x=$.errors?[...$.errors,Y(W)]:[Y(W)];B.err(x)}else throw W}}function J$($){let B=[],W=!1,x={};for(let[J,F]of Object.entries($))try{let H=F.get();if(H===C)W=!0;else x[J]=H}catch(H){B.push(Y(H))}if(W)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:x}}export{m as watch,x$ as toSignal,Y as toError,U as subscribe,T as store,S as state,J$ as resolve,y as observe,M as notify,G$ as match,g as isStore,b as isState,$$ as isSignal,A as isFunction,P as isEqual,s as isComputedCallback,t as isComputed,E as isAsyncFunction,R as isAbortError,w as flush,W$ as enqueue,c as effect,u as diff,i as computed,v as batch,C as UNSET,n as TYPE_STORE,o as TYPE_STATE,r as TYPE_COMPUTED,D as CircularDependencyError};
1
+ class A extends Error{constructor($){super(`Circular dependency detected in ${$}`);this.name="CircularDependencyError"}}class c extends TypeError{constructor($,B){super(`Invalid signal value ${B} in ${$}`);this.name="InvalidSignalValueError"}}class V extends TypeError{constructor($){super(`Nullish signal values are not allowed in ${$}`);this.name="NullishSignalValueError"}}class n extends Error{constructor($,B){super(`Could not add store key "${$}" with value ${B} because it already exists`);this.name="StoreKeyExistsError"}}class v extends RangeError{constructor($){super(`Could not remove store index ${String($)} because it is out of range`);this.name="StoreKeyRangeError"}}class t extends Error{constructor($,B){super(`Could not set store key "${$}" to ${B} because it is readonly`);this.name="StoreKeyReadonlyError"}}var z=Symbol(),$$=($)=>typeof $==="string",M$=($)=>typeof $==="number",w=($)=>typeof $==="symbol",q=($)=>typeof $==="function",g=($)=>q($)&&$.constructor.name==="AsyncFunction",U$=($)=>!!$&&typeof $==="object",f=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,L=($)=>f($,"Object"),u=($)=>L($)||Array.isArray($),q$=($)=>{if(!$.length)return null;let B=$.map((Q)=>$$(Q)?parseInt(Q,10):M$(Q)?Q:NaN);return B.every((Q)=>Number.isFinite(Q)&&Q>=0)?B.sort((Q,X)=>Q-X):null};var O=($)=>$ instanceof DOMException&&$.name==="AbortError",N=($)=>$ instanceof Error?$:Error(String($));var s=($)=>{let B=q$(Object.keys($));if(B===null)return $;let Q=[];for(let X of B)Q.push($[String(X)]);return Q},i=($)=>$$($)?`"${$}"`:U$($)?JSON.stringify($):String($);var P=($,B,Q)=>{if(Object.is($,B))return!0;if(typeof $!==typeof B)return!1;if(typeof $!=="object"||$===null||B===null)return!1;if(!Q)Q=new WeakSet;if(Q.has($)||Q.has(B))throw new A("isEqual");Q.add($),Q.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let X=0;X<$.length;X++)if(!P($[X],B[X],Q))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(L($)&&L(B)){let X=Object.keys($),Z=Object.keys(B);if(X.length!==Z.length)return!1;for(let R of X){if(!(R in B))return!1;if(!P($[R],B[R],Q))return!1}return!0}return!1}finally{Q.delete($),Q.delete(B)}},B$=($,B)=>{let Q=u($),X=u(B);if(!Q||!X){let M=!Object.is($,B);return{changed:M,add:M&&X?B:{},change:{},remove:M&&Q?$:{}}}let Z=new WeakSet,R={},K={},H={},Y=Object.keys($),m=Object.keys(B),S=new Set([...Y,...m]);for(let M of S){let D=M in $,U=M in B;if(!D&&U){R[M]=B[M];continue}else if(D&&!U){H[M]=z;continue}let W=$[M],J=B[M];if(!P(W,J,Z))K[M]=J}return{changed:Object.keys(R).length>0||Object.keys(K).length>0||Object.keys(H).length>0,add:R,change:K,remove:H}};var T,r=new Set,J$=0,W$=new Map,l,z$=()=>{l=void 0;let $=Array.from(W$.values());W$.clear();for(let B of $)B()},L$=()=>{if(l)cancelAnimationFrame(l);l=requestAnimationFrame(z$)};queueMicrotask(z$);var b=($)=>{let B=new Set,Q=$;return Q.off=(X)=>{B.add(X)},Q.cleanup=()=>{for(let X of B)X();B.clear()},Q},j=($)=>{if(T&&!$.has(T)){let B=T;$.add(B),T.off(()=>{$.delete(B)})}},I=($)=>{for(let B of $)if(J$)r.add(B);else B()},a=()=>{while(r.size){let $=Array.from(r);r.clear();for(let B of $)B()}},Q$=($)=>{J$++;try{$()}finally{a(),J$--}},k=($,B)=>{let Q=T;T=B;try{$()}finally{T=Q}},N$=($,B)=>new Promise((Q,X)=>{W$.set(B||Symbol(),()=>{try{Q($())}catch(Z){X(Z)}}),L$()});var X$="Computed",Z$=($)=>{let B=new Set,Q=z,X,Z,R=!0,K=!1,H=!1,Y=(W)=>{if(!P(W,Q))Q=W,K=!0;X=void 0,R=!1},m=()=>{K=z!==Q,Q=z,X=void 0},S=(W)=>{let J=N(W);K=!X||J.name!==X.name||J.message!==X.message,Q=z,X=J},_=(W)=>(J)=>{if(H=!1,Z=void 0,W(J),K)I(B)},M=b(()=>{if(R=!0,Z?.abort(),B.size)I(B);else M.cleanup()});M.off(()=>{Z?.abort()});let D=()=>k(()=>{if(H)throw new A("computed");if(K=!1,g($)){if(Z)return Q;Z=new AbortController,Z.signal.addEventListener("abort",()=>{H=!1,Z=void 0,D()},{once:!0})}let W;H=!0;try{W=Z?$(Z.signal):$()}catch(J){if(O(J))m();else S(J);H=!1;return}if(W instanceof Promise)W.then(_(Y),_(S));else if(W==null||z===W)m();else Y(W);H=!1},M);return{[Symbol.toStringTag]:X$,get:()=>{if(j(B),a(),R)D();if(X)throw X;return Q}}},h=($)=>f($,X$),x$=($)=>q($)&&$.length<2;var C$=($)=>{let B=g($),Q=!1,X,Z=b(()=>k(()=>{if(Q)throw new A("effect");Q=!0,X?.abort(),X=void 0;let R;try{if(B){X=new AbortController;let K=X;$(X.signal).then((H)=>{if(q(H)&&X===K)Z.off(H)}).catch((H)=>{if(!O(H))console.error("Async effect error:",H)})}else if(R=$(),q(R))Z.off(R)}catch(K){if(!O(K))console.error("Effect callback error:",K)}Q=!1},Z));return Z(),()=>{X?.abort(),Z.cleanup()}};function A$($,B){try{if($.pending)B.nil?.();else if($.errors)B.err?.($.errors);else if($.ok)B.ok($.values)}catch(Q){if(B.err&&(!$.errors||!$.errors.includes(N(Q))))B.err($.errors?[...$.errors,N(Q)]:[N(Q)]);else throw Q}}function P$($){let B=[],Q=!1,X={};for(let[Z,R]of Object.entries($))try{let K=R.get();if(K===z)Q=!0;else X[Z]=K}catch(K){B.push(N(K))}if(Q)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:X}}var G$="State",y=($)=>{let B=new Set,Q=$,X={[Symbol.toStringTag]:G$,get:()=>{return j(B),Q},set:(Z)=>{if(Z==null)throw new V("state");if(P(Q,Z))return;if(Q=Z,I(B),z===Q)B.clear()},update:(Z)=>{X.set(Z(Q))}};return X},E=($)=>f($,G$);var e="Store",R$="store-add",F$="store-change",Y$="store-remove",j$="store-sort",p=($)=>{let B=new Set,Q=new EventTarget,X=new Map,Z=new Map,R=Array.isArray($),K=y(0),H=()=>{let W={};for(let[J,C]of X)W[J]=C.get();return W},Y=(W,J)=>Q.dispatchEvent(new CustomEvent(W,{detail:J})),m=()=>Array.from(X.keys()).map((W)=>Number(W)).filter((W)=>Number.isInteger(W)).sort((W,J)=>W-J),S=(W,J)=>{if(J==null)throw new V(`store for key "${W}"`);if(J===z)return!0;if(w(J)||q(J)||h(J))throw new c(`store for key "${W}"`,i(J));return!0},_=(W,J,C=!1)=>{if(!S(W,J))return!1;let x=E(J)||o(J)?J:L(J)?p(J):Array.isArray(J)?p(J):y(J);X.set(W,x);let G=C$(()=>{let F=x.get();if(F!=null)Y(F$,{[W]:F})});if(Z.set(W,G),C)K.set(X.size),I(B),Y(R$,{[W]:J});return!0},M=(W,J=!1)=>{let C=X.delete(W);if(C){let x=Z.get(W);if(x)x();Z.delete(W)}if(J)K.set(X.size),I(B),Y(Y$,{[W]:z});return C},D=(W,J,C)=>{let x=B$(W,J);return Q$(()=>{if(Object.keys(x.add).length){for(let G in x.add){let F=x.add[G]??z;_(G,F)}if(C)setTimeout(()=>{Y(R$,x.add)},0);else Y(R$,x.add)}if(Object.keys(x.change).length){for(let G in x.change){let F=x.change[G];if(!S(G,F))continue;let d=X.get(G);if(K$(d))d.set(F);else throw new t(G,i(F))}Y(F$,x.change)}if(Object.keys(x.remove).length){for(let G in x.remove)M(G);Y(Y$,x.remove)}K.set(X.size)}),x.changed};D({},$,!0);let U={add:R?(W)=>{let J=X.size,C=String(J);_(C,W,!0)}:(W,J)=>{if(!X.has(W))_(W,J,!0);else throw new n(W,i(J))},get:()=>{return j(B),s(H())},remove:R?(W)=>{let J=s(H()),C=X.size;if(!Array.isArray(J)||W<=-C||W>=C)throw new v(W);let x=[...J];if(x.splice(W,1),D(J,x))I(B)}:(W)=>{if(X.has(W))M(W,!0)},set:(W)=>{if(D(H(),W)){if(I(B),z===W)B.clear()}},update:(W)=>{let J=H(),C=W(s(J));if(D(J,C)){if(I(B),z===C)B.clear()}},sort:(W)=>{let J=Array.from(X.entries()).map(([G,F])=>[G,F.get()]).sort(W?(G,F)=>W(G[1],F[1]):(G,F)=>String(G[1]).localeCompare(String(F[1]))),C=J.map(([G])=>String(G)),x=new Map;J.forEach(([G],F)=>{let d=String(G),D$=R?String(F):String(G),H$=X.get(d);if(H$)x.set(D$,H$)}),X.clear(),x.forEach((G,F)=>X.set(F,G)),I(B),Y(j$,C)},addEventListener:Q.addEventListener.bind(Q),removeEventListener:Q.removeEventListener.bind(Q),dispatchEvent:Q.dispatchEvent.bind(Q),size:K};return new Proxy({},{get(W,J){if(J===Symbol.toStringTag)return e;if(J===Symbol.isConcatSpreadable)return R;if(J===Symbol.iterator)return R?function*(){let C=m();for(let x of C){let G=X.get(String(x));if(G)yield G}}:function*(){for(let[C,x]of X)yield[C,x]};if(w(J))return;if(J in U)return U[J];if(J==="length"&&R)return j(B),K.get();return X.get(J)},has(W,J){let C=String(J);return C&&X.has(C)||Object.keys(U).includes(C)||J===Symbol.toStringTag||J===Symbol.iterator||J===Symbol.isConcatSpreadable||J==="length"&&R},ownKeys(){return R?m().map((W)=>String(W)).concat(["length"]):Array.from(X.keys()).map((W)=>String(W))},getOwnPropertyDescriptor(W,J){let C=(G)=>({enumerable:!1,configurable:!0,writable:!1,value:G});if(J==="length"&&R)return{enumerable:!0,configurable:!0,writable:!1,value:K.get()};if(J===Symbol.isConcatSpreadable)return C(R);if(J===Symbol.toStringTag)return C(e);if(w(J))return;if(Object.keys(U).includes(J))return C(U[J]);let x=X.get(J);return x?{enumerable:!0,configurable:!0,writable:!0,value:x}:void 0}})},o=($)=>f($,e);var I$=($)=>E($)||h($)||o($),K$=($)=>E($)||o($);function m$($){if(I$($))return $;if(x$($))return Z$($);if(Array.isArray($)||L($))return p($);return y($)}export{b as watch,m$ as toSignal,N as toError,j as subscribe,p as store,y as state,P$ as resolve,k as observe,I as notify,A$ as match,w as isSymbol,$$ as isString,o as isStore,E as isState,I$ as isSignal,u as isRecordOrArray,L as isRecord,M$ as isNumber,K$ as isMutableSignal,q as isFunction,P as isEqual,x$ as isComputedCallback,h as isComputed,g as isAsyncFunction,O as isAbortError,a as flush,N$ as enqueue,C$ as effect,B$ as diff,Z$ as computed,Q$ as batch,z as UNSET,e as TYPE_STORE,G$ as TYPE_STATE,X$ as TYPE_COMPUTED,t as StoreKeyReadonlyError,v as StoreKeyRangeError,n as StoreKeyExistsError,V as NullishSignalValueError,c as InvalidSignalValueError,A as CircularDependencyError};
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.15.0
3
+ * @version 0.15.1
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
@@ -12,8 +12,23 @@ export {
12
12
  isComputedCallback,
13
13
  TYPE_COMPUTED,
14
14
  } from './src/computed'
15
- export { type DiffResult, diff, isEqual, type UnknownRecord } from './src/diff'
15
+ export {
16
+ type DiffResult,
17
+ diff,
18
+ isEqual,
19
+ type UnknownArray,
20
+ type UnknownRecord,
21
+ type UnknownRecordOrArray,
22
+ } from './src/diff'
16
23
  export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
24
+ export {
25
+ CircularDependencyError,
26
+ InvalidSignalValueError,
27
+ NullishSignalValueError,
28
+ StoreKeyExistsError,
29
+ StoreKeyRangeError,
30
+ StoreKeyReadonlyError,
31
+ } from './src/errors'
17
32
  export { type MatchHandlers, match } from './src/match'
18
33
  export { type ResolveResult, resolve } from './src/resolve'
19
34
  export {
@@ -29,12 +44,12 @@ export {
29
44
  watch,
30
45
  } from './src/scheduler'
31
46
  export {
47
+ isMutableSignal,
32
48
  isSignal,
33
- type MaybeSignal,
34
49
  type Signal,
35
50
  type SignalValues,
36
51
  toSignal,
37
- UNSET,
52
+ type UnknownSignalRecord,
38
53
  } from './src/signal'
39
54
  export { isState, type State, state, TYPE_STATE } from './src/state'
40
55
  export {
@@ -44,13 +59,19 @@ export {
44
59
  type StoreChangeEvent,
45
60
  type StoreEventMap,
46
61
  type StoreRemoveEvent,
62
+ type StoreSortEvent,
47
63
  store,
48
64
  TYPE_STORE,
49
65
  } from './src/store'
50
66
  export {
51
- CircularDependencyError,
52
67
  isAbortError,
53
68
  isAsyncFunction,
54
69
  isFunction,
70
+ isNumber,
71
+ isRecord,
72
+ isRecordOrArray,
73
+ isString,
74
+ isSymbol,
55
75
  toError,
76
+ UNSET,
56
77
  } from './src/util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
package/src/computed.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isEqual } from './diff'
2
+ import { CircularDependencyError } from './errors'
2
3
  import {
3
4
  flush,
4
5
  notify,
@@ -7,14 +8,13 @@ import {
7
8
  type Watcher,
8
9
  watch,
9
10
  } from './scheduler'
10
- import { UNSET } from './signal'
11
11
  import {
12
- CircularDependencyError,
13
12
  isAbortError,
14
13
  isAsyncFunction,
15
14
  isFunction,
16
15
  isObjectOfType,
17
16
  toError,
17
+ UNSET,
18
18
  } from './util'
19
19
 
20
20
  /* === Types === */
package/src/diff.ts CHANGED
@@ -1,11 +1,16 @@
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 UnknownArray = ReadonlyArray<unknown & {}>
8
+ type ArrayToRecord<T extends UnknownArray> = {
9
+ [key: string]: T extends Array<infer U extends {}> ? U : never
10
+ }
11
+ type UnknownRecordOrArray = UnknownRecord | ArrayToRecord<UnknownArray>
7
12
 
8
- type DiffResult<T extends UnknownRecord = UnknownRecord> = {
13
+ type DiffResult<T extends UnknownRecordOrArray = UnknownRecord> = {
9
14
  changed: boolean
10
15
  add: Partial<T>
11
16
  change: Partial<T>
@@ -66,6 +71,8 @@ const isEqual = <T>(a: T, b: T, visited?: WeakSet<object>): boolean => {
66
71
  return true
67
72
  }
68
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)
69
76
  return false
70
77
  } finally {
71
78
  visited.delete(a as object)
@@ -81,56 +88,74 @@ const isEqual = <T>(a: T, b: T, visited?: WeakSet<object>): boolean => {
81
88
  * @param {T} newObj - The new record to compare
82
89
  * @returns {DiffResult<T>} The result of the comparison
83
90
  */
84
- const diff = <T extends UnknownRecord>(oldObj: T, newObj: T): DiffResult<T> => {
85
- const visited = new WeakSet<object>()
86
-
87
- const diffRecords = (
88
- oldRecord: Record<string, unknown>,
89
- newRecord: Record<string, unknown>,
90
- ): DiffResult<T> => {
91
- const add: Partial<T> = {}
92
- const change: Partial<T> = {}
93
- const remove: Partial<T> = {}
94
-
95
- const oldKeys = Object.keys(oldRecord)
96
- const newKeys = Object.keys(newRecord)
97
- const allKeys = new Set([...oldKeys, ...newKeys])
98
-
99
- for (const key of allKeys) {
100
- const oldHas = key in oldRecord
101
- const newHas = key in newRecord
102
-
103
- if (!oldHas && newHas) {
104
- add[key as keyof T] = newRecord[key] as T[keyof T]
105
- continue
106
- } else if (oldHas && !newHas) {
107
- remove[key as keyof T] = UNSET
108
- continue
109
- }
91
+ const diff = <T extends UnknownRecordOrArray>(
92
+ oldObj: T,
93
+ newObj: T,
94
+ ): DiffResult<T> => {
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
+ }
110
108
 
111
- const oldValue = oldRecord[key] as T[keyof T]
112
- const newValue = newRecord[key] as T[keyof T]
109
+ const visited = new WeakSet()
113
110
 
114
- if (!isEqual(oldValue, newValue, visited))
115
- change[key as keyof T] = newValue
116
- }
111
+ const add: Partial<T> = {}
112
+ const change: Partial<T> = {}
113
+ const remove: Partial<T> = {}
117
114
 
118
- const changed =
119
- Object.keys(add).length > 0 ||
120
- Object.keys(change).length > 0 ||
121
- Object.keys(remove).length > 0
115
+ const oldKeys = Object.keys(oldObj)
116
+ const newKeys = Object.keys(newObj)
117
+ const allKeys = new Set([...oldKeys, ...newKeys])
122
118
 
123
- return {
124
- changed,
125
- add,
126
- change,
127
- 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
128
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
129
136
  }
130
137
 
131
- 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
+ }
132
149
  }
133
150
 
134
151
  /* === Exports === */
135
152
 
136
- export { type DiffResult, diff, isEqual, type UnknownRecord }
153
+ export {
154
+ type ArrayToRecord,
155
+ type DiffResult,
156
+ diff,
157
+ isEqual,
158
+ type UnknownRecord,
159
+ type UnknownArray,
160
+ type UnknownRecordOrArray,
161
+ }
package/src/effect.ts CHANGED
@@ -1,10 +1,6 @@
1
+ import { CircularDependencyError } from './errors'
1
2
  import { type Cleanup, observe, watch } from './scheduler'
2
- import {
3
- CircularDependencyError,
4
- isAbortError,
5
- isAsyncFunction,
6
- isFunction,
7
- } from './util'
3
+ import { isAbortError, isAsyncFunction, isFunction } from './util'
8
4
 
9
5
  /* === Types === */
10
6
 
@@ -55,9 +51,8 @@ const effect = (callback: EffectCallback): Cleanup => {
55
51
  if (
56
52
  isFunction(cleanup) &&
57
53
  controller === currentController
58
- ) {
54
+ )
59
55
  run.off(cleanup)
60
- }
61
56
  })
62
57
  .catch(error => {
63
58
  if (!isAbortError(error))
package/src/errors.ts ADDED
@@ -0,0 +1,56 @@
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 InvalidSignalValueError extends TypeError {
9
+ constructor(where: string, value: string) {
10
+ super(`Invalid signal value ${value} in ${where}`)
11
+ this.name = 'InvalidSignalValueError'
12
+ }
13
+ }
14
+
15
+ class NullishSignalValueError extends TypeError {
16
+ constructor(where: string) {
17
+ super(`Nullish signal values are not allowed in ${where}`)
18
+ this.name = 'NullishSignalValueError'
19
+ }
20
+ }
21
+
22
+ class StoreKeyExistsError extends Error {
23
+ constructor(key: string, value: string) {
24
+ super(
25
+ `Could not add store key "${key}" with value ${value} because it already exists`,
26
+ )
27
+ this.name = 'StoreKeyExistsError'
28
+ }
29
+ }
30
+
31
+ class StoreKeyRangeError extends RangeError {
32
+ constructor(index: number) {
33
+ super(
34
+ `Could not remove store index ${String(index)} because it is out of range`,
35
+ )
36
+ this.name = 'StoreKeyRangeError'
37
+ }
38
+ }
39
+
40
+ class StoreKeyReadonlyError extends Error {
41
+ constructor(key: string, value: string) {
42
+ super(
43
+ `Could not set store key "${key}" to ${value} because it is readonly`,
44
+ )
45
+ this.name = 'StoreKeyReadonlyError'
46
+ }
47
+ }
48
+
49
+ export {
50
+ CircularDependencyError,
51
+ InvalidSignalValueError,
52
+ NullishSignalValueError,
53
+ StoreKeyExistsError,
54
+ StoreKeyRangeError,
55
+ StoreKeyReadonlyError,
56
+ }
package/src/match.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type { ResolveResult } from './resolve'
2
- import type { Signal, SignalValues } from './signal'
2
+ import type { SignalValues, UnknownSignalRecord } from './signal'
3
3
  import { toError } from './util'
4
4
 
5
5
  /* === Types === */
6
6
 
7
- type MatchHandlers<S extends Record<string, Signal<unknown & {}>>> = {
8
- ok?: (values: SignalValues<S>) => void
7
+ type MatchHandlers<S extends UnknownSignalRecord> = {
8
+ ok: (values: SignalValues<S>) => void
9
9
  err?: (errors: readonly Error[]) => void
10
10
  nil?: () => void
11
11
  }
@@ -24,31 +24,26 @@ type MatchHandlers<S extends Record<string, Signal<unknown & {}>>> = {
24
24
  * @param {MatchHandlers<S>} handlers - Handlers for different states (side effects only)
25
25
  * @returns {void} - Always returns void
26
26
  */
27
- function match<S extends Record<string, Signal<unknown & {}>>>(
27
+ function match<S extends UnknownSignalRecord>(
28
28
  result: ResolveResult<S>,
29
29
  handlers: MatchHandlers<S>,
30
30
  ): void {
31
31
  try {
32
- if (result.pending) {
33
- handlers.nil?.()
34
- } else if (result.errors) {
35
- handlers.err?.(result.errors)
36
- } else {
37
- handlers.ok?.(result.values as SignalValues<S>)
38
- }
32
+ if (result.pending) handlers.nil?.()
33
+ else if (result.errors) handlers.err?.(result.errors)
34
+ else if (result.ok) handlers.ok(result.values)
39
35
  } catch (error) {
40
36
  // If handler throws, try error handler, otherwise rethrow
41
37
  if (
42
38
  handlers.err &&
43
39
  (!result.errors || !result.errors.includes(toError(error)))
44
- ) {
45
- const allErrors = result.errors
46
- ? [...result.errors, toError(error)]
47
- : [toError(error)]
48
- handlers.err(allErrors)
49
- } else {
50
- throw error
51
- }
40
+ )
41
+ handlers.err(
42
+ result.errors
43
+ ? [...result.errors, toError(error)]
44
+ : [toError(error)],
45
+ )
46
+ else throw error
52
47
  }
53
48
  }
54
49
 
package/src/resolve.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { UnknownRecord } from './diff'
2
- import { type Signal, type SignalValues, UNSET } 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
 
7
- type ResolveResult<S extends Record<string, Signal<unknown & {}>>> =
7
+ type ResolveResult<S extends UnknownSignalRecord> =
8
8
  | { ok: true; values: SignalValues<S>; errors?: never; pending?: never }
9
9
  | { ok: false; errors: readonly Error[]; values?: never; pending?: never }
10
10
  | { ok: false; pending: true; values?: never; errors?: never }
@@ -21,9 +21,7 @@ type ResolveResult<S extends Record<string, Signal<unknown & {}>>> =
21
21
  * @param {S} signals - Signals to resolve
22
22
  * @returns {ResolveResult<S>} - Discriminated union result
23
23
  */
24
- function resolve<S extends Record<string, Signal<unknown & {}>>>(
25
- signals: S,
26
- ): ResolveResult<S> {
24
+ function resolve<S extends UnknownSignalRecord>(signals: S): ResolveResult<S> {
27
25
  const errors: Error[] = []
28
26
  let pending = false
29
27
  const values: UnknownRecord = {}
@@ -32,24 +30,16 @@ function resolve<S extends Record<string, Signal<unknown & {}>>>(
32
30
  for (const [key, signal] of Object.entries(signals)) {
33
31
  try {
34
32
  const value = signal.get()
35
-
36
- if (value === UNSET) {
37
- pending = true
38
- } else {
39
- values[key] = value
40
- }
33
+ if (value === UNSET) pending = true
34
+ else values[key] = value
41
35
  } catch (e) {
42
36
  errors.push(toError(e))
43
37
  }
44
38
  }
45
39
 
46
40
  // Return discriminated union
47
- if (pending) {
48
- return { ok: false, pending: true }
49
- }
50
- if (errors.length > 0) {
51
- return { ok: false, errors }
52
- }
41
+ if (pending) return { ok: false, pending: true }
42
+ if (errors.length > 0) return { ok: false, errors }
53
43
  return { ok: true, values: values as SignalValues<S> }
54
44
  }
55
45
 
package/src/signal.ts CHANGED
@@ -7,28 +7,24 @@ import {
7
7
  } from './computed'
8
8
  import { isState, type State, state } from './state'
9
9
  import { isStore, type Store, store } from './store'
10
- import { arrayToRecord, isRecord } from './util'
10
+ import { isRecord } from './util'
11
11
 
12
12
  /* === Types === */
13
13
 
14
14
  type Signal<T extends {}> = {
15
15
  get(): T
16
16
  }
17
- type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>
18
17
 
19
- type SignalValues<S extends Record<string, Signal<unknown & {}>>> = {
18
+ type UnknownSignalRecord = Record<string, Signal<unknown & {}>>
19
+
20
+ type SignalValues<S extends UnknownSignalRecord> = {
20
21
  [K in keyof S]: S[K] extends Signal<infer T> ? T : never
21
22
  }
22
23
 
23
- /* === Constants === */
24
-
25
- // biome-ignore lint/suspicious/noExplicitAny: Deliberately using any to be used as a placeholder value in any signal
26
- const UNSET: any = Symbol()
27
-
28
24
  /* === Functions === */
29
25
 
30
26
  /**
31
- * Check whether a value is a Signal or not
27
+ * Check whether a value is a Signal
32
28
  *
33
29
  * @since 0.9.0
34
30
  * @param {unknown} value - value to check
@@ -39,58 +35,54 @@ const isSignal = /*#__PURE__*/ <T extends {}>(
39
35
  ): value is Signal<T> => isState(value) || isComputed(value) || isStore(value)
40
36
 
41
37
  /**
42
- * Convert a value to a Signal if it's not already a Signal
38
+ * Check whether a value is a State or Store
43
39
  *
44
- * @since 0.9.6
40
+ * @since 0.15.2
41
+ * @param {unknown} value - value to check
42
+ * @returns {boolean} - true if value is a State or Store, false otherwise
45
43
  */
46
- function toSignal<T extends Array<unknown & {}>>(
47
- value: T[],
48
- ): Store<Record<string, T>>
49
- function toSignal<T extends Record<keyof T, T[keyof T]>>(value: T): Store<T>
50
- function toSignal<T extends {}>(value: ComputedCallback<T>): Computed<T>
51
- function toSignal<T extends {}>(value: Signal<T>): Signal<T>
52
- function toSignal<T extends {}>(value: T): State<T>
53
- function toSignal<T extends {}>(
54
- value: MaybeSignal<T> | T[],
55
- ): Signal<T> | Store<Record<string, T>> {
56
- if (isSignal<T>(value)) return value
57
- if (isComputedCallback<T>(value)) return computed(value)
58
- if (Array.isArray(value)) return store(arrayToRecord(value))
59
- if (isRecord(value)) return store(value as T)
60
- return state(value as T)
61
- }
44
+ const isMutableSignal = /*#__PURE__*/ <T extends {}>(
45
+ value: unknown,
46
+ ): value is State<T> | Store<T> => isState(value) || isStore(value)
62
47
 
63
48
  /**
64
- * Convert a value to a mutable Signal if it's not already a Signal
49
+ * Convert a value to a Signal if it's not already a Signal
65
50
  *
66
51
  * @since 0.9.6
52
+ * @param {T} value - value to convert
53
+ * @returns {Signal<T>} - Signal instance
67
54
  */
68
- function toMutableSignal<T extends Array<unknown & {}>>(
69
- value: T[],
70
- ): Store<Record<string, T>>
71
- function toMutableSignal<T extends Record<keyof T, T[keyof T]>>(
55
+ function toSignal<T extends {}>(
72
56
  value: T,
73
- ): Store<T>
74
- function toMutableSignal<T extends State<T>>(value: State<T>): State<T>
75
- function toMutableSignal<T extends Store<T>>(value: Store<T>): Store<T>
76
- function toMutableSignal<T extends {}>(value: T): State<T>
77
- function toMutableSignal<T extends {}>(
78
- value: T | State<T> | Store<T> | T[],
79
- ): Signal<T> | Store<Record<string, T>> {
80
- if (isState<T>(value) || isStore<T>(value)) return value
81
- if (Array.isArray(value)) return store(arrayToRecord(value))
82
- if (isRecord(value)) return store(value as T)
83
- return state(value as T)
57
+ ): T extends Store<infer U>
58
+ ? Store<U>
59
+ : T extends State<infer U>
60
+ ? State<U>
61
+ : T extends Computed<infer U>
62
+ ? Computed<U>
63
+ : T extends Signal<infer U>
64
+ ? Signal<U>
65
+ : T extends ReadonlyArray<infer U extends {}>
66
+ ? Store<U[]>
67
+ : T extends Record<string, unknown & {}>
68
+ ? Store<{ [K in keyof T]: T[K] }>
69
+ : T extends ComputedCallback<infer U extends {}>
70
+ ? Computed<U>
71
+ : State<T>
72
+ function toSignal<T extends {}>(value: T) {
73
+ if (isSignal<T>(value)) return value
74
+ if (isComputedCallback(value)) return computed(value)
75
+ if (Array.isArray(value) || isRecord(value)) return store(value)
76
+ return state(value)
84
77
  }
85
78
 
86
79
  /* === Exports === */
87
80
 
88
81
  export {
89
82
  type Signal,
90
- type MaybeSignal,
83
+ type UnknownSignalRecord,
91
84
  type SignalValues,
92
- UNSET,
93
85
  isSignal,
86
+ isMutableSignal,
94
87
  toSignal,
95
- toMutableSignal,
96
88
  }
package/src/state.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { isEqual } from './diff'
2
+ import { NullishSignalValueError } from './errors'
2
3
  import { notify, subscribe, type Watcher } from './scheduler'
3
- import { UNSET } from './signal'
4
- import { isObjectOfType } from './util'
4
+ import { isObjectOfType, UNSET } from './util'
5
5
 
6
6
  /* === Types === */
7
7
 
@@ -51,6 +51,7 @@ const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
51
51
  * @returns {void}
52
52
  */
53
53
  set: (v: T): void => {
54
+ if (v == null) throw new NullishSignalValueError('state')
54
55
  if (isEqual(value, v)) return
55
56
  value = v
56
57
  notify(watchers)