@zeix/cause-effect 0.12.3 → 0.13.0
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/.prettierrc +7 -0
- package/README.md +140 -94
- package/index.d.ts +5 -4
- package/index.js +1 -1
- package/index.ts +6 -6
- package/lib/computed.d.ts +16 -7
- package/lib/computed.ts +100 -44
- package/lib/effect.d.ts +17 -4
- package/lib/effect.ts +49 -17
- package/lib/scheduler.d.ts +9 -6
- package/lib/scheduler.ts +18 -9
- package/lib/signal.d.ts +22 -31
- package/lib/signal.ts +55 -81
- package/lib/state.d.ts +3 -3
- package/lib/state.ts +29 -22
- package/lib/util.d.ts +8 -5
- package/lib/util.ts +18 -11
- package/package.json +3 -3
- package/test/batch.test.ts +43 -42
- package/test/benchmark.test.ts +315 -319
- package/test/computed.test.ts +341 -242
- package/test/effect.test.ts +136 -119
- package/test/state.test.ts +126 -118
package/lib/computed.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
resolve, UNSET
|
|
4
|
-
} from './signal'
|
|
5
|
-
import { isError, isObjectOfType, isPromise, toError } from './util'
|
|
1
|
+
import { type Signal, match, UNSET } from './signal'
|
|
2
|
+
import { CircularDependencyError, isAbortError, isAsyncFunction, isFunction, isObjectOfType, isPromise, toError } from './util'
|
|
6
3
|
import { type Watcher, flush, notify, subscribe, watch } from './scheduler'
|
|
7
|
-
import { effect } from './effect'
|
|
4
|
+
import { type TapMatcher, type EffectMatcher, effect } from './effect'
|
|
8
5
|
|
|
9
6
|
/* === Types === */
|
|
10
7
|
|
|
8
|
+
export type ComputedMatcher<S extends Signal<{}>[], R extends {}> = {
|
|
9
|
+
signals: S,
|
|
10
|
+
abort?: AbortSignal
|
|
11
|
+
ok: (...values: {
|
|
12
|
+
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
13
|
+
}) => R | Promise<R>
|
|
14
|
+
err?: (...errors: Error[]) => R | Promise<R>
|
|
15
|
+
nil?: () => R | Promise<R>
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export type Computed<T extends {}> = {
|
|
12
19
|
[Symbol.toStringTag]: 'Computed'
|
|
13
|
-
get
|
|
14
|
-
map
|
|
15
|
-
|
|
20
|
+
get(): T
|
|
21
|
+
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>
|
|
22
|
+
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/* === Constants === */
|
|
@@ -35,20 +42,30 @@ const isEquivalentError = /*#__PURE__*/ (
|
|
|
35
42
|
* Create a derived signal from existing signals
|
|
36
43
|
*
|
|
37
44
|
* @since 0.9.0
|
|
38
|
-
* @param {() => T}
|
|
39
|
-
* @param {U} maybeSignals - signals of functions using signals this values depends on
|
|
45
|
+
* @param {ComputedMatcher<S, T> | ((abort?: AbortSignal) => T | Promise<T>)} matcher - computed matcher or callback
|
|
40
46
|
* @returns {Computed<T>} - Computed signal
|
|
41
47
|
*/
|
|
42
|
-
export const computed = <T extends {},
|
|
43
|
-
|
|
44
|
-
...maybeSignals: U
|
|
48
|
+
export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
49
|
+
matcher: ComputedMatcher<S, T> | ((abort?: AbortSignal) => T | Promise<T>)
|
|
45
50
|
): Computed<T> => {
|
|
46
|
-
const watchers: Watcher
|
|
51
|
+
const watchers: Set<Watcher> = new Set()
|
|
52
|
+
const m = isFunction(matcher) ? undefined : {
|
|
53
|
+
nil: () => UNSET,
|
|
54
|
+
err: (...errors: Error[]) => {
|
|
55
|
+
if (errors.length > 1) throw new AggregateError(errors)
|
|
56
|
+
else throw errors[0]
|
|
57
|
+
},
|
|
58
|
+
...matcher,
|
|
59
|
+
} as Required<ComputedMatcher<S, T>>
|
|
60
|
+
const fn = (m ? m.ok : matcher) as (abort?: AbortSignal) => T | Promise<T>
|
|
61
|
+
|
|
62
|
+
// Internal state
|
|
47
63
|
let value: T = UNSET
|
|
48
64
|
let error: Error | undefined
|
|
49
65
|
let dirty = true
|
|
50
|
-
let
|
|
66
|
+
let changed = false
|
|
51
67
|
let computing = false
|
|
68
|
+
let controller: AbortController | undefined
|
|
52
69
|
|
|
53
70
|
// Functions to update internal state
|
|
54
71
|
const ok = (v: T) => {
|
|
@@ -56,41 +73,76 @@ export const computed = <T extends {}, U extends MaybeSignal<{}>[]>(
|
|
|
56
73
|
value = v
|
|
57
74
|
dirty = false
|
|
58
75
|
error = undefined
|
|
59
|
-
|
|
76
|
+
changed = true
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
const nil = () => {
|
|
63
|
-
|
|
80
|
+
changed = (UNSET !== value)
|
|
64
81
|
value = UNSET
|
|
65
82
|
error = undefined
|
|
66
83
|
}
|
|
67
84
|
const err = (e: unknown) => {
|
|
68
85
|
const newError = toError(e)
|
|
69
|
-
|
|
86
|
+
changed = !isEquivalentError(newError, error)
|
|
70
87
|
value = UNSET
|
|
71
88
|
error = newError
|
|
72
89
|
}
|
|
90
|
+
const resolve = (v: T) => {
|
|
91
|
+
computing = false
|
|
92
|
+
controller = undefined
|
|
93
|
+
ok(v)
|
|
94
|
+
if (changed) notify(watchers)
|
|
95
|
+
}
|
|
96
|
+
const reject = (e: unknown) => {
|
|
97
|
+
computing = false
|
|
98
|
+
controller = undefined
|
|
99
|
+
err(e)
|
|
100
|
+
if (changed) notify(watchers)
|
|
101
|
+
}
|
|
102
|
+
const abort = () => {
|
|
103
|
+
computing = false
|
|
104
|
+
controller = undefined
|
|
105
|
+
compute() // retry
|
|
106
|
+
}
|
|
73
107
|
|
|
74
108
|
// Called when notified from sources (push)
|
|
75
|
-
const mark = () => {
|
|
109
|
+
const mark = (() => {
|
|
76
110
|
dirty = true
|
|
77
|
-
|
|
78
|
-
|
|
111
|
+
controller?.abort('Aborted because source signal changed')
|
|
112
|
+
if (watchers.size) {
|
|
113
|
+
if (changed) notify(watchers)
|
|
114
|
+
} else {
|
|
115
|
+
mark.cleanups.forEach((fn: () => void) => fn())
|
|
116
|
+
mark.cleanups.clear()
|
|
117
|
+
}
|
|
118
|
+
}) as Watcher
|
|
119
|
+
mark.cleanups = new Set()
|
|
79
120
|
|
|
80
121
|
// Called when requested by dependencies (pull)
|
|
81
122
|
const compute = () => watch(() => {
|
|
82
|
-
if (computing) throw new
|
|
83
|
-
|
|
123
|
+
if (computing) throw new CircularDependencyError('computed')
|
|
124
|
+
changed = false
|
|
125
|
+
if (isAsyncFunction(fn)) {
|
|
126
|
+
if (controller) return value // return current value until promise resolves
|
|
127
|
+
controller = new AbortController()
|
|
128
|
+
if (m) m.abort = m.abort instanceof AbortSignal
|
|
129
|
+
? AbortSignal.any([m.abort, controller.signal])
|
|
130
|
+
: controller.signal
|
|
131
|
+
controller.signal.addEventListener('abort', abort, { once: true })
|
|
132
|
+
}
|
|
133
|
+
let result: T | Promise<T>
|
|
84
134
|
computing = true
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
135
|
+
try {
|
|
136
|
+
result = m && m.signals.length
|
|
137
|
+
? match<S, T>(m)
|
|
138
|
+
: fn(controller?.signal)
|
|
139
|
+
} catch (e) {
|
|
140
|
+
isAbortError(e) ? nil() : err(e)
|
|
141
|
+
computing = false
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (isPromise(result)) result.then(resolve, reject)
|
|
145
|
+
else if (null == result || UNSET === result) nil()
|
|
94
146
|
else ok(result)
|
|
95
147
|
computing = false
|
|
96
148
|
}, mark)
|
|
@@ -116,23 +168,27 @@ export const computed = <T extends {}, U extends MaybeSignal<{}>[]>(
|
|
|
116
168
|
* Create a computed signal from the current computed signal
|
|
117
169
|
*
|
|
118
170
|
* @since 0.9.0
|
|
119
|
-
* @param {
|
|
171
|
+
* @param {((v: T) => U | Promise<U>)} fn - computed callback
|
|
120
172
|
* @returns {Computed<U>} - computed signal
|
|
121
173
|
*/
|
|
122
|
-
map: <U extends {}>(
|
|
123
|
-
computed(
|
|
174
|
+
map: <U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U> =>
|
|
175
|
+
computed({
|
|
176
|
+
signals: [c],
|
|
177
|
+
ok: fn
|
|
178
|
+
}),
|
|
124
179
|
|
|
125
|
-
/**
|
|
180
|
+
/**
|
|
126
181
|
* Case matching for the computed signal with effect callbacks
|
|
127
182
|
*
|
|
128
|
-
* @since 0.
|
|
129
|
-
* @param {
|
|
130
|
-
* @returns {
|
|
183
|
+
* @since 0.13.0
|
|
184
|
+
* @param {TapMatcher<T> | ((v: T) => void | (() => void))} matcher - tap matcher or effect callback
|
|
185
|
+
* @returns {() => void} - cleanup function for the effect
|
|
131
186
|
*/
|
|
132
|
-
|
|
133
|
-
effect(
|
|
134
|
-
|
|
135
|
-
|
|
187
|
+
tap: (matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void =>
|
|
188
|
+
effect({
|
|
189
|
+
signals: [c],
|
|
190
|
+
...(isFunction(matcher) ? { ok: matcher } : matcher)
|
|
191
|
+
} as EffectMatcher<[Computed<T>]>)
|
|
136
192
|
}
|
|
137
193
|
return c
|
|
138
194
|
}
|
package/lib/effect.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Signal } from './signal';
|
|
2
|
+
export type TapMatcher<T extends {}> = {
|
|
3
|
+
ok: (value: T) => void | (() => void);
|
|
4
|
+
err?: (error: Error) => void | (() => void);
|
|
5
|
+
nil?: () => void | (() => void);
|
|
6
|
+
};
|
|
7
|
+
export type EffectMatcher<S extends Signal<{}>[]> = {
|
|
8
|
+
signals: S;
|
|
9
|
+
ok: (...values: {
|
|
10
|
+
[K in keyof S]: S[K] extends Signal<infer T> ? T : never;
|
|
11
|
+
}) => void | (() => void);
|
|
12
|
+
err?: (...errors: Error[]) => void | (() => void);
|
|
13
|
+
nil?: () => void | (() => void);
|
|
14
|
+
};
|
|
2
15
|
/**
|
|
3
16
|
* Define what happens when a reactive state changes
|
|
4
17
|
*
|
|
5
18
|
* @since 0.1.0
|
|
6
|
-
* @param {() => void}
|
|
7
|
-
* @
|
|
19
|
+
* @param {EffectMatcher<S> | (() => void | (() => void))} matcher - effect matcher or callback
|
|
20
|
+
* @returns {() => void} - cleanup function for the effect
|
|
8
21
|
*/
|
|
9
|
-
export declare function effect<
|
|
22
|
+
export declare function effect<S extends Signal<{}>[]>(matcher: EffectMatcher<S> | (() => void | (() => void))): () => void;
|
package/lib/effect.ts
CHANGED
|
@@ -1,29 +1,61 @@
|
|
|
1
|
+
import { type Signal, match } from './signal'
|
|
2
|
+
import { CircularDependencyError, isFunction, toError } from './util'
|
|
3
|
+
import { watch, type Watcher } from './scheduler'
|
|
1
4
|
|
|
2
|
-
|
|
3
|
-
import { isError } from './util'
|
|
4
|
-
import { watch } from './scheduler'
|
|
5
|
+
/* === Types === */
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
export type TapMatcher<T extends {}> = {
|
|
8
|
+
ok: (value: T) => void | (() => void)
|
|
9
|
+
err?: (error: Error) => void | (() => void)
|
|
10
|
+
nil?: () => void | (() => void)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type EffectMatcher<S extends Signal<{}>[]> = {
|
|
14
|
+
signals: S
|
|
15
|
+
ok: (...values: {
|
|
16
|
+
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
17
|
+
}) => void | (() => void)
|
|
18
|
+
err?: (...errors: Error[]) => void | (() => void)
|
|
19
|
+
nil?: () => void | (() => void)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* === Exported Functions === */
|
|
7
23
|
|
|
8
24
|
/**
|
|
9
25
|
* Define what happens when a reactive state changes
|
|
10
26
|
*
|
|
11
27
|
* @since 0.1.0
|
|
12
|
-
* @param {() => void}
|
|
13
|
-
* @
|
|
28
|
+
* @param {EffectMatcher<S> | (() => void | (() => void))} matcher - effect matcher or callback
|
|
29
|
+
* @returns {() => void} - cleanup function for the effect
|
|
14
30
|
*/
|
|
15
|
-
export function effect<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
export function effect<S extends Signal<{}>[]>(
|
|
32
|
+
matcher: EffectMatcher<S> | (() => void | (() => void))
|
|
33
|
+
): () => void {
|
|
34
|
+
const {
|
|
35
|
+
signals,
|
|
36
|
+
ok,
|
|
37
|
+
err = console.error,
|
|
38
|
+
nil = () => {}
|
|
39
|
+
} = isFunction(matcher)
|
|
40
|
+
? { signals: [] as unknown as S, ok: matcher }
|
|
41
|
+
: matcher
|
|
19
42
|
let running = false
|
|
20
|
-
const run = () => watch(() => {
|
|
21
|
-
if (running) throw new
|
|
43
|
+
const run = (() => watch(() => {
|
|
44
|
+
if (running) throw new CircularDependencyError('effect')
|
|
22
45
|
running = true
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
let cleanup: void | (() => void) = undefined
|
|
47
|
+
try {
|
|
48
|
+
cleanup = match<S, void | (() => void)>({ signals, ok, err, nil }) as void | (() => void)
|
|
49
|
+
} catch (e) {
|
|
50
|
+
err(toError(e))
|
|
51
|
+
}
|
|
52
|
+
if (isFunction(cleanup)) run.cleanups.add(cleanup)
|
|
26
53
|
running = false
|
|
27
|
-
}, run)
|
|
54
|
+
}, run)) as Watcher
|
|
55
|
+
run.cleanups = new Set()
|
|
28
56
|
run()
|
|
29
|
-
|
|
57
|
+
return () => {
|
|
58
|
+
run.cleanups.forEach((fn: () => void) => fn())
|
|
59
|
+
run.cleanups.clear()
|
|
60
|
+
}
|
|
61
|
+
}
|
package/lib/scheduler.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
export type EnqueueDedupe = [Element, string];
|
|
2
|
-
export type Watcher =
|
|
2
|
+
export type Watcher = {
|
|
3
|
+
(): void;
|
|
4
|
+
cleanups: Set<() => void>;
|
|
5
|
+
};
|
|
3
6
|
export type Updater = <T>() => T | boolean | void;
|
|
4
7
|
/**
|
|
5
|
-
* Add active watcher to the
|
|
8
|
+
* Add active watcher to the Set of watchers
|
|
6
9
|
*
|
|
7
|
-
* @param {Watcher
|
|
10
|
+
* @param {Set<Watcher>} watchers - watchers of the signal
|
|
8
11
|
*/
|
|
9
|
-
export declare const subscribe: (watchers: Watcher
|
|
12
|
+
export declare const subscribe: (watchers: Set<Watcher>) => void;
|
|
10
13
|
/**
|
|
11
14
|
* Add watchers to the pending set of change notifications
|
|
12
15
|
*
|
|
13
|
-
* @param {Watcher
|
|
16
|
+
* @param {Set<Watcher>} watchers - watchers of the signal
|
|
14
17
|
*/
|
|
15
|
-
export declare const notify: (watchers: Watcher
|
|
18
|
+
export declare const notify: (watchers: Set<Watcher>) => void;
|
|
16
19
|
/**
|
|
17
20
|
* Flush all pending changes to notify watchers
|
|
18
21
|
*/
|
package/lib/scheduler.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
export type EnqueueDedupe = [Element, string]
|
|
4
4
|
|
|
5
|
-
export type Watcher =
|
|
5
|
+
export type Watcher = {
|
|
6
|
+
(): void,
|
|
7
|
+
cleanups: Set<() => void>
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export type Updater = <T>() => T | boolean | void
|
|
7
11
|
|
|
8
12
|
/* === Internal === */
|
|
@@ -38,25 +42,30 @@ queueMicrotask(updateDOM)
|
|
|
38
42
|
/* === Exported Functions === */
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
|
-
* Add active watcher to the
|
|
45
|
+
* Add active watcher to the Set of watchers
|
|
42
46
|
*
|
|
43
|
-
* @param {Watcher
|
|
47
|
+
* @param {Set<Watcher>} watchers - watchers of the signal
|
|
44
48
|
*/
|
|
45
|
-
export const subscribe = (watchers: Watcher
|
|
49
|
+
export const subscribe = (watchers: Set<Watcher>) => {
|
|
46
50
|
// if (!active) console.warn('Calling .get() outside of a reactive context')
|
|
47
|
-
if (active && !watchers.
|
|
48
|
-
|
|
51
|
+
if (active && !watchers.has(active)) {
|
|
52
|
+
const watcher = active
|
|
53
|
+
watchers.add(watcher)
|
|
54
|
+
active.cleanups.add(() => {
|
|
55
|
+
watchers.delete(watcher)
|
|
56
|
+
})
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
/**
|
|
53
61
|
* Add watchers to the pending set of change notifications
|
|
54
62
|
*
|
|
55
|
-
* @param {Watcher
|
|
63
|
+
* @param {Set<Watcher>} watchers - watchers of the signal
|
|
56
64
|
*/
|
|
57
|
-
export const notify = (watchers: Watcher
|
|
65
|
+
export const notify = (watchers: Set<Watcher>) => {
|
|
58
66
|
for (const mark of watchers) {
|
|
59
|
-
batchDepth
|
|
67
|
+
if (batchDepth) pending.add(mark)
|
|
68
|
+
else mark()
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
|
package/lib/signal.d.ts
CHANGED
|
@@ -1,53 +1,44 @@
|
|
|
1
1
|
import { type State } from "./state";
|
|
2
2
|
import { type Computed } from "./computed";
|
|
3
3
|
type Signal<T extends {}> = State<T> | Computed<T>;
|
|
4
|
-
type MaybeSignal<T extends {}> = Signal<T> | T | (() => T | Promise<T>);
|
|
5
|
-
type InferMaybeSignalType<T> = T extends Signal<infer U> ? U : T extends (() => infer U) ? U : T;
|
|
6
|
-
type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
|
|
7
|
-
[K in keyof U]: InferMaybeSignalType<U[K]>;
|
|
8
|
-
}) => T | Promise<T> | Error;
|
|
9
|
-
type NilCallback<T> = () => T | Promise<T> | Error;
|
|
10
|
-
type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error;
|
|
11
|
-
type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
|
|
12
|
-
ok: OkCallback<T, U>;
|
|
13
|
-
nil?: NilCallback<T>;
|
|
14
|
-
err?: ErrCallback<T>;
|
|
15
|
-
};
|
|
16
|
-
type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
|
|
17
|
-
ok: OkCallback<void, U>;
|
|
18
|
-
nil?: NilCallback<void>;
|
|
19
|
-
err?: ErrCallback<void>;
|
|
20
|
-
};
|
|
21
|
-
type CallbackReturnType<T> = T | Promise<T> | Error | void;
|
|
4
|
+
type MaybeSignal<T extends {}> = Signal<T> | T | (() => T) | ((abort: AbortSignal) => Promise<T>);
|
|
22
5
|
declare const UNSET: any;
|
|
23
6
|
/**
|
|
24
7
|
* Check whether a value is a Signal or not
|
|
25
8
|
*
|
|
26
9
|
* @since 0.9.0
|
|
27
|
-
* @param {
|
|
10
|
+
* @param {unknown} value - value to check
|
|
28
11
|
* @returns {boolean} - true if value is a Signal, false otherwise
|
|
29
12
|
*/
|
|
30
13
|
declare const isSignal: <T extends {}>(value: any) => value is Signal<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
16
|
+
*
|
|
17
|
+
* @since 0.12.0
|
|
18
|
+
* @param {unknown} value - value to check
|
|
19
|
+
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
20
|
+
*/
|
|
21
|
+
declare const isComputedCallback: <T extends {}>(value: unknown) => value is (abort?: AbortSignal) => T | Promise<T>;
|
|
31
22
|
/**
|
|
32
23
|
* Convert a value to a Signal if it's not already a Signal
|
|
33
24
|
*
|
|
34
25
|
* @since 0.9.6
|
|
35
26
|
* @param {MaybeSignal<T>} value - value to convert to a Signal
|
|
36
|
-
* @param memo
|
|
37
27
|
* @returns {Signal<T>} - converted Signal
|
|
38
28
|
*/
|
|
39
|
-
declare const toSignal: <T extends {}>(value: MaybeSignal<T>
|
|
29
|
+
declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
|
|
40
30
|
/**
|
|
41
31
|
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
42
32
|
*
|
|
43
|
-
* @since 0.
|
|
44
|
-
* @param {
|
|
45
|
-
* @
|
|
46
|
-
* @returns {CallbackReturnType<T>} - result of chosen callback
|
|
33
|
+
* @since 0.13.0
|
|
34
|
+
* @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
|
|
35
|
+
* @returns {R | Promise<R>} - result of the matched callback
|
|
47
36
|
*/
|
|
48
|
-
declare const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
declare const match: <S extends Signal<{}>[], R>(matcher: {
|
|
38
|
+
signals: S;
|
|
39
|
+
abort?: AbortSignal;
|
|
40
|
+
ok: ((...values: { [K in keyof S]: S[K] extends Signal<infer T> ? T : never; }) => R | Promise<R>);
|
|
41
|
+
err: ((...errors: Error[]) => R | Promise<R>);
|
|
42
|
+
nil: (abort?: AbortSignal) => R | Promise<R>;
|
|
43
|
+
}) => R | Promise<R>;
|
|
44
|
+
export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal, match, };
|
package/lib/signal.ts
CHANGED
|
@@ -1,127 +1,101 @@
|
|
|
1
1
|
import { type State, isState, state } from "./state"
|
|
2
2
|
import { type Computed, computed, isComputed } from "./computed"
|
|
3
|
-
import { isFunction, toError } from "./util"
|
|
3
|
+
import { isAbortError, isFunction, toError } from "./util"
|
|
4
4
|
|
|
5
5
|
/* === Types === */
|
|
6
6
|
|
|
7
7
|
type Signal<T extends {}> = State<T> | Computed<T>
|
|
8
|
-
type MaybeSignal<T extends {}> = Signal<T> | T | (() => T | Promise<T>)
|
|
9
|
-
type InferMaybeSignalType<T> = T extends Signal<infer U> ? U :
|
|
10
|
-
T extends (() => infer U) ? U :
|
|
11
|
-
T
|
|
12
|
-
|
|
13
|
-
type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
|
|
14
|
-
[K in keyof U]: InferMaybeSignalType<U[K]>
|
|
15
|
-
}) => T | Promise<T> | Error
|
|
16
|
-
type NilCallback<T> = () => T | Promise<T> | Error
|
|
17
|
-
type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error
|
|
18
|
-
|
|
19
|
-
type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
|
|
20
|
-
ok: OkCallback<T, U>,
|
|
21
|
-
nil?: NilCallback<T>,
|
|
22
|
-
err?: ErrCallback<T>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
|
|
26
|
-
ok: OkCallback<void, U>,
|
|
27
|
-
nil?: NilCallback<void>,
|
|
28
|
-
err?: ErrCallback<void>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type CallbackReturnType<T> = T | Promise<T> | Error | void
|
|
8
|
+
type MaybeSignal<T extends {}> = Signal<T> | T | (() => T) | ((abort: AbortSignal) => Promise<T>)
|
|
32
9
|
|
|
33
10
|
/* === Constants === */
|
|
34
11
|
|
|
35
12
|
const UNSET: any = Symbol()
|
|
36
13
|
|
|
37
|
-
/* === Private Functions === */
|
|
38
|
-
|
|
39
|
-
const isComputedCallbacks = /*#__PURE__*/ <T extends {}>(value: unknown): value is ComputedCallbacks<T, []> =>
|
|
40
|
-
(isFunction(value) && !value.length)
|
|
41
|
-
|| (typeof value === 'object' && value !== null && 'ok' in value && isFunction(value.ok))
|
|
42
|
-
|
|
43
14
|
/* === Exported Functions === */
|
|
44
15
|
|
|
45
16
|
/**
|
|
46
17
|
* Check whether a value is a Signal or not
|
|
47
18
|
*
|
|
48
19
|
* @since 0.9.0
|
|
49
|
-
* @param {
|
|
20
|
+
* @param {unknown} value - value to check
|
|
50
21
|
* @returns {boolean} - true if value is a Signal, false otherwise
|
|
51
22
|
*/
|
|
52
23
|
const isSignal = /*#__PURE__*/ <T extends {}>(value: any): value is Signal<T> =>
|
|
53
24
|
isState(value) || isComputed(value)
|
|
54
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
28
|
+
*
|
|
29
|
+
* @since 0.12.0
|
|
30
|
+
* @param {unknown} value - value to check
|
|
31
|
+
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
32
|
+
*/
|
|
33
|
+
const isComputedCallback = /*#__PURE__*/ <T extends {}>(
|
|
34
|
+
value: unknown
|
|
35
|
+
): value is (abort?: AbortSignal) => T | Promise<T> =>
|
|
36
|
+
isFunction(value) && value.length < 2
|
|
37
|
+
|
|
55
38
|
/**
|
|
56
39
|
* Convert a value to a Signal if it's not already a Signal
|
|
57
40
|
*
|
|
58
41
|
* @since 0.9.6
|
|
59
42
|
* @param {MaybeSignal<T>} value - value to convert to a Signal
|
|
60
|
-
* @param memo
|
|
61
43
|
* @returns {Signal<T>} - converted Signal
|
|
62
44
|
*/
|
|
63
45
|
const toSignal = /*#__PURE__*/ <T extends {}>(
|
|
64
|
-
value: MaybeSignal<T>
|
|
65
|
-
): Signal<T> =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
: state(value as T)
|
|
46
|
+
value: MaybeSignal<T>
|
|
47
|
+
): Signal<T> => isSignal<T>(value) ? value
|
|
48
|
+
: isComputedCallback<T>(value) ? computed(value)
|
|
49
|
+
: state(value as T)
|
|
69
50
|
|
|
70
51
|
|
|
71
52
|
/**
|
|
72
53
|
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
73
54
|
*
|
|
74
|
-
* @since 0.
|
|
75
|
-
* @param {
|
|
76
|
-
* @
|
|
77
|
-
* @returns {CallbackReturnType<T>} - result of chosen callback
|
|
55
|
+
* @since 0.13.0
|
|
56
|
+
* @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
|
|
57
|
+
* @returns {R | Promise<R>} - result of the matched callback
|
|
78
58
|
*/
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
59
|
+
const match = <S extends Signal<{}>[], R>(
|
|
60
|
+
matcher: {
|
|
61
|
+
signals: S,
|
|
62
|
+
abort?: AbortSignal,
|
|
63
|
+
ok: ((...values: {
|
|
64
|
+
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
65
|
+
}) => R | Promise<R>),
|
|
66
|
+
err: ((...errors: Error[]) => R | Promise<R>),
|
|
67
|
+
nil: (abort?: AbortSignal) => R | Promise<R>
|
|
85
68
|
}
|
|
86
|
-
):
|
|
87
|
-
const {
|
|
88
|
-
? { ok: cb }
|
|
89
|
-
: cb as {
|
|
90
|
-
ok: OkCallback<T | Promise<T>, U>
|
|
91
|
-
nil?: NilCallback<T>
|
|
92
|
-
err?: ErrCallback<T>
|
|
93
|
-
}
|
|
94
|
-
const values = [] as {
|
|
95
|
-
[K in keyof U]: InferMaybeSignalType<U[K]>
|
|
96
|
-
}
|
|
97
|
-
const errors: Error[] = []
|
|
98
|
-
let hasUnset = false
|
|
69
|
+
): R | Promise<R> => {
|
|
70
|
+
const { signals, abort, ok, err, nil } = matcher
|
|
99
71
|
|
|
100
|
-
|
|
101
|
-
|
|
72
|
+
const errors: Error[] = []
|
|
73
|
+
let suspense = false
|
|
74
|
+
const values = signals.map(signal => {
|
|
102
75
|
try {
|
|
103
|
-
const value =
|
|
104
|
-
if (value === UNSET)
|
|
105
|
-
|
|
76
|
+
const value = signal.get()
|
|
77
|
+
if (value === UNSET) suspense = true
|
|
78
|
+
return value
|
|
106
79
|
} catch (e) {
|
|
80
|
+
if (isAbortError(e)) throw e
|
|
107
81
|
errors.push(toError(e))
|
|
108
82
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
83
|
+
}) as {
|
|
84
|
+
[K in keyof S]: S[K] extends Signal<infer T extends {}> ? T : never
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
return suspense ? nil(abort)
|
|
89
|
+
: errors.length ? err(...errors)
|
|
90
|
+
: ok(...values)
|
|
91
|
+
} catch (e) {
|
|
92
|
+
if (isAbortError(e)) throw e
|
|
93
|
+
const error = toError(e)
|
|
94
|
+
return err(error)
|
|
95
|
+
}
|
|
121
96
|
}
|
|
122
97
|
|
|
123
98
|
export {
|
|
124
|
-
type Signal, type MaybeSignal,
|
|
125
|
-
|
|
126
|
-
UNSET, isSignal, toSignal, resolve,
|
|
99
|
+
type Signal, type MaybeSignal,
|
|
100
|
+
UNSET, isSignal, isComputedCallback, toSignal, match,
|
|
127
101
|
}
|