@zeix/cause-effect 0.12.4 → 0.13.1

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/lib/computed.ts CHANGED
@@ -1,18 +1,25 @@
1
- import {
2
- type Signal, type EffectCallbacks, type ComputedCallbacks,
3
- resolve, UNSET
4
- } from './signal'
5
- import { isError, isObjectOfType, isPromise, toError } from './util'
1
+ import { type Signal, type ComputedCallback, 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: () => T
14
- map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>) => Computed<U>
15
- match: (cb: EffectCallbacks<[Computed<T>]>) => void
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} cb - compute callback or object of ok, nil, err callbacks to derive state
39
- * @param {U} maybeSignals - signals of functions using signals this values depends on
45
+ * @param {ComputedMatcher<S, T> | ComputedCallback<T>} matcher - computed matcher or callback
40
46
  * @returns {Computed<T>} - Computed signal
41
47
  */
42
- export const computed = <T extends {}, U extends Signal<{}>[]>(
43
- cb: ComputedCallbacks<T, U>,
44
- ...maybeSignals: U
48
+ export const computed = <T extends {}, S extends Signal<{}>[] = []>(
49
+ matcher: ComputedMatcher<S, T> | ComputedCallback<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 ComputedCallback<T>
61
+
62
+ // Internal state
47
63
  let value: T = UNSET
48
64
  let error: Error | undefined
49
65
  let dirty = true
50
- let unchanged = false
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 Signal<{}>[]>(
56
73
  value = v
57
74
  dirty = false
58
75
  error = undefined
59
- unchanged = false
76
+ changed = true
60
77
  }
61
78
  }
62
79
  const nil = () => {
63
- unchanged = (UNSET === value)
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
- unchanged = isEquivalentError(newError, error)
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
- if (!unchanged) notify(watchers)
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 Error('Circular dependency in computed detected')
83
- unchanged = true
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
- const result = resolve(maybeSignals, cb)
86
- if (isPromise(result)) {
87
- nil() // sync
88
- result.then(v => {
89
- ok(v) // async
90
- notify(watchers)
91
- }).catch(err)
92
- } else if (null == result || UNSET === result) nil()
93
- else if (isError(result)) err(result)
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 Signal<{}>[]>(
116
168
  * Create a computed signal from the current computed signal
117
169
  *
118
170
  * @since 0.9.0
119
- * @param {ComputedCallbacks<U, [Computed<T>]>} cb - compute callback or object of ok, nil, err callbacks to map this value to new computed
171
+ * @param {((v: T) => U | Promise<U>)} fn - computed callback
120
172
  * @returns {Computed<U>} - computed signal
121
173
  */
122
- map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>): Computed<U> =>
123
- computed(cb, c),
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.12.0
129
- * @param {EffectCallbacks[Computed<T>]} cb - effect callback or object of ok, nil, err callbacks to be executed when the computed changes
130
- * @returns {Computed<T>} - self, for chaining effect callbacks
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
- match: (cb: EffectCallbacks<[Computed<T>]>): Computed<T> => {
133
- effect(cb, c)
134
- return c
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 EffectCallbacks, type Signal } from './signal';
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} cb - effect callback or object of ok, nil, err callbacks to be executed when a state changes
7
- * @param {U} maybeSignals - signals of functions using signals that should trigger the effect
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<U extends Signal<{}>[]>(cb: EffectCallbacks<U>, ...maybeSignals: U): void;
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
- import { type EffectCallbacks, type Signal, resolve } from './signal'
3
- import { isError } from './util'
4
- import { watch } from './scheduler'
5
+ /* === Types === */
5
6
 
6
- /* === Exported Function === */
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} cb - effect callback or object of ok, nil, err callbacks to be executed when a state changes
13
- * @param {U} maybeSignals - signals of functions using signals that should trigger the effect
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<U extends Signal<{}>[]>(
16
- cb: EffectCallbacks<U>,
17
- ...maybeSignals: U
18
- ): void {
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 Error('Circular dependency in effect detected')
43
+ const run = (() => watch(() => {
44
+ if (running) throw new CircularDependencyError('effect')
22
45
  running = true
23
- const result = resolve(maybeSignals, cb)
24
- if (isError(result))
25
- console.error('Unhandled error in effect:', result)
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
+ }
@@ -1,18 +1,21 @@
1
1
  export type EnqueueDedupe = [Element, string];
2
- export type Watcher = () => void;
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 array of watchers
8
+ * Add active watcher to the Set of watchers
6
9
  *
7
- * @param {Watcher[]} watchers - watchers of the signal
10
+ * @param {Set<Watcher>} watchers - watchers of the signal
8
11
  */
9
- export declare const subscribe: (watchers: Watcher[]) => void;
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[]} watchers - watchers of the signal
16
+ * @param {Set<Watcher>} watchers - watchers of the signal
14
17
  */
15
- export declare const notify: (watchers: Watcher[]) => void;
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 = () => void
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,23 +42,27 @@ queueMicrotask(updateDOM)
38
42
  /* === Exported Functions === */
39
43
 
40
44
  /**
41
- * Add active watcher to the array of watchers
45
+ * Add active watcher to the Set of watchers
42
46
  *
43
- * @param {Watcher[]} watchers - watchers of the signal
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.includes(active)) {
48
- watchers.push(active)
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[]} watchers - watchers of the signal
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
67
  if (batchDepth) pending.add(mark)
60
68
  else mark()
package/lib/signal.d.ts CHANGED
@@ -1,24 +1,8 @@
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 InferSignalType<T> = T extends Signal<infer U> ? U : never;
6
- type OkCallback<T, U extends Signal<{}>[]> = (...values: {
7
- [K in keyof U]: InferSignalType<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 Signal<{}>[]> = OkCallback<T, U> | {
12
- ok: OkCallback<T, U>;
13
- nil?: NilCallback<T>;
14
- err?: ErrCallback<T>;
15
- };
16
- type EffectCallbacks<U extends Signal<{}>[]> = 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 ComputedCallback<T extends {}> = (abort?: AbortSignal) => T | Promise<T>;
5
+ type MaybeSignal<T extends {}> = Signal<T> | T | ComputedCallback<T>;
22
6
  declare const UNSET: any;
23
7
  /**
24
8
  * Check whether a value is a Signal or not
@@ -29,33 +13,33 @@ declare const UNSET: any;
29
13
  */
30
14
  declare const isSignal: <T extends {}>(value: any) => value is Signal<T>;
31
15
  /**
32
- * Check if the provided value is a callback or callbacks object of { ok, nil?, err? } that may be used as input for toSignal() to derive a computed state
16
+ * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
33
17
  *
34
- * @since 0.12.4
18
+ * @since 0.12.0
35
19
  * @param {unknown} value - value to check
36
20
  * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
37
21
  */
38
- declare const isComputedCallbacks: <T extends {}>(value: unknown) => value is ComputedCallbacks<T, []>;
22
+ declare const isComputedCallback: <T extends {}>(value: unknown) => value is ComputedCallback<T>;
39
23
  /**
40
24
  * Convert a value to a Signal if it's not already a Signal
41
25
  *
42
26
  * @since 0.9.6
43
27
  * @param {MaybeSignal<T>} value - value to convert to a Signal
44
- * @param memo
45
28
  * @returns {Signal<T>} - converted Signal
46
29
  */
47
- declare const toSignal: <T extends {}>(value: MaybeSignal<T> | ComputedCallbacks<T, []>) => Signal<T>;
30
+ declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
48
31
  /**
49
32
  * Resolve signals or functions using signals and apply callbacks based on the results
50
33
  *
51
- * @since 0.12.0
52
- * @param {U} maybeSignals - dependency signals (or functions using signals)
53
- * @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
54
- * @returns {CallbackReturnType<T>} - result of chosen callback
34
+ * @since 0.13.0
35
+ * @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
36
+ * @returns {R | Promise<R>} - result of the matched callback
55
37
  */
56
- declare const resolve: <T, U extends Signal<{}>[]>(maybeSignals: U, cb: OkCallback<T | Promise<T>, U> | {
57
- ok: OkCallback<T | Promise<T>, U>;
58
- nil?: NilCallback<T>;
59
- err?: ErrCallback<T>;
60
- }) => CallbackReturnType<T>;
61
- export { type Signal, type MaybeSignal, type InferSignalType, type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType, UNSET, isSignal, isComputedCallbacks, toSignal, resolve, };
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 CHANGED
@@ -1,32 +1,12 @@
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 InferSignalType<T> = T extends Signal<infer U> ? U : never
10
-
11
- type OkCallback<T, U extends Signal<{}>[]> = (...values: {
12
- [K in keyof U]: InferSignalType<U[K]>
13
- }) => T | Promise<T> | Error
14
- type NilCallback<T> = () => T | Promise<T> | Error
15
- type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error
16
-
17
- type ComputedCallbacks<T extends {}, U extends Signal<{}>[]> = OkCallback<T, U> | {
18
- ok: OkCallback<T, U>,
19
- nil?: NilCallback<T>,
20
- err?: ErrCallback<T>
21
- }
22
-
23
- type EffectCallbacks<U extends Signal<{}>[]> = OkCallback<void, U> | {
24
- ok: OkCallback<void, U>,
25
- nil?: NilCallback<void>,
26
- err?: ErrCallback<void>
27
- }
28
-
29
- type CallbackReturnType<T> = T | Promise<T> | Error | void
8
+ type ComputedCallback<T extends {}> = (abort?: AbortSignal) => T | Promise<T>
9
+ type MaybeSignal<T extends {}> = Signal<T> | T | ComputedCallback<T>
30
10
 
31
11
  /* === Constants === */
32
12
 
@@ -45,86 +25,78 @@ const isSignal = /*#__PURE__*/ <T extends {}>(value: any): value is Signal<T> =>
45
25
  isState(value) || isComputed(value)
46
26
 
47
27
  /**
48
- * Check if the provided value is a callback or callbacks object of { ok, nil?, err? } that may be used as input for toSignal() to derive a computed state
28
+ * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
49
29
  *
50
- * @since 0.12.4
30
+ * @since 0.12.0
51
31
  * @param {unknown} value - value to check
52
32
  * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
53
33
  */
54
- const isComputedCallbacks = /*#__PURE__*/ <T extends {}>(value: unknown): value is ComputedCallbacks<T, []> =>
55
- (isFunction(value) && !value.length)
56
- || (typeof value === 'object' && value !== null && 'ok' in value && isFunction(value.ok))
34
+ const isComputedCallback = /*#__PURE__*/ <T extends {}>(
35
+ value: unknown
36
+ ): value is ComputedCallback<T> =>
37
+ isFunction(value) && value.length < 2
57
38
 
58
39
  /**
59
40
  * Convert a value to a Signal if it's not already a Signal
60
41
  *
61
42
  * @since 0.9.6
62
43
  * @param {MaybeSignal<T>} value - value to convert to a Signal
63
- * @param memo
64
44
  * @returns {Signal<T>} - converted Signal
65
45
  */
66
46
  const toSignal = /*#__PURE__*/ <T extends {}>(
67
- value: MaybeSignal<T> | ComputedCallbacks<T, []>
68
- ): Signal<T> =>
69
- isSignal<T>(value) ? value
70
- : isComputedCallbacks<T>(value) ? computed(value)
71
- : state(value as T)
47
+ value: MaybeSignal<T>
48
+ ): Signal<T> => isSignal<T>(value) ? value
49
+ : isComputedCallback<T>(value) ? computed(value)
50
+ : state(value as T)
72
51
 
73
52
 
74
53
  /**
75
54
  * Resolve signals or functions using signals and apply callbacks based on the results
76
55
  *
77
- * @since 0.12.0
78
- * @param {U} maybeSignals - dependency signals (or functions using signals)
79
- * @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
80
- * @returns {CallbackReturnType<T>} - result of chosen callback
56
+ * @since 0.13.0
57
+ * @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
58
+ * @returns {R | Promise<R>} - result of the matched callback
81
59
  */
82
- const resolve = <T, U extends Signal<{}>[]>(
83
- maybeSignals: U,
84
- cb: OkCallback<T | Promise<T>, U> | {
85
- ok: OkCallback<T | Promise<T>, U>
86
- nil?: NilCallback<T>
87
- err?: ErrCallback<T>
88
- }
89
- ): CallbackReturnType<T> => {
90
- const { ok, nil, err } = isFunction(cb)
91
- ? { ok: cb }
92
- : cb as {
93
- ok: OkCallback<T | Promise<T>, U>
94
- nil?: NilCallback<T>
95
- err?: ErrCallback<T>
96
- }
97
- const values = [] as {
98
- [K in keyof U]: InferSignalType<U[K]>
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>
99
69
  }
100
- const errors: Error[] = []
101
- let hasUnset = false
70
+ ): R | Promise<R> => {
71
+ const { signals, abort, ok, err, nil } = matcher
102
72
 
103
- for (let i = 0; i < maybeSignals.length; i++) {
104
- const s = maybeSignals[i]
73
+ const errors: Error[] = []
74
+ let suspense = false
75
+ const values = signals.map(signal => {
105
76
  try {
106
- const value = s.get()
107
- if (value === UNSET) hasUnset = true
108
- values[i] = value as InferSignalType<typeof s>
77
+ const value = signal.get()
78
+ if (value === UNSET) suspense = true
79
+ return value
109
80
  } catch (e) {
81
+ if (isAbortError(e)) throw e
110
82
  errors.push(toError(e))
111
83
  }
112
- }
113
-
114
- let result: CallbackReturnType<T> = undefined
115
- try {
116
- if (hasUnset && nil) result = nil()
117
- else if (errors.length) result = err ? err(...errors) : errors[0]
118
- else if (!hasUnset) result = ok(...values) as CallbackReturnType<T>
119
- } catch (e) {
120
- result = toError(e)
121
- if (err) result = err(result)
122
- }
123
- return result
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
+ }
124
97
  }
125
98
 
126
99
  export {
127
- type Signal, type MaybeSignal, type InferSignalType,
128
- type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType,
129
- UNSET, isSignal, isComputedCallbacks, toSignal, resolve,
100
+ type Signal, type MaybeSignal, type ComputedCallback,
101
+ UNSET, isSignal, isComputedCallback, toSignal, match,
130
102
  }
package/lib/state.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { type ComputedCallbacks, type EffectCallbacks } from './signal';
2
1
  import { type Computed } from './computed';
2
+ import { type TapMatcher } from './effect';
3
3
  export type State<T extends {}> = {
4
4
  [Symbol.toStringTag]: 'State';
5
5
  get(): T;
6
6
  set(v: T): void;
7
7
  update(fn: (v: T) => T): void;
8
- map<U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U>;
9
- match: (cb: EffectCallbacks<[State<T>]>) => void;
8
+ map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>;
9
+ tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void;
10
10
  };
11
11
  /**
12
12
  * Create a new state signal