@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
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InvalidCollectionSourceError,
|
|
3
|
-
InvalidHookError,
|
|
4
|
-
validateCallback,
|
|
5
|
-
} from '../errors'
|
|
6
|
-
import type { Signal } from '../signal'
|
|
7
|
-
import {
|
|
8
|
-
type Cleanup,
|
|
9
|
-
createWatcher,
|
|
10
|
-
HOOK_ADD,
|
|
11
|
-
HOOK_CHANGE,
|
|
12
|
-
HOOK_REMOVE,
|
|
13
|
-
HOOK_SORT,
|
|
14
|
-
HOOK_WATCH,
|
|
15
|
-
type Hook,
|
|
16
|
-
type HookCallback,
|
|
17
|
-
type HookCallbacks,
|
|
18
|
-
isHandledHook,
|
|
19
|
-
notifyWatchers,
|
|
20
|
-
subscribeActiveWatcher,
|
|
21
|
-
trackSignalReads,
|
|
22
|
-
triggerHook,
|
|
23
|
-
UNSET,
|
|
24
|
-
type Watcher,
|
|
25
|
-
} from '../system'
|
|
26
|
-
import { isAsyncFunction, isFunction, isObjectOfType } from '../util'
|
|
27
|
-
import { type Computed, createComputed } from './computed'
|
|
28
|
-
import { isList, type List } from './list'
|
|
29
|
-
|
|
30
|
-
/* === Types === */
|
|
31
|
-
|
|
32
|
-
type CollectionSource<T extends {}> = List<T> | Collection<T>
|
|
33
|
-
|
|
34
|
-
type CollectionCallback<T extends {}, U extends {}> =
|
|
35
|
-
| ((sourceValue: U) => T)
|
|
36
|
-
| ((sourceValue: U, abort: AbortSignal) => Promise<T>)
|
|
37
|
-
|
|
38
|
-
type Collection<T extends {}> = {
|
|
39
|
-
readonly [Symbol.toStringTag]: 'Collection'
|
|
40
|
-
readonly [Symbol.isConcatSpreadable]: true
|
|
41
|
-
[Symbol.iterator](): IterableIterator<Signal<T>>
|
|
42
|
-
keys(): IterableIterator<string>
|
|
43
|
-
get: () => T[]
|
|
44
|
-
at: (index: number) => Signal<T> | undefined
|
|
45
|
-
byKey: (key: string) => Signal<T> | undefined
|
|
46
|
-
keyAt: (index: number) => string | undefined
|
|
47
|
-
indexOfKey: (key: string) => number | undefined
|
|
48
|
-
on: <K extends Hook>(type: K, callback: HookCallback) => Cleanup
|
|
49
|
-
deriveCollection: <R extends {}>(
|
|
50
|
-
callback: CollectionCallback<R, T>,
|
|
51
|
-
) => DerivedCollection<R, T>
|
|
52
|
-
readonly length: number
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* === Constants === */
|
|
56
|
-
|
|
57
|
-
const TYPE_COLLECTION = 'Collection' as const
|
|
58
|
-
|
|
59
|
-
/* === Class === */
|
|
60
|
-
|
|
61
|
-
class DerivedCollection<T extends {}, U extends {}> implements Collection<T> {
|
|
62
|
-
#watchers = new Set<Watcher>()
|
|
63
|
-
#source: CollectionSource<U>
|
|
64
|
-
#callback: CollectionCallback<T, U>
|
|
65
|
-
#signals = new Map<string, Computed<T>>()
|
|
66
|
-
#ownWatchers = new Map<string, Watcher>()
|
|
67
|
-
#hookCallbacks: HookCallbacks = {}
|
|
68
|
-
#order: string[] = []
|
|
69
|
-
|
|
70
|
-
constructor(
|
|
71
|
-
source: CollectionSource<U> | (() => CollectionSource<U>),
|
|
72
|
-
callback: CollectionCallback<T, U>,
|
|
73
|
-
) {
|
|
74
|
-
validateCallback(TYPE_COLLECTION, callback)
|
|
75
|
-
|
|
76
|
-
if (isFunction(source)) source = source()
|
|
77
|
-
if (!isCollectionSource(source))
|
|
78
|
-
throw new InvalidCollectionSourceError(TYPE_COLLECTION, source)
|
|
79
|
-
this.#source = source
|
|
80
|
-
|
|
81
|
-
this.#callback = callback
|
|
82
|
-
|
|
83
|
-
for (let i = 0; i < this.#source.length; i++) {
|
|
84
|
-
const key = this.#source.keyAt(i)
|
|
85
|
-
if (!key) continue
|
|
86
|
-
|
|
87
|
-
this.#add(key)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
this.#source.on(HOOK_ADD, additions => {
|
|
91
|
-
if (!additions) return
|
|
92
|
-
for (const key of additions) {
|
|
93
|
-
if (!this.#signals.has(key)) {
|
|
94
|
-
this.#add(key)
|
|
95
|
-
// For async computations, trigger initial computation
|
|
96
|
-
const signal = this.#signals.get(key)
|
|
97
|
-
if (signal && isAsyncCollectionCallback(this.#callback))
|
|
98
|
-
signal.get()
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
notifyWatchers(this.#watchers)
|
|
102
|
-
triggerHook(this.#hookCallbacks.add, additions)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
this.#source.on(HOOK_REMOVE, removals => {
|
|
106
|
-
if (!removals) return
|
|
107
|
-
for (const key of removals) {
|
|
108
|
-
if (!this.#signals.has(key)) continue
|
|
109
|
-
|
|
110
|
-
this.#signals.delete(key)
|
|
111
|
-
const index = this.#order.indexOf(key)
|
|
112
|
-
if (index >= 0) this.#order.splice(index, 1)
|
|
113
|
-
|
|
114
|
-
const watcher = this.#ownWatchers.get(key)
|
|
115
|
-
if (watcher) {
|
|
116
|
-
watcher.stop()
|
|
117
|
-
this.#ownWatchers.delete(key)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
this.#order = this.#order.filter(() => true) // Compact array
|
|
121
|
-
notifyWatchers(this.#watchers)
|
|
122
|
-
triggerHook(this.#hookCallbacks.remove, removals)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
this.#source.on(HOOK_SORT, newOrder => {
|
|
126
|
-
if (newOrder) this.#order = [...newOrder]
|
|
127
|
-
notifyWatchers(this.#watchers)
|
|
128
|
-
triggerHook(this.#hookCallbacks.sort, newOrder)
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
#add(key: string): boolean {
|
|
133
|
-
const computedCallback = isAsyncCollectionCallback<T>(this.#callback)
|
|
134
|
-
? async (_: T, abort: AbortSignal) => {
|
|
135
|
-
const sourceValue = this.#source.byKey(key)?.get() as U
|
|
136
|
-
if (sourceValue === UNSET) return UNSET
|
|
137
|
-
return this.#callback(sourceValue, abort)
|
|
138
|
-
}
|
|
139
|
-
: () => {
|
|
140
|
-
const sourceValue = this.#source.byKey(key)?.get() as U
|
|
141
|
-
if (sourceValue === UNSET) return UNSET
|
|
142
|
-
return (this.#callback as (sourceValue: U) => T)(
|
|
143
|
-
sourceValue,
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const signal = createComputed(computedCallback)
|
|
148
|
-
|
|
149
|
-
this.#signals.set(key, signal)
|
|
150
|
-
if (!this.#order.includes(key)) this.#order.push(key)
|
|
151
|
-
if (this.#hookCallbacks.change?.size) this.#addWatcher(key)
|
|
152
|
-
return true
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
#addWatcher(key: string): void {
|
|
156
|
-
const watcher = createWatcher(() => {
|
|
157
|
-
trackSignalReads(watcher, () => {
|
|
158
|
-
this.#signals.get(key)?.get() // Subscribe to the signal
|
|
159
|
-
})
|
|
160
|
-
})
|
|
161
|
-
this.#ownWatchers.set(key, watcher)
|
|
162
|
-
watcher()
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
get [Symbol.toStringTag](): 'Collection' {
|
|
166
|
-
return TYPE_COLLECTION
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
get [Symbol.isConcatSpreadable](): true {
|
|
170
|
-
return true
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
*[Symbol.iterator](): IterableIterator<Computed<T>> {
|
|
174
|
-
for (const key of this.#order) {
|
|
175
|
-
const signal = this.#signals.get(key)
|
|
176
|
-
if (signal) yield signal as Computed<T>
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
keys(): IterableIterator<string> {
|
|
181
|
-
return this.#order.values()
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
get(): T[] {
|
|
185
|
-
subscribeActiveWatcher(this.#watchers, this.#hookCallbacks[HOOK_WATCH])
|
|
186
|
-
return this.#order
|
|
187
|
-
.map(key => this.#signals.get(key)?.get())
|
|
188
|
-
.filter(v => v != null && v !== UNSET) as T[]
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
at(index: number): Computed<T> | undefined {
|
|
192
|
-
return this.#signals.get(this.#order[index])
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
byKey(key: string): Computed<T> | undefined {
|
|
196
|
-
return this.#signals.get(key)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
keyAt(index: number): string | undefined {
|
|
200
|
-
return this.#order[index]
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
indexOfKey(key: string): number {
|
|
204
|
-
return this.#order.indexOf(key)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
on(type: Hook, callback: HookCallback): Cleanup {
|
|
208
|
-
if (
|
|
209
|
-
isHandledHook(type, [
|
|
210
|
-
HOOK_ADD,
|
|
211
|
-
HOOK_CHANGE,
|
|
212
|
-
HOOK_REMOVE,
|
|
213
|
-
HOOK_SORT,
|
|
214
|
-
HOOK_WATCH,
|
|
215
|
-
])
|
|
216
|
-
) {
|
|
217
|
-
this.#hookCallbacks[type] ||= new Set()
|
|
218
|
-
this.#hookCallbacks[type].add(callback)
|
|
219
|
-
if (type === HOOK_CHANGE && !this.#ownWatchers.size) {
|
|
220
|
-
for (const key of this.#signals.keys()) this.#addWatcher(key)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return () => {
|
|
224
|
-
this.#hookCallbacks[type]?.delete(callback)
|
|
225
|
-
if (type === HOOK_CHANGE && !this.#hookCallbacks.change?.size) {
|
|
226
|
-
if (this.#ownWatchers.size) {
|
|
227
|
-
for (const watcher of this.#ownWatchers.values())
|
|
228
|
-
watcher.stop()
|
|
229
|
-
this.#ownWatchers.clear()
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
throw new InvalidHookError(TYPE_COLLECTION, type)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
deriveCollection<R extends {}>(
|
|
238
|
-
callback: (sourceValue: T) => R,
|
|
239
|
-
): DerivedCollection<R, T>
|
|
240
|
-
deriveCollection<R extends {}>(
|
|
241
|
-
callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
|
|
242
|
-
): DerivedCollection<R, T>
|
|
243
|
-
deriveCollection<R extends {}>(
|
|
244
|
-
callback: CollectionCallback<R, T>,
|
|
245
|
-
): DerivedCollection<R, T> {
|
|
246
|
-
return new DerivedCollection(this, callback)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
get length(): number {
|
|
250
|
-
subscribeActiveWatcher(this.#watchers, this.#hookCallbacks[HOOK_WATCH])
|
|
251
|
-
return this.#order.length
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/* === Functions === */
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Check if a value is a collection signal
|
|
259
|
-
*
|
|
260
|
-
* @since 0.17.2
|
|
261
|
-
* @param {unknown} value - Value to check
|
|
262
|
-
* @returns {boolean} - True if value is a collection signal, false otherwise
|
|
263
|
-
*/
|
|
264
|
-
const isCollection = /*#__PURE__*/ <T extends {}>(
|
|
265
|
-
value: unknown,
|
|
266
|
-
): value is Collection<T> => isObjectOfType(value, TYPE_COLLECTION)
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Check if a value is a collection source
|
|
270
|
-
*
|
|
271
|
-
* @since 0.17.0
|
|
272
|
-
* @param {unknown} value - Value to check
|
|
273
|
-
* @returns {boolean} - True if value is a collection source, false otherwise
|
|
274
|
-
*/
|
|
275
|
-
const isCollectionSource = /*#__PURE__*/ <T extends {}>(
|
|
276
|
-
value: unknown,
|
|
277
|
-
): value is CollectionSource<T> => isList(value) || isCollection(value)
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Check if the provided callback is an async function
|
|
281
|
-
*
|
|
282
|
-
* @since 0.17.0
|
|
283
|
-
* @param {unknown} callback - Value to check
|
|
284
|
-
* @returns {boolean} - True if value is an async collection callback, false otherwise
|
|
285
|
-
*/
|
|
286
|
-
const isAsyncCollectionCallback = <T extends {}>(
|
|
287
|
-
callback: unknown,
|
|
288
|
-
): callback is (sourceValue: unknown, abort: AbortSignal) => Promise<T> =>
|
|
289
|
-
isAsyncFunction(callback)
|
|
290
|
-
|
|
291
|
-
export {
|
|
292
|
-
type Collection,
|
|
293
|
-
type CollectionSource,
|
|
294
|
-
type CollectionCallback,
|
|
295
|
-
DerivedCollection,
|
|
296
|
-
isCollection,
|
|
297
|
-
TYPE_COLLECTION,
|
|
298
|
-
}
|
package/src/classes/composite.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import type { DiffResult, UnknownRecord } from '../diff'
|
|
2
|
-
import { guardMutableSignal, InvalidHookError } from '../errors'
|
|
3
|
-
import type { Signal } from '../signal'
|
|
4
|
-
import {
|
|
5
|
-
batchSignalWrites,
|
|
6
|
-
type Cleanup,
|
|
7
|
-
createWatcher,
|
|
8
|
-
HOOK_ADD,
|
|
9
|
-
HOOK_CHANGE,
|
|
10
|
-
HOOK_REMOVE,
|
|
11
|
-
type HookCallback,
|
|
12
|
-
type HookCallbacks,
|
|
13
|
-
isHandledHook,
|
|
14
|
-
trackSignalReads,
|
|
15
|
-
triggerHook,
|
|
16
|
-
type Watcher,
|
|
17
|
-
} from '../system'
|
|
18
|
-
|
|
19
|
-
/* === Types === */
|
|
20
|
-
|
|
21
|
-
type CompositeHook = 'add' | 'change' | 'remove'
|
|
22
|
-
|
|
23
|
-
/* === Class Definitions === */
|
|
24
|
-
|
|
25
|
-
class Composite<T extends UnknownRecord, S extends Signal<T[keyof T] & {}>> {
|
|
26
|
-
signals = new Map<string, S>()
|
|
27
|
-
#validate: <K extends keyof T & string>(
|
|
28
|
-
key: K,
|
|
29
|
-
value: unknown,
|
|
30
|
-
) => value is T[K] & {}
|
|
31
|
-
#create: <V extends T[keyof T] & {}>(value: V) => S
|
|
32
|
-
#watchers = new Map<string, Watcher>()
|
|
33
|
-
#hookCallbacks: HookCallbacks = {}
|
|
34
|
-
#batching = false
|
|
35
|
-
|
|
36
|
-
constructor(
|
|
37
|
-
values: T,
|
|
38
|
-
validate: <K extends keyof T & string>(
|
|
39
|
-
key: K,
|
|
40
|
-
value: unknown,
|
|
41
|
-
) => value is T[K] & {},
|
|
42
|
-
create: <V extends T[keyof T] & {}>(value: V) => S,
|
|
43
|
-
) {
|
|
44
|
-
this.#validate = validate
|
|
45
|
-
this.#create = create
|
|
46
|
-
this.change(
|
|
47
|
-
{
|
|
48
|
-
add: values,
|
|
49
|
-
change: {},
|
|
50
|
-
remove: {},
|
|
51
|
-
changed: true,
|
|
52
|
-
},
|
|
53
|
-
true,
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#addWatcher(key: string): void {
|
|
58
|
-
const watcher = createWatcher(() => {
|
|
59
|
-
trackSignalReads(watcher, () => {
|
|
60
|
-
this.signals.get(key)?.get() // Subscribe to the signal
|
|
61
|
-
if (!this.#batching)
|
|
62
|
-
triggerHook(this.#hookCallbacks.change, [key])
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
this.#watchers.set(key, watcher)
|
|
66
|
-
watcher()
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
add<K extends keyof T & string>(key: K, value: T[K]): boolean {
|
|
70
|
-
if (!this.#validate(key, value)) return false
|
|
71
|
-
|
|
72
|
-
this.signals.set(key, this.#create(value))
|
|
73
|
-
if (this.#hookCallbacks.change?.size) this.#addWatcher(key)
|
|
74
|
-
|
|
75
|
-
if (!this.#batching) triggerHook(this.#hookCallbacks.add, [key])
|
|
76
|
-
return true
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
remove<K extends keyof T & string>(key: K): boolean {
|
|
80
|
-
const ok = this.signals.delete(key)
|
|
81
|
-
if (!ok) return false
|
|
82
|
-
|
|
83
|
-
const watcher = this.#watchers.get(key)
|
|
84
|
-
if (watcher) {
|
|
85
|
-
watcher.stop()
|
|
86
|
-
this.#watchers.delete(key)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!this.#batching) triggerHook(this.#hookCallbacks.remove, [key])
|
|
90
|
-
return true
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
change(changes: DiffResult, initialRun?: boolean): boolean {
|
|
94
|
-
this.#batching = true
|
|
95
|
-
|
|
96
|
-
// Additions
|
|
97
|
-
if (Object.keys(changes.add).length) {
|
|
98
|
-
for (const key in changes.add)
|
|
99
|
-
this.add(
|
|
100
|
-
key as Extract<keyof T, string>,
|
|
101
|
-
changes.add[key] as T[Extract<keyof T, string>] & {},
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
// Queue initial additions event to allow listeners to be added first
|
|
105
|
-
const notify = () =>
|
|
106
|
-
triggerHook(this.#hookCallbacks.add, Object.keys(changes.add))
|
|
107
|
-
if (initialRun) setTimeout(notify, 0)
|
|
108
|
-
else notify()
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Changes
|
|
112
|
-
if (Object.keys(changes.change).length) {
|
|
113
|
-
batchSignalWrites(() => {
|
|
114
|
-
for (const key in changes.change) {
|
|
115
|
-
const value = changes.change[key]
|
|
116
|
-
if (!this.#validate(key as keyof T & string, value))
|
|
117
|
-
continue
|
|
118
|
-
|
|
119
|
-
const signal = this.signals.get(key)
|
|
120
|
-
if (guardMutableSignal(`list item "${key}"`, value, signal))
|
|
121
|
-
signal.set(value)
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
triggerHook(this.#hookCallbacks.change, Object.keys(changes.change))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Removals
|
|
128
|
-
if (Object.keys(changes.remove).length) {
|
|
129
|
-
for (const key in changes.remove)
|
|
130
|
-
this.remove(key as keyof T & string)
|
|
131
|
-
triggerHook(this.#hookCallbacks.remove, Object.keys(changes.remove))
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
this.#batching = false
|
|
135
|
-
return changes.changed
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
clear(): boolean {
|
|
139
|
-
const keys = Array.from(this.signals.keys())
|
|
140
|
-
this.signals.clear()
|
|
141
|
-
this.#watchers.clear()
|
|
142
|
-
triggerHook(this.#hookCallbacks.remove, keys)
|
|
143
|
-
return true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
on(type: CompositeHook, callback: HookCallback): Cleanup {
|
|
147
|
-
if (!isHandledHook(type, [HOOK_ADD, HOOK_CHANGE, HOOK_REMOVE]))
|
|
148
|
-
throw new InvalidHookError('Composite', type)
|
|
149
|
-
|
|
150
|
-
this.#hookCallbacks[type] ||= new Set()
|
|
151
|
-
this.#hookCallbacks[type].add(callback)
|
|
152
|
-
if (type === HOOK_CHANGE && !this.#watchers.size) {
|
|
153
|
-
this.#batching = true
|
|
154
|
-
for (const key of this.signals.keys()) this.#addWatcher(key)
|
|
155
|
-
this.#batching = false
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return () => {
|
|
159
|
-
this.#hookCallbacks[type]?.delete(callback)
|
|
160
|
-
if (type === HOOK_CHANGE && !this.#hookCallbacks.change?.size) {
|
|
161
|
-
if (this.#watchers.size) {
|
|
162
|
-
for (const watcher of this.#watchers.values())
|
|
163
|
-
watcher.stop()
|
|
164
|
-
this.#watchers.clear()
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export { Composite, type CompositeHook as CompositeListeners }
|