@zeix/cause-effect 0.13.0 → 0.13.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 +2 -2
- package/eslint.config.js +35 -0
- package/index.d.ts +7 -7
- package/index.js +1 -1
- package/index.ts +19 -9
- package/package.json +32 -29
- package/{lib → src}/computed.d.ts +3 -3
- package/{lib → src}/computed.ts +87 -65
- package/{lib → src}/effect.ts +27 -19
- package/{lib → src}/scheduler.d.ts +1 -1
- package/{lib → src}/scheduler.ts +30 -35
- package/{lib → src}/signal.d.ts +5 -4
- package/{lib → src}/signal.ts +5 -4
- package/{lib → src}/state.ts +35 -34
- package/{lib → src}/util.d.ts +1 -1
- package/{lib → src}/util.ts +21 -11
- package/test/batch.test.ts +18 -19
- package/test/benchmark.test.ts +36 -36
- package/test/computed.test.ts +43 -42
- package/test/effect.test.ts +18 -21
- package/test/state.test.ts +15 -31
- package/test/util/dependency-graph.ts +147 -145
- package/test/util/framework-types.ts +22 -22
- package/test/util/perf-tests.ts +28 -28
- package/test/util/reactive-framework.ts +11 -12
- /package/{lib → src}/effect.d.ts +0 -0
- /package/{lib → src}/state.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cause & Effect
|
|
2
2
|
|
|
3
|
-
Version 0.13.
|
|
3
|
+
Version 0.13.2
|
|
4
4
|
|
|
5
5
|
**Cause & Effect** is a lightweight, reactive state management library for JavaScript applications. It uses the concept of signals to create a predictable and efficient data flow in your app.
|
|
6
6
|
|
|
@@ -301,4 +301,4 @@ Feel free to contribute, report issues, or suggest improvements.
|
|
|
301
301
|
|
|
302
302
|
Licence: [MIT](LICENCE.md)
|
|
303
303
|
|
|
304
|
-
(c) 2025 [Zeix AG](https://zeix.com)
|
|
304
|
+
(c) 2025 [Zeix AG](https://zeix.com)
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import globals from 'globals'
|
|
2
|
+
import pluginJs from '@eslint/js'
|
|
3
|
+
import tseslint from 'typescript-eslint'
|
|
4
|
+
|
|
5
|
+
/** @type {import('eslint').Linter.Config[]} */
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
ignores: ['index.js', '**/*.min.js'],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{js,mjs,cjs,ts}'],
|
|
12
|
+
},
|
|
13
|
+
{ languageOptions: { globals: globals.browser } },
|
|
14
|
+
pluginJs.configs.recommended,
|
|
15
|
+
...tseslint.configs.recommended,
|
|
16
|
+
{
|
|
17
|
+
rules: {
|
|
18
|
+
// we know what we're doing ;-)
|
|
19
|
+
'@typescript-eslint/no-empty-object-type': 'off',
|
|
20
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
21
|
+
'@typescript-eslint/no-unused-vars': [
|
|
22
|
+
'error',
|
|
23
|
+
{
|
|
24
|
+
args: 'all',
|
|
25
|
+
argsIgnorePattern: '^_',
|
|
26
|
+
caughtErrors: 'all',
|
|
27
|
+
caughtErrorsIgnorePattern: '^_',
|
|
28
|
+
destructuredArrayIgnorePattern: '^_',
|
|
29
|
+
varsIgnorePattern: '^_',
|
|
30
|
+
ignoreRestSiblings: true,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
]
|
package/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.13.
|
|
3
|
+
* @version 0.13.2
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
|
-
export { CircularDependencyError } from './
|
|
7
|
-
export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal } from './
|
|
8
|
-
export { type State, state, isState } from './
|
|
9
|
-
export { type Computed, type ComputedMatcher, computed, isComputed } from './
|
|
10
|
-
export { type EffectMatcher, type TapMatcher, effect } from './
|
|
11
|
-
export { type EnqueueDedupe, batch, watch, enqueue } from './
|
|
6
|
+
export { CircularDependencyError } from './src/util';
|
|
7
|
+
export { type Signal, type MaybeSignal, type ComputedCallback, UNSET, isSignal, isComputedCallback, toSignal, } from './src/signal';
|
|
8
|
+
export { type State, state, isState } from './src/state';
|
|
9
|
+
export { type Computed, type ComputedMatcher, computed, isComputed, } from './src/computed';
|
|
10
|
+
export { type EffectMatcher, type TapMatcher, effect } from './src/effect';
|
|
11
|
+
export { type EnqueueDedupe, batch, watch, enqueue } from './src/scheduler';
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var X=(x)=>typeof x==="function",d=(x)=>X(x)&&x.constructor.name==="AsyncFunction",P=(x,y)=>Object.prototype.toString.call(x)===`[object ${y}]`,c=(x)=>x instanceof Error,I=(x)=>x instanceof DOMException&&x.name==="AbortError",m=(x)=>x instanceof Promise,
|
|
1
|
+
var X=(x)=>typeof x==="function",d=(x)=>X(x)&&x.constructor.name==="AsyncFunction",P=(x,y)=>Object.prototype.toString.call(x)===`[object ${y}]`,c=(x)=>x instanceof Error,I=(x)=>x instanceof DOMException&&x.name==="AbortError",m=(x)=>x instanceof Promise,C=(x)=>c(x)?x:Error(String(x));class A extends Error{constructor(x){super(`Circular dependency in ${x} detected`);return this}}var j,R=new Set,U=0,_=new Map,D,v=()=>{D=void 0;let x=Array.from(_.values());_.clear();for(let y of x)y()},t=()=>{if(D)cancelAnimationFrame(D);D=requestAnimationFrame(v)};queueMicrotask(v);var N=(x)=>{if(j&&!x.has(j)){let y=j;x.add(y),j.cleanups.add(()=>{x.delete(y)})}},V=(x)=>{for(let y of x)if(U)R.add(y);else y()},k=()=>{while(R.size){let x=Array.from(R);R.clear();for(let y of x)y()}},a=(x)=>{U++;try{x()}finally{k(),U--}},M=(x,y)=>{let z=j;j=y;try{x()}finally{j=z}},r=(x,y)=>new Promise((z,J)=>{_.set(y,()=>{try{z(x())}catch(B){J(B)}}),t()});function Y(x){let{signals:y,ok:z,err:J=console.error,nil:B=()=>{}}=X(x)?{signals:[],ok:x}:x,L=!1,K=()=>M(()=>{if(L)throw new A("effect");L=!0;let H=void 0;try{H=S({signals:y,ok:z,err:J,nil:B})}catch(Q){J(C(Q))}if(X(H))K.cleanups.add(H);L=!1},K);return K.cleanups=new Set,K(),()=>{K.cleanups.forEach((H)=>H()),K.cleanups.clear()}}var o="Computed",e=(x,y)=>{if(!y)return!1;return x.name===y.name&&x.message===y.message},F=(x)=>{let y=new Set,z=X(x)?void 0:{nil:()=>Z,err:(...$)=>{if($.length>1)throw new AggregateError($);else throw $[0]},...x},J=z?z.ok:x,B=Z,L,K=!0,H=!1,Q=!1,G,W=($)=>{if(!Object.is($,B))B=$,K=!1,L=void 0,H=!0},g=()=>{H=Z!==B,B=Z,L=void 0},p=($)=>{let O=C($);H=!e(O,L),B=Z,L=O},n=($)=>{if(Q=!1,G=void 0,W($),H)V(y)},u=($)=>{if(Q=!1,G=void 0,p($),H)V(y)},l=()=>{Q=!1,G=void 0,f()},q=()=>{if(K=!0,G?.abort("Aborted because source signal changed"),y.size)V(y);else q.cleanups.forEach(($)=>$()),q.cleanups.clear()};q.cleanups=new Set;let f=()=>M(()=>{if(Q)throw new A("computed");if(H=!1,d(J)){if(G)return B;if(G=new AbortController,z)z.abort=z.abort instanceof AbortSignal?AbortSignal.any([z.abort,G.signal]):G.signal;G.signal.addEventListener("abort",l,{once:!0})}let $;Q=!0;try{$=z&&z.signals.length?S(z):J(G?.signal)}catch(O){if(I(O))g();else p(O);Q=!1;return}if(m($))$.then(n,u);else if($==null||Z===$)g();else W($);Q=!1},q),T={[Symbol.toStringTag]:o,get:()=>{if(N(y),k(),K)f();if(L)throw L;return B},map:($)=>F({signals:[T],ok:$}),tap:($)=>Y({signals:[T],...X($)?{ok:$}:$})};return T},b=(x)=>P(x,o);var i="State",E=(x)=>{let y=new Set,z=x,J={[Symbol.toStringTag]:i,get:()=>{return N(y),z},set:(B)=>{if(Object.is(z,B))return;if(z=B,V(y),Z===z)y.clear()},update:(B)=>{J.set(B(z))},map:(B)=>F({signals:[J],ok:B}),tap:(B)=>Y({signals:[J],...X(B)?{ok:B}:B})};return J},w=(x)=>P(x,i);var Z=Symbol(),h=(x)=>w(x)||b(x),s=(x)=>X(x)&&x.length<2,xx=(x)=>h(x)?x:s(x)?F(x):E(x),S=(x)=>{let{signals:y,abort:z,ok:J,err:B,nil:L}=x,K=[],H=!1,Q=y.map((G)=>{try{let W=G.get();if(W===Z)H=!0;return W}catch(W){if(I(W))throw W;K.push(C(W))}});try{return H?L(z):K.length?B(...K):J(...Q)}catch(G){if(I(G))throw G;let W=C(G);return B(W)}};export{M as watch,xx as toSignal,E as state,w as isState,h as isSignal,s as isComputedCallback,b as isComputed,r as enqueue,Y as effect,F as computed,a as batch,Z as UNSET,A as CircularDependencyError};
|
package/index.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.13.
|
|
3
|
+
* @version 0.13.2
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
|
-
export { CircularDependencyError } from './
|
|
6
|
+
export { CircularDependencyError } from './src/util'
|
|
7
7
|
export {
|
|
8
|
-
type Signal,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
type Signal,
|
|
9
|
+
type MaybeSignal,
|
|
10
|
+
type ComputedCallback,
|
|
11
|
+
UNSET,
|
|
12
|
+
isSignal,
|
|
13
|
+
isComputedCallback,
|
|
14
|
+
toSignal,
|
|
15
|
+
} from './src/signal'
|
|
11
16
|
|
|
12
|
-
export { type State, state, isState } from './
|
|
13
|
-
export {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
export { type State, state, isState } from './src/state'
|
|
18
|
+
export {
|
|
19
|
+
type Computed,
|
|
20
|
+
type ComputedMatcher,
|
|
21
|
+
computed,
|
|
22
|
+
isComputed,
|
|
23
|
+
} from './src/computed'
|
|
24
|
+
export { type EffectMatcher, type TapMatcher, effect } from './src/effect'
|
|
25
|
+
export { type EnqueueDedupe, batch, watch, enqueue } from './src/scheduler'
|
package/package.json
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
2
|
+
"name": "@zeix/cause-effect",
|
|
3
|
+
"version": "0.13.2",
|
|
4
|
+
"author": "Esther Brunner",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.ts",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "latest",
|
|
9
|
+
"eslint": "^9.27.0",
|
|
10
|
+
"random": "^5.4.0",
|
|
11
|
+
"typescript-eslint": "^8.32.1"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"typescript": "^5.6.3"
|
|
15
|
+
},
|
|
16
|
+
"description": "Cause & Effect - reactive state management with signals.",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"Cause & Effect",
|
|
20
|
+
"Reactivity",
|
|
21
|
+
"Signals",
|
|
22
|
+
"Effects"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bun build index.ts --outdir ./ --minify && bunx tsc",
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"lint": "bunx eslint src/"
|
|
31
|
+
},
|
|
32
|
+
"type": "module",
|
|
33
|
+
"types": "index.d.ts"
|
|
31
34
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Signal } from './signal';
|
|
1
|
+
import { type Signal, type ComputedCallback } from './signal';
|
|
2
2
|
import { type TapMatcher } from './effect';
|
|
3
3
|
export type ComputedMatcher<S extends Signal<{}>[], R extends {}> = {
|
|
4
4
|
signals: S;
|
|
@@ -19,10 +19,10 @@ export type Computed<T extends {}> = {
|
|
|
19
19
|
* Create a derived signal from existing signals
|
|
20
20
|
*
|
|
21
21
|
* @since 0.9.0
|
|
22
|
-
* @param {ComputedMatcher<S, T> |
|
|
22
|
+
* @param {ComputedMatcher<S, T> | ComputedCallback<T>} matcher - computed matcher or callback
|
|
23
23
|
* @returns {Computed<T>} - Computed signal
|
|
24
24
|
*/
|
|
25
|
-
export declare const computed: <T extends {}, S extends Signal<{}>[] = []>(matcher: ComputedMatcher<S, T> |
|
|
25
|
+
export declare const computed: <T extends {}, S extends Signal<{}>[] = []>(matcher: ComputedMatcher<S, T> | ComputedCallback<T>) => Computed<T>;
|
|
26
26
|
/**
|
|
27
27
|
* Check if a value is a computed state
|
|
28
28
|
*
|
package/{lib → src}/computed.ts
RENAMED
|
@@ -1,24 +1,34 @@
|
|
|
1
|
-
import { type Signal, match, UNSET } from './signal'
|
|
2
|
-
import {
|
|
1
|
+
import { type Signal, type ComputedCallback, match, UNSET } from './signal'
|
|
2
|
+
import {
|
|
3
|
+
CircularDependencyError,
|
|
4
|
+
isAbortError,
|
|
5
|
+
isAsyncFunction,
|
|
6
|
+
isFunction,
|
|
7
|
+
isObjectOfType,
|
|
8
|
+
isPromise,
|
|
9
|
+
toError,
|
|
10
|
+
} from './util'
|
|
3
11
|
import { type Watcher, flush, notify, subscribe, watch } from './scheduler'
|
|
4
12
|
import { type TapMatcher, type EffectMatcher, effect } from './effect'
|
|
5
13
|
|
|
6
14
|
/* === Types === */
|
|
7
15
|
|
|
8
16
|
export type ComputedMatcher<S extends Signal<{}>[], R extends {}> = {
|
|
9
|
-
signals: S
|
|
17
|
+
signals: S
|
|
10
18
|
abort?: AbortSignal
|
|
11
|
-
ok: (
|
|
19
|
+
ok: (
|
|
20
|
+
...values: {
|
|
12
21
|
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
13
|
-
}
|
|
22
|
+
}
|
|
23
|
+
) => R | Promise<R>
|
|
14
24
|
err?: (...errors: Error[]) => R | Promise<R>
|
|
15
25
|
nil?: () => R | Promise<R>
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export type Computed<T extends {}> = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
[Symbol.toStringTag]: 'Computed'
|
|
30
|
+
get(): T
|
|
31
|
+
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>
|
|
22
32
|
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void
|
|
23
33
|
}
|
|
24
34
|
|
|
@@ -30,34 +40,36 @@ const TYPE_COMPUTED = 'Computed'
|
|
|
30
40
|
|
|
31
41
|
const isEquivalentError = /*#__PURE__*/ (
|
|
32
42
|
error1: Error,
|
|
33
|
-
error2: Error | undefined
|
|
43
|
+
error2: Error | undefined,
|
|
34
44
|
): boolean => {
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
if (!error2) return false
|
|
46
|
+
return error1.name === error2.name && error1.message === error2.message
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/* === Computed Factory === */
|
|
40
50
|
|
|
41
51
|
/**
|
|
42
52
|
* Create a derived signal from existing signals
|
|
43
|
-
*
|
|
53
|
+
*
|
|
44
54
|
* @since 0.9.0
|
|
45
|
-
* @param {ComputedMatcher<S, T> |
|
|
55
|
+
* @param {ComputedMatcher<S, T> | ComputedCallback<T>} matcher - computed matcher or callback
|
|
46
56
|
* @returns {Computed<T>} - Computed signal
|
|
47
57
|
*/
|
|
48
58
|
export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
49
|
-
matcher: ComputedMatcher<S, T> |
|
|
59
|
+
matcher: ComputedMatcher<S, T> | ComputedCallback<T>,
|
|
50
60
|
): Computed<T> => {
|
|
51
61
|
const watchers: Set<Watcher> = new Set()
|
|
52
|
-
const m = isFunction(matcher)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
const m = isFunction(matcher)
|
|
63
|
+
? undefined
|
|
64
|
+
: ({
|
|
65
|
+
nil: () => UNSET,
|
|
66
|
+
err: (...errors: Error[]) => {
|
|
67
|
+
if (errors.length > 1) throw new AggregateError(errors)
|
|
68
|
+
else throw errors[0]
|
|
69
|
+
},
|
|
70
|
+
...matcher,
|
|
71
|
+
} as Required<ComputedMatcher<S, T>>)
|
|
72
|
+
const fn = (m ? m.ok : matcher) as ComputedCallback<T>
|
|
61
73
|
|
|
62
74
|
// Internal state
|
|
63
75
|
let value: T = UNSET
|
|
@@ -77,7 +89,7 @@ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
91
|
const nil = () => {
|
|
80
|
-
changed =
|
|
92
|
+
changed = UNSET !== value
|
|
81
93
|
value = UNSET
|
|
82
94
|
error = undefined
|
|
83
95
|
}
|
|
@@ -100,17 +112,17 @@ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
|
100
112
|
if (changed) notify(watchers)
|
|
101
113
|
}
|
|
102
114
|
const abort = () => {
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
computing = false
|
|
116
|
+
controller = undefined
|
|
105
117
|
compute() // retry
|
|
106
|
-
|
|
118
|
+
}
|
|
107
119
|
|
|
108
120
|
// Called when notified from sources (push)
|
|
109
121
|
const mark = (() => {
|
|
110
122
|
dirty = true
|
|
111
123
|
controller?.abort('Aborted because source signal changed')
|
|
112
124
|
if (watchers.size) {
|
|
113
|
-
|
|
125
|
+
notify(watchers)
|
|
114
126
|
} else {
|
|
115
127
|
mark.cleanups.forEach((fn: () => void) => fn())
|
|
116
128
|
mark.cleanups.clear()
|
|
@@ -119,40 +131,47 @@ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
|
119
131
|
mark.cleanups = new Set()
|
|
120
132
|
|
|
121
133
|
// Called when requested by dependencies (pull)
|
|
122
|
-
const compute = () =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
const compute = () =>
|
|
135
|
+
watch(() => {
|
|
136
|
+
if (computing) throw new CircularDependencyError('computed')
|
|
137
|
+
changed = false
|
|
138
|
+
if (isAsyncFunction(fn)) {
|
|
139
|
+
if (controller) return value // return current value until promise resolves
|
|
140
|
+
controller = new AbortController()
|
|
141
|
+
if (m)
|
|
142
|
+
m.abort =
|
|
143
|
+
m.abort instanceof AbortSignal
|
|
144
|
+
? AbortSignal.any([m.abort, controller.signal])
|
|
145
|
+
: controller.signal
|
|
146
|
+
controller.signal.addEventListener('abort', abort, {
|
|
147
|
+
once: true,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
let result: T | Promise<T>
|
|
151
|
+
computing = true
|
|
152
|
+
try {
|
|
153
|
+
result =
|
|
154
|
+
m && m.signals.length
|
|
155
|
+
? match<S, T>(m)
|
|
156
|
+
: fn(controller?.signal)
|
|
157
|
+
} catch (e) {
|
|
158
|
+
if (isAbortError(e)) nil()
|
|
159
|
+
else err(e)
|
|
160
|
+
computing = false
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (isPromise(result)) result.then(resolve, reject)
|
|
164
|
+
else if (null == result || UNSET === result) nil()
|
|
165
|
+
else ok(result)
|
|
141
166
|
computing = false
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
if (isPromise(result)) result.then(resolve, reject)
|
|
145
|
-
else if (null == result || UNSET === result) nil()
|
|
146
|
-
else ok(result)
|
|
147
|
-
computing = false
|
|
148
|
-
}, mark)
|
|
167
|
+
}, mark)
|
|
149
168
|
|
|
150
169
|
const c: Computed<T> = {
|
|
151
170
|
[Symbol.toStringTag]: TYPE_COMPUTED,
|
|
152
171
|
|
|
153
172
|
/**
|
|
154
173
|
* Get the current value of the computed
|
|
155
|
-
*
|
|
174
|
+
*
|
|
156
175
|
* @since 0.9.0
|
|
157
176
|
* @returns {T} - current value of the computed
|
|
158
177
|
*/
|
|
@@ -166,7 +185,7 @@ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
|
166
185
|
|
|
167
186
|
/**
|
|
168
187
|
* Create a computed signal from the current computed signal
|
|
169
|
-
*
|
|
188
|
+
*
|
|
170
189
|
* @since 0.9.0
|
|
171
190
|
* @param {((v: T) => U | Promise<U>)} fn - computed callback
|
|
172
191
|
* @returns {Computed<U>} - computed signal
|
|
@@ -174,21 +193,23 @@ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
|
174
193
|
map: <U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U> =>
|
|
175
194
|
computed({
|
|
176
195
|
signals: [c],
|
|
177
|
-
ok: fn
|
|
196
|
+
ok: fn,
|
|
178
197
|
}),
|
|
179
198
|
|
|
180
|
-
/**
|
|
199
|
+
/**
|
|
181
200
|
* Case matching for the computed signal with effect callbacks
|
|
182
|
-
*
|
|
201
|
+
*
|
|
183
202
|
* @since 0.13.0
|
|
184
203
|
* @param {TapMatcher<T> | ((v: T) => void | (() => void))} matcher - tap matcher or effect callback
|
|
185
204
|
* @returns {() => void} - cleanup function for the effect
|
|
186
205
|
*/
|
|
187
|
-
tap: (
|
|
206
|
+
tap: (
|
|
207
|
+
matcher: TapMatcher<T> | ((v: T) => void | (() => void)),
|
|
208
|
+
): (() => void) =>
|
|
188
209
|
effect({
|
|
189
210
|
signals: [c],
|
|
190
|
-
...(isFunction(matcher) ? { ok: matcher } : matcher)
|
|
191
|
-
} as EffectMatcher<[Computed<T>]>)
|
|
211
|
+
...(isFunction(matcher) ? { ok: matcher } : matcher),
|
|
212
|
+
} as EffectMatcher<[Computed<T>]>),
|
|
192
213
|
}
|
|
193
214
|
return c
|
|
194
215
|
}
|
|
@@ -197,10 +218,11 @@ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
|
197
218
|
|
|
198
219
|
/**
|
|
199
220
|
* Check if a value is a computed state
|
|
200
|
-
*
|
|
221
|
+
*
|
|
201
222
|
* @since 0.9.0
|
|
202
223
|
* @param {unknown} value - value to check
|
|
203
224
|
* @returns {boolean} - true if value is a computed state, false otherwise
|
|
204
225
|
*/
|
|
205
|
-
export const isComputed = /*#__PURE__*/ <T extends {}>(
|
|
206
|
-
|
|
226
|
+
export const isComputed = /*#__PURE__*/ <T extends {}>(
|
|
227
|
+
value: unknown,
|
|
228
|
+
): value is Computed<T> => isObjectOfType(value, TYPE_COMPUTED)
|
package/{lib → src}/effect.ts
RENAMED
|
@@ -12,9 +12,11 @@ export type TapMatcher<T extends {}> = {
|
|
|
12
12
|
|
|
13
13
|
export type EffectMatcher<S extends Signal<{}>[]> = {
|
|
14
14
|
signals: S
|
|
15
|
-
ok: (
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
ok: (
|
|
16
|
+
...values: {
|
|
17
|
+
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
18
|
+
}
|
|
19
|
+
) => void | (() => void)
|
|
18
20
|
err?: (...errors: Error[]) => void | (() => void)
|
|
19
21
|
nil?: () => void | (() => void)
|
|
20
22
|
}
|
|
@@ -23,39 +25,45 @@ export type EffectMatcher<S extends Signal<{}>[]> = {
|
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Define what happens when a reactive state changes
|
|
26
|
-
*
|
|
28
|
+
*
|
|
27
29
|
* @since 0.1.0
|
|
28
30
|
* @param {EffectMatcher<S> | (() => void | (() => void))} matcher - effect matcher or callback
|
|
29
31
|
* @returns {() => void} - cleanup function for the effect
|
|
30
32
|
*/
|
|
31
33
|
export function effect<S extends Signal<{}>[]>(
|
|
32
|
-
matcher: EffectMatcher<S> | (() => void | (() => void))
|
|
34
|
+
matcher: EffectMatcher<S> | (() => void | (() => void)),
|
|
33
35
|
): () => void {
|
|
34
36
|
const {
|
|
35
37
|
signals,
|
|
36
38
|
ok,
|
|
37
39
|
err = console.error,
|
|
38
|
-
nil = () => {}
|
|
40
|
+
nil = () => {},
|
|
39
41
|
} = isFunction(matcher)
|
|
40
42
|
? { signals: [] as unknown as S, ok: matcher }
|
|
41
43
|
: matcher
|
|
42
44
|
let running = false
|
|
43
|
-
const run = (() =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
const run = (() =>
|
|
46
|
+
watch(() => {
|
|
47
|
+
if (running) throw new CircularDependencyError('effect')
|
|
48
|
+
running = true
|
|
49
|
+
let cleanup: void | (() => void) = undefined
|
|
50
|
+
try {
|
|
51
|
+
cleanup = match<S, void | (() => void)>({
|
|
52
|
+
signals,
|
|
53
|
+
ok,
|
|
54
|
+
err,
|
|
55
|
+
nil,
|
|
56
|
+
}) as void | (() => void)
|
|
57
|
+
} catch (e) {
|
|
58
|
+
err(toError(e))
|
|
59
|
+
}
|
|
60
|
+
if (isFunction(cleanup)) run.cleanups.add(cleanup)
|
|
61
|
+
running = false
|
|
62
|
+
}, run)) as Watcher
|
|
55
63
|
run.cleanups = new Set()
|
|
56
64
|
run()
|
|
57
65
|
return () => {
|
|
58
66
|
run.cleanups.forEach((fn: () => void) => fn())
|
|
59
67
|
run.cleanups.clear()
|
|
60
68
|
}
|
|
61
|
-
}
|
|
69
|
+
}
|
|
@@ -39,4 +39,4 @@ export declare const watch: (run: () => void, mark?: Watcher) => void;
|
|
|
39
39
|
* @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
|
|
40
40
|
* @param {EnqueueDedupe} dedupe - [element, operation] pair for deduplication
|
|
41
41
|
*/
|
|
42
|
-
export declare const enqueue: <T>(fn: Updater, dedupe
|
|
42
|
+
export declare const enqueue: <T>(fn: Updater, dedupe: EnqueueDedupe) => Promise<boolean | void | T>;
|