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