@zeix/cause-effect 0.15.2 → 0.16.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 +254 -0
- package/.cursorrules +54 -0
- package/.github/copilot-instructions.md +132 -0
- package/CLAUDE.md +319 -0
- package/README.md +136 -166
- package/eslint.config.js +1 -1
- package/index.dev.js +263 -262
- package/index.js +1 -1
- package/index.ts +24 -23
- package/package.json +1 -1
- package/src/computed.ts +54 -44
- package/src/diff.ts +24 -21
- package/src/effect.ts +15 -12
- package/src/errors.ts +8 -0
- package/src/signal.ts +6 -6
- package/src/state.ts +44 -48
- package/src/store.ts +236 -309
- package/src/system.ts +122 -0
- package/src/util.ts +3 -12
- package/test/batch.test.ts +18 -11
- package/test/benchmark.test.ts +4 -4
- package/test/computed.test.ts +508 -72
- package/test/effect.test.ts +61 -61
- package/test/match.test.ts +25 -25
- package/test/resolve.test.ts +16 -16
- package/test/signal.test.ts +9 -9
- package/test/state.test.ts +212 -25
- package/test/store.test.ts +1007 -1357
- package/test/util/dependency-graph.ts +1 -1
- package/types/index.d.ts +9 -9
- package/types/src/collection.d.ts +26 -0
- package/types/src/computed.d.ts +9 -9
- package/types/src/diff.d.ts +7 -7
- package/types/src/effect.d.ts +3 -3
- package/types/src/errors.d.ts +4 -1
- package/types/src/state.d.ts +5 -5
- package/types/src/store.d.ts +29 -44
- package/types/src/system.d.ts +44 -0
- package/types/src/util.d.ts +1 -2
- package/src/scheduler.ts +0 -172
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class
|
|
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};
|
package/index.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.16.1
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export {
|
|
8
8
|
type Computed,
|
|
9
9
|
type ComputedCallback,
|
|
10
|
-
|
|
10
|
+
createComputed,
|
|
11
11
|
isComputed,
|
|
12
12
|
isComputedCallback,
|
|
13
13
|
TYPE_COMPUTED,
|
|
@@ -16,13 +16,18 @@ export {
|
|
|
16
16
|
type DiffResult,
|
|
17
17
|
diff,
|
|
18
18
|
isEqual,
|
|
19
|
+
type PartialRecord,
|
|
19
20
|
type UnknownArray,
|
|
20
21
|
type UnknownRecord,
|
|
21
|
-
type UnknownRecordOrArray,
|
|
22
22
|
} from './src/diff'
|
|
23
|
-
export {
|
|
23
|
+
export {
|
|
24
|
+
createEffect,
|
|
25
|
+
type EffectCallback,
|
|
26
|
+
type MaybeCleanup,
|
|
27
|
+
} from './src/effect'
|
|
24
28
|
export {
|
|
25
29
|
CircularDependencyError,
|
|
30
|
+
InvalidCallbackError,
|
|
26
31
|
InvalidSignalValueError,
|
|
27
32
|
NullishSignalValueError,
|
|
28
33
|
StoreKeyExistsError,
|
|
@@ -31,18 +36,6 @@ export {
|
|
|
31
36
|
} from './src/errors'
|
|
32
37
|
export { type MatchHandlers, match } from './src/match'
|
|
33
38
|
export { type ResolveResult, resolve } from './src/resolve'
|
|
34
|
-
export {
|
|
35
|
-
batch,
|
|
36
|
-
type Cleanup,
|
|
37
|
-
enqueue,
|
|
38
|
-
flush,
|
|
39
|
-
notify,
|
|
40
|
-
observe,
|
|
41
|
-
subscribe,
|
|
42
|
-
type Updater,
|
|
43
|
-
type Watcher,
|
|
44
|
-
watch,
|
|
45
|
-
} from './src/scheduler'
|
|
46
39
|
export {
|
|
47
40
|
isMutableSignal,
|
|
48
41
|
isSignal,
|
|
@@ -51,27 +44,35 @@ export {
|
|
|
51
44
|
toSignal,
|
|
52
45
|
type UnknownSignalRecord,
|
|
53
46
|
} from './src/signal'
|
|
54
|
-
export { isState, type State,
|
|
47
|
+
export { createState, isState, type State, TYPE_STATE } from './src/state'
|
|
55
48
|
export {
|
|
49
|
+
createStore,
|
|
56
50
|
isStore,
|
|
57
51
|
type Store,
|
|
58
|
-
type
|
|
59
|
-
type StoreChangeEvent,
|
|
60
|
-
type StoreEventMap,
|
|
61
|
-
type StoreRemoveEvent,
|
|
62
|
-
type StoreSortEvent,
|
|
63
|
-
store,
|
|
52
|
+
type StoreChanges,
|
|
64
53
|
TYPE_STORE,
|
|
65
54
|
} from './src/store'
|
|
55
|
+
export {
|
|
56
|
+
batch,
|
|
57
|
+
type Cleanup,
|
|
58
|
+
createWatcher,
|
|
59
|
+
flush,
|
|
60
|
+
notify,
|
|
61
|
+
observe,
|
|
62
|
+
subscribe,
|
|
63
|
+
type Watcher,
|
|
64
|
+
} from './src/system'
|
|
66
65
|
export {
|
|
67
66
|
isAbortError,
|
|
68
67
|
isAsyncFunction,
|
|
69
68
|
isFunction,
|
|
70
69
|
isNumber,
|
|
70
|
+
isObjectOfType,
|
|
71
71
|
isRecord,
|
|
72
72
|
isRecordOrArray,
|
|
73
73
|
isString,
|
|
74
74
|
isSymbol,
|
|
75
75
|
toError,
|
|
76
76
|
UNSET,
|
|
77
|
+
valueString,
|
|
77
78
|
} from './src/util'
|
package/package.json
CHANGED
package/src/computed.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { isEqual } from './diff'
|
|
2
|
-
import { CircularDependencyError } from './errors'
|
|
3
2
|
import {
|
|
3
|
+
CircularDependencyError,
|
|
4
|
+
InvalidCallbackError,
|
|
5
|
+
NullishSignalValueError,
|
|
6
|
+
} from './errors'
|
|
7
|
+
import {
|
|
8
|
+
createWatcher,
|
|
4
9
|
flush,
|
|
5
10
|
notify,
|
|
6
11
|
observe,
|
|
7
12
|
subscribe,
|
|
8
13
|
type Watcher,
|
|
9
|
-
|
|
10
|
-
} from './scheduler'
|
|
14
|
+
} from './system'
|
|
11
15
|
import {
|
|
12
16
|
isAbortError,
|
|
13
17
|
isAsyncFunction,
|
|
@@ -15,17 +19,18 @@ import {
|
|
|
15
19
|
isObjectOfType,
|
|
16
20
|
toError,
|
|
17
21
|
UNSET,
|
|
22
|
+
valueString,
|
|
18
23
|
} from './util'
|
|
19
24
|
|
|
20
25
|
/* === Types === */
|
|
21
26
|
|
|
22
27
|
type Computed<T extends {}> = {
|
|
23
|
-
[Symbol.toStringTag]: 'Computed'
|
|
28
|
+
readonly [Symbol.toStringTag]: 'Computed'
|
|
24
29
|
get(): T
|
|
25
30
|
}
|
|
26
31
|
type ComputedCallback<T extends {} & { then?: undefined }> =
|
|
27
|
-
| ((abort: AbortSignal) => Promise<T>)
|
|
28
|
-
| (() => T)
|
|
32
|
+
| ((oldValue: T, abort: AbortSignal) => Promise<T>)
|
|
33
|
+
| ((oldValue: T) => T)
|
|
29
34
|
|
|
30
35
|
/* === Constants === */
|
|
31
36
|
|
|
@@ -37,14 +42,21 @@ const TYPE_COMPUTED = 'Computed'
|
|
|
37
42
|
* Create a derived signal from existing signals
|
|
38
43
|
*
|
|
39
44
|
* @since 0.9.0
|
|
40
|
-
* @param {ComputedCallback<T>}
|
|
45
|
+
* @param {ComputedCallback<T>} callback - Computation callback function
|
|
41
46
|
* @returns {Computed<T>} - Computed signal
|
|
42
47
|
*/
|
|
43
|
-
const
|
|
48
|
+
const createComputed = <T extends {}>(
|
|
49
|
+
callback: ComputedCallback<T>,
|
|
50
|
+
initialValue: T = UNSET,
|
|
51
|
+
): Computed<T> => {
|
|
52
|
+
if (!isComputedCallback(callback))
|
|
53
|
+
throw new InvalidCallbackError('computed', valueString(callback))
|
|
54
|
+
if (initialValue == null) throw new NullishSignalValueError('computed')
|
|
55
|
+
|
|
44
56
|
const watchers: Set<Watcher> = new Set()
|
|
45
57
|
|
|
46
58
|
// Internal state
|
|
47
|
-
let value: T =
|
|
59
|
+
let value: T = initialValue
|
|
48
60
|
let error: Error | undefined
|
|
49
61
|
let controller: AbortController | undefined
|
|
50
62
|
let dirty = true
|
|
@@ -75,22 +87,22 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
75
87
|
error = newError
|
|
76
88
|
}
|
|
77
89
|
const settle =
|
|
78
|
-
<T>(
|
|
90
|
+
<T>(fn: (arg: T) => void) =>
|
|
79
91
|
(arg: T) => {
|
|
80
92
|
computing = false
|
|
81
93
|
controller = undefined
|
|
82
|
-
|
|
94
|
+
fn(arg)
|
|
83
95
|
if (changed) notify(watchers)
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
// Own watcher: called when notified from sources (push)
|
|
87
|
-
const
|
|
99
|
+
const watcher = createWatcher(() => {
|
|
88
100
|
dirty = true
|
|
89
101
|
controller?.abort()
|
|
90
102
|
if (watchers.size) notify(watchers)
|
|
91
|
-
else
|
|
103
|
+
else watcher.cleanup()
|
|
92
104
|
})
|
|
93
|
-
|
|
105
|
+
watcher.unwatch(() => {
|
|
94
106
|
controller?.abort()
|
|
95
107
|
})
|
|
96
108
|
|
|
@@ -99,7 +111,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
99
111
|
observe(() => {
|
|
100
112
|
if (computing) throw new CircularDependencyError('computed')
|
|
101
113
|
changed = false
|
|
102
|
-
if (isAsyncFunction(
|
|
114
|
+
if (isAsyncFunction(callback)) {
|
|
103
115
|
// Return current value until promise resolves
|
|
104
116
|
if (controller) return value
|
|
105
117
|
controller = new AbortController()
|
|
@@ -108,9 +120,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
108
120
|
() => {
|
|
109
121
|
computing = false
|
|
110
122
|
controller = undefined
|
|
111
|
-
|
|
112
|
-
// Retry computation with updated state
|
|
113
|
-
compute()
|
|
123
|
+
compute() // Retry computation with updated state
|
|
114
124
|
},
|
|
115
125
|
{
|
|
116
126
|
once: true,
|
|
@@ -120,7 +130,9 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
120
130
|
let result: T | Promise<T>
|
|
121
131
|
computing = true
|
|
122
132
|
try {
|
|
123
|
-
result = controller
|
|
133
|
+
result = controller
|
|
134
|
+
? callback(value, controller.signal)
|
|
135
|
+
: (callback as (oldValue: T) => T)(value)
|
|
124
136
|
} catch (e) {
|
|
125
137
|
if (isAbortError(e)) nil()
|
|
126
138
|
else err(e)
|
|
@@ -131,34 +143,32 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
131
143
|
else if (null == result || UNSET === result) nil()
|
|
132
144
|
else ok(result)
|
|
133
145
|
computing = false
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
* Get the current value of the computed
|
|
141
|
-
*
|
|
142
|
-
* @since 0.9.0
|
|
143
|
-
* @returns {T} - current value of the computed
|
|
144
|
-
*/
|
|
145
|
-
get: (): T => {
|
|
146
|
-
subscribe(watchers)
|
|
147
|
-
flush()
|
|
148
|
-
if (dirty) compute()
|
|
149
|
-
if (error) throw error
|
|
150
|
-
return value
|
|
146
|
+
}, watcher)
|
|
147
|
+
|
|
148
|
+
const computed: Record<PropertyKey, unknown> = {}
|
|
149
|
+
Object.defineProperties(computed, {
|
|
150
|
+
[Symbol.toStringTag]: {
|
|
151
|
+
value: TYPE_COMPUTED,
|
|
151
152
|
},
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
get: {
|
|
154
|
+
value: (): T => {
|
|
155
|
+
subscribe(watchers)
|
|
156
|
+
flush()
|
|
157
|
+
if (dirty) compute()
|
|
158
|
+
if (error) throw error
|
|
159
|
+
return value
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
return computed as Computed<T>
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
/**
|
|
157
|
-
* Check if a value is a computed
|
|
167
|
+
* Check if a value is a computed signal
|
|
158
168
|
*
|
|
159
169
|
* @since 0.9.0
|
|
160
|
-
* @param {unknown} value -
|
|
161
|
-
* @returns {boolean} - true if value is a computed
|
|
170
|
+
* @param {unknown} value - Value to check
|
|
171
|
+
* @returns {boolean} - true if value is a computed signal, false otherwise
|
|
162
172
|
*/
|
|
163
173
|
const isComputed = /*#__PURE__*/ <T extends {}>(
|
|
164
174
|
value: unknown,
|
|
@@ -168,18 +178,18 @@ const isComputed = /*#__PURE__*/ <T extends {}>(
|
|
|
168
178
|
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
169
179
|
*
|
|
170
180
|
* @since 0.12.0
|
|
171
|
-
* @param {unknown} value -
|
|
181
|
+
* @param {unknown} value - Value to check
|
|
172
182
|
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
173
183
|
*/
|
|
174
184
|
const isComputedCallback = /*#__PURE__*/ <T extends {}>(
|
|
175
185
|
value: unknown,
|
|
176
|
-
): value is ComputedCallback<T> => isFunction(value) && value.length <
|
|
186
|
+
): value is ComputedCallback<T> => isFunction(value) && value.length < 3
|
|
177
187
|
|
|
178
188
|
/* === Exports === */
|
|
179
189
|
|
|
180
190
|
export {
|
|
181
191
|
TYPE_COMPUTED,
|
|
182
|
-
|
|
192
|
+
createComputed,
|
|
183
193
|
isComputed,
|
|
184
194
|
isComputedCallback,
|
|
185
195
|
type Computed,
|
package/src/diff.ts
CHANGED
|
@@ -5,16 +5,20 @@ import { isRecord, isRecordOrArray, UNSET } from './util'
|
|
|
5
5
|
|
|
6
6
|
type UnknownRecord = Record<string, unknown & {}>
|
|
7
7
|
type UnknownArray = ReadonlyArray<unknown & {}>
|
|
8
|
+
|
|
8
9
|
type ArrayToRecord<T extends UnknownArray> = {
|
|
9
10
|
[key: string]: T extends Array<infer U extends {}> ? U : never
|
|
10
11
|
}
|
|
11
|
-
type UnknownRecordOrArray = UnknownRecord | ArrayToRecord<UnknownArray>
|
|
12
12
|
|
|
13
|
-
type
|
|
13
|
+
type PartialRecord<T> = T extends UnknownArray
|
|
14
|
+
? Partial<ArrayToRecord<T>>
|
|
15
|
+
: Partial<T>
|
|
16
|
+
|
|
17
|
+
type DiffResult<T extends UnknownRecord | UnknownArray = UnknownRecord> = {
|
|
14
18
|
changed: boolean
|
|
15
|
-
add:
|
|
16
|
-
change:
|
|
17
|
-
remove:
|
|
19
|
+
add: PartialRecord<T>
|
|
20
|
+
change: PartialRecord<T>
|
|
21
|
+
remove: PartialRecord<T>
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
/* === Functions === */
|
|
@@ -88,9 +92,9 @@ const isEqual = <T>(a: T, b: T, visited?: WeakSet<object>): boolean => {
|
|
|
88
92
|
* @param {T} newObj - The new record to compare
|
|
89
93
|
* @returns {DiffResult<T>} The result of the comparison
|
|
90
94
|
*/
|
|
91
|
-
const diff = <T extends
|
|
92
|
-
oldObj: T,
|
|
93
|
-
newObj: T,
|
|
95
|
+
const diff = <T extends UnknownRecord | UnknownArray>(
|
|
96
|
+
oldObj: T extends UnknownArray ? ArrayToRecord<T> : T,
|
|
97
|
+
newObj: T extends UnknownArray ? ArrayToRecord<T> : T,
|
|
94
98
|
): DiffResult<T> => {
|
|
95
99
|
// Guard against non-objects that can't be diffed properly with Object.keys and 'in' operator
|
|
96
100
|
const oldValid = isRecordOrArray(oldObj)
|
|
@@ -100,17 +104,17 @@ const diff = <T extends UnknownRecordOrArray>(
|
|
|
100
104
|
const changed = !Object.is(oldObj, newObj)
|
|
101
105
|
return {
|
|
102
106
|
changed,
|
|
103
|
-
add: changed && newValid ? newObj : {},
|
|
104
|
-
change: {}
|
|
105
|
-
remove: changed && oldValid ? oldObj : {},
|
|
107
|
+
add: changed && newValid ? newObj : ({} as PartialRecord<T>),
|
|
108
|
+
change: {} as PartialRecord<T>,
|
|
109
|
+
remove: changed && oldValid ? oldObj : ({} as PartialRecord<T>),
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
const visited = new WeakSet()
|
|
110
114
|
|
|
111
|
-
const add
|
|
112
|
-
const change
|
|
113
|
-
const remove
|
|
115
|
+
const add = {} as PartialRecord<T>
|
|
116
|
+
const change = {} as PartialRecord<T>
|
|
117
|
+
const remove = {} as PartialRecord<T>
|
|
114
118
|
|
|
115
119
|
const oldKeys = Object.keys(oldObj)
|
|
116
120
|
const newKeys = Object.keys(newObj)
|
|
@@ -121,18 +125,17 @@ const diff = <T extends UnknownRecordOrArray>(
|
|
|
121
125
|
const newHas = key in newObj
|
|
122
126
|
|
|
123
127
|
if (!oldHas && newHas) {
|
|
124
|
-
add[key
|
|
128
|
+
add[key] = newObj[key]
|
|
125
129
|
continue
|
|
126
130
|
} else if (oldHas && !newHas) {
|
|
127
|
-
remove[key
|
|
131
|
+
remove[key] = UNSET
|
|
128
132
|
continue
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
const oldValue = oldObj[key]
|
|
132
|
-
const newValue = newObj[key]
|
|
135
|
+
const oldValue = oldObj[key]
|
|
136
|
+
const newValue = newObj[key]
|
|
133
137
|
|
|
134
|
-
if (!isEqual(oldValue, newValue, visited))
|
|
135
|
-
change[key as keyof T] = newValue
|
|
138
|
+
if (!isEqual(oldValue, newValue, visited)) change[key] = newValue
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
const changed =
|
|
@@ -157,5 +160,5 @@ export {
|
|
|
157
160
|
isEqual,
|
|
158
161
|
type UnknownRecord,
|
|
159
162
|
type UnknownArray,
|
|
160
|
-
type
|
|
163
|
+
type PartialRecord,
|
|
161
164
|
}
|
package/src/effect.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CircularDependencyError } from './errors'
|
|
2
|
-
import { type Cleanup,
|
|
3
|
-
import { isAbortError, isAsyncFunction, isFunction } from './util'
|
|
1
|
+
import { CircularDependencyError, InvalidCallbackError } from './errors'
|
|
2
|
+
import { type Cleanup, createWatcher, observe } from './system'
|
|
3
|
+
import { isAbortError, isAsyncFunction, isFunction, valueString } from './util'
|
|
4
4
|
|
|
5
5
|
/* === Types === */
|
|
6
6
|
|
|
@@ -24,12 +24,15 @@ type EffectCallback =
|
|
|
24
24
|
* @param {EffectCallback} callback - Synchronous or asynchronous effect callback
|
|
25
25
|
* @returns {Cleanup} - Cleanup function for the effect
|
|
26
26
|
*/
|
|
27
|
-
const
|
|
28
|
-
|
|
27
|
+
const createEffect = (callback: EffectCallback): Cleanup => {
|
|
28
|
+
if (!isFunction(callback) || callback.length > 1)
|
|
29
|
+
throw new InvalidCallbackError('effect', valueString(callback))
|
|
30
|
+
|
|
31
|
+
const isAsync = isAsyncFunction(callback)
|
|
29
32
|
let running = false
|
|
30
33
|
let controller: AbortController | undefined
|
|
31
34
|
|
|
32
|
-
const
|
|
35
|
+
const watcher = createWatcher(() =>
|
|
33
36
|
observe(() => {
|
|
34
37
|
if (running) throw new CircularDependencyError('effect')
|
|
35
38
|
running = true
|
|
@@ -52,7 +55,7 @@ const effect = (callback: EffectCallback): Cleanup => {
|
|
|
52
55
|
isFunction(cleanup) &&
|
|
53
56
|
controller === currentController
|
|
54
57
|
)
|
|
55
|
-
|
|
58
|
+
watcher.unwatch(cleanup)
|
|
56
59
|
})
|
|
57
60
|
.catch(error => {
|
|
58
61
|
if (!isAbortError(error))
|
|
@@ -60,7 +63,7 @@ const effect = (callback: EffectCallback): Cleanup => {
|
|
|
60
63
|
})
|
|
61
64
|
} else {
|
|
62
65
|
cleanup = (callback as () => MaybeCleanup)()
|
|
63
|
-
if (isFunction(cleanup))
|
|
66
|
+
if (isFunction(cleanup)) watcher.unwatch(cleanup)
|
|
64
67
|
}
|
|
65
68
|
} catch (error) {
|
|
66
69
|
if (!isAbortError(error))
|
|
@@ -68,16 +71,16 @@ const effect = (callback: EffectCallback): Cleanup => {
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
running = false
|
|
71
|
-
},
|
|
74
|
+
}, watcher),
|
|
72
75
|
)
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
watcher()
|
|
75
78
|
return () => {
|
|
76
79
|
controller?.abort()
|
|
77
|
-
|
|
80
|
+
watcher.cleanup()
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
/* === Exports === */
|
|
82
85
|
|
|
83
|
-
export { type MaybeCleanup, type EffectCallback,
|
|
86
|
+
export { type MaybeCleanup, type EffectCallback, createEffect }
|
package/src/errors.ts
CHANGED
|
@@ -5,6 +5,13 @@ class CircularDependencyError extends Error {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
class InvalidCallbackError extends TypeError {
|
|
9
|
+
constructor(where: string, value: string) {
|
|
10
|
+
super(`Invalid ${where} callback ${value}`)
|
|
11
|
+
this.name = 'InvalidCallbackError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
class InvalidSignalValueError extends TypeError {
|
|
9
16
|
constructor(where: string, value: string) {
|
|
10
17
|
super(`Invalid signal value ${value} in ${where}`)
|
|
@@ -48,6 +55,7 @@ class StoreKeyReadonlyError extends Error {
|
|
|
48
55
|
|
|
49
56
|
export {
|
|
50
57
|
CircularDependencyError,
|
|
58
|
+
InvalidCallbackError,
|
|
51
59
|
InvalidSignalValueError,
|
|
52
60
|
NullishSignalValueError,
|
|
53
61
|
StoreKeyExistsError,
|
package/src/signal.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Computed,
|
|
3
3
|
type ComputedCallback,
|
|
4
|
-
|
|
4
|
+
createComputed,
|
|
5
5
|
isComputed,
|
|
6
6
|
isComputedCallback,
|
|
7
7
|
} from './computed'
|
|
8
|
-
import { isState, type State
|
|
9
|
-
import { isStore, type Store
|
|
8
|
+
import { createState, isState, type State } from './state'
|
|
9
|
+
import { createStore, isStore, type Store } from './store'
|
|
10
10
|
import { isRecord } from './util'
|
|
11
11
|
|
|
12
12
|
/* === Types === */
|
|
@@ -71,9 +71,9 @@ function toSignal<T extends {}>(
|
|
|
71
71
|
: State<T>
|
|
72
72
|
function toSignal<T extends {}>(value: T) {
|
|
73
73
|
if (isSignal<T>(value)) return value
|
|
74
|
-
if (isComputedCallback(value)) return
|
|
75
|
-
if (Array.isArray(value) || isRecord(value)) return
|
|
76
|
-
return
|
|
74
|
+
if (isComputedCallback(value)) return createComputed(value)
|
|
75
|
+
if (Array.isArray(value) || isRecord(value)) return createStore(value)
|
|
76
|
+
return createState(value)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/* === Exports === */
|