@zeix/cause-effect 0.15.1 → 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 +39 -1
- package/index.dev.js +517 -392
- package/index.js +1 -1
- package/index.ts +15 -4
- package/package.json +1 -1
- package/src/computed.ts +2 -2
- package/src/diff.ts +57 -44
- package/src/effect.ts +2 -6
- package/src/errors.ts +56 -0
- package/src/match.ts +2 -2
- package/src/resolve.ts +2 -2
- package/src/signal.ts +21 -43
- package/src/state.ts +3 -2
- package/src/store.ts +402 -179
- package/src/util.ts +50 -6
- 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 +12 -140
- package/test/store.test.ts +963 -20
- package/types/index.d.ts +5 -4
- package/types/src/diff.d.ts +5 -3
- package/types/src/errors.d.ts +19 -0
- package/types/src/match.d.ts +1 -1
- package/types/src/resolve.d.ts +1 -1
- package/types/src/signal.d.ts +12 -19
- package/types/src/store.d.ts +48 -30
- package/types/src/util.d.ts +8 -5
- 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
|
@@ -16,10 +16,19 @@ export {
|
|
|
16
16
|
type DiffResult,
|
|
17
17
|
diff,
|
|
18
18
|
isEqual,
|
|
19
|
+
type UnknownArray,
|
|
19
20
|
type UnknownRecord,
|
|
20
21
|
type UnknownRecordOrArray,
|
|
21
22
|
} from './src/diff'
|
|
22
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'
|
|
23
32
|
export { type MatchHandlers, match } from './src/match'
|
|
24
33
|
export { type ResolveResult, resolve } from './src/resolve'
|
|
25
34
|
export {
|
|
@@ -35,13 +44,11 @@ export {
|
|
|
35
44
|
watch,
|
|
36
45
|
} from './src/scheduler'
|
|
37
46
|
export {
|
|
47
|
+
isMutableSignal,
|
|
38
48
|
isSignal,
|
|
39
|
-
type MaybeSignal,
|
|
40
49
|
type Signal,
|
|
41
50
|
type SignalValues,
|
|
42
|
-
toMutableSignal,
|
|
43
51
|
toSignal,
|
|
44
|
-
UNSET,
|
|
45
52
|
type UnknownSignalRecord,
|
|
46
53
|
} from './src/signal'
|
|
47
54
|
export { isState, type State, state, TYPE_STATE } from './src/state'
|
|
@@ -52,15 +59,19 @@ export {
|
|
|
52
59
|
type StoreChangeEvent,
|
|
53
60
|
type StoreEventMap,
|
|
54
61
|
type StoreRemoveEvent,
|
|
62
|
+
type StoreSortEvent,
|
|
55
63
|
store,
|
|
56
64
|
TYPE_STORE,
|
|
57
65
|
} from './src/store'
|
|
58
66
|
export {
|
|
59
|
-
CircularDependencyError,
|
|
60
67
|
isAbortError,
|
|
61
68
|
isAsyncFunction,
|
|
62
69
|
isFunction,
|
|
63
70
|
isNumber,
|
|
71
|
+
isRecord,
|
|
72
|
+
isRecordOrArray,
|
|
64
73
|
isString,
|
|
74
|
+
isSymbol,
|
|
65
75
|
toError,
|
|
76
|
+
UNSET,
|
|
66
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,12 +1,14 @@
|
|
|
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
|
|
8
|
-
|
|
7
|
+
type UnknownArray = ReadonlyArray<unknown & {}>
|
|
8
|
+
type ArrayToRecord<T extends UnknownArray> = {
|
|
9
|
+
[key: string]: T extends Array<infer U extends {}> ? U : never
|
|
9
10
|
}
|
|
11
|
+
type UnknownRecordOrArray = UnknownRecord | ArrayToRecord<UnknownArray>
|
|
10
12
|
|
|
11
13
|
type DiffResult<T extends UnknownRecordOrArray = UnknownRecord> = {
|
|
12
14
|
changed: boolean
|
|
@@ -69,6 +71,8 @@ const isEqual = <T>(a: T, b: T, visited?: WeakSet<object>): boolean => {
|
|
|
69
71
|
return true
|
|
70
72
|
}
|
|
71
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)
|
|
72
76
|
return false
|
|
73
77
|
} finally {
|
|
74
78
|
visited.delete(a as object)
|
|
@@ -88,61 +92,70 @@ const diff = <T extends UnknownRecordOrArray>(
|
|
|
88
92
|
oldObj: T,
|
|
89
93
|
newObj: T,
|
|
90
94
|
): DiffResult<T> => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for (const key of allKeys) {
|
|
106
|
-
const oldHas = key in oldRecord
|
|
107
|
-
const newHas = key in newRecord
|
|
108
|
-
|
|
109
|
-
if (!oldHas && newHas) {
|
|
110
|
-
add[key as keyof T] = newRecord[key] as T[keyof T]
|
|
111
|
-
continue
|
|
112
|
-
} else if (oldHas && !newHas) {
|
|
113
|
-
remove[key as keyof T] = UNSET
|
|
114
|
-
continue
|
|
115
|
-
}
|
|
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
|
+
}
|
|
116
108
|
|
|
117
|
-
|
|
118
|
-
const newValue = newRecord[key] as T[keyof T]
|
|
109
|
+
const visited = new WeakSet()
|
|
119
110
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
111
|
+
const add: Partial<T> = {}
|
|
112
|
+
const change: Partial<T> = {}
|
|
113
|
+
const remove: Partial<T> = {}
|
|
123
114
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
Object.keys(remove).length > 0
|
|
115
|
+
const oldKeys = Object.keys(oldObj)
|
|
116
|
+
const newKeys = Object.keys(newObj)
|
|
117
|
+
const allKeys = new Set([...oldKeys, ...newKeys])
|
|
128
118
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
134
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
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
|
|
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
|
+
}
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
/* === Exports === */
|
|
141
152
|
|
|
142
153
|
export {
|
|
154
|
+
type ArrayToRecord,
|
|
143
155
|
type DiffResult,
|
|
144
156
|
diff,
|
|
145
157
|
isEqual,
|
|
146
158
|
type UnknownRecord,
|
|
159
|
+
type UnknownArray,
|
|
147
160
|
type UnknownRecordOrArray,
|
|
148
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
|
|
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
|
@@ -5,7 +5,7 @@ import { toError } from './util'
|
|
|
5
5
|
/* === Types === */
|
|
6
6
|
|
|
7
7
|
type MatchHandlers<S extends UnknownSignalRecord> = {
|
|
8
|
-
ok
|
|
8
|
+
ok: (values: SignalValues<S>) => void
|
|
9
9
|
err?: (errors: readonly Error[]) => void
|
|
10
10
|
nil?: () => void
|
|
11
11
|
}
|
|
@@ -31,7 +31,7 @@ function match<S extends UnknownSignalRecord>(
|
|
|
31
31
|
try {
|
|
32
32
|
if (result.pending) handlers.nil?.()
|
|
33
33
|
else if (result.errors) handlers.err?.(result.errors)
|
|
34
|
-
else handlers.ok
|
|
34
|
+
else if (result.ok) handlers.ok(result.values)
|
|
35
35
|
} catch (error) {
|
|
36
36
|
// If handler throws, try error handler, otherwise rethrow
|
|
37
37
|
if (
|
package/src/resolve.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
package/src/signal.ts
CHANGED
|
@@ -14,7 +14,6 @@ import { isRecord } from './util'
|
|
|
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
18
|
type UnknownSignalRecord = Record<string, Signal<unknown & {}>>
|
|
20
19
|
|
|
@@ -22,15 +21,10 @@ type SignalValues<S extends UnknownSignalRecord> = {
|
|
|
22
21
|
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
/* === Constants === */
|
|
26
|
-
|
|
27
|
-
// biome-ignore lint/suspicious/noExplicitAny: Deliberately using any to be used as a placeholder value in any signal
|
|
28
|
-
const UNSET: any = Symbol()
|
|
29
|
-
|
|
30
24
|
/* === Functions === */
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
|
-
* Check whether a value is a Signal
|
|
27
|
+
* Check whether a value is a Signal
|
|
34
28
|
*
|
|
35
29
|
* @since 0.9.0
|
|
36
30
|
* @param {unknown} value - value to check
|
|
@@ -40,6 +34,17 @@ const isSignal = /*#__PURE__*/ <T extends {}>(
|
|
|
40
34
|
value: unknown,
|
|
41
35
|
): value is Signal<T> => isState(value) || isComputed(value) || isStore(value)
|
|
42
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Check whether a value is a State or Store
|
|
39
|
+
*
|
|
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
|
|
43
|
+
*/
|
|
44
|
+
const isMutableSignal = /*#__PURE__*/ <T extends {}>(
|
|
45
|
+
value: unknown,
|
|
46
|
+
): value is State<T> | Store<T> => isState(value) || isStore(value)
|
|
47
|
+
|
|
43
48
|
/**
|
|
44
49
|
* Convert a value to a Signal if it's not already a Signal
|
|
45
50
|
*
|
|
@@ -47,10 +52,6 @@ const isSignal = /*#__PURE__*/ <T extends {}>(
|
|
|
47
52
|
* @param {T} value - value to convert
|
|
48
53
|
* @returns {Signal<T>} - Signal instance
|
|
49
54
|
*/
|
|
50
|
-
function toSignal<T extends {}>(value: T[]): Store<Record<string, T>>
|
|
51
|
-
function toSignal<T extends {}>(
|
|
52
|
-
value: (() => T) | ((abort: AbortSignal) => Promise<T>),
|
|
53
|
-
): Computed<T>
|
|
54
55
|
function toSignal<T extends {}>(
|
|
55
56
|
value: T,
|
|
56
57
|
): T extends Store<infer U>
|
|
@@ -61,50 +62,27 @@ function toSignal<T extends {}>(
|
|
|
61
62
|
? Computed<U>
|
|
62
63
|
: T extends Signal<infer U>
|
|
63
64
|
? Signal<U>
|
|
64
|
-
: T extends
|
|
65
|
-
? Store<
|
|
66
|
-
:
|
|
67
|
-
|
|
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) {
|
|
68
73
|
if (isSignal<T>(value)) return value
|
|
69
74
|
if (isComputedCallback(value)) return computed(value)
|
|
70
|
-
if (Array.isArray(value)) return store(value as T)
|
|
71
75
|
if (Array.isArray(value) || isRecord(value)) return store(value)
|
|
72
76
|
return state(value)
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
/**
|
|
76
|
-
* Convert a value to a mutable Signal if it's not already a Signal
|
|
77
|
-
*
|
|
78
|
-
* @since 0.15.0
|
|
79
|
-
* @param {T} value - value to convert
|
|
80
|
-
* @returns {State<T> | Store<T>} - Signal instance
|
|
81
|
-
*/
|
|
82
|
-
function toMutableSignal<T extends {}>(value: T[]): Store<Record<string, T>>
|
|
83
|
-
function toMutableSignal<T extends {}>(
|
|
84
|
-
value: T,
|
|
85
|
-
): T extends Store<infer U>
|
|
86
|
-
? Store<U>
|
|
87
|
-
: T extends State<infer U>
|
|
88
|
-
? State<U>
|
|
89
|
-
: T extends Record<string, unknown & {}>
|
|
90
|
-
? Store<{ [K in keyof T]: T[K] }>
|
|
91
|
-
: State<T>
|
|
92
|
-
function toMutableSignal<T extends {}>(value: T): State<T> | Store<T> {
|
|
93
|
-
if (isState<T>(value) || isStore<T>(value)) return value
|
|
94
|
-
if (Array.isArray(value)) return store(value as T)
|
|
95
|
-
if (isRecord(value)) return store(value)
|
|
96
|
-
return state(value)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
79
|
/* === Exports === */
|
|
100
80
|
|
|
101
81
|
export {
|
|
102
82
|
type Signal,
|
|
103
|
-
type MaybeSignal,
|
|
104
83
|
type UnknownSignalRecord,
|
|
105
84
|
type SignalValues,
|
|
106
|
-
UNSET,
|
|
107
85
|
isSignal,
|
|
86
|
+
isMutableSignal,
|
|
108
87
|
toSignal,
|
|
109
|
-
toMutableSignal,
|
|
110
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)
|