@zeix/cause-effect 0.16.1 → 0.17.1

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 (66) hide show
  1. package/.ai-context.md +85 -21
  2. package/.cursorrules +11 -5
  3. package/.github/copilot-instructions.md +64 -13
  4. package/CLAUDE.md +143 -163
  5. package/LICENSE +1 -1
  6. package/README.md +248 -333
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +21 -21
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +139 -0
  12. package/{src → archive}/state.ts +13 -11
  13. package/archive/store.ts +368 -0
  14. package/archive/task.ts +194 -0
  15. package/eslint.config.js +1 -0
  16. package/index.dev.js +938 -509
  17. package/index.js +1 -1
  18. package/index.ts +50 -23
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +282 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +305 -0
  24. package/src/classes/ref.ts +68 -0
  25. package/src/classes/state.ts +98 -0
  26. package/src/classes/store.ts +210 -0
  27. package/src/diff.ts +26 -53
  28. package/src/effect.ts +9 -9
  29. package/src/errors.ts +71 -25
  30. package/src/match.ts +5 -12
  31. package/src/resolve.ts +3 -2
  32. package/src/signal.ts +58 -41
  33. package/src/system.ts +79 -42
  34. package/src/util.ts +16 -34
  35. package/test/batch.test.ts +15 -17
  36. package/test/benchmark.test.ts +4 -4
  37. package/test/collection.test.ts +853 -0
  38. package/test/computed.test.ts +138 -130
  39. package/test/diff.test.ts +2 -2
  40. package/test/effect.test.ts +36 -35
  41. package/test/list.test.ts +754 -0
  42. package/test/match.test.ts +25 -25
  43. package/test/ref.test.ts +227 -0
  44. package/test/resolve.test.ts +17 -19
  45. package/test/signal.test.ts +70 -119
  46. package/test/state.test.ts +44 -44
  47. package/test/store.test.ts +253 -929
  48. package/types/index.d.ts +12 -9
  49. package/types/src/classes/collection.d.ts +46 -0
  50. package/types/src/classes/composite.d.ts +15 -0
  51. package/types/src/classes/computed.d.ts +97 -0
  52. package/types/src/classes/list.d.ts +41 -0
  53. package/types/src/classes/ref.d.ts +39 -0
  54. package/types/src/classes/state.d.ts +52 -0
  55. package/types/src/classes/store.d.ts +51 -0
  56. package/types/src/diff.d.ts +8 -12
  57. package/types/src/errors.d.ts +17 -11
  58. package/types/src/signal.d.ts +27 -14
  59. package/types/src/system.d.ts +41 -20
  60. package/types/src/util.d.ts +6 -4
  61. package/src/store.ts +0 -474
  62. package/types/src/collection.d.ts +0 -26
  63. package/types/src/computed.d.ts +0 -33
  64. package/types/src/scheduler.d.ts +0 -55
  65. package/types/src/state.d.ts +0 -24
  66. package/types/src/store.d.ts +0 -65
