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