@zeix/cause-effect 0.13.1 → 0.14.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/README.md +161 -131
- package/eslint.config.js +35 -0
- package/index.d.ts +9 -7
- package/index.js +1 -1
- package/index.ts +20 -10
- package/package.json +32 -29
- package/src/computed.d.ts +31 -0
- package/src/computed.ts +54 -0
- package/src/effect.d.ts +19 -0
- package/src/effect.ts +95 -0
- package/src/memo.d.ts +13 -0
- package/src/memo.ts +91 -0
- package/{lib → src}/scheduler.d.ts +15 -11
- package/{lib → src}/scheduler.ts +60 -48
- package/src/signal.d.ts +31 -0
- package/src/signal.ts +69 -0
- package/{lib → src}/state.d.ts +4 -7
- package/src/state.ts +89 -0
- package/src/task.d.ts +17 -0
- package/src/task.ts +153 -0
- package/{lib → src}/util.d.ts +1 -1
- package/{lib → src}/util.ts +23 -11
- package/test/batch.test.ts +23 -28
- package/test/benchmark.test.ts +115 -103
- package/test/computed.test.ts +133 -147
- package/test/effect.test.ts +42 -37
- package/test/state.test.ts +12 -79
- 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/computed.d.ts +0 -33
- package/lib/computed.ts +0 -206
- package/lib/effect.d.ts +0 -22
- package/lib/effect.ts +0 -61
- package/lib/signal.d.ts +0 -45
- package/lib/signal.ts +0 -102
- package/lib/state.ts +0 -118
package/lib/computed.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { type Signal, type ComputedCallback, match, UNSET } from './signal'
|
|
2
|
-
import { CircularDependencyError, isAbortError, isAsyncFunction, isFunction, isObjectOfType, isPromise, toError } from './util'
|
|
3
|
-
import { type Watcher, flush, notify, subscribe, watch } from './scheduler'
|
|
4
|
-
import { type TapMatcher, type EffectMatcher, effect } from './effect'
|
|
5
|
-
|
|
6
|
-
/* === Types === */
|
|
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
|
-
|
|
18
|
-
export type Computed<T extends {}> = {
|
|
19
|
-
[Symbol.toStringTag]: 'Computed'
|
|
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
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* === Constants === */
|
|
26
|
-
|
|
27
|
-
const TYPE_COMPUTED = 'Computed'
|
|
28
|
-
|
|
29
|
-
/* === Private Functions === */
|
|
30
|
-
|
|
31
|
-
const isEquivalentError = /*#__PURE__*/ (
|
|
32
|
-
error1: Error,
|
|
33
|
-
error2: Error | undefined
|
|
34
|
-
): boolean => {
|
|
35
|
-
if (!error2) return false
|
|
36
|
-
return error1.name === error2.name && error1.message === error2.message
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* === Computed Factory === */
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Create a derived signal from existing signals
|
|
43
|
-
*
|
|
44
|
-
* @since 0.9.0
|
|
45
|
-
* @param {ComputedMatcher<S, T> | ComputedCallback<T>} matcher - computed matcher or callback
|
|
46
|
-
* @returns {Computed<T>} - Computed signal
|
|
47
|
-
*/
|
|
48
|
-
export const computed = <T extends {}, S extends Signal<{}>[] = []>(
|
|
49
|
-
matcher: ComputedMatcher<S, T> | ComputedCallback<T>,
|
|
50
|
-
): Computed<T> => {
|
|
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 ComputedCallback<T>
|
|
61
|
-
|
|
62
|
-
// Internal state
|
|
63
|
-
let value: T = UNSET
|
|
64
|
-
let error: Error | undefined
|
|
65
|
-
let dirty = true
|
|
66
|
-
let changed = false
|
|
67
|
-
let computing = false
|
|
68
|
-
let controller: AbortController | undefined
|
|
69
|
-
|
|
70
|
-
// Functions to update internal state
|
|
71
|
-
const ok = (v: T) => {
|
|
72
|
-
if (!Object.is(v, value)) {
|
|
73
|
-
value = v
|
|
74
|
-
dirty = false
|
|
75
|
-
error = undefined
|
|
76
|
-
changed = true
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const nil = () => {
|
|
80
|
-
changed = (UNSET !== value)
|
|
81
|
-
value = UNSET
|
|
82
|
-
error = undefined
|
|
83
|
-
}
|
|
84
|
-
const err = (e: unknown) => {
|
|
85
|
-
const newError = toError(e)
|
|
86
|
-
changed = !isEquivalentError(newError, error)
|
|
87
|
-
value = UNSET
|
|
88
|
-
error = newError
|
|
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
|
-
}
|
|
107
|
-
|
|
108
|
-
// Called when notified from sources (push)
|
|
109
|
-
const mark = (() => {
|
|
110
|
-
dirty = true
|
|
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()
|
|
120
|
-
|
|
121
|
-
// Called when requested by dependencies (pull)
|
|
122
|
-
const compute = () => watch(() => {
|
|
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>
|
|
134
|
-
computing = true
|
|
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()
|
|
146
|
-
else ok(result)
|
|
147
|
-
computing = false
|
|
148
|
-
}, mark)
|
|
149
|
-
|
|
150
|
-
const c: Computed<T> = {
|
|
151
|
-
[Symbol.toStringTag]: TYPE_COMPUTED,
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get the current value of the computed
|
|
155
|
-
*
|
|
156
|
-
* @since 0.9.0
|
|
157
|
-
* @returns {T} - current value of the computed
|
|
158
|
-
*/
|
|
159
|
-
get: (): T => {
|
|
160
|
-
subscribe(watchers)
|
|
161
|
-
flush()
|
|
162
|
-
if (dirty) compute()
|
|
163
|
-
if (error) throw error
|
|
164
|
-
return value
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Create a computed signal from the current computed signal
|
|
169
|
-
*
|
|
170
|
-
* @since 0.9.0
|
|
171
|
-
* @param {((v: T) => U | Promise<U>)} fn - computed callback
|
|
172
|
-
* @returns {Computed<U>} - computed signal
|
|
173
|
-
*/
|
|
174
|
-
map: <U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U> =>
|
|
175
|
-
computed({
|
|
176
|
-
signals: [c],
|
|
177
|
-
ok: fn
|
|
178
|
-
}),
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Case matching for the computed signal with effect callbacks
|
|
182
|
-
*
|
|
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
|
|
186
|
-
*/
|
|
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>]>)
|
|
192
|
-
}
|
|
193
|
-
return c
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/* === Helper Functions === */
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Check if a value is a computed state
|
|
200
|
-
*
|
|
201
|
-
* @since 0.9.0
|
|
202
|
-
* @param {unknown} value - value to check
|
|
203
|
-
* @returns {boolean} - true if value is a computed state, false otherwise
|
|
204
|
-
*/
|
|
205
|
-
export const isComputed = /*#__PURE__*/ <T extends {}>(value: unknown): value is Computed<T> =>
|
|
206
|
-
isObjectOfType(value, TYPE_COMPUTED)
|
package/lib/effect.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Define what happens when a reactive state changes
|
|
17
|
-
*
|
|
18
|
-
* @since 0.1.0
|
|
19
|
-
* @param {EffectMatcher<S> | (() => void | (() => void))} matcher - effect matcher or callback
|
|
20
|
-
* @returns {() => void} - cleanup function for the effect
|
|
21
|
-
*/
|
|
22
|
-
export declare function effect<S extends Signal<{}>[]>(matcher: EffectMatcher<S> | (() => void | (() => void))): () => void;
|
package/lib/effect.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { type Signal, match } from './signal'
|
|
2
|
-
import { CircularDependencyError, isFunction, toError } from './util'
|
|
3
|
-
import { watch, type Watcher } from './scheduler'
|
|
4
|
-
|
|
5
|
-
/* === Types === */
|
|
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 === */
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Define what happens when a reactive state changes
|
|
26
|
-
*
|
|
27
|
-
* @since 0.1.0
|
|
28
|
-
* @param {EffectMatcher<S> | (() => void | (() => void))} matcher - effect matcher or callback
|
|
29
|
-
* @returns {() => void} - cleanup function for the effect
|
|
30
|
-
*/
|
|
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
|
|
42
|
-
let running = false
|
|
43
|
-
const run = (() => watch(() => {
|
|
44
|
-
if (running) throw new CircularDependencyError('effect')
|
|
45
|
-
running = true
|
|
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)
|
|
53
|
-
running = false
|
|
54
|
-
}, run)) as Watcher
|
|
55
|
-
run.cleanups = new Set()
|
|
56
|
-
run()
|
|
57
|
-
return () => {
|
|
58
|
-
run.cleanups.forEach((fn: () => void) => fn())
|
|
59
|
-
run.cleanups.clear()
|
|
60
|
-
}
|
|
61
|
-
}
|
package/lib/signal.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type State } from "./state";
|
|
2
|
-
import { type Computed } from "./computed";
|
|
3
|
-
type Signal<T extends {}> = State<T> | Computed<T>;
|
|
4
|
-
type ComputedCallback<T extends {}> = (abort?: AbortSignal) => T | Promise<T>;
|
|
5
|
-
type MaybeSignal<T extends {}> = Signal<T> | T | ComputedCallback<T>;
|
|
6
|
-
declare const UNSET: any;
|
|
7
|
-
/**
|
|
8
|
-
* Check whether a value is a Signal or not
|
|
9
|
-
*
|
|
10
|
-
* @since 0.9.0
|
|
11
|
-
* @param {unknown} value - value to check
|
|
12
|
-
* @returns {boolean} - true if value is a Signal, false otherwise
|
|
13
|
-
*/
|
|
14
|
-
declare const isSignal: <T extends {}>(value: any) => value is Signal<T>;
|
|
15
|
-
/**
|
|
16
|
-
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
17
|
-
*
|
|
18
|
-
* @since 0.12.0
|
|
19
|
-
* @param {unknown} value - value to check
|
|
20
|
-
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
21
|
-
*/
|
|
22
|
-
declare const isComputedCallback: <T extends {}>(value: unknown) => value is ComputedCallback<T>;
|
|
23
|
-
/**
|
|
24
|
-
* Convert a value to a Signal if it's not already a Signal
|
|
25
|
-
*
|
|
26
|
-
* @since 0.9.6
|
|
27
|
-
* @param {MaybeSignal<T>} value - value to convert to a Signal
|
|
28
|
-
* @returns {Signal<T>} - converted Signal
|
|
29
|
-
*/
|
|
30
|
-
declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
|
|
31
|
-
/**
|
|
32
|
-
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
33
|
-
*
|
|
34
|
-
* @since 0.13.0
|
|
35
|
-
* @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
|
|
36
|
-
* @returns {R | Promise<R>} - result of the matched callback
|
|
37
|
-
*/
|
|
38
|
-
declare const match: <S extends Signal<{}>[], R>(matcher: {
|
|
39
|
-
signals: S;
|
|
40
|
-
abort?: AbortSignal;
|
|
41
|
-
ok: ((...values: { [K in keyof S]: S[K] extends Signal<infer T> ? T : never; }) => R | Promise<R>);
|
|
42
|
-
err: ((...errors: Error[]) => R | Promise<R>);
|
|
43
|
-
nil: (abort?: AbortSignal) => R | Promise<R>;
|
|
44
|
-
}) => R | Promise<R>;
|
|
45
|
-
export { type Signal, type MaybeSignal, type ComputedCallback, UNSET, isSignal, isComputedCallback, toSignal, match, };
|
package/lib/signal.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { type State, isState, state } from "./state"
|
|
2
|
-
import { type Computed, computed, isComputed } from "./computed"
|
|
3
|
-
import { isAbortError, isFunction, toError } from "./util"
|
|
4
|
-
|
|
5
|
-
/* === Types === */
|
|
6
|
-
|
|
7
|
-
type Signal<T extends {}> = State<T> | Computed<T>
|
|
8
|
-
type ComputedCallback<T extends {}> = (abort?: AbortSignal) => T | Promise<T>
|
|
9
|
-
type MaybeSignal<T extends {}> = Signal<T> | T | ComputedCallback<T>
|
|
10
|
-
|
|
11
|
-
/* === Constants === */
|
|
12
|
-
|
|
13
|
-
const UNSET: any = Symbol()
|
|
14
|
-
|
|
15
|
-
/* === Exported Functions === */
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Check whether a value is a Signal or not
|
|
19
|
-
*
|
|
20
|
-
* @since 0.9.0
|
|
21
|
-
* @param {unknown} value - value to check
|
|
22
|
-
* @returns {boolean} - true if value is a Signal, false otherwise
|
|
23
|
-
*/
|
|
24
|
-
const isSignal = /*#__PURE__*/ <T extends {}>(value: any): value is Signal<T> =>
|
|
25
|
-
isState(value) || isComputed(value)
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
29
|
-
*
|
|
30
|
-
* @since 0.12.0
|
|
31
|
-
* @param {unknown} value - value to check
|
|
32
|
-
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
33
|
-
*/
|
|
34
|
-
const isComputedCallback = /*#__PURE__*/ <T extends {}>(
|
|
35
|
-
value: unknown
|
|
36
|
-
): value is ComputedCallback<T> =>
|
|
37
|
-
isFunction(value) && value.length < 2
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Convert a value to a Signal if it's not already a Signal
|
|
41
|
-
*
|
|
42
|
-
* @since 0.9.6
|
|
43
|
-
* @param {MaybeSignal<T>} value - value to convert to a Signal
|
|
44
|
-
* @returns {Signal<T>} - converted Signal
|
|
45
|
-
*/
|
|
46
|
-
const toSignal = /*#__PURE__*/ <T extends {}>(
|
|
47
|
-
value: MaybeSignal<T>
|
|
48
|
-
): Signal<T> => isSignal<T>(value) ? value
|
|
49
|
-
: isComputedCallback<T>(value) ? computed(value)
|
|
50
|
-
: state(value as T)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
55
|
-
*
|
|
56
|
-
* @since 0.13.0
|
|
57
|
-
* @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
|
|
58
|
-
* @returns {R | Promise<R>} - result of the matched callback
|
|
59
|
-
*/
|
|
60
|
-
const match = <S extends Signal<{}>[], R>(
|
|
61
|
-
matcher: {
|
|
62
|
-
signals: S,
|
|
63
|
-
abort?: AbortSignal,
|
|
64
|
-
ok: ((...values: {
|
|
65
|
-
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
66
|
-
}) => R | Promise<R>),
|
|
67
|
-
err: ((...errors: Error[]) => R | Promise<R>),
|
|
68
|
-
nil: (abort?: AbortSignal) => R | Promise<R>
|
|
69
|
-
}
|
|
70
|
-
): R | Promise<R> => {
|
|
71
|
-
const { signals, abort, ok, err, nil } = matcher
|
|
72
|
-
|
|
73
|
-
const errors: Error[] = []
|
|
74
|
-
let suspense = false
|
|
75
|
-
const values = signals.map(signal => {
|
|
76
|
-
try {
|
|
77
|
-
const value = signal.get()
|
|
78
|
-
if (value === UNSET) suspense = true
|
|
79
|
-
return value
|
|
80
|
-
} catch (e) {
|
|
81
|
-
if (isAbortError(e)) throw e
|
|
82
|
-
errors.push(toError(e))
|
|
83
|
-
}
|
|
84
|
-
}) as {
|
|
85
|
-
[K in keyof S]: S[K] extends Signal<infer T extends {}> ? T : never
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
return suspense ? nil(abort)
|
|
90
|
-
: errors.length ? err(...errors)
|
|
91
|
-
: ok(...values)
|
|
92
|
-
} catch (e) {
|
|
93
|
-
if (isAbortError(e)) throw e
|
|
94
|
-
const error = toError(e)
|
|
95
|
-
return err(error)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export {
|
|
100
|
-
type Signal, type MaybeSignal, type ComputedCallback,
|
|
101
|
-
UNSET, isSignal, isComputedCallback, toSignal, match,
|
|
102
|
-
}
|
package/lib/state.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { UNSET } from './signal'
|
|
2
|
-
import { type Computed, computed } from './computed'
|
|
3
|
-
import { isFunction, isObjectOfType } from './util'
|
|
4
|
-
import { type Watcher, notify, subscribe } from './scheduler'
|
|
5
|
-
import { type TapMatcher, type EffectMatcher, effect } from './effect'
|
|
6
|
-
|
|
7
|
-
/* === Types === */
|
|
8
|
-
|
|
9
|
-
export type State<T extends {}> = {
|
|
10
|
-
[Symbol.toStringTag]: 'State'
|
|
11
|
-
get(): T
|
|
12
|
-
set(v: T): void
|
|
13
|
-
update(fn: (v: T) => T): void
|
|
14
|
-
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>
|
|
15
|
-
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/* === Constants === */
|
|
19
|
-
|
|
20
|
-
const TYPE_STATE = 'State'
|
|
21
|
-
|
|
22
|
-
/* === State Factory === */
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create a new state signal
|
|
26
|
-
*
|
|
27
|
-
* @since 0.9.0
|
|
28
|
-
* @param {T} initialValue - initial value of the state
|
|
29
|
-
* @returns {State<T>} - new state signal
|
|
30
|
-
*/
|
|
31
|
-
export const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
|
|
32
|
-
const watchers: Set<Watcher> = new Set()
|
|
33
|
-
let value: T = initialValue
|
|
34
|
-
|
|
35
|
-
const s: State<T> = {
|
|
36
|
-
[Symbol.toStringTag]: TYPE_STATE,
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get the current value of the state
|
|
40
|
-
*
|
|
41
|
-
* @since 0.9.0
|
|
42
|
-
* @returns {T} - current value of the state
|
|
43
|
-
*/
|
|
44
|
-
get: (): T => {
|
|
45
|
-
subscribe(watchers)
|
|
46
|
-
return value
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Set a new value of the state
|
|
51
|
-
*
|
|
52
|
-
* @since 0.9.0
|
|
53
|
-
* @param {T} v
|
|
54
|
-
* @returns {void}
|
|
55
|
-
*/
|
|
56
|
-
set: (v: T): void => {
|
|
57
|
-
if (Object.is(value, v)) return
|
|
58
|
-
value = v
|
|
59
|
-
notify(watchers)
|
|
60
|
-
|
|
61
|
-
// Setting to UNSET clears the watchers so the signal can be garbage collected
|
|
62
|
-
if (UNSET === value) watchers.clear()
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Update the state with a new value using a function
|
|
67
|
-
*
|
|
68
|
-
* @since 0.10.0
|
|
69
|
-
* @param {(v: T) => T} fn - function to update the state
|
|
70
|
-
* @returns {void} - updates the state with the result of the function
|
|
71
|
-
*/
|
|
72
|
-
update: (fn: (v: T) => T): void => {
|
|
73
|
-
s.set(fn(value))
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Create a computed signal from the current state signal
|
|
78
|
-
*
|
|
79
|
-
* @since 0.9.0
|
|
80
|
-
* @param {(v: T) => U | Promise<U>} fn - computed callback
|
|
81
|
-
* @returns {Computed<U>} - computed signal
|
|
82
|
-
*/
|
|
83
|
-
map: <U extends {}>(
|
|
84
|
-
fn: (v: T) => U | Promise<U>
|
|
85
|
-
): Computed<U> =>
|
|
86
|
-
computed({
|
|
87
|
-
signals: [s],
|
|
88
|
-
ok: fn
|
|
89
|
-
}),
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Case matching for the state signal with effect callbacks
|
|
93
|
-
*
|
|
94
|
-
* @since 0.13.0
|
|
95
|
-
* @param {TapMatcher<T> | ((v: T) => void | (() => void))} matcher - tap matcher or effect callback
|
|
96
|
-
* @returns {() => void} - cleanup function for the effect
|
|
97
|
-
*/
|
|
98
|
-
tap: (
|
|
99
|
-
matcher: TapMatcher<T> | ((v: T) => void | (() => void))
|
|
100
|
-
): () => void =>
|
|
101
|
-
effect({
|
|
102
|
-
signals: [s],
|
|
103
|
-
...(isFunction(matcher) ? { ok: matcher } : matcher)
|
|
104
|
-
} as EffectMatcher<[State<T>]>)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return s
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Check if the provided value is a State instance
|
|
112
|
-
*
|
|
113
|
-
* @since 0.9.0
|
|
114
|
-
* @param {unknown} value - value to check
|
|
115
|
-
* @returns {boolean} - true if the value is a State instance, false otherwise
|
|
116
|
-
*/
|
|
117
|
-
export const isState = /*#__PURE__*/ <T extends {}>(value: unknown): value is State<T> =>
|
|
118
|
-
isObjectOfType(value, TYPE_STATE)
|