@zeix/cause-effect 0.17.2 → 0.18.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/.ai-context.md +163 -226
- package/.cursorrules +41 -35
- package/.github/copilot-instructions.md +166 -116
- package/.zed/settings.json +3 -0
- package/ARCHITECTURE.md +274 -0
- package/CLAUDE.md +197 -202
- package/COLLECTION_REFACTORING.md +161 -0
- package/GUIDE.md +298 -0
- package/README.md +241 -220
- package/REQUIREMENTS.md +100 -0
- package/bench/reactivity.bench.ts +577 -0
- package/index.dev.js +1326 -1174
- package/index.js +1 -1
- package/index.ts +58 -85
- package/package.json +9 -6
- package/src/errors.ts +118 -70
- package/src/graph.ts +601 -0
- package/src/nodes/collection.ts +474 -0
- package/src/nodes/effect.ts +149 -0
- package/src/nodes/list.ts +588 -0
- package/src/nodes/memo.ts +120 -0
- package/src/nodes/sensor.ts +139 -0
- package/src/nodes/state.ts +135 -0
- package/src/nodes/store.ts +383 -0
- package/src/nodes/task.ts +146 -0
- package/src/signal.ts +112 -64
- package/src/util.ts +26 -57
- package/test/batch.test.ts +96 -69
- package/test/benchmark.test.ts +473 -485
- package/test/collection.test.ts +455 -955
- package/test/effect.test.ts +293 -696
- package/test/list.test.ts +332 -857
- package/test/memo.test.ts +380 -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 -271
- package/test/store.test.ts +346 -898
- package/test/task.test.ts +395 -0
- package/test/untrack.test.ts +167 -0
- package/test/util/dependency-graph.ts +2 -2
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +5 -7
- package/types/index.d.ts +13 -15
- package/types/src/errors.d.ts +73 -19
- package/types/src/graph.d.ts +208 -0
- package/types/src/nodes/collection.d.ts +64 -0
- package/types/src/nodes/effect.d.ts +48 -0
- package/types/src/nodes/list.d.ts +65 -0
- package/types/src/nodes/memo.d.ts +57 -0
- package/types/src/nodes/sensor.d.ts +75 -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 +73 -0
- package/types/src/signal.d.ts +43 -28
- package/types/src/util.d.ts +9 -16
- package/archive/benchmark.ts +0 -688
- package/archive/collection.ts +0 -310
- package/archive/computed.ts +0 -198
- package/archive/list.ts +0 -544
- package/archive/memo.ts +0 -140
- package/archive/state.ts +0 -90
- package/archive/store.ts +0 -357
- package/archive/task.ts +0 -191
- package/src/classes/collection.ts +0 -298
- package/src/classes/composite.ts +0 -171
- package/src/classes/computed.ts +0 -392
- package/src/classes/list.ts +0 -310
- package/src/classes/ref.ts +0 -96
- package/src/classes/state.ts +0 -131
- package/src/classes/store.ts +0 -227
- package/src/diff.ts +0 -138
- package/src/effect.ts +0 -96
- package/src/match.ts +0 -45
- package/src/resolve.ts +0 -49
- package/src/system.ts +0 -275
- package/test/computed.test.ts +0 -1126
- package/test/diff.test.ts +0 -955
- package/test/match.test.ts +0 -388
- package/test/ref.test.ts +0 -381
- package/test/resolve.test.ts +0 -154
- package/types/src/classes/collection.d.ts +0 -47
- package/types/src/classes/composite.d.ts +0 -15
- package/types/src/classes/computed.d.ts +0 -114
- package/types/src/classes/list.d.ts +0 -41
- package/types/src/classes/ref.d.ts +0 -48
- package/types/src/classes/state.d.ts +0 -61
- package/types/src/classes/store.d.ts +0 -51
- 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 -81
package/archive/collection.ts
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import type { UnknownArray } from '../src/diff'
|
|
2
|
-
import { match } from '../src/match'
|
|
3
|
-
import { resolve } from '../src/resolve'
|
|
4
|
-
import type { Signal } from '../src/signal'
|
|
5
|
-
import {
|
|
6
|
-
type Cleanup,
|
|
7
|
-
createWatcher,
|
|
8
|
-
triggerHook,
|
|
9
|
-
type HookCallback,
|
|
10
|
-
type HookCallbacks,
|
|
11
|
-
type Hook,
|
|
12
|
-
notifyWatchers,
|
|
13
|
-
subscribeActiveWatcher,
|
|
14
|
-
trackSignalReads,
|
|
15
|
-
type Watcher,
|
|
16
|
-
UNSET,
|
|
17
|
-
} from '../src/system'
|
|
18
|
-
import { isAsyncFunction, isObjectOfType, isSymbol } from '../src/util'
|
|
19
|
-
import { type Computed, createComputed } from './computed'
|
|
20
|
-
import type { List } from './list'
|
|
21
|
-
|
|
22
|
-
/* === Types === */
|
|
23
|
-
|
|
24
|
-
type CollectionKeySignal<T extends {}> = T extends UnknownArray
|
|
25
|
-
? Collection<T>
|
|
26
|
-
: Computed<T>
|
|
27
|
-
|
|
28
|
-
type CollectionCallback<T extends {} & { then?: undefined }, O extends {}> =
|
|
29
|
-
| ((originValue: O, abort: AbortSignal) => Promise<T>)
|
|
30
|
-
| ((originValue: O) => T)
|
|
31
|
-
|
|
32
|
-
type Collection<T extends {}> = {
|
|
33
|
-
readonly [Symbol.toStringTag]: typeof TYPE_COLLECTION
|
|
34
|
-
readonly [Symbol.isConcatSpreadable]: boolean
|
|
35
|
-
[Symbol.iterator](): IterableIterator<CollectionKeySignal<T>>
|
|
36
|
-
readonly [n: number]: CollectionKeySignal<T>
|
|
37
|
-
readonly length: number
|
|
38
|
-
|
|
39
|
-
byKey(key: string): CollectionKeySignal<T> | undefined
|
|
40
|
-
get(): T[]
|
|
41
|
-
keyAt(index: number): string | undefined
|
|
42
|
-
indexOfKey(key: string): number
|
|
43
|
-
on(type: Hook, callback: HookCallback): Cleanup
|
|
44
|
-
sort(compareFn?: (a: T, b: T) => number): void
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* === Constants === */
|
|
48
|
-
|
|
49
|
-
const TYPE_COLLECTION = 'Collection' as const
|
|
50
|
-
|
|
51
|
-
/* === Exported Functions === */
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Collections - Read-Only Derived Array-Like Stores
|
|
55
|
-
*
|
|
56
|
-
* Collections are the read-only, derived counterpart to array-like Stores.
|
|
57
|
-
* They provide reactive, memoized, and lazily-evaluated array transformations
|
|
58
|
-
* while maintaining the familiar array-like store interface.
|
|
59
|
-
*
|
|
60
|
-
* @since 0.16.2
|
|
61
|
-
* @param {List<O> | Collection<O>} origin - Origin of collection to derive values from
|
|
62
|
-
* @param {ComputedCallback<ArrayItem<T>>} callback - Callback function to transform array items
|
|
63
|
-
* @returns {Collection<T>} - New collection with reactive properties that preserves the original type T
|
|
64
|
-
*/
|
|
65
|
-
const createCollection = <T extends {}, O extends {}>(
|
|
66
|
-
origin: List<O> | Collection<O>,
|
|
67
|
-
callback: CollectionCallback<T, O>,
|
|
68
|
-
): Collection<T> => {
|
|
69
|
-
const watchers = new Set<Watcher>()
|
|
70
|
-
const hookCallbacks: HookCallbacks = {}
|
|
71
|
-
const signals = new Map<string, Signal<T>>()
|
|
72
|
-
const signalWatchers = new Map<string, Watcher>()
|
|
73
|
-
|
|
74
|
-
let order: string[] = []
|
|
75
|
-
|
|
76
|
-
// Add nested signal and effect
|
|
77
|
-
const addProperty = (key: string): boolean => {
|
|
78
|
-
const computedCallback = isAsyncFunction(callback)
|
|
79
|
-
? async (_: T, abort: AbortSignal) => {
|
|
80
|
-
const originSignal = origin.byKey(key)
|
|
81
|
-
if (!originSignal) return UNSET
|
|
82
|
-
|
|
83
|
-
let result = UNSET
|
|
84
|
-
match(resolve({ originSignal }), {
|
|
85
|
-
ok: async ({ originSignal: originValue }) => {
|
|
86
|
-
result = await callback(originValue, abort)
|
|
87
|
-
},
|
|
88
|
-
err: (errors: readonly Error[]) => {
|
|
89
|
-
console.log(errors)
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
return result
|
|
93
|
-
}
|
|
94
|
-
: () => {
|
|
95
|
-
const originSignal = origin.byKey(key)
|
|
96
|
-
if (!originSignal) return UNSET
|
|
97
|
-
|
|
98
|
-
let result = UNSET
|
|
99
|
-
match(resolve({ originSignal }), {
|
|
100
|
-
ok: ({ originSignal: originValue }) => {
|
|
101
|
-
result = (callback as (originValue: O) => T)(
|
|
102
|
-
originValue as unknown as O,
|
|
103
|
-
)
|
|
104
|
-
},
|
|
105
|
-
err: (errors: readonly Error[]) => {
|
|
106
|
-
console.log(errors)
|
|
107
|
-
},
|
|
108
|
-
})
|
|
109
|
-
return result
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const signal = createComputed(computedCallback)
|
|
113
|
-
|
|
114
|
-
// Set internal states
|
|
115
|
-
signals.set(key, signal)
|
|
116
|
-
if (!order.includes(key)) order.push(key)
|
|
117
|
-
const watcher = createWatcher(() =>
|
|
118
|
-
trackSignalReads(watcher, () => {
|
|
119
|
-
signal.get() // Subscribe to the signal
|
|
120
|
-
triggerHook(hookCallbacks.change, [key])
|
|
121
|
-
}),
|
|
122
|
-
)
|
|
123
|
-
watcher()
|
|
124
|
-
signalWatchers.set(key, watcher)
|
|
125
|
-
return true
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Remove nested signal and effect
|
|
129
|
-
const removeProperty = (key: string) => {
|
|
130
|
-
// Remove signal for key
|
|
131
|
-
const ok = signals.delete(key)
|
|
132
|
-
if (!ok) return
|
|
133
|
-
|
|
134
|
-
// Clean up internal states
|
|
135
|
-
const index = order.indexOf(key)
|
|
136
|
-
if (index >= 0) order.splice(index, 1)
|
|
137
|
-
const watcher = signalWatchers.get(key)
|
|
138
|
-
if (watcher) {
|
|
139
|
-
watcher.stop()
|
|
140
|
-
signalWatchers.delete(key)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Initialize properties
|
|
145
|
-
for (let i = 0; i < origin.length; i++) {
|
|
146
|
-
const key = origin.keyAt(i)
|
|
147
|
-
if (!key) continue
|
|
148
|
-
addProperty(key)
|
|
149
|
-
}
|
|
150
|
-
origin.on('add', additions => {
|
|
151
|
-
if (!additions?.length) return
|
|
152
|
-
for (const key of additions) {
|
|
153
|
-
if (!signals.has(key)) addProperty(key)
|
|
154
|
-
}
|
|
155
|
-
notifyWatchers(watchers)
|
|
156
|
-
triggerHook(hookCallbacks.add, additions)
|
|
157
|
-
})
|
|
158
|
-
origin.on('remove', removals => {
|
|
159
|
-
if (!removals?.length) return
|
|
160
|
-
for (const key of Object.keys(removals)) {
|
|
161
|
-
if (!signals.has(key)) continue
|
|
162
|
-
removeProperty(key)
|
|
163
|
-
}
|
|
164
|
-
order = order.filter(() => true) // Compact array
|
|
165
|
-
notifyWatchers(watchers)
|
|
166
|
-
triggerHook(hookCallbacks.remove, removals)
|
|
167
|
-
})
|
|
168
|
-
origin.on('sort', newOrder => {
|
|
169
|
-
if (newOrder) {
|
|
170
|
-
order = [...newOrder]
|
|
171
|
-
notifyWatchers(watchers)
|
|
172
|
-
triggerHook(hookCallbacks.sort, newOrder)
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
// Get signal by key or index
|
|
177
|
-
const getSignal = (prop: string): Signal<T> | undefined => {
|
|
178
|
-
let key = prop
|
|
179
|
-
const index = Number(prop)
|
|
180
|
-
if (Number.isInteger(index) && index >= 0) key = order[index] ?? prop
|
|
181
|
-
return signals.get(key)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Get current array
|
|
185
|
-
const current = (): T =>
|
|
186
|
-
order
|
|
187
|
-
.map(key => signals.get(key)?.get())
|
|
188
|
-
.filter(v => v !== UNSET) as unknown as T
|
|
189
|
-
|
|
190
|
-
// Methods and Properties
|
|
191
|
-
const collection: Record<PropertyKey, unknown> = {}
|
|
192
|
-
Object.defineProperties(collection, {
|
|
193
|
-
[Symbol.toStringTag]: {
|
|
194
|
-
value: TYPE_COLLECTION,
|
|
195
|
-
},
|
|
196
|
-
[Symbol.isConcatSpreadable]: {
|
|
197
|
-
value: true,
|
|
198
|
-
},
|
|
199
|
-
[Symbol.iterator]: {
|
|
200
|
-
value: function* () {
|
|
201
|
-
for (const key of order) {
|
|
202
|
-
const signal = signals.get(key)
|
|
203
|
-
if (signal) yield signal
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
byKey: {
|
|
208
|
-
value(key: string) {
|
|
209
|
-
return getSignal(key)
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
keyAt: {
|
|
213
|
-
value(index: number): string | undefined {
|
|
214
|
-
return order[index]
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
indexOfKey: {
|
|
218
|
-
value(key: string): number {
|
|
219
|
-
return order.indexOf(key)
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
get: {
|
|
223
|
-
value: (): T => {
|
|
224
|
-
subscribeActiveWatcher(watchers)
|
|
225
|
-
return current()
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
sort: {
|
|
229
|
-
value: (compareFn?: (a: T, b: T) => number): void => {
|
|
230
|
-
const entries = order
|
|
231
|
-
.map((key, index) => {
|
|
232
|
-
const signal = signals.get(key)
|
|
233
|
-
return [
|
|
234
|
-
index,
|
|
235
|
-
key,
|
|
236
|
-
signal ? signal.get() : undefined,
|
|
237
|
-
] as [number, string, T]
|
|
238
|
-
})
|
|
239
|
-
.sort(
|
|
240
|
-
compareFn
|
|
241
|
-
? (a, b) => compareFn(a[2], b[2])
|
|
242
|
-
: (a, b) =>
|
|
243
|
-
String(a[2]).localeCompare(String(b[2])),
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
// Set new order
|
|
247
|
-
order = entries.map(([_, key]) => key)
|
|
248
|
-
|
|
249
|
-
notifyWatchers(watchers)
|
|
250
|
-
triggerHook(hookCallbacks.sort, order)
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
on: {
|
|
254
|
-
value: (type: Hook, callback: HookCallback): Cleanup => {
|
|
255
|
-
hookCallbacks[type] ||= new Set()
|
|
256
|
-
hookCallbacks[type].add(callback)
|
|
257
|
-
return () => hookCallbacks[type]?.delete(callback)
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
length: {
|
|
261
|
-
get(): number {
|
|
262
|
-
subscribeActiveWatcher(watchers)
|
|
263
|
-
return signals.size
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
// Return proxy directly with integrated signal methods
|
|
269
|
-
return new Proxy(collection as Collection<T>, {
|
|
270
|
-
get(target, prop) {
|
|
271
|
-
if (prop in target) return Reflect.get(target, prop)
|
|
272
|
-
if (!isSymbol(prop)) return getSignal(prop)
|
|
273
|
-
},
|
|
274
|
-
has(target, prop) {
|
|
275
|
-
if (prop in target) return true
|
|
276
|
-
return signals.has(String(prop))
|
|
277
|
-
},
|
|
278
|
-
ownKeys(target) {
|
|
279
|
-
const staticKeys = Reflect.ownKeys(target)
|
|
280
|
-
return [...new Set([...order, ...staticKeys])]
|
|
281
|
-
},
|
|
282
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
283
|
-
if (prop in target)
|
|
284
|
-
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
285
|
-
if (isSymbol(prop)) return undefined
|
|
286
|
-
|
|
287
|
-
const signal = getSignal(prop)
|
|
288
|
-
return signal
|
|
289
|
-
? {
|
|
290
|
-
enumerable: true,
|
|
291
|
-
configurable: true,
|
|
292
|
-
writable: true,
|
|
293
|
-
value: signal,
|
|
294
|
-
}
|
|
295
|
-
: undefined
|
|
296
|
-
},
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const isCollection = /*#__PURE__*/ <T extends UnknownArray>(
|
|
301
|
-
value: unknown,
|
|
302
|
-
): value is Collection<T> => isObjectOfType(value, TYPE_COLLECTION)
|
|
303
|
-
|
|
304
|
-
export {
|
|
305
|
-
type Collection,
|
|
306
|
-
type CollectionCallback,
|
|
307
|
-
createCollection,
|
|
308
|
-
isCollection,
|
|
309
|
-
TYPE_COLLECTION,
|
|
310
|
-
}
|
package/archive/computed.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { isEqual } from '../src/diff'
|
|
2
|
-
import {
|
|
3
|
-
CircularDependencyError,
|
|
4
|
-
createError,
|
|
5
|
-
InvalidCallbackError,
|
|
6
|
-
NullishSignalValueError,
|
|
7
|
-
} from '../src/errors'
|
|
8
|
-
import {
|
|
9
|
-
createWatcher,
|
|
10
|
-
flushPendingReactions,
|
|
11
|
-
HOOK_CLEANUP,
|
|
12
|
-
notifyWatchers,
|
|
13
|
-
subscribeActiveWatcher,
|
|
14
|
-
trackSignalReads,
|
|
15
|
-
UNSET,
|
|
16
|
-
type Watcher,
|
|
17
|
-
} from '../src/system'
|
|
18
|
-
import {
|
|
19
|
-
isAbortError,
|
|
20
|
-
isAsyncFunction,
|
|
21
|
-
isFunction,
|
|
22
|
-
isObjectOfType,
|
|
23
|
-
} from '../src/util'
|
|
24
|
-
|
|
25
|
-
/* === Types === */
|
|
26
|
-
|
|
27
|
-
type Computed<T extends {}> = {
|
|
28
|
-
readonly [Symbol.toStringTag]: 'Computed'
|
|
29
|
-
get(): T
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type ComputedCallback<T extends {} & { then?: undefined }> =
|
|
33
|
-
| ((oldValue: T, abort: AbortSignal) => Promise<T>)
|
|
34
|
-
| ((oldValue: T) => T)
|
|
35
|
-
|
|
36
|
-
/* === Constants === */
|
|
37
|
-
|
|
38
|
-
const TYPE_COMPUTED = 'Computed' as const
|
|
39
|
-
|
|
40
|
-
/* === Functions === */
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create a derived signal from existing signals
|
|
44
|
-
*
|
|
45
|
-
* @since 0.9.0
|
|
46
|
-
* @param {ComputedCallback<T>} callback - Computation callback function
|
|
47
|
-
* @returns {Computed<T>} - Computed signal
|
|
48
|
-
*/
|
|
49
|
-
const createComputed = <T extends {}>(
|
|
50
|
-
callback: ComputedCallback<T>,
|
|
51
|
-
initialValue: T = UNSET,
|
|
52
|
-
): Computed<T> => {
|
|
53
|
-
if (!isComputedCallback(callback))
|
|
54
|
-
throw new InvalidCallbackError('computed', callback)
|
|
55
|
-
if (initialValue == null) throw new NullishSignalValueError('computed')
|
|
56
|
-
|
|
57
|
-
const watchers: Set<Watcher> = new Set()
|
|
58
|
-
|
|
59
|
-
// Internal state
|
|
60
|
-
let value: T = initialValue
|
|
61
|
-
let error: Error | undefined
|
|
62
|
-
let controller: AbortController | undefined
|
|
63
|
-
let dirty = true
|
|
64
|
-
let changed = false
|
|
65
|
-
let computing = false
|
|
66
|
-
|
|
67
|
-
// Functions to update internal state
|
|
68
|
-
const ok = (v: T): undefined => {
|
|
69
|
-
if (!isEqual(v, value)) {
|
|
70
|
-
value = v
|
|
71
|
-
changed = true
|
|
72
|
-
}
|
|
73
|
-
error = undefined
|
|
74
|
-
dirty = false
|
|
75
|
-
}
|
|
76
|
-
const nil = (): undefined => {
|
|
77
|
-
changed = UNSET !== value
|
|
78
|
-
value = UNSET
|
|
79
|
-
error = undefined
|
|
80
|
-
}
|
|
81
|
-
const err = (e: unknown): undefined => {
|
|
82
|
-
const newError = createError(e)
|
|
83
|
-
changed =
|
|
84
|
-
!error ||
|
|
85
|
-
newError.name !== error.name ||
|
|
86
|
-
newError.message !== error.message
|
|
87
|
-
value = UNSET
|
|
88
|
-
error = newError
|
|
89
|
-
}
|
|
90
|
-
const settle =
|
|
91
|
-
<T>(fn: (arg: T) => void) =>
|
|
92
|
-
(arg: T) => {
|
|
93
|
-
computing = false
|
|
94
|
-
controller = undefined
|
|
95
|
-
fn(arg)
|
|
96
|
-
if (changed) notifyWatchers(watchers)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Own watcher: called when notified from sources (push)
|
|
100
|
-
const watcher = createWatcher(() => {
|
|
101
|
-
dirty = true
|
|
102
|
-
controller?.abort()
|
|
103
|
-
if (watchers.size) notifyWatchers(watchers)
|
|
104
|
-
else watcher.stop()
|
|
105
|
-
})
|
|
106
|
-
watcher.on(HOOK_CLEANUP, () => {
|
|
107
|
-
controller?.abort()
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// Called when requested by dependencies (pull)
|
|
111
|
-
const compute = () =>
|
|
112
|
-
trackSignalReads(watcher, () => {
|
|
113
|
-
if (computing) throw new CircularDependencyError('computed')
|
|
114
|
-
changed = false
|
|
115
|
-
if (isAsyncFunction(callback)) {
|
|
116
|
-
// Return current value until promise resolves
|
|
117
|
-
if (controller) return value
|
|
118
|
-
controller = new AbortController()
|
|
119
|
-
controller.signal.addEventListener(
|
|
120
|
-
'abort',
|
|
121
|
-
() => {
|
|
122
|
-
computing = false
|
|
123
|
-
controller = undefined
|
|
124
|
-
compute() // Retry computation with updated state
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
once: true,
|
|
128
|
-
},
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
let result: T | Promise<T>
|
|
132
|
-
computing = true
|
|
133
|
-
try {
|
|
134
|
-
result = controller
|
|
135
|
-
? callback(value, controller.signal)
|
|
136
|
-
: (callback as (oldValue: T) => T)(value)
|
|
137
|
-
} catch (e) {
|
|
138
|
-
if (isAbortError(e)) nil()
|
|
139
|
-
else err(e)
|
|
140
|
-
computing = false
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
if (result instanceof Promise) result.then(settle(ok), settle(err))
|
|
144
|
-
else if (null == result || UNSET === result) nil()
|
|
145
|
-
else ok(result)
|
|
146
|
-
computing = false
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
const computed: Record<PropertyKey, unknown> = {}
|
|
150
|
-
Object.defineProperties(computed, {
|
|
151
|
-
[Symbol.toStringTag]: {
|
|
152
|
-
value: TYPE_COMPUTED,
|
|
153
|
-
},
|
|
154
|
-
get: {
|
|
155
|
-
value: (): T => {
|
|
156
|
-
subscribeActiveWatcher(watchers)
|
|
157
|
-
flushPendingReactions()
|
|
158
|
-
if (dirty) compute()
|
|
159
|
-
if (error) throw error
|
|
160
|
-
return value
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
})
|
|
164
|
-
return computed as Computed<T>
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check if a value is a computed signal
|
|
169
|
-
*
|
|
170
|
-
* @since 0.9.0
|
|
171
|
-
* @param {unknown} value - Value to check
|
|
172
|
-
* @returns {boolean} - true if value is a computed signal, false otherwise
|
|
173
|
-
*/
|
|
174
|
-
const isComputed = /*#__PURE__*/ <T extends {}>(
|
|
175
|
-
value: unknown,
|
|
176
|
-
): value is Computed<T> => isObjectOfType(value, TYPE_COMPUTED)
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
180
|
-
*
|
|
181
|
-
* @since 0.12.0
|
|
182
|
-
* @param {unknown} value - Value to check
|
|
183
|
-
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
184
|
-
*/
|
|
185
|
-
const isComputedCallback = /*#__PURE__*/ <T extends {}>(
|
|
186
|
-
value: unknown,
|
|
187
|
-
): value is ComputedCallback<T> => isFunction(value) && value.length < 3
|
|
188
|
-
|
|
189
|
-
/* === Exports === */
|
|
190
|
-
|
|
191
|
-
export {
|
|
192
|
-
TYPE_COMPUTED,
|
|
193
|
-
createComputed,
|
|
194
|
-
isComputed,
|
|
195
|
-
isComputedCallback,
|
|
196
|
-
type Computed,
|
|
197
|
-
type ComputedCallback,
|
|
198
|
-
}
|