@zeix/cause-effect 0.17.3 → 0.18.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/.ai-context.md +169 -227
- package/.cursorrules +41 -35
- package/.github/copilot-instructions.md +176 -116
- package/ARCHITECTURE.md +276 -0
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +201 -143
- package/GUIDE.md +298 -0
- package/README.md +246 -193
- package/REQUIREMENTS.md +100 -0
- package/bench/reactivity.bench.ts +577 -0
- package/context7.json +4 -0
- package/examples/events-sensor.ts +187 -0
- package/examples/selector-sensor.ts +173 -0
- package/index.dev.js +1390 -1008
- package/index.js +1 -1
- package/index.ts +60 -74
- package/package.json +5 -2
- package/skills/changelog-keeper/SKILL.md +59 -0
- package/skills/changelog-keeper/agents/openai.yaml +4 -0
- package/src/errors.ts +118 -74
- package/src/graph.ts +612 -0
- package/src/nodes/collection.ts +512 -0
- package/src/nodes/effect.ts +149 -0
- package/src/nodes/list.ts +589 -0
- package/src/nodes/memo.ts +148 -0
- package/src/nodes/sensor.ts +149 -0
- package/src/nodes/state.ts +135 -0
- package/src/nodes/store.ts +378 -0
- package/src/nodes/task.ts +174 -0
- package/src/signal.ts +112 -66
- package/src/util.ts +26 -57
- package/test/batch.test.ts +96 -62
- package/test/benchmark.test.ts +473 -487
- package/test/collection.test.ts +456 -707
- package/test/effect.test.ts +293 -696
- package/test/list.test.ts +335 -592
- package/test/memo.test.ts +574 -0
- package/test/regression.test.ts +156 -0
- package/test/scope.test.ts +191 -0
- package/test/sensor.test.ts +454 -0
- package/test/signal.test.ts +220 -213
- package/test/state.test.ts +217 -265
- package/test/store.test.ts +346 -446
- package/test/task.test.ts +529 -0
- package/test/untrack.test.ts +167 -0
- package/types/index.d.ts +13 -15
- package/types/src/errors.d.ts +73 -17
- package/types/src/graph.d.ts +218 -0
- package/types/src/nodes/collection.d.ts +69 -0
- package/types/src/nodes/effect.d.ts +48 -0
- package/types/src/nodes/list.d.ts +66 -0
- package/types/src/nodes/memo.d.ts +63 -0
- package/types/src/nodes/sensor.d.ts +81 -0
- package/types/src/nodes/state.d.ts +78 -0
- package/types/src/nodes/store.d.ts +51 -0
- package/types/src/nodes/task.d.ts +79 -0
- package/types/src/signal.d.ts +43 -29
- package/types/src/util.d.ts +9 -16
- package/archive/benchmark.ts +0 -683
- package/archive/collection.ts +0 -253
- package/archive/composite.ts +0 -85
- package/archive/computed.ts +0 -195
- package/archive/list.ts +0 -483
- package/archive/memo.ts +0 -139
- package/archive/state.ts +0 -90
- package/archive/store.ts +0 -298
- package/archive/task.ts +0 -189
- package/src/classes/collection.ts +0 -245
- package/src/classes/computed.ts +0 -349
- package/src/classes/list.ts +0 -343
- package/src/classes/ref.ts +0 -70
- package/src/classes/state.ts +0 -102
- package/src/classes/store.ts +0 -262
- package/src/diff.ts +0 -138
- package/src/effect.ts +0 -93
- package/src/match.ts +0 -45
- package/src/resolve.ts +0 -49
- package/src/system.ts +0 -257
- package/test/computed.test.ts +0 -1108
- package/test/diff.test.ts +0 -955
- package/test/match.test.ts +0 -388
- package/test/ref.test.ts +0 -353
- package/test/resolve.test.ts +0 -154
- package/types/src/classes/collection.d.ts +0 -45
- package/types/src/classes/computed.d.ts +0 -94
- package/types/src/classes/list.d.ts +0 -43
- package/types/src/classes/ref.d.ts +0 -35
- package/types/src/classes/state.d.ts +0 -49
- package/types/src/classes/store.d.ts +0 -52
- package/types/src/diff.d.ts +0 -28
- package/types/src/effect.d.ts +0 -15
- package/types/src/match.d.ts +0 -21
- package/types/src/resolve.d.ts +0 -29
- package/types/src/system.d.ts +0 -78
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { InvalidCollectionSourceError, validateCallback } from '../errors'
|
|
2
|
-
import type { Signal } from '../signal'
|
|
3
|
-
import {
|
|
4
|
-
createWatcher,
|
|
5
|
-
notifyOf,
|
|
6
|
-
registerWatchCallbacks,
|
|
7
|
-
type SignalOptions,
|
|
8
|
-
subscribeTo,
|
|
9
|
-
UNSET,
|
|
10
|
-
type Watcher,
|
|
11
|
-
} from '../system'
|
|
12
|
-
import { isAsyncFunction, isFunction, isObjectOfType } from '../util'
|
|
13
|
-
import { type Computed, createComputed } from './computed'
|
|
14
|
-
import { isList, type List } from './list'
|
|
15
|
-
|
|
16
|
-
/* === Types === */
|
|
17
|
-
|
|
18
|
-
type CollectionSource<T extends {}> = List<T> | Collection<T>
|
|
19
|
-
|
|
20
|
-
type CollectionCallback<T extends {}, U extends {}> =
|
|
21
|
-
| ((sourceValue: U) => T)
|
|
22
|
-
| ((sourceValue: U, abort: AbortSignal) => Promise<T>)
|
|
23
|
-
|
|
24
|
-
type Collection<T extends {}> = {
|
|
25
|
-
readonly [Symbol.toStringTag]: 'Collection'
|
|
26
|
-
readonly [Symbol.isConcatSpreadable]: true
|
|
27
|
-
[Symbol.iterator](): IterableIterator<Signal<T>>
|
|
28
|
-
keys(): IterableIterator<string>
|
|
29
|
-
get: () => T[]
|
|
30
|
-
at: (index: number) => Signal<T> | undefined
|
|
31
|
-
byKey: (key: string) => Signal<T> | undefined
|
|
32
|
-
keyAt: (index: number) => string | undefined
|
|
33
|
-
indexOfKey: (key: string) => number | undefined
|
|
34
|
-
deriveCollection: <R extends {}>(
|
|
35
|
-
callback: CollectionCallback<R, T>,
|
|
36
|
-
) => DerivedCollection<R, T>
|
|
37
|
-
readonly length: number
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* === Constants === */
|
|
41
|
-
|
|
42
|
-
const TYPE_COLLECTION = 'Collection' as const
|
|
43
|
-
|
|
44
|
-
/* === Class === */
|
|
45
|
-
|
|
46
|
-
class DerivedCollection<T extends {}, U extends {}> implements Collection<T> {
|
|
47
|
-
#source: CollectionSource<U>
|
|
48
|
-
#callback: CollectionCallback<T, U>
|
|
49
|
-
#signals = new Map<string, Computed<T>>()
|
|
50
|
-
#keys: string[] = []
|
|
51
|
-
#dirty = true
|
|
52
|
-
#watcher: Watcher | undefined
|
|
53
|
-
|
|
54
|
-
constructor(
|
|
55
|
-
source: CollectionSource<U> | (() => CollectionSource<U>),
|
|
56
|
-
callback: CollectionCallback<T, U>,
|
|
57
|
-
options?: SignalOptions<T[]>,
|
|
58
|
-
) {
|
|
59
|
-
validateCallback(TYPE_COLLECTION, callback)
|
|
60
|
-
|
|
61
|
-
if (isFunction(source)) source = source()
|
|
62
|
-
if (!isCollectionSource(source))
|
|
63
|
-
throw new InvalidCollectionSourceError(TYPE_COLLECTION, source)
|
|
64
|
-
this.#source = source
|
|
65
|
-
|
|
66
|
-
this.#callback = callback
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < this.#source.length; i++) {
|
|
69
|
-
const key = this.#source.keyAt(i)
|
|
70
|
-
if (!key) continue
|
|
71
|
-
|
|
72
|
-
this.#add(key)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (options?.watched)
|
|
76
|
-
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
#getWatcher(): Watcher {
|
|
80
|
-
this.#watcher ||= createWatcher(
|
|
81
|
-
() => {
|
|
82
|
-
this.#dirty = true
|
|
83
|
-
if (!notifyOf(this)) this.#watcher?.stop()
|
|
84
|
-
},
|
|
85
|
-
() => {
|
|
86
|
-
const newKeys = Array.from(this.#source.keys())
|
|
87
|
-
const allKeys = new Set([...this.#keys, ...newKeys])
|
|
88
|
-
const addedKeys: string[] = []
|
|
89
|
-
const removedKeys: string[] = []
|
|
90
|
-
|
|
91
|
-
for (const key of allKeys) {
|
|
92
|
-
const oldHas = this.#keys.includes(key)
|
|
93
|
-
const newHas = newKeys.includes(key)
|
|
94
|
-
|
|
95
|
-
if (!oldHas && newHas) addedKeys.push(key)
|
|
96
|
-
else if (oldHas && !newHas) removedKeys.push(key)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for (const key of removedKeys) this.#signals.delete(key)
|
|
100
|
-
for (const key of addedKeys) this.#add(key)
|
|
101
|
-
this.#keys = newKeys
|
|
102
|
-
this.#dirty = false
|
|
103
|
-
},
|
|
104
|
-
)
|
|
105
|
-
this.#watcher.onCleanup(() => {
|
|
106
|
-
this.#watcher = undefined
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return this.#watcher
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#add(key: string): boolean {
|
|
113
|
-
const computedCallback = isAsyncCollectionCallback<T>(this.#callback)
|
|
114
|
-
? async (_: T, abort: AbortSignal) => {
|
|
115
|
-
const sourceValue = this.#source.byKey(key)?.get() as U
|
|
116
|
-
if (sourceValue === UNSET) return UNSET
|
|
117
|
-
return this.#callback(sourceValue, abort)
|
|
118
|
-
}
|
|
119
|
-
: () => {
|
|
120
|
-
const sourceValue = this.#source.byKey(key)?.get() as U
|
|
121
|
-
if (sourceValue === UNSET) return UNSET
|
|
122
|
-
return (this.#callback as (sourceValue: U) => T)(
|
|
123
|
-
sourceValue,
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const signal = createComputed(computedCallback)
|
|
128
|
-
|
|
129
|
-
this.#signals.set(key, signal)
|
|
130
|
-
if (!this.#keys.includes(key)) this.#keys.push(key)
|
|
131
|
-
return true
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
get [Symbol.toStringTag](): 'Collection' {
|
|
135
|
-
return TYPE_COLLECTION
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
get [Symbol.isConcatSpreadable](): true {
|
|
139
|
-
return true
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
*[Symbol.iterator](): IterableIterator<Computed<T>> {
|
|
143
|
-
for (const key of this.#keys) {
|
|
144
|
-
const signal = this.#signals.get(key)
|
|
145
|
-
if (signal) yield signal as Computed<T>
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
keys(): IterableIterator<string> {
|
|
150
|
-
subscribeTo(this)
|
|
151
|
-
if (this.#dirty) this.#getWatcher().run()
|
|
152
|
-
return this.#keys.values()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
get(): T[] {
|
|
156
|
-
subscribeTo(this)
|
|
157
|
-
|
|
158
|
-
if (this.#dirty) this.#getWatcher().run()
|
|
159
|
-
return this.#keys
|
|
160
|
-
.map(key => this.#signals.get(key)?.get())
|
|
161
|
-
.filter(v => v != null && v !== UNSET) as T[]
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
at(index: number): Computed<T> | undefined {
|
|
165
|
-
return this.#signals.get(this.#keys[index])
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
byKey(key: string): Computed<T> | undefined {
|
|
169
|
-
return this.#signals.get(key)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
keyAt(index: number): string | undefined {
|
|
173
|
-
return this.#keys[index]
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
indexOfKey(key: string): number {
|
|
177
|
-
return this.#keys.indexOf(key)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
deriveCollection<R extends {}>(
|
|
181
|
-
callback: (sourceValue: T) => R,
|
|
182
|
-
options?: SignalOptions<R[]>,
|
|
183
|
-
): DerivedCollection<R, T>
|
|
184
|
-
deriveCollection<R extends {}>(
|
|
185
|
-
callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
|
|
186
|
-
options?: SignalOptions<R[]>,
|
|
187
|
-
): DerivedCollection<R, T>
|
|
188
|
-
deriveCollection<R extends {}>(
|
|
189
|
-
callback: CollectionCallback<R, T>,
|
|
190
|
-
options?: SignalOptions<R[]>,
|
|
191
|
-
): DerivedCollection<R, T> {
|
|
192
|
-
return new DerivedCollection(this, callback, options)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
get length(): number {
|
|
196
|
-
subscribeTo(this)
|
|
197
|
-
if (this.#dirty) this.#getWatcher().run()
|
|
198
|
-
return this.#keys.length
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/* === Functions === */
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Check if a value is a collection signal
|
|
206
|
-
*
|
|
207
|
-
* @since 0.17.2
|
|
208
|
-
* @param {unknown} value - Value to check
|
|
209
|
-
* @returns {boolean} - True if value is a collection signal, false otherwise
|
|
210
|
-
*/
|
|
211
|
-
const isCollection = /*#__PURE__*/ <T extends {}>(
|
|
212
|
-
value: unknown,
|
|
213
|
-
): value is Collection<T> => isObjectOfType(value, TYPE_COLLECTION)
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Check if a value is a collection source
|
|
217
|
-
*
|
|
218
|
-
* @since 0.17.0
|
|
219
|
-
* @param {unknown} value - Value to check
|
|
220
|
-
* @returns {boolean} - True if value is a collection source, false otherwise
|
|
221
|
-
*/
|
|
222
|
-
const isCollectionSource = /*#__PURE__*/ <T extends {}>(
|
|
223
|
-
value: unknown,
|
|
224
|
-
): value is CollectionSource<T> => isList(value) || isCollection(value)
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Check if the provided callback is an async function
|
|
228
|
-
*
|
|
229
|
-
* @since 0.17.0
|
|
230
|
-
* @param {unknown} callback - Value to check
|
|
231
|
-
* @returns {boolean} - True if value is an async collection callback, false otherwise
|
|
232
|
-
*/
|
|
233
|
-
const isAsyncCollectionCallback = <T extends {}>(
|
|
234
|
-
callback: unknown,
|
|
235
|
-
): callback is (sourceValue: unknown, abort: AbortSignal) => Promise<T> =>
|
|
236
|
-
isAsyncFunction(callback)
|
|
237
|
-
|
|
238
|
-
export {
|
|
239
|
-
type Collection,
|
|
240
|
-
type CollectionSource,
|
|
241
|
-
type CollectionCallback,
|
|
242
|
-
DerivedCollection,
|
|
243
|
-
isCollection,
|
|
244
|
-
TYPE_COLLECTION,
|
|
245
|
-
}
|
package/src/classes/computed.ts
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import { isEqual } from '../diff'
|
|
2
|
-
import {
|
|
3
|
-
CircularDependencyError,
|
|
4
|
-
createError,
|
|
5
|
-
validateCallback,
|
|
6
|
-
validateSignalValue,
|
|
7
|
-
} from '../errors'
|
|
8
|
-
import {
|
|
9
|
-
createWatcher,
|
|
10
|
-
flush,
|
|
11
|
-
notifyOf,
|
|
12
|
-
registerWatchCallbacks,
|
|
13
|
-
type SignalOptions,
|
|
14
|
-
subscribeTo,
|
|
15
|
-
UNSET,
|
|
16
|
-
type Watcher,
|
|
17
|
-
} from '../system'
|
|
18
|
-
import {
|
|
19
|
-
isAbortError,
|
|
20
|
-
isAsyncFunction,
|
|
21
|
-
isObjectOfType,
|
|
22
|
-
isSyncFunction,
|
|
23
|
-
} from '../util'
|
|
24
|
-
|
|
25
|
-
/* === Types === */
|
|
26
|
-
|
|
27
|
-
type Computed<T extends {}> = {
|
|
28
|
-
readonly [Symbol.toStringTag]: 'Computed'
|
|
29
|
-
get(): T
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type ComputedOptions<T extends {}> = SignalOptions<T> & {
|
|
33
|
-
initialValue?: T
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type MemoCallback<T extends {} & { then?: undefined }> = (oldValue: T) => T
|
|
37
|
-
|
|
38
|
-
type TaskCallback<T extends {} & { then?: undefined }> = (
|
|
39
|
-
oldValue: T,
|
|
40
|
-
abort: AbortSignal,
|
|
41
|
-
) => Promise<T>
|
|
42
|
-
|
|
43
|
-
/* === Constants === */
|
|
44
|
-
|
|
45
|
-
const TYPE_COMPUTED = 'Computed' as const
|
|
46
|
-
|
|
47
|
-
/* === Classes === */
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create a new memoized signal for a synchronous function.
|
|
51
|
-
*
|
|
52
|
-
* @since 0.17.0
|
|
53
|
-
* @param {MemoCallback<T>} callback - Callback function to compute the memoized value
|
|
54
|
-
* @param {T} [initialValue = UNSET] - Initial value of the signal
|
|
55
|
-
* @throws {InvalidCallbackError} If the callback is not an sync function
|
|
56
|
-
* @throws {InvalidSignalValueError} If the initial value is not valid
|
|
57
|
-
*/
|
|
58
|
-
class Memo<T extends {}> {
|
|
59
|
-
#callback: MemoCallback<T>
|
|
60
|
-
#value: T
|
|
61
|
-
#error: Error | undefined
|
|
62
|
-
#dirty = true
|
|
63
|
-
#computing = false
|
|
64
|
-
#watcher: Watcher | undefined
|
|
65
|
-
|
|
66
|
-
constructor(callback: MemoCallback<T>, options?: ComputedOptions<T>) {
|
|
67
|
-
validateCallback(this.constructor.name, callback, isMemoCallback)
|
|
68
|
-
const initialValue = options?.initialValue ?? UNSET
|
|
69
|
-
validateSignalValue(this.constructor.name, initialValue, options?.guard)
|
|
70
|
-
|
|
71
|
-
this.#callback = callback
|
|
72
|
-
this.#value = initialValue
|
|
73
|
-
if (options?.watched)
|
|
74
|
-
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
#getWatcher(): Watcher {
|
|
78
|
-
// Own watcher: called by notifyWatchers() in upstream signals (push)
|
|
79
|
-
this.#watcher ||= createWatcher(
|
|
80
|
-
() => {
|
|
81
|
-
this.#dirty = true
|
|
82
|
-
if (!notifyOf(this)) this.#watcher?.stop()
|
|
83
|
-
},
|
|
84
|
-
() => {
|
|
85
|
-
if (this.#computing) throw new CircularDependencyError('memo')
|
|
86
|
-
|
|
87
|
-
let result: T
|
|
88
|
-
this.#computing = true
|
|
89
|
-
try {
|
|
90
|
-
result = this.#callback(this.#value)
|
|
91
|
-
} catch (e) {
|
|
92
|
-
// Err track
|
|
93
|
-
this.#value = UNSET
|
|
94
|
-
this.#error = createError(e)
|
|
95
|
-
this.#computing = false
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (null == result || UNSET === result) {
|
|
100
|
-
// Nil track
|
|
101
|
-
this.#value = UNSET
|
|
102
|
-
this.#error = undefined
|
|
103
|
-
} else {
|
|
104
|
-
// Ok track
|
|
105
|
-
this.#value = result
|
|
106
|
-
this.#error = undefined
|
|
107
|
-
this.#dirty = false
|
|
108
|
-
}
|
|
109
|
-
this.#computing = false
|
|
110
|
-
},
|
|
111
|
-
)
|
|
112
|
-
this.#watcher.onCleanup(() => {
|
|
113
|
-
this.#watcher = undefined
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
return this.#watcher
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
get [Symbol.toStringTag](): 'Computed' {
|
|
120
|
-
return TYPE_COMPUTED
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Return the memoized value after computing it if necessary.
|
|
125
|
-
*
|
|
126
|
-
* @returns {T}
|
|
127
|
-
* @throws {CircularDependencyError} If a circular dependency is detected
|
|
128
|
-
* @throws {Error} If an error occurs during computation
|
|
129
|
-
*/
|
|
130
|
-
get(): T {
|
|
131
|
-
subscribeTo(this)
|
|
132
|
-
flush()
|
|
133
|
-
|
|
134
|
-
if (this.#dirty) this.#getWatcher().run()
|
|
135
|
-
if (this.#error) throw this.#error
|
|
136
|
-
return this.#value
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Create a new task signals that memoizes the result of an asynchronous function.
|
|
142
|
-
*
|
|
143
|
-
* @since 0.17.0
|
|
144
|
-
* @param {TaskCallback<T>} callback - The asynchronous function to compute the memoized value
|
|
145
|
-
* @param {T} [initialValue = UNSET] - Initial value of the signal
|
|
146
|
-
* @throws {InvalidCallbackError} If the callback is not an async function
|
|
147
|
-
* @throws {InvalidSignalValueError} If the initial value is not valid
|
|
148
|
-
*/
|
|
149
|
-
class Task<T extends {}> {
|
|
150
|
-
#callback: TaskCallback<T>
|
|
151
|
-
#value: T
|
|
152
|
-
#error: Error | undefined
|
|
153
|
-
#dirty = true
|
|
154
|
-
#computing = false
|
|
155
|
-
#changed = false
|
|
156
|
-
#watcher: Watcher | undefined
|
|
157
|
-
#controller: AbortController | undefined
|
|
158
|
-
|
|
159
|
-
constructor(callback: TaskCallback<T>, options?: ComputedOptions<T>) {
|
|
160
|
-
validateCallback(this.constructor.name, callback, isTaskCallback)
|
|
161
|
-
const initialValue = options?.initialValue ?? UNSET
|
|
162
|
-
validateSignalValue(this.constructor.name, initialValue, options?.guard)
|
|
163
|
-
|
|
164
|
-
this.#callback = callback
|
|
165
|
-
this.#value = initialValue
|
|
166
|
-
if (options?.watched)
|
|
167
|
-
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
#getWatcher(): Watcher {
|
|
171
|
-
if (!this.#watcher) {
|
|
172
|
-
// Functions to update internal state
|
|
173
|
-
const ok = (v: T): undefined => {
|
|
174
|
-
if (!isEqual(v, this.#value)) {
|
|
175
|
-
this.#value = v
|
|
176
|
-
this.#changed = true
|
|
177
|
-
}
|
|
178
|
-
this.#error = undefined
|
|
179
|
-
this.#dirty = false
|
|
180
|
-
}
|
|
181
|
-
const nil = (): undefined => {
|
|
182
|
-
this.#changed = UNSET !== this.#value
|
|
183
|
-
this.#value = UNSET
|
|
184
|
-
this.#error = undefined
|
|
185
|
-
}
|
|
186
|
-
const err = (e: unknown): undefined => {
|
|
187
|
-
const newError = createError(e)
|
|
188
|
-
this.#changed =
|
|
189
|
-
!this.#error ||
|
|
190
|
-
newError.name !== this.#error.name ||
|
|
191
|
-
newError.message !== this.#error.message
|
|
192
|
-
this.#value = UNSET
|
|
193
|
-
this.#error = newError
|
|
194
|
-
}
|
|
195
|
-
const settle =
|
|
196
|
-
<T>(fn: (arg: T) => void) =>
|
|
197
|
-
(arg: T) => {
|
|
198
|
-
this.#computing = false
|
|
199
|
-
this.#controller = undefined
|
|
200
|
-
fn(arg)
|
|
201
|
-
if (this.#changed && !notifyOf(this)) this.#watcher?.stop()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Own watcher: called by notifyOf() in upstream signals (push)
|
|
205
|
-
this.#watcher = createWatcher(
|
|
206
|
-
() => {
|
|
207
|
-
this.#dirty = true
|
|
208
|
-
this.#controller?.abort()
|
|
209
|
-
if (!notifyOf(this)) this.#watcher?.stop()
|
|
210
|
-
},
|
|
211
|
-
() => {
|
|
212
|
-
if (this.#computing)
|
|
213
|
-
throw new CircularDependencyError('task')
|
|
214
|
-
this.#changed = false
|
|
215
|
-
|
|
216
|
-
// Return current value until promise resolves
|
|
217
|
-
if (this.#controller) return this.#value
|
|
218
|
-
|
|
219
|
-
this.#controller = new AbortController()
|
|
220
|
-
this.#controller.signal.addEventListener(
|
|
221
|
-
'abort',
|
|
222
|
-
() => {
|
|
223
|
-
this.#computing = false
|
|
224
|
-
this.#controller = undefined
|
|
225
|
-
|
|
226
|
-
// Retry computation with updated state
|
|
227
|
-
this.#getWatcher().run()
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
once: true,
|
|
231
|
-
},
|
|
232
|
-
)
|
|
233
|
-
let result: Promise<T>
|
|
234
|
-
this.#computing = true
|
|
235
|
-
try {
|
|
236
|
-
result = this.#callback(
|
|
237
|
-
this.#value,
|
|
238
|
-
this.#controller.signal,
|
|
239
|
-
)
|
|
240
|
-
} catch (e) {
|
|
241
|
-
if (isAbortError(e)) nil()
|
|
242
|
-
else err(e)
|
|
243
|
-
this.#computing = false
|
|
244
|
-
return
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (result instanceof Promise)
|
|
248
|
-
result.then(settle(ok), settle(err))
|
|
249
|
-
else if (null == result || UNSET === result) nil()
|
|
250
|
-
else ok(result)
|
|
251
|
-
this.#computing = false
|
|
252
|
-
},
|
|
253
|
-
)
|
|
254
|
-
this.#watcher.onCleanup(() => {
|
|
255
|
-
this.#controller?.abort()
|
|
256
|
-
this.#controller = undefined
|
|
257
|
-
this.#watcher = undefined
|
|
258
|
-
})
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return this.#watcher
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
get [Symbol.toStringTag](): 'Computed' {
|
|
265
|
-
return TYPE_COMPUTED
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Return the memoized value after executing the async function if necessary.
|
|
270
|
-
*
|
|
271
|
-
* @returns {T}
|
|
272
|
-
* @throws {CircularDependencyError} If a circular dependency is detected
|
|
273
|
-
* @throws {Error} If an error occurs during computation
|
|
274
|
-
*/
|
|
275
|
-
get(): T {
|
|
276
|
-
subscribeTo(this)
|
|
277
|
-
flush()
|
|
278
|
-
|
|
279
|
-
if (this.#dirty) this.#getWatcher().run()
|
|
280
|
-
if (this.#error) throw this.#error
|
|
281
|
-
return this.#value
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/* === Functions === */
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Create a derived signal from existing signals
|
|
289
|
-
*
|
|
290
|
-
* @since 0.9.0
|
|
291
|
-
* @param {MemoCallback<T> | TaskCallback<T>} callback - Computation callback function
|
|
292
|
-
* @param {ComputedOptions<T>} options - Optional configuration
|
|
293
|
-
*/
|
|
294
|
-
const createComputed = <T extends {}>(
|
|
295
|
-
callback: TaskCallback<T> | MemoCallback<T>,
|
|
296
|
-
options?: ComputedOptions<T>,
|
|
297
|
-
) =>
|
|
298
|
-
isAsyncFunction(callback)
|
|
299
|
-
? new Task(callback as TaskCallback<T>, options)
|
|
300
|
-
: new Memo(callback as MemoCallback<T>, options)
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Check if a value is a computed signal
|
|
304
|
-
*
|
|
305
|
-
* @since 0.9.0
|
|
306
|
-
* @param {unknown} value - Value to check
|
|
307
|
-
* @returns {boolean} - True if value is a computed signal, false otherwise
|
|
308
|
-
*/
|
|
309
|
-
const isComputed = /*#__PURE__*/ <T extends {}>(
|
|
310
|
-
value: unknown,
|
|
311
|
-
): value is Memo<T> => isObjectOfType(value, TYPE_COMPUTED)
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Check if the provided value is a callback that may be used as input for createSignal() to derive a computed state
|
|
315
|
-
*
|
|
316
|
-
* @since 0.12.0
|
|
317
|
-
* @param {unknown} value - Value to check
|
|
318
|
-
* @returns {boolean} - True if value is a sync callback, false otherwise
|
|
319
|
-
*/
|
|
320
|
-
const isMemoCallback = /*#__PURE__*/ <T extends {} & { then?: undefined }>(
|
|
321
|
-
value: unknown,
|
|
322
|
-
): value is MemoCallback<T> => isSyncFunction(value) && value.length < 2
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Check if the provided value is a callback that may be used as input for createSignal() to derive a computed state
|
|
326
|
-
*
|
|
327
|
-
* @since 0.17.0
|
|
328
|
-
* @param {unknown} value - Value to check
|
|
329
|
-
* @returns {boolean} - True if value is an async callback, false otherwise
|
|
330
|
-
*/
|
|
331
|
-
const isTaskCallback = /*#__PURE__*/ <T extends {}>(
|
|
332
|
-
value: unknown,
|
|
333
|
-
): value is TaskCallback<T> => isAsyncFunction(value) && value.length < 3
|
|
334
|
-
|
|
335
|
-
/* === Exports === */
|
|
336
|
-
|
|
337
|
-
export {
|
|
338
|
-
TYPE_COMPUTED,
|
|
339
|
-
createComputed,
|
|
340
|
-
isComputed,
|
|
341
|
-
isMemoCallback,
|
|
342
|
-
isTaskCallback,
|
|
343
|
-
Memo,
|
|
344
|
-
Task,
|
|
345
|
-
type Computed,
|
|
346
|
-
type ComputedOptions,
|
|
347
|
-
type MemoCallback,
|
|
348
|
-
type TaskCallback,
|
|
349
|
-
}
|