package/index.js CHANGED
@@ -1 +1 @@
1
- class _ extends Error{constructor($){super(`Circular dependency detected in ${$}`);this.name="CircularDependencyError"}}class W extends TypeError{constructor($,x){super(`Invalid ${$} callback ${x}`);this.name="InvalidCallbackError"}}class h 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 v extends Error{constructor($,x){super(`Could not add store key "${$}" with value ${x} because it already exists`);this.name="StoreKeyExistsError"}}class n extends RangeError{constructor($){super(`Could not remove store index ${String($)} because it is out of range`);this.name="StoreKeyRangeError"}}class u extends Error{constructor($,x){super(`Could not set store key "${$}" to ${x} because it is readonly`);this.name="StoreKeyReadonlyError"}}var q=Symbol(),e=($)=>typeof $==="string",X$=($)=>typeof $==="number",c=($)=>typeof $==="symbol",K=($)=>typeof $==="function",g=($)=>K($)&&$.constructor.name==="AsyncFunction",m=($,x)=>Object.prototype.toString.call($)===`[object ${x}]`,A=($)=>m($,"Object"),s=($)=>A($)||Array.isArray($),H$=($)=>{if(!$.length)return null;let x=$.map((z)=>e(z)?parseInt(z,10):X$(z)?z:NaN);return x.every((z)=>Number.isFinite(z)&&z>=0)?x.sort((z,J)=>z-J):null};var y=($)=>$ instanceof DOMException&&$.name==="AbortError",U=($)=>$ instanceof Error?$:Error(String($));var t=($)=>{let x=H$(Object.keys($));if(x===null)return $;let z=[];for(let J of x)z.push($[String(J)]);return z},F=($)=>e($)?`"${$}"`:!!$&&typeof $==="object"?JSON.stringify($):String($);var f=($,x,z)=>{if(Object.is($,x))return!0;if(typeof $!==typeof x)return!1;if(typeof $!=="object"||$===null||x===null)return!1;if(!z)z=new WeakSet;if(z.has($)||z.has(x))throw new _("isEqual");z.add($),z.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],z))return!1;return!0}if(Array.isArray($)!==Array.isArray(x))return!1;if(A($)&&A(x)){let J=Object.keys($),X=Object.keys(x);if(J.length!==X.length)return!1;for(let M of J){if(!(M in x))return!1;if(!f($[M],x[M],z))return!1}return!0}return!1}finally{z.delete($),z.delete(x)}},$$=($,x)=>{let z=s($),J=s(x);if(!z||!J){let C=!Object.is($,x);return{changed:C,add:C&&J?x:{},change:{},remove:C&&z?$:{}}}let X=new WeakSet,M={},H={},I={},Y=Object.keys($),E=Object.keys(x),P=new Set([...Y,...E]);for(let C of P){let N=C in $,R=C in x;if(!N&&R){M[C]=x[C];continue}else if(N&&!R){I[C]=q;continue}let B=$[C],G=x[C];if(!f(B,G,X))H[C]=G}return{changed:Object.keys(M).length>0||Object.keys(H).length>0||Object.keys(I).length>0,add:M,change:H,remove:I}};var p,i=new Set,x$=0,O=($)=>{let x=new Set,z=$;return z.unwatch=(J)=>{x.add(J)},z.cleanup=()=>{for(let J of x)J();x.clear()},z},j=($)=>{if(p&&!$.has(p)){let x=p;x.unwatch(()=>{$.delete(x)}),$.add(x)}},T=($)=>{for(let x of $)if(x$)i.add(x);else x()},r=()=>{while(i.size){let $=Array.from(i);i.clear();for(let x of $)x()}},R$=($)=>{x$++;try{$()}finally{r(),x$--}},S=($,x)=>{let z=p;p=x;try{$()}finally{p=z}};var B$="Computed",J$=($,x=q)=>{if(!l($))throw new W("computed",F($));if(x==null)throw new L("computed");let z=new Set,J=x,X,M,H=!0,I=!1,Y=!1,E=(G)=>{if(!f(G,J))J=G,I=!0;X=void 0,H=!1},P=()=>{I=q!==J,J=q,X=void 0},V=(G)=>{let Q=U(G);I=!X||Q.name!==X.name||Q.message!==X.message,J=q,X=Q},C=(G)=>(Q)=>{if(Y=!1,M=void 0,G(Q),I)T(z)},N=O(()=>{if(H=!0,M?.abort(),z.size)T(z);else N.cleanup()});N.unwatch(()=>{M?.abort()});let R=()=>S(()=>{if(Y)throw new _("computed");if(I=!1,g($)){if(M)return J;M=new AbortController,M.signal.addEventListener("abort",()=>{Y=!1,M=void 0,R()},{once:!0})}let G;Y=!0;try{G=M?$(J,M.signal):$(J)}catch(Q){if(y(Q))P();else V(Q);Y=!1;return}if(G instanceof Promise)G.then(C(E),C(V));else if(G==null||q===G)P();else E(G);Y=!1},N),B={};return Object.defineProperties(B,{[Symbol.toStringTag]:{value:B$},get:{value:()=>{if(j(z),r(),H)R();if(X)throw X;return J}}}),B},w=($)=>m($,B$),l=($)=>K($)&&$.length<3;var C$=($)=>{if(!K($)||$.length>1)throw new W("effect",F($));let x=g($),z=!1,J,X=O(()=>S(()=>{if(z)throw new _("effect");z=!0,J?.abort(),J=void 0;let M;try{if(x){J=new AbortController;let H=J;$(J.signal).then((I)=>{if(K(I)&&J===H)X.unwatch(I)}).catch((I)=>{if(!y(I))console.error("Async effect error:",I)})}else if(M=$(),K(M))X.unwatch(M)}catch(H){if(!y(H))console.error("Effect callback error:",H)}z=!1},X));return X(),()=>{J?.abort(),X.cleanup()}};function q$($,x){try{if($.pending)x.nil?.();else if($.errors)x.err?.($.errors);else if($.ok)x.ok($.values)}catch(z){if(x.err&&(!$.errors||!$.errors.includes(U(z))))x.err($.errors?[...$.errors,U(z)]:[U(z)]);else throw z}}function D$($){let x=[],z=!1,J={};for(let[X,M]of Object.entries($))try{let H=M.get();if(H===q)z=!0;else J[X]=H}catch(H){x.push(U(H))}if(z)return{ok:!1,pending:!0};if(x.length>0)return{ok:!1,errors:x};return{ok:!0,values:J}}var z$="State",k=($)=>{if($==null)throw new L("state");let x=new Set,z=$,J=(M)=>{if(M==null)throw new L("state");if(f(z,M))return;if(z=M,T(x),q===z)x.clear()},X={};return Object.defineProperties(X,{[Symbol.toStringTag]:{value:z$},get:{value:()=>{return j(x),z}},set:{value:(M)=>{J(M)}},update:{value:(M)=>{if(!K(M))throw new W("state update",F(M));J(M(z))}}}),X},b=($)=>m($,z$);var G$="Store",a=($)=>{if($==null)throw new L("store");let x=new Set,z={add:new Set,change:new Set,remove:new Set,sort:new Set},J=new Map,X=new Map,M=Array.isArray($),H=()=>{let R={};for(let[B,G]of J)R[B]=G.get();return R},I=(R,B)=>{Object.freeze(B);for(let G of z[R])G(B)},Y=()=>Array.from(J.keys()).map((R)=>Number(R)).filter((R)=>Number.isInteger(R)).sort((R,B)=>R-B),E=(R,B)=>{if(B==null)throw new L(`store for key "${R}"`);if(B===q)return!0;if(c(B)||K(B)||w(B))throw new h(`store for key "${R}"`,F(B));return!0},P=(R,B,G=!1)=>{if(!E(R,B))return!1;let Q=b(B)||o(B)?B:A(B)||Array.isArray(B)?a(B):k(B);J.set(R,Q);let Z=O(()=>S(()=>{I("change",{[R]:Q.get()})},Z));if(Z(),X.set(R,Z),G)T(x),I("add",{[R]:B});return!0},V=(R,B=!1)=>{let G=J.delete(R);if(G){let Q=X.get(R);if(Q)Q.cleanup();X.delete(R)}if(B)T(x),I("remove",{[R]:q});return G},C=(R,B,G)=>{let Q=$$(R,B);return R$(()=>{if(Object.keys(Q.add).length){for(let Z in Q.add)P(Z,Q.add[Z]??q);if(G)setTimeout(()=>{I("add",Q.add)},0);else I("add",Q.add)}if(Object.keys(Q.change).length){for(let Z in Q.change){let D=Q.change[Z];if(!E(Z,D))continue;let d=J.get(Z);if(M$(d))d.set(D);else throw new u(Z,F(D))}I("change",Q.change)}if(Object.keys(Q.remove).length){for(let Z in Q.remove)V(Z);I("remove",Q.remove)}}),Q.changed};C({},$,!0);let N={};return Object.defineProperties(N,{[Symbol.toStringTag]:{value:G$},[Symbol.isConcatSpreadable]:{value:M},[Symbol.iterator]:{value:M?function*(){let R=Y();for(let B of R){let G=J.get(String(B));if(G)yield G}}:function*(){for(let[R,B]of J)yield[R,B]}},add:{value:M?(R)=>{P(String(J.size),R,!0)}:(R,B)=>{if(!J.has(R))P(R,B,!0);else throw new v(R,F(B))}},get:{value:()=>{return j(x),t(H())}},remove:{value:M?(R)=>{let B=t(H()),G=J.size;if(!Array.isArray(B)||R<=-G||R>=G)throw new n(R);let Q=[...B];if(Q.splice(R,1),C(B,Q))T(x)}:(R)=>{if(J.has(R))V(R,!0)}},set:{value:(R)=>{if(C(H(),R)){if(T(x),q===R)x.clear()}}},update:{value:(R)=>{let B=H(),G=R(t(B));if(C(B,G)){if(T(x),q===G)x.clear()}}},sort:{value:(R)=>{let B=Array.from(J.entries()).map(([Z,D])=>[Z,D.get()]).sort(R?(Z,D)=>R(Z[1],D[1]):(Z,D)=>String(Z[1]).localeCompare(String(D[1]))),G=B.map(([Z])=>String(Z)),Q=new Map;B.forEach(([Z],D)=>{let d=String(Z),I$=M?String(D):String(Z),Q$=J.get(d);if(Q$)Q.set(I$,Q$)}),J.clear(),Q.forEach((Z,D)=>J.set(D,Z)),T(x),I("sort",G)}},on:{value:(R,B)=>{return z[R].add(B),()=>z[R].delete(B)}},length:{get(){return j(x),J.size}}}),new Proxy(N,{get(R,B){if(B in R)return Reflect.get(R,B);if(c(B))return;return J.get(B)},has(R,B){if(B in R)return!0;return J.has(String(B))},ownKeys(R){let B=Reflect.ownKeys(R),G=M?Y().map((Q)=>String(Q)):Array.from(J.keys());return[...new Set([...G,...B])]},getOwnPropertyDescriptor(R,B){if(B in R)return Reflect.getOwnPropertyDescriptor(R,B);let G=J.get(String(B));return G?{enumerable:!0,configurable:!0,writable:!0,value:G}:void 0}})},o=($)=>m($,G$);var Z$=($)=>b($)||w($)||o($),M$=($)=>b($)||o($);function T$($){if(Z$($))return $;if(l($))return J$($);if(Array.isArray($)||A($))return a($);return k($)}export{F as valueString,T$ as toSignal,U as toError,j as subscribe,D$ as resolve,S as observe,T as notify,q$ as match,c as isSymbol,e as isString,o as isStore,b as isState,Z$ as isSignal,s as isRecordOrArray,A as isRecord,m as isObjectOfType,X$ as isNumber,M$ as isMutableSignal,K as isFunction,f as isEqual,l as isComputedCallback,w as isComputed,g as isAsyncFunction,y as isAbortError,r as flush,$$ as diff,O as createWatcher,a as createStore,k as createState,C$ as createEffect,J$ as createComputed,R$ as batch,q as UNSET,G$ as TYPE_STORE,z$ as TYPE_STATE,B$ as TYPE_COMPUTED,u as StoreKeyReadonlyError,n as StoreKeyRangeError,v as StoreKeyExistsError,L as NullishSignalValueError,h as InvalidSignalValueError,W as InvalidCallbackError,_ as CircularDependencyError};
1
+ var B=Symbol(),o=($)=>typeof $==="string",s=($)=>typeof $==="number",E=($)=>typeof $==="symbol",M=($)=>typeof $==="function",j=($)=>M($)&&$.constructor.name==="AsyncFunction",P$=($)=>M($)&&$.constructor.name!=="AsyncFunction",J$=($)=>$!=null&&typeof $==="object",K=($,G)=>Object.prototype.toString.call($)===`[object ${G}]`,D=($)=>K($,"Object"),c=($)=>D($)||Array.isArray($),z$=($,G=(J)=>J!=null)=>Array.isArray($)&&$.every(G);var S=($)=>$ instanceof DOMException&&$.name==="AbortError",T=($)=>o($)?`"${$}"`:!!$&&typeof $==="object"?JSON.stringify($):String($);var L=($,G,J)=>{if(Object.is($,G))return!0;if(typeof $!==typeof G)return!1;if(!J$($)||!J$(G))return!1;if(!J)J=new WeakSet;if(J.has($)||J.has(G))throw new U("isEqual");J.add($),J.add(G);try{if(Array.isArray($)&&Array.isArray(G)){if($.length!==G.length)return!1;for(let z=0;z<$.length;z++)if(!L($[z],G[z],J))return!1;return!0}if(Array.isArray($)!==Array.isArray(G))return!1;if(D($)&&D(G)){let z=Object.keys($),X=Object.keys(G);if(z.length!==X.length)return!1;for(let Z of z){if(!(Z in G))return!1;if(!L($[Z],G[Z],J))return!1}return!0}return!1}finally{J.delete($),J.delete(G)}},w=($,G)=>{let J=c($),z=c(G);if(!J||!z){let H=!Object.is($,G);return{changed:H,add:H&&z?G:{},change:{},remove:H&&J?$:{}}}let X=new WeakSet,Z={},Q={},I={},f=Object.keys($),u=Object.keys(G),_=new Set([...f,...u]);for(let H of _){let W=H in $,m=H in G;if(!W&&m){Z[H]=G[H];continue}else if(W&&!m){I[H]=B;continue}let N$=$[H],Y$=G[H];if(!L(N$,Y$,X))Q[H]=Y$}return{add:Z,change:Q,remove:I,changed:!!(Object.keys(Z).length||Object.keys(Q).length||Object.keys(I).length)}};var O,g=new Set,i=0,Y=($)=>{let G=new Set,J=$;return J.onCleanup=(z)=>{G.add(z)},J.stop=()=>{for(let z of G)z();G.clear()},J},q=($)=>{if(O&&!$.has(O)){let G=O;G.onCleanup(()=>$.delete(G)),$.add(G)}},x=($)=>{for(let G of $)if(i)g.add(G);else G()},p=()=>{while(g.size){let $=Array.from(g);g.clear();for(let G of $)G()}},X$=($)=>{i++;try{$()}finally{p(),i--}},P=($,G)=>{let J=O;O=$||void 0;try{G()}finally{O=J}},A=($,G)=>{for(let J of $)if(i)g.add(()=>J(G));else J(G)};var t="Computed";class b{#G=new Set;#J;#z;#$;#X=!0;#Q=!1;#Z;constructor($,G=B){N("memo",$,l),F("memo",G),this.#J=$,this.#z=G,this.#Z=Y(()=>{if(this.#X=!0,this.#G.size)x(this.#G);else this.#Z.stop()})}get[Symbol.toStringTag](){return t}get(){if(q(this.#G),p(),this.#X)P(this.#Z,()=>{if(this.#Q)throw new U("memo");let $;this.#Q=!0;try{$=this.#J(this.#z)}catch(G){this.#z=B,this.#$=R(G),this.#Q=!1;return}if($==null||B===$)this.#z=B,this.#$=void 0;else this.#z=$,this.#$=void 0,this.#X=!1;this.#Q=!1});if(this.#$)throw this.#$;return this.#z}}class y{#G=new Set;#J;#z;#$;#X=!0;#Q=!1;#Z=!1;#x;#B;constructor($,G=B){N("task",$,r),F("task",G),this.#J=$,this.#z=G,this.#x=Y(()=>{if(this.#X=!0,this.#B?.abort(),this.#G.size)x(this.#G);else this.#x.stop()}),this.#x.onCleanup(()=>{this.#B?.abort()})}get[Symbol.toStringTag](){return t}get(){q(this.#G),p();let $=(Z)=>{if(!L(Z,this.#z))this.#z=Z,this.#Z=!0;this.#$=void 0,this.#X=!1},G=()=>{this.#Z=B!==this.#z,this.#z=B,this.#$=void 0},J=(Z)=>{let Q=R(Z);this.#Z=!this.#$||Q.name!==this.#$.name||Q.message!==this.#$.message,this.#z=B,this.#$=Q},z=(Z)=>(Q)=>{if(this.#Q=!1,this.#B=void 0,Z(Q),this.#Z)x(this.#G)},X=()=>P(this.#x,()=>{if(this.#Q)throw new U("task");if(this.#Z=!1,this.#B)return this.#z;this.#B=new AbortController,this.#B.signal.addEventListener("abort",()=>{this.#Q=!1,this.#B=void 0,X()},{once:!0});let Z;this.#Q=!0;try{Z=this.#J(this.#z,this.#B.signal)}catch(Q){if(S(Q))G();else J(Q);this.#Q=!1;return}if(Z instanceof Promise)Z.then(z($),z(J));else if(Z==null||B===Z)G();else $(Z);this.#Q=!1});if(this.#X)X();if(this.#$)throw this.#$;return this.#z}}var Z$=($,G=B)=>j($)?new y($,G):new b($,G),Q$=($)=>K($,t),l=($)=>P$($)&&$.length<2,r=($)=>j($)&&$.length<3;class k{signals=new Map;#G;#J;#z=new Map;#$={add:new Set,change:new Set,remove:new Set};#X=!1;constructor($,G,J){this.#G=G,this.#J=J,this.change({add:$,change:{},remove:{},changed:!0},!0)}#Q($){let G=Y(()=>{P(G,()=>{if(this.signals.get($)?.get(),!this.#X)A(this.#$.change,[$])})});this.#z.set($,G),G()}add($,G){if(!this.#G($,G))return!1;if(this.signals.set($,this.#J(G)),this.#$.change.size)this.#Q($);if(!this.#X)A(this.#$.add,[$]);return!0}remove($){if(!this.signals.delete($))return!1;let J=this.#z.get($);if(J)J.stop(),this.#z.delete($);if(!this.#X)A(this.#$.remove,[$]);return!0}change($,G){if(this.#X=!0,Object.keys($.add).length){for(let z in $.add)this.add(z,$.add[z]);let J=()=>A(this.#$.add,Object.keys($.add));if(G)setTimeout(J,0);else J()}if(Object.keys($.change).length)X$(()=>{for(let J in $.change){let z=$.change[J];if(!this.#G(J,z))continue;let X=this.signals.get(J);if(B$(`list item "${J}"`,z,X))X.set(z)}}),A(this.#$.change,Object.keys($.change));if(Object.keys($.remove).length){for(let J in $.remove)this.remove(J);A(this.#$.remove,Object.keys($.remove))}return this.#X=!1,$.changed}clear(){let $=Array.from(this.signals.keys());return this.signals.clear(),this.#z.clear(),A(this.#$.remove,$),!0}on($,G){if(this.#$[$].add(G),$==="change"&&!this.#z.size){this.#X=!0;for(let J of this.signals.keys())this.#Q(J);this.#X=!1}return()=>{if(this.#$[$].delete(G),$==="change"&&!this.#$.change.size){if(this.#z.size){for(let J of this.#z.values())J.stop();this.#z.clear()}}}}}var x$="State";class C{#G=new Set;#J;constructor($){F("state",$),this.#J=$}get[Symbol.toStringTag](){return x$}get(){return q(this.#G),this.#J}set($){if(F("state",$),L(this.#J,$))return;if(this.#J=$,x(this.#G),B===this.#J)this.#G.clear()}update($){N("state update",$),this.set($(this.#J))}}var a=($)=>K($,x$);var H$="List";class h{#G;#J=new Set;#z={sort:new Set};#$=[];#X;constructor($,G){F("list",$,Array.isArray);let J=0;this.#X=o(G)?()=>`${G}${J++}`:M(G)?(z)=>G(z):()=>String(J++),this.#G=new k(this.#Q($),(z,X)=>{return F(`list for key "${z}"`,X),!0},(z)=>new C(z))}#Q($){let G={};for(let J=0;J<$.length;J++){let z=$[J];if(z===void 0)continue;let X=this.#$[J];if(!X)X=this.#X(z),this.#$[J]=X;G[X]=z}return G}get#Z(){return this.#$.map(($)=>this.#G.signals.get($)?.get()).filter(($)=>$!==void 0)}get[Symbol.toStringTag](){return H$}get[Symbol.isConcatSpreadable](){return!0}*[Symbol.iterator](){for(let $ of this.#$){let G=this.#G.signals.get($);if(G)yield G}}get length(){return q(this.#J),this.#$.length}get(){return q(this.#J),this.#Z}set($){if(B===$){this.#G.clear(),x(this.#J),this.#J.clear();return}let G=this.#Z,J=w(this.#Q(G),this.#Q($)),z=Object.keys(J.remove);if(this.#G.change(J)){for(let Z of z){let Q=this.#$.indexOf(Z);if(Q!==-1)this.#$.splice(Q,1)}this.#$=this.#$.filter(()=>!0),x(this.#J)}}update($){this.set($(this.get()))}at($){return this.#G.signals.get(this.#$[$])}keys(){return this.#$.values()}byKey($){return this.#G.signals.get($)}keyAt($){return this.#$[$]}indexOfKey($){return this.#$.indexOf($)}add($){let G=this.#X($);if(this.#G.signals.has(G))throw new V("store",G,$);if(!this.#$.includes(G))this.#$.push(G);if(this.#G.add(G,$))x(this.#J);return G}remove($){let G=s($)?this.#$[$]:$;if(this.#G.remove(G)){let z=s($)?$:this.#$.indexOf(G);if(z>=0)this.#$.splice(z,1);this.#$=this.#$.filter(()=>!0),x(this.#J)}}sort($){let J=this.#$.map((z)=>[z,this.#G.signals.get(z)?.get()]).sort(M($)?(z,X)=>$(z[1],X[1]):(z,X)=>String(z[1]).localeCompare(String(X[1]))).map(([z])=>z);if(!L(this.#$,J))this.#$=J,x(this.#J),A(this.#z.sort,this.#$)}splice($,G,...J){let z=this.#$.length,X=$<0?Math.max(0,z+$):Math.min($,z),Z=Math.max(0,Math.min(G??Math.max(0,z-Math.max(0,X)),z-X)),Q={},I={};for(let _=0;_<Z;_++){let H=X+_,W=this.#$[H];if(W){let m=this.#G.signals.get(W);if(m)I[W]=m.get()}}let f=this.#$.slice(0,X);for(let _ of J){let H=this.#X(_);f.push(H),Q[H]=_}f.push(...this.#$.slice(X+Z));let u=!!(Object.keys(Q).length||Object.keys(I).length);if(u)this.#G.change({add:Q,change:{},remove:I,changed:u}),this.#$=f.filter(()=>!0),x(this.#J);return Object.values(I)}on($,G){if($==="sort")return this.#z.sort.add(G),()=>{this.#z.sort.delete(G)};return this.#G.on($,G)}deriveCollection($){return new d(this,$)}}var v=($)=>K($,H$);var M$="Store";class q${#G;#J=new Set;constructor($){F("store",$,D),this.#G=new k($,(G,J)=>{return F(`store for key "${G}"`,J),!0},(G)=>U$(G))}get#z(){let $={};for(let[G,J]of this.#G.signals.entries())$[G]=J.get();return $}get[Symbol.toStringTag](){return M$}get[Symbol.isConcatSpreadable](){return!1}*[Symbol.iterator](){for(let[$,G]of this.#G.signals.entries())yield[$,G]}get(){return q(this.#J),this.#z}set($){if(B===$){this.#G.clear(),x(this.#J),this.#J.clear();return}let G=this.#z;if(this.#G.change(w(G,$)))x(this.#J)}keys(){return this.#G.signals.keys()}byKey($){return this.#G.signals.get($)}update($){this.set($(this.get()))}add($,G){if(this.#G.signals.has($))throw new V("store",$,G);if(this.#G.add($,G))x(this.#J);return $}remove($){if(this.#G.remove($))x(this.#J)}on($,G){return this.#G.on($,G)}}var e=($)=>{let G=new q$($);return new Proxy(G,{get(J,z){if(z in J){let X=Reflect.get(J,z);return M(X)?X.bind(J):X}if(!E(z))return J.byKey(z)},has(J,z){if(z in J)return!0;return J.byKey(String(z))!==void 0},ownKeys(J){return Array.from(J.keys())},getOwnPropertyDescriptor(J,z){if(z in J)return Reflect.getOwnPropertyDescriptor(J,z);if(E(z))return;let X=J.byKey(String(z));return X?{enumerable:!0,configurable:!0,writable:!0,value:X}:void 0}})},$$=($)=>K($,M$);var T$=($)=>a($)||Q$($)||$$($),A$=($)=>a($)||$$($)||v($);function C$($){if(l($))return new b($);if(r($))return new y($);if(z$($))return new h($);if(D($))return e($);return new C($)}function U$($){if(z$($))return new h($);if(D($))return e($);return new C($)}class U extends Error{constructor($){super(`Circular dependency detected in ${$}`);this.name="CircularDependencyError"}}class V extends Error{constructor($,G,J){super(`Could not add ${$} key "${G}"${J?` with value ${T(J)}`:""} because it already exists`);this.name="DuplicateKeyError"}}class n extends TypeError{constructor($,G){super(`Invalid ${$} callback ${T(G)}`);this.name="InvalidCallbackError"}}class G$ extends TypeError{constructor($,G){super(`Invalid ${$} source ${T(G)}`);this.name="InvalidCollectionSourceError"}}class F$ extends TypeError{constructor($,G){super(`Invalid signal value ${T(G)} in ${$}`);this.name="InvalidSignalValueError"}}class I$ extends TypeError{constructor($){super(`Nullish signal values are not allowed in ${$}`);this.name="NullishSignalValueError"}}class K$ extends Error{constructor($,G){super(`Could not set ${$} to ${T(G)} because signal is read-only`);this.name="ReadonlySignalError"}}var R=($)=>$ instanceof Error?$:Error(String($)),N=($,G,J=M)=>{if(!J(G))throw new n($,G)},F=($,G,J=()=>!(E(G)&&G!==B)||M(G))=>{if(G==null)throw new I$($);if(!J(G))throw new F$($,G)},B$=($,G,J)=>{if(!A$(J))throw new K$($,G);return!0};var D$="Collection";class d{#G=new Set;#J;#z;#$=new Map;#X=new Map;#Q={add:new Set,change:new Set,remove:new Set,sort:new Set};#Z=[];constructor($,G){if(N("collection",G),M($))$=$();if(!W$($))throw new G$("derived collection",$);this.#J=$,this.#z=G;for(let J=0;J<this.#J.length;J++){let z=this.#J.keyAt(J);if(!z)continue;this.#x(z)}this.#J.on("add",(J)=>{for(let z of J)if(!this.#$.has(z)){this.#x(z);let X=this.#$.get(z);if(X&&_$(this.#z))X.get()}x(this.#G),A(this.#Q.add,J)}),this.#J.on("remove",(J)=>{for(let z of J){if(!this.#$.has(z))continue;this.#$.delete(z);let X=this.#Z.indexOf(z);if(X>=0)this.#Z.splice(X,1);let Z=this.#X.get(z);if(Z)Z.stop(),this.#X.delete(z)}this.#Z=this.#Z.filter(()=>!0),x(this.#G),A(this.#Q.remove,J)}),this.#J.on("sort",(J)=>{this.#Z=[...J],x(this.#G),A(this.#Q.sort,J)})}#x($){let G=_$(this.#z)?async(z,X)=>{let Z=this.#J.byKey($);if(!Z)return B;let Q=Z.get();if(Q===B)return B;return this.#z(Q,X)}:()=>{let z=this.#J.byKey($);if(!z)return B;let X=z.get();if(X===B)return B;return this.#z(X)},J=Z$(G);if(this.#$.set($,J),!this.#Z.includes($))this.#Z.push($);if(this.#Q.change.size)this.#B($);return!0}#B($){let G=Y(()=>{P(G,()=>{this.#$.get($)?.get()})});this.#X.set($,G),G()}get[Symbol.toStringTag](){return D$}get[Symbol.isConcatSpreadable](){return!0}*[Symbol.iterator](){for(let $ of this.#Z){let G=this.#$.get($);if(G)yield G}}get length(){return q(this.#G),this.#Z.length}get(){return q(this.#G),this.#Z.map(($)=>this.#$.get($)?.get()).filter(($)=>$!=null&&$!==B)}at($){return this.#$.get(this.#Z[$])}keys(){return this.#Z.values()}byKey($){return this.#$.get($)}keyAt($){return this.#Z[$]}indexOfKey($){return this.#Z.indexOf($)}on($,G){if(this.#Q[$].add(G),$==="change"&&!this.#X.size)for(let J of this.#$.keys())this.#B(J);return()=>{if(this.#Q[$].delete(G),$==="change"&&!this.#Q.change.size){if(this.#X.size){for(let J of this.#X.values())J.stop();this.#X.clear()}}}}deriveCollection($){return new d(this,$)}}var j$=($)=>K($,D$),W$=($)=>v($)||j$($),_$=($)=>j($);var L$="Ref";class R${#G=new Set;#J;constructor($,G){F("ref",$,G),this.#J=$}get[Symbol.toStringTag](){return L$}get(){return q(this.#G),this.#J}notify(){x(this.#G)}}var E$=($)=>K($,L$);var S$=($)=>{if(!M($)||$.length>1)throw new n("effect",$);let G=j($),J=!1,z,X=Y(()=>P(X,()=>{if(J)throw new U("effect");J=!0,z?.abort(),z=void 0;let Z;try{if(G){z=new AbortController;let Q=z;$(z.signal).then((I)=>{if(M(I)&&z===Q)X.onCleanup(I)}).catch((I)=>{if(!S(I))console.error("Async effect error:",I)})}else if(Z=$(),M(Z))X.onCleanup(Z)}catch(Q){if(!S(Q))console.error("Effect callback error:",Q)}J=!1}));return X(),()=>{z?.abort(),X.stop()}};function O$($,G){try{if($.pending)G.nil?.();else if($.errors)G.err?.($.errors);else if($.ok)G.ok($.values)}catch(J){let z=R(J);if(G.err&&(!$.errors||!$.errors.includes(z)))G.err($.errors?[...$.errors,z]:[z]);else throw z}}function V$($){let G=[],J=!1,z={};for(let[X,Z]of Object.entries($))try{let Q=Z.get();if(Q===B)J=!0;else z[X]=Q}catch(Q){G.push(R(Q))}if(J)return{ok:!1,pending:!0};if(G.length>0)return{ok:!1,errors:G};return{ok:!0,values:z}}export{T as valueString,F as validateSignalValue,N as validateCallback,P as trackSignalReads,q as subscribeActiveWatcher,V$ as resolve,x as notifyWatchers,O$ as match,r as isTaskCallback,E as isSymbol,o as isString,$$ as isStore,a as isState,T$ as isSignal,E$ as isRef,c as isRecordOrArray,D as isRecord,K as isObjectOfType,s as isNumber,A$ as isMutableSignal,l as isMemoCallback,v as isList,M as isFunction,L as isEqual,Q$ as isComputed,j$ as isCollection,j as isAsyncFunction,S as isAbortError,B$ as guardMutableSignal,p as flushPendingReactions,A as emitNotification,w as diff,Y as createWatcher,e as createStore,C$ as createSignal,R as createError,S$ as createEffect,Z$ as createComputed,X$ as batchSignalWrites,B as UNSET,y as Task,M$ as TYPE_STORE,x$ as TYPE_STATE,L$ as TYPE_REF,H$ as TYPE_LIST,t as TYPE_COMPUTED,D$ as TYPE_COLLECTION,C as State,R$ as Ref,K$ as ReadonlySignalError,I$ as NullishSignalValueError,b as Memo,h as List,F$ as InvalidSignalValueError,G$ as InvalidCollectionSourceError,n as InvalidCallbackError,V as DuplicateKeyError,d as DerivedCollection,U as CircularDependencyError,q$ as BaseStore};
package/index.ts CHANGED
@@ -1,22 +1,49 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.16.1
3
+ * @version 0.17.1
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
7
+ export {
8
+ type Collection,
9
+ type CollectionCallback,
10
+ type CollectionSource,
11
+ DerivedCollection,
12
+ isCollection,
13
+ TYPE_COLLECTION,
14
+ } from './src/classes/collection'
7
15
  export {
8
16
  type Computed,
9
- type ComputedCallback,
10
17
  createComputed,
11
18
  isComputed,
12
- isComputedCallback,
19
+ isMemoCallback,
20
+ isTaskCallback,
21
+ Memo,
22
+ type MemoCallback,
23
+ Task,
24
+ type TaskCallback,
13
25
  TYPE_COMPUTED,
14
- } from './src/computed'
26
+ } from './src/classes/computed'
27
+ export {
28
+ type ArrayToRecord,
29
+ isList,
30
+ type KeyConfig,
31
+ List,
32
+ TYPE_LIST,
33
+ } from './src/classes/list'
34
+ export { isRef, Ref, TYPE_REF } from './src/classes/ref'
35
+ export { isState, State, TYPE_STATE } from './src/classes/state'
36
+ export {
37
+ BaseStore,
38
+ createStore,
39
+ isStore,
40
+ type Store,
41
+ TYPE_STORE,
42
+ } from './src/classes/store'
15
43
  export {
16
44
  type DiffResult,
17
45
  diff,
18
46
  isEqual,
19
- type PartialRecord,
20
47
  type UnknownArray,
21
48
  type UnknownRecord,
22
49
  } from './src/diff'
@@ -27,39 +54,40 @@ export {
27
54
  } from './src/effect'
28
55
  export {
29
56
  CircularDependencyError,
57
+ createError,
58
+ DuplicateKeyError,
59
+ type Guard,
60
+ guardMutableSignal,
30
61
  InvalidCallbackError,
62
+ InvalidCollectionSourceError,
31
63
  InvalidSignalValueError,
32
64
  NullishSignalValueError,
33
- StoreKeyExistsError,
34
- StoreKeyRangeError,
35
- StoreKeyReadonlyError,
65
+ ReadonlySignalError,
66
+ validateCallback,
67
+ validateSignalValue,
36
68
  } from './src/errors'
37
69
  export { type MatchHandlers, match } from './src/match'
38
70
  export { type ResolveResult, resolve } from './src/resolve'
39
71
  export {
72
+ createSignal,
40
73
  isMutableSignal,
41
74
  isSignal,
42
75
  type Signal,
43
76
  type SignalValues,
44
- toSignal,
45
77
  type UnknownSignalRecord,
46
78
  } from './src/signal'
47
- export { createState, isState, type State, TYPE_STATE } from './src/state'
48
- export {
49
- createStore,
50
- isStore,
51
- type Store,
52
- type StoreChanges,
53
- TYPE_STORE,
54
- } from './src/store'
55
79
  export {
56
- batch,
80
+ batchSignalWrites,
57
81
  type Cleanup,
58
82
  createWatcher,
59
- flush,
60
- notify,
61
- observe,
62
- subscribe,
83
+ emitNotification,
84
+ flushPendingReactions,
85
+ type Listener,
86
+ type Listeners,
87
+ type Notifications,
88
+ notifyWatchers,
89
+ subscribeActiveWatcher,
90
+ trackSignalReads,
63
91
  type Watcher,
64
92
  } from './src/system'
65
93
  export {
@@ -72,7 +100,6 @@ export {
72
100
  isRecordOrArray,
73
101
  isString,
74
102
  isSymbol,
75
- toError,
76
103
  UNSET,
77
104
  valueString,
78
105
  } from './src/util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
@@ -0,0 +1,282 @@
1
+ import { InvalidCollectionSourceError, validateCallback } from '../errors'
2
+ import type { Signal } from '../signal'
3
+ import {
4
+ type Cleanup,
5
+ createWatcher,
6
+ emitNotification,
7
+ type Listener,
8
+ type Listeners,
9
+ notifyWatchers,
10
+ subscribeActiveWatcher,
11
+ trackSignalReads,
12
+ type Watcher,
13
+ } from '../system'
14
+ import { isAsyncFunction, isFunction, isObjectOfType, UNSET } from '../util'
15
+ import { type Computed, createComputed } from './computed'
16
+ import { isList, type List } from './list'
17
+
18
+ /* === Types === */
19
+
20
+ type CollectionSource<T extends {}> = List<T> | Collection<T>
21
+
22
+ type CollectionCallback<T extends {}, U extends {}> =
23
+ | ((sourceValue: U) => T)
24
+ | ((sourceValue: U, abort: AbortSignal) => Promise<T>)
25
+
26
+ type Collection<T extends {}> = {
27
+ readonly [Symbol.toStringTag]: 'Collection'
28
+ readonly [Symbol.isConcatSpreadable]: true
29
+ [Symbol.iterator](): IterableIterator<Signal<T>>
30
+ get: () => T[]
31
+ at: (index: number) => Signal<T> | undefined
32
+ byKey: (key: string) => Signal<T> | undefined
33
+ keyAt: (index: number) => string | undefined
34
+ indexOfKey: (key: string) => number | undefined
35
+ on: <K extends keyof Listeners>(type: K, listener: Listener<K>) => Cleanup
36
+ deriveCollection: <R extends {}>(
37
+ callback: CollectionCallback<R, T>,
38
+ ) => DerivedCollection<R, T>
39
+ readonly length: number
40
+ }
41
+
42
+ /* === Constants === */
43
+
44
+ const TYPE_COLLECTION = 'Collection' as const
45
+
46
+ /* === Class === */
47
+
48
+ class DerivedCollection<T extends {}, U extends {}> implements Collection<T> {
49
+ #watchers = new Set<Watcher>()
50
+ #source: CollectionSource<U>
51
+ #callback: CollectionCallback<T, U>
52
+ #signals = new Map<string, Computed<T>>()
53
+ #ownWatchers = new Map<string, Watcher>()
54
+ #listeners: Listeners = {
55
+ add: new Set<Listener<'add'>>(),
56
+ change: new Set<Listener<'change'>>(),
57
+ remove: new Set<Listener<'remove'>>(),
58
+ sort: new Set<Listener<'sort'>>(),
59
+ }
60
+ #order: string[] = []
61
+
62
+ constructor(
63
+ source: CollectionSource<U> | (() => CollectionSource<U>),
64
+ callback: CollectionCallback<T, U>,
65
+ ) {
66
+ validateCallback('collection', callback)
67
+
68
+ if (isFunction(source)) source = source()
69
+ if (!isCollectionSource(source))
70
+ throw new InvalidCollectionSourceError('derived collection', source)
71
+ this.#source = source
72
+
73
+ this.#callback = callback
74
+
75
+ for (let i = 0; i < this.#source.length; i++) {
76
+ const key = this.#source.keyAt(i)
77
+ if (!key) continue
78
+
79
+ this.#add(key)
80
+ }
81
+
82
+ this.#source.on('add', additions => {
83
+ for (const key of additions) {
84
+ if (!this.#signals.has(key)) {
85
+ this.#add(key)
86
+ // For async computations, trigger initial computation
87
+ const signal = this.#signals.get(key)
88
+ if (signal && isAsyncCollectionCallback(this.#callback))
89
+ signal.get()
90
+ }
91
+ }
92
+ notifyWatchers(this.#watchers)
93
+ emitNotification(this.#listeners.add, additions)
94
+ })
95
+
96
+ this.#source.on('remove', removals => {
97
+ for (const key of removals) {
98
+ if (!this.#signals.has(key)) continue
99
+
100
+ this.#signals.delete(key)
101
+ const index = this.#order.indexOf(key)
102
+ if (index >= 0) this.#order.splice(index, 1)
103
+
104
+ const watcher = this.#ownWatchers.get(key)
105
+ if (watcher) {
106
+ watcher.stop()
107
+ this.#ownWatchers.delete(key)
108
+ }
109
+ }
110
+ this.#order = this.#order.filter(() => true) // Compact array
111
+ notifyWatchers(this.#watchers)
112
+ emitNotification(this.#listeners.remove, removals)
113
+ })
114
+
115
+ this.#source.on('sort', newOrder => {
116
+ this.#order = [...newOrder]
117
+ notifyWatchers(this.#watchers)
118
+ emitNotification(this.#listeners.sort, newOrder)
119
+ })
120
+ }
121
+
122
+ #add(key: string): boolean {
123
+ const computedCallback = isAsyncCollectionCallback<T>(this.#callback)
124
+ ? async (_: T, abort: AbortSignal) => {
125
+ const sourceSignal = this.#source.byKey(key)
126
+ if (!sourceSignal) return UNSET
127
+
128
+ const sourceValue = sourceSignal.get() as U
129
+ if (sourceValue === UNSET) return UNSET
130
+ return this.#callback(sourceValue, abort)
131
+ }
132
+ : () => {
133
+ const sourceSignal = this.#source.byKey(key)
134
+ if (!sourceSignal) return UNSET
135
+
136
+ const sourceValue = sourceSignal.get() as U
137
+ if (sourceValue === UNSET) return UNSET
138
+ return (this.#callback as (sourceValue: U) => T)(
139
+ sourceValue,
140
+ )
141
+ }
142
+
143
+ const signal = createComputed(computedCallback)
144
+
145
+ this.#signals.set(key, signal)
146
+ if (!this.#order.includes(key)) this.#order.push(key)
147
+ if (this.#listeners.change.size) this.#addWatcher(key)
148
+ return true
149
+ }
150
+
151
+ #addWatcher(key: string): void {
152
+ const watcher = createWatcher(() => {
153
+ trackSignalReads(watcher, () => {
154
+ this.#signals.get(key)?.get() // Subscribe to the signal
155
+ })
156
+ })
157
+ this.#ownWatchers.set(key, watcher)
158
+ watcher()
159
+ }
160
+
161
+ get [Symbol.toStringTag](): 'Collection' {
162
+ return TYPE_COLLECTION
163
+ }
164
+
165
+ get [Symbol.isConcatSpreadable](): true {
166
+ return true
167
+ }
168
+
169
+ *[Symbol.iterator](): IterableIterator<Computed<T>> {
170
+ for (const key of this.#order) {
171
+ const signal = this.#signals.get(key)
172
+ if (signal) yield signal as Computed<T>
173
+ }
174
+ }
175
+
176
+ get length(): number {
177
+ subscribeActiveWatcher(this.#watchers)
178
+ return this.#order.length
179
+ }
180
+
181
+ get(): T[] {
182
+ subscribeActiveWatcher(this.#watchers)
183
+ return this.#order
184
+ .map(key => this.#signals.get(key)?.get())
185
+ .filter(v => v != null && v !== UNSET) as T[]
186
+ }
187
+
188
+ at(index: number): Computed<T> | undefined {
189
+ return this.#signals.get(this.#order[index])
190
+ }
191
+
192
+ keys(): IterableIterator<string> {
193
+ return this.#order.values()
194
+ }
195
+
196
+ byKey(key: string): Computed<T> | undefined {
197
+ return this.#signals.get(key)
198
+ }
199
+
200
+ keyAt(index: number): string | undefined {
201
+ return this.#order[index]
202
+ }
203
+
204
+ indexOfKey(key: string): number {
205
+ return this.#order.indexOf(key)
206
+ }
207
+
208
+ on<K extends keyof Listeners>(type: K, listener: Listener<K>): Cleanup {
209
+ this.#listeners[type].add(listener)
210
+ if (type === 'change' && !this.#ownWatchers.size) {
211
+ for (const key of this.#signals.keys()) this.#addWatcher(key)
212
+ }
213
+
214
+ return () => {
215
+ this.#listeners[type].delete(listener)
216
+ if (type === 'change' && !this.#listeners.change.size) {
217
+ if (this.#ownWatchers.size) {
218
+ for (const watcher of this.#ownWatchers.values())
219
+ watcher.stop()
220
+ this.#ownWatchers.clear()
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ deriveCollection<R extends {}>(
227
+ callback: (sourceValue: T) => R,
228
+ ): DerivedCollection<R, T>
229
+ deriveCollection<R extends {}>(
230
+ callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
231
+ ): DerivedCollection<R, T>
232
+ deriveCollection<R extends {}>(
233
+ callback: CollectionCallback<R, T>,
234
+ ): DerivedCollection<R, T> {
235
+ return new DerivedCollection(this, callback)
236
+ }
237
+ }
238
+
239
+ /* === Functions === */
240
+
241
+ /**
242
+ * Check if a value is a collection signal
243
+ *
244
+ * @since 0.17.0
245
+ * @param {unknown} value - Value to check
246
+ * @returns {boolean} - True if value is a collection signal, false otherwise
247
+ */
248
+ const isCollection = /*#__PURE__*/ <T extends {}, U extends {}>(
249
+ value: unknown,
250
+ ): value is DerivedCollection<T, U> => isObjectOfType(value, TYPE_COLLECTION)
251
+
252
+ /**
253
+ * Check if a value is a collection source
254
+ *
255
+ * @since 0.17.0
256
+ * @param {unknown} value - Value to check
257
+ * @returns {boolean} - True if value is a collection source, false otherwise
258
+ */
259
+ const isCollectionSource = /*#__PURE__*/ <T extends {}>(
260
+ value: unknown,
261
+ ): value is CollectionSource<T> => isList(value) || isCollection(value)
262
+
263
+ /**
264
+ * Check if the provided callback is an async function
265
+ *
266
+ * @since 0.17.0
267
+ * @param {unknown} callback - Value to check
268
+ * @returns {boolean} - True if value is an async collection callback, false otherwise
269
+ */
270
+ const isAsyncCollectionCallback = <T extends {}>(
271
+ callback: unknown,
272
+ ): callback is (sourceValue: unknown, abort: AbortSignal) => Promise<T> =>
273
+ isAsyncFunction(callback)
274
+
275
+ export {
276
+ type Collection,
277
+ type CollectionSource,
278
+ type CollectionCallback,
279
+ DerivedCollection,
280
+ isCollection,
281
+ TYPE_COLLECTION,
282
+ }
@@ -0,0 +1,176 @@
1
+ import type { DiffResult, UnknownRecord } from '../diff'
2
+ import { guardMutableSignal } from '../errors'
3
+ import type { Signal } from '../signal'
4
+ import {
5
+ batchSignalWrites,
6
+ type Cleanup,
7
+ createWatcher,
8
+ emitNotification,
9
+ type Listener,
10
+ type Listeners,
11
+ trackSignalReads,
12
+ type Watcher,
13
+ } from '../system'
14
+
15
+ /* === Types === */
16
+
17
+ type CompositeListeners = Pick<Listeners, 'add' | 'change' | 'remove'>
18
+
19
+ /* === Class Definitions === */
20
+
21
+ class Composite<T extends UnknownRecord, S extends Signal<T[keyof T] & {}>> {
22
+ signals = new Map<string, S>()
23
+ #validate: <K extends keyof T & string>(
24
+ key: K,
25
+ value: unknown,
26
+ ) => value is T[K] & {}
27
+ #create: <V extends T[keyof T] & {}>(value: V) => S
28
+ #watchers = new Map<string, Watcher>()
29
+ #listeners: CompositeListeners = {
30
+ add: new Set<Listener<'add'>>(),
31
+ change: new Set<Listener<'change'>>(),
32
+ remove: new Set<Listener<'remove'>>(),
33
+ }
34
+ #batching = false
35
+
36
+ constructor(
37
+ values: T,
38
+ validate: <K extends keyof T & string>(
39
+ key: K,
40
+ value: unknown,
41
+ ) => value is T[K] & {},
42
+ create: <V extends T[keyof T] & {}>(value: V) => S,
43
+ ) {
44
+ this.#validate = validate
45
+ this.#create = create
46
+ this.change(
47
+ {
48
+ add: values,
49
+ change: {},
50
+ remove: {},
51
+ changed: true,
52
+ },
53
+ true,
54
+ )
55
+ }
56
+
57
+ #addWatcher(key: string): void {
58
+ const watcher = createWatcher(() => {
59
+ trackSignalReads(watcher, () => {
60
+ this.signals.get(key)?.get() // Subscribe to the signal
61
+ if (!this.#batching)
62
+ emitNotification(this.#listeners.change, [key])
63
+ })
64
+ })
65
+ this.#watchers.set(key, watcher)
66
+ watcher()
67
+ }
68
+
69
+ add<K extends keyof T & string>(key: K, value: T[K]): boolean {
70
+ if (!this.#validate(key, value)) return false
71
+
72
+ this.signals.set(key, this.#create(value))
73
+ if (this.#listeners.change.size) this.#addWatcher(key)
74
+
75
+ if (!this.#batching) emitNotification(this.#listeners.add, [key])
76
+ return true
77
+ }
78
+
79
+ remove<K extends keyof T & string>(key: K): boolean {
80
+ const ok = this.signals.delete(key)
81
+ if (!ok) return false
82
+
83
+ const watcher = this.#watchers.get(key)
84
+ if (watcher) {
85
+ watcher.stop()
86
+ this.#watchers.delete(key)
87
+ }
88
+
89
+ if (!this.#batching) emitNotification(this.#listeners.remove, [key])
90
+ return true
91
+ }
92
+
93
+ change(changes: DiffResult, initialRun?: boolean): boolean {
94
+ this.#batching = true
95
+
96
+ // Additions
97
+ if (Object.keys(changes.add).length) {
98
+ for (const key in changes.add)
99
+ this.add(
100
+ key as Extract<keyof T, string>,
101
+ changes.add[key] as T[Extract<keyof T, string>] & {},
102
+ )
103
+
104
+ // Queue initial additions event to allow listeners to be added first
105
+ const notify = () =>
106
+ emitNotification(this.#listeners.add, Object.keys(changes.add))
107
+ if (initialRun) setTimeout(notify, 0)
108
+ else notify()
109
+ }
110
+
111
+ // Changes
112
+ if (Object.keys(changes.change).length) {
113
+ batchSignalWrites(() => {
114
+ for (const key in changes.change) {
115
+ const value = changes.change[key]
116
+ if (!this.#validate(key as keyof T & string, value))
117
+ continue
118
+
119
+ const signal = this.signals.get(key)
120
+ if (guardMutableSignal(`list item "${key}"`, value, signal))
121
+ signal.set(value)
122
+ }
123
+ })
124
+ emitNotification(
125
+ this.#listeners.change,
126
+ Object.keys(changes.change),
127
+ )
128
+ }
129
+
130
+ // Removals
131
+ if (Object.keys(changes.remove).length) {
132
+ for (const key in changes.remove)
133
+ this.remove(key as keyof T & string)
134
+ emitNotification(
135
+ this.#listeners.remove,
136
+ Object.keys(changes.remove),
137
+ )
138
+ }
139
+
140
+ this.#batching = false
141
+ return changes.changed
142
+ }
143
+
144
+ clear(): boolean {
145
+ const keys = Array.from(this.signals.keys())
146
+ this.signals.clear()
147
+ this.#watchers.clear()
148
+ emitNotification(this.#listeners.remove, keys)
149
+ return true
150
+ }
151
+
152
+ on<K extends keyof CompositeListeners>(
153
+ type: K,
154
+ listener: Listener<K>,
155
+ ): Cleanup {
156
+ this.#listeners[type].add(listener)
157
+ if (type === 'change' && !this.#watchers.size) {
158
+ this.#batching = true
159
+ for (const key of this.signals.keys()) this.#addWatcher(key)
160
+ this.#batching = false
161
+ }
162
+
163
+ return () => {
164
+ this.#listeners[type].delete(listener)
165
+ if (type === 'change' && !this.#listeners.change.size) {
166
+ if (this.#watchers.size) {
167
+ for (const watcher of this.#watchers.values())
168
+ watcher.stop()
169
+ this.#watchers.clear()
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ export { Composite, type CompositeListeners }