@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.
- package/.ai-context.md +85 -21
- package/.cursorrules +11 -5
- package/.github/copilot-instructions.md +64 -13
- package/CLAUDE.md +143 -163
- package/LICENSE +1 -1
- package/README.md +248 -333
- package/archive/benchmark.ts +688 -0
- package/archive/collection.ts +312 -0
- package/{src → archive}/computed.ts +21 -21
- package/archive/list.ts +551 -0
- package/archive/memo.ts +139 -0
- package/{src → archive}/state.ts +13 -11
- package/archive/store.ts +368 -0
- package/archive/task.ts +194 -0
- package/eslint.config.js +1 -0
- package/index.dev.js +938 -509
- package/index.js +1 -1
- package/index.ts +50 -23
- package/package.json +1 -1
- package/src/classes/collection.ts +282 -0
- package/src/classes/composite.ts +176 -0
- package/src/classes/computed.ts +333 -0
- package/src/classes/list.ts +305 -0
- package/src/classes/ref.ts +68 -0
- package/src/classes/state.ts +98 -0
- package/src/classes/store.ts +210 -0
- package/src/diff.ts +26 -53
- package/src/effect.ts +9 -9
- package/src/errors.ts +71 -25
- package/src/match.ts +5 -12
- package/src/resolve.ts +3 -2
- package/src/signal.ts +58 -41
- package/src/system.ts +79 -42
- package/src/util.ts +16 -34
- package/test/batch.test.ts +15 -17
- package/test/benchmark.test.ts +4 -4
- package/test/collection.test.ts +853 -0
- package/test/computed.test.ts +138 -130
- package/test/diff.test.ts +2 -2
- package/test/effect.test.ts +36 -35
- package/test/list.test.ts +754 -0
- package/test/match.test.ts +25 -25
- package/test/ref.test.ts +227 -0
- package/test/resolve.test.ts +17 -19
- package/test/signal.test.ts +70 -119
- package/test/state.test.ts +44 -44
- package/test/store.test.ts +253 -929
- package/types/index.d.ts +12 -9
- package/types/src/classes/collection.d.ts +46 -0
- package/types/src/classes/composite.d.ts +15 -0
- package/types/src/classes/computed.d.ts +97 -0
- package/types/src/classes/list.d.ts +41 -0
- package/types/src/classes/ref.d.ts +39 -0
- package/types/src/classes/state.d.ts +52 -0
- package/types/src/classes/store.d.ts +51 -0
- package/types/src/diff.d.ts +8 -12
- package/types/src/errors.d.ts +17 -11
- package/types/src/signal.d.ts +27 -14
- package/types/src/system.d.ts +41 -20
- package/types/src/util.d.ts +6 -4
- package/src/store.ts +0 -474
- package/types/src/collection.d.ts +0 -26
- package/types/src/computed.d.ts +0 -33
- package/types/src/scheduler.d.ts +0 -55
- package/types/src/state.d.ts +0 -24
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
80
|
+
batchSignalWrites,
|
|
57
81
|
type Cleanup,
|
|
58
82
|
createWatcher,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
@@ -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 }
|