@zeix/cause-effect 0.17.2 → 0.17.3
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 +11 -5
- package/.github/copilot-instructions.md +1 -1
- package/.zed/settings.json +3 -0
- package/CLAUDE.md +18 -79
- package/README.md +23 -37
- package/archive/benchmark.ts +0 -5
- package/archive/collection.ts +5 -62
- package/archive/composite.ts +85 -0
- package/archive/computed.ts +17 -20
- package/archive/list.ts +6 -67
- package/archive/memo.ts +13 -14
- package/archive/store.ts +7 -66
- package/archive/task.ts +18 -20
- package/index.dev.js +438 -614
- package/index.js +1 -1
- package/index.ts +8 -19
- package/package.json +6 -6
- package/src/classes/collection.ts +59 -112
- package/src/classes/computed.ts +146 -189
- package/src/classes/list.ts +138 -105
- package/src/classes/ref.ts +16 -42
- package/src/classes/state.ts +16 -45
- package/src/classes/store.ts +107 -72
- package/src/effect.ts +9 -12
- package/src/errors.ts +12 -8
- package/src/signal.ts +3 -1
- package/src/system.ts +136 -154
- package/test/batch.test.ts +4 -11
- package/test/benchmark.test.ts +4 -2
- package/test/collection.test.ts +46 -306
- package/test/computed.test.ts +205 -223
- package/test/list.test.ts +35 -303
- package/test/ref.test.ts +38 -66
- package/test/state.test.ts +6 -12
- package/test/store.test.ts +37 -489
- package/test/util/dependency-graph.ts +2 -2
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +5 -7
- package/types/index.d.ts +2 -2
- package/types/src/classes/collection.d.ts +4 -6
- package/types/src/classes/computed.d.ts +17 -37
- package/types/src/classes/list.d.ts +8 -6
- package/types/src/classes/ref.d.ts +7 -20
- package/types/src/classes/state.d.ts +5 -17
- package/types/src/classes/store.d.ts +12 -11
- package/types/src/errors.d.ts +2 -4
- package/types/src/signal.d.ts +3 -2
- package/types/src/system.d.ts +41 -44
- package/src/classes/composite.ts +0 -171
- package/types/src/classes/composite.d.ts +0 -15
package/src/classes/list.ts
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
import { diff, isEqual, type UnknownArray } from '../diff'
|
|
1
|
+
import { type DiffResult, diff, isEqual, type UnknownArray } from '../diff'
|
|
2
2
|
import {
|
|
3
3
|
DuplicateKeyError,
|
|
4
|
-
|
|
4
|
+
guardMutableSignal,
|
|
5
5
|
validateSignalValue,
|
|
6
6
|
} from '../errors'
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
HOOK_WATCH,
|
|
14
|
-
type Hook,
|
|
15
|
-
type HookCallback,
|
|
16
|
-
type HookCallbacks,
|
|
17
|
-
isHandledHook,
|
|
18
|
-
notifyWatchers,
|
|
19
|
-
subscribeActiveWatcher,
|
|
20
|
-
triggerHook,
|
|
8
|
+
batch,
|
|
9
|
+
notifyOf,
|
|
10
|
+
registerWatchCallbacks,
|
|
11
|
+
type SignalOptions,
|
|
12
|
+
subscribeTo,
|
|
21
13
|
UNSET,
|
|
22
|
-
|
|
14
|
+
unsubscribeAllFrom,
|
|
23
15
|
} from '../system'
|
|
24
16
|
import { isFunction, isNumber, isObjectOfType, isString } from '../util'
|
|
25
17
|
import { type CollectionCallback, DerivedCollection } from './collection'
|
|
26
|
-
import { Composite } from './composite'
|
|
27
18
|
import { State } from './state'
|
|
28
19
|
|
|
29
20
|
/* === Types === */
|
|
@@ -33,6 +24,9 @@ type ArrayToRecord<T extends UnknownArray> = {
|
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
type KeyConfig<T> = string | ((item: T) => string)
|
|
27
|
+
type ListOptions<T extends {}> = SignalOptions<T> & {
|
|
28
|
+
keyConfig?: KeyConfig<T>
|
|
29
|
+
}
|
|
36
30
|
|
|
37
31
|
/* === Constants === */
|
|
38
32
|
|
|
@@ -41,30 +35,39 @@ const TYPE_LIST = 'List' as const
|
|
|
41
35
|
/* === Class === */
|
|
42
36
|
|
|
43
37
|
class List<T extends {}> {
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#hookCallbacks: HookCallbacks = {}
|
|
47
|
-
#order: string[] = []
|
|
38
|
+
#signals = new Map<string, State<T>>()
|
|
39
|
+
#keys: string[] = []
|
|
48
40
|
#generateKey: (item: T) => string
|
|
41
|
+
#validate: (key: string, value: unknown) => value is T
|
|
49
42
|
|
|
50
|
-
constructor(initialValue: T[],
|
|
43
|
+
constructor(initialValue: T[], options?: ListOptions<T>) {
|
|
51
44
|
validateSignalValue(TYPE_LIST, initialValue, Array.isArray)
|
|
52
45
|
|
|
53
46
|
let keyCounter = 0
|
|
47
|
+
const keyConfig = options?.keyConfig
|
|
54
48
|
this.#generateKey = isString(keyConfig)
|
|
55
49
|
? () => `${keyConfig}${keyCounter++}`
|
|
56
50
|
: isFunction<string>(keyConfig)
|
|
57
51
|
? (item: T) => keyConfig(item)
|
|
58
52
|
: () => String(keyCounter++)
|
|
59
53
|
|
|
60
|
-
this.#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
this.#validate = (key: string, value: unknown): value is T => {
|
|
55
|
+
validateSignalValue(
|
|
56
|
+
`${TYPE_LIST} item for key "${key}"`,
|
|
57
|
+
value,
|
|
58
|
+
options?.guard,
|
|
59
|
+
)
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.#change({
|
|
64
|
+
add: this.#toRecord(initialValue),
|
|
65
|
+
change: {},
|
|
66
|
+
remove: {},
|
|
67
|
+
changed: true,
|
|
68
|
+
})
|
|
69
|
+
if (options?.watched)
|
|
70
|
+
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
// Convert array to record with stable keys
|
|
@@ -75,19 +78,66 @@ class List<T extends {}> {
|
|
|
75
78
|
const value = array[i]
|
|
76
79
|
if (value === undefined) continue // Skip sparse array positions
|
|
77
80
|
|
|
78
|
-
let key = this.#
|
|
81
|
+
let key = this.#keys[i]
|
|
79
82
|
if (!key) {
|
|
80
83
|
key = this.#generateKey(value)
|
|
81
|
-
this.#
|
|
84
|
+
this.#keys[i] = key
|
|
82
85
|
}
|
|
83
86
|
record[key] = value
|
|
84
87
|
}
|
|
85
88
|
return record
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
#add(key: string, value: T) {
|
|
92
|
+
if (!this.#validate(key, value)) return false
|
|
93
|
+
|
|
94
|
+
this.#signals.set(key, new State(value))
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#change(changes: DiffResult) {
|
|
99
|
+
// Additions
|
|
100
|
+
if (Object.keys(changes.add).length) {
|
|
101
|
+
for (const key in changes.add) this.#add(key, changes.add[key] as T)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Changes
|
|
105
|
+
if (Object.keys(changes.change).length) {
|
|
106
|
+
batch(() => {
|
|
107
|
+
for (const key in changes.change) {
|
|
108
|
+
const value = changes.change[key]
|
|
109
|
+
if (!this.#validate(key as keyof T & string, value))
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
const signal = this.#signals.get(key)
|
|
113
|
+
if (
|
|
114
|
+
guardMutableSignal(
|
|
115
|
+
`${TYPE_LIST} item "${key}"`,
|
|
116
|
+
value,
|
|
117
|
+
signal,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
signal.set(value)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Removals
|
|
126
|
+
if (Object.keys(changes.remove).length) {
|
|
127
|
+
for (const key in changes.remove) {
|
|
128
|
+
this.#signals.delete(key)
|
|
129
|
+
const index = this.#keys.indexOf(key)
|
|
130
|
+
if (index !== -1) this.#keys.splice(index, 1)
|
|
131
|
+
}
|
|
132
|
+
this.#keys = this.#keys.filter(() => true)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return changes.changed
|
|
136
|
+
}
|
|
137
|
+
|
|
88
138
|
get #value(): T[] {
|
|
89
|
-
return this.#
|
|
90
|
-
.map(key => this.#
|
|
139
|
+
return this.#keys
|
|
140
|
+
.map(key => this.#signals.get(key)?.get())
|
|
91
141
|
.filter(v => v !== undefined) as T[]
|
|
92
142
|
}
|
|
93
143
|
|
|
@@ -101,43 +151,35 @@ class List<T extends {}> {
|
|
|
101
151
|
}
|
|
102
152
|
|
|
103
153
|
*[Symbol.iterator](): IterableIterator<State<T>> {
|
|
104
|
-
for (const key of this.#
|
|
105
|
-
const signal = this.#
|
|
154
|
+
for (const key of this.#keys) {
|
|
155
|
+
const signal = this.#signals.get(key)
|
|
106
156
|
if (signal) yield signal as State<T>
|
|
107
157
|
}
|
|
108
158
|
}
|
|
109
159
|
|
|
110
160
|
get length(): number {
|
|
111
|
-
|
|
112
|
-
return this.#
|
|
161
|
+
subscribeTo(this)
|
|
162
|
+
return this.#keys.length
|
|
113
163
|
}
|
|
114
164
|
|
|
115
165
|
get(): T[] {
|
|
116
|
-
|
|
166
|
+
subscribeTo(this)
|
|
117
167
|
return this.#value
|
|
118
168
|
}
|
|
119
169
|
|
|
120
170
|
set(newValue: T[]): void {
|
|
121
171
|
if (UNSET === newValue) {
|
|
122
|
-
this.#
|
|
123
|
-
|
|
124
|
-
this
|
|
172
|
+
this.#signals.clear()
|
|
173
|
+
notifyOf(this)
|
|
174
|
+
unsubscribeAllFrom(this)
|
|
125
175
|
return
|
|
126
176
|
}
|
|
127
177
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (changed) {
|
|
134
|
-
for (const key of removedKeys) {
|
|
135
|
-
const index = this.#order.indexOf(key)
|
|
136
|
-
if (index !== -1) this.#order.splice(index, 1)
|
|
137
|
-
}
|
|
138
|
-
this.#order = this.#order.filter(() => true)
|
|
139
|
-
notifyWatchers(this.#watchers)
|
|
140
|
-
}
|
|
178
|
+
const changes = diff(
|
|
179
|
+
this.#toRecord(this.#value),
|
|
180
|
+
this.#toRecord(newValue),
|
|
181
|
+
)
|
|
182
|
+
if (this.#change(changes)) notifyOf(this)
|
|
141
183
|
}
|
|
142
184
|
|
|
143
185
|
update(fn: (oldValue: T[]) => T[]): void {
|
|
@@ -145,58 +187,53 @@ class List<T extends {}> {
|
|
|
145
187
|
}
|
|
146
188
|
|
|
147
189
|
at(index: number): State<T> | undefined {
|
|
148
|
-
return this.#
|
|
190
|
+
return this.#signals.get(this.#keys[index])
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
keys(): IterableIterator<string> {
|
|
152
|
-
|
|
194
|
+
subscribeTo(this)
|
|
195
|
+
return this.#keys.values()
|
|
153
196
|
}
|
|
154
197
|
|
|
155
198
|
byKey(key: string): State<T> | undefined {
|
|
156
|
-
return this.#
|
|
199
|
+
return this.#signals.get(key)
|
|
157
200
|
}
|
|
158
201
|
|
|
159
202
|
keyAt(index: number): string | undefined {
|
|
160
|
-
return this.#
|
|
203
|
+
return this.#keys[index]
|
|
161
204
|
}
|
|
162
205
|
|
|
163
206
|
indexOfKey(key: string): number {
|
|
164
|
-
return this.#
|
|
207
|
+
return this.#keys.indexOf(key)
|
|
165
208
|
}
|
|
166
209
|
|
|
167
210
|
add(value: T): string {
|
|
168
211
|
const key = this.#generateKey(value)
|
|
169
|
-
if (this.#
|
|
212
|
+
if (this.#signals.has(key))
|
|
170
213
|
throw new DuplicateKeyError('store', key, value)
|
|
171
214
|
|
|
172
|
-
if (!this.#
|
|
173
|
-
const ok = this.#
|
|
174
|
-
if (ok)
|
|
215
|
+
if (!this.#keys.includes(key)) this.#keys.push(key)
|
|
216
|
+
const ok = this.#add(key, value)
|
|
217
|
+
if (ok) notifyOf(this)
|
|
175
218
|
return key
|
|
176
219
|
}
|
|
177
220
|
|
|
178
221
|
remove(keyOrIndex: string | number): void {
|
|
179
|
-
const key = isNumber(keyOrIndex) ? this.#
|
|
180
|
-
const ok = this.#
|
|
222
|
+
const key = isNumber(keyOrIndex) ? this.#keys[keyOrIndex] : keyOrIndex
|
|
223
|
+
const ok = this.#signals.delete(key)
|
|
181
224
|
if (ok) {
|
|
182
225
|
const index = isNumber(keyOrIndex)
|
|
183
226
|
? keyOrIndex
|
|
184
|
-
: this.#
|
|
185
|
-
if (index >= 0) this.#
|
|
186
|
-
this.#
|
|
187
|
-
|
|
227
|
+
: this.#keys.indexOf(key)
|
|
228
|
+
if (index >= 0) this.#keys.splice(index, 1)
|
|
229
|
+
this.#keys = this.#keys.filter(() => true)
|
|
230
|
+
notifyOf(this)
|
|
188
231
|
}
|
|
189
232
|
}
|
|
190
233
|
|
|
191
234
|
sort(compareFn?: (a: T, b: T) => number): void {
|
|
192
|
-
const entries = this.#
|
|
193
|
-
.map(
|
|
194
|
-
key =>
|
|
195
|
-
[key, this.#composite.signals.get(key)?.get()] as [
|
|
196
|
-
string,
|
|
197
|
-
T,
|
|
198
|
-
],
|
|
199
|
-
)
|
|
235
|
+
const entries = this.#keys
|
|
236
|
+
.map(key => [key, this.#signals.get(key)?.get()] as [string, T])
|
|
200
237
|
.sort(
|
|
201
238
|
isFunction(compareFn)
|
|
202
239
|
? (a, b) => compareFn(a[1], b[1])
|
|
@@ -204,15 +241,14 @@ class List<T extends {}> {
|
|
|
204
241
|
)
|
|
205
242
|
const newOrder = entries.map(([key]) => key)
|
|
206
243
|
|
|
207
|
-
if (!isEqual(this.#
|
|
208
|
-
this.#
|
|
209
|
-
|
|
210
|
-
triggerHook(this.#hookCallbacks.sort, this.#order)
|
|
244
|
+
if (!isEqual(this.#keys, newOrder)) {
|
|
245
|
+
this.#keys = newOrder
|
|
246
|
+
notifyOf(this)
|
|
211
247
|
}
|
|
212
248
|
}
|
|
213
249
|
|
|
214
250
|
splice(start: number, deleteCount?: number, ...items: T[]): T[] {
|
|
215
|
-
const length = this.#
|
|
251
|
+
const length = this.#keys.length
|
|
216
252
|
const actualStart =
|
|
217
253
|
start < 0 ? Math.max(0, length + start) : Math.min(start, length)
|
|
218
254
|
const actualDeleteCount = Math.max(
|
|
@@ -229,15 +265,15 @@ class List<T extends {}> {
|
|
|
229
265
|
// Collect items to delete and their keys
|
|
230
266
|
for (let i = 0; i < actualDeleteCount; i++) {
|
|
231
267
|
const index = actualStart + i
|
|
232
|
-
const key = this.#
|
|
268
|
+
const key = this.#keys[index]
|
|
233
269
|
if (key) {
|
|
234
|
-
const signal = this.#
|
|
270
|
+
const signal = this.#signals.get(key)
|
|
235
271
|
if (signal) remove[key] = signal.get() as T
|
|
236
272
|
}
|
|
237
273
|
}
|
|
238
274
|
|
|
239
275
|
// Build new order: items before splice point
|
|
240
|
-
const newOrder = this.#
|
|
276
|
+
const newOrder = this.#keys.slice(0, actualStart)
|
|
241
277
|
|
|
242
278
|
// Add new items
|
|
243
279
|
for (const item of items) {
|
|
@@ -247,49 +283,39 @@ class List<T extends {}> {
|
|
|
247
283
|
}
|
|
248
284
|
|
|
249
285
|
// Add items after splice point
|
|
250
|
-
newOrder.push(...this.#
|
|
286
|
+
newOrder.push(...this.#keys.slice(actualStart + actualDeleteCount))
|
|
251
287
|
|
|
252
288
|
const changed = !!(
|
|
253
289
|
Object.keys(add).length || Object.keys(remove).length
|
|
254
290
|
)
|
|
255
291
|
|
|
256
292
|
if (changed) {
|
|
257
|
-
this.#
|
|
293
|
+
this.#change({
|
|
258
294
|
add,
|
|
259
295
|
change: {} as Record<string, T>,
|
|
260
296
|
remove,
|
|
261
297
|
changed,
|
|
262
298
|
})
|
|
263
|
-
this.#
|
|
264
|
-
|
|
299
|
+
this.#keys = newOrder.filter(() => true) // Update order array
|
|
300
|
+
notifyOf(this)
|
|
265
301
|
}
|
|
266
302
|
|
|
267
303
|
return Object.values(remove)
|
|
268
304
|
}
|
|
269
305
|
|
|
270
|
-
on(type: Hook, callback: HookCallback): Cleanup {
|
|
271
|
-
if (isHandledHook(type, [HOOK_SORT, HOOK_WATCH])) {
|
|
272
|
-
this.#hookCallbacks[type] ||= new Set<HookCallback>()
|
|
273
|
-
this.#hookCallbacks[type].add(callback)
|
|
274
|
-
return () => {
|
|
275
|
-
this.#hookCallbacks[type]?.delete(callback)
|
|
276
|
-
}
|
|
277
|
-
} else if (isHandledHook(type, [HOOK_ADD, HOOK_CHANGE, HOOK_REMOVE])) {
|
|
278
|
-
return this.#composite.on(type, callback)
|
|
279
|
-
}
|
|
280
|
-
throw new InvalidHookError(TYPE_LIST, type)
|
|
281
|
-
}
|
|
282
|
-
|
|
283
306
|
deriveCollection<R extends {}>(
|
|
284
307
|
callback: (sourceValue: T) => R,
|
|
308
|
+
options?: SignalOptions<R[]>,
|
|
285
309
|
): DerivedCollection<R, T>
|
|
286
310
|
deriveCollection<R extends {}>(
|
|
287
311
|
callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
|
|
312
|
+
options?: SignalOptions<R[]>,
|
|
288
313
|
): DerivedCollection<R, T>
|
|
289
314
|
deriveCollection<R extends {}>(
|
|
290
315
|
callback: CollectionCallback<R, T>,
|
|
316
|
+
options?: SignalOptions<R[]>,
|
|
291
317
|
): DerivedCollection<R, T> {
|
|
292
|
-
return new DerivedCollection(this, callback)
|
|
318
|
+
return new DerivedCollection(this, callback, options)
|
|
293
319
|
}
|
|
294
320
|
}
|
|
295
321
|
|
|
@@ -307,4 +333,11 @@ const isList = <T extends {}>(value: unknown): value is List<T> =>
|
|
|
307
333
|
|
|
308
334
|
/* === Exports === */
|
|
309
335
|
|
|
310
|
-
export {
|
|
336
|
+
export {
|
|
337
|
+
isList,
|
|
338
|
+
List,
|
|
339
|
+
TYPE_LIST,
|
|
340
|
+
type ArrayToRecord,
|
|
341
|
+
type KeyConfig,
|
|
342
|
+
type ListOptions,
|
|
343
|
+
}
|
package/src/classes/ref.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateSignalValue } from '../errors'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
subscribeActiveWatcher,
|
|
8
|
-
type Watcher,
|
|
9
|
-
type WatchHook,
|
|
3
|
+
notifyOf,
|
|
4
|
+
registerWatchCallbacks,
|
|
5
|
+
type SignalOptions,
|
|
6
|
+
subscribeTo,
|
|
10
7
|
} from '../system'
|
|
11
8
|
import { isObjectOfType } from '../util'
|
|
12
9
|
|
|
@@ -20,24 +17,20 @@ const TYPE_REF = 'Ref'
|
|
|
20
17
|
* Create a new ref signal.
|
|
21
18
|
*
|
|
22
19
|
* @since 0.17.1
|
|
20
|
+
* @param {T} value - Reference to external object
|
|
21
|
+
* @param {Guard<T>} guard - Optional guard function to validate the value
|
|
22
|
+
* @throws {NullishSignalValueError} - If the value is null or undefined
|
|
23
|
+
* @throws {InvalidSignalValueError} - If the value is invalid
|
|
23
24
|
*/
|
|
24
25
|
class Ref<T extends {}> {
|
|
25
|
-
#watchers = new Set<Watcher>()
|
|
26
26
|
#value: T
|
|
27
|
-
#watchHookCallbacks: Set<HookCallback> | undefined
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
* @param {T} value - Reference to external object
|
|
33
|
-
* @param {Guard<T>} guard - Optional guard function to validate the value
|
|
34
|
-
* @throws {NullishSignalValueError} - If the value is null or undefined
|
|
35
|
-
* @throws {InvalidSignalValueError} - If the value is invalid
|
|
36
|
-
*/
|
|
37
|
-
constructor(value: T, guard?: Guard<T>) {
|
|
38
|
-
validateSignalValue(TYPE_REF, value, guard)
|
|
28
|
+
constructor(value: T, options?: SignalOptions<T>) {
|
|
29
|
+
validateSignalValue(TYPE_REF, value, options?.guard)
|
|
39
30
|
|
|
40
31
|
this.#value = value
|
|
32
|
+
if (options?.watched)
|
|
33
|
+
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
get [Symbol.toStringTag](): string {
|
|
@@ -50,8 +43,7 @@ class Ref<T extends {}> {
|
|
|
50
43
|
* @returns {T} - Object reference
|
|
51
44
|
*/
|
|
52
45
|
get(): T {
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
subscribeTo(this)
|
|
55
47
|
return this.#value
|
|
56
48
|
}
|
|
57
49
|
|
|
@@ -59,25 +51,7 @@ class Ref<T extends {}> {
|
|
|
59
51
|
* Notify watchers of relevant changes in the external reference.
|
|
60
52
|
*/
|
|
61
53
|
notify(): void {
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Register a callback to be called when HOOK_WATCH is triggered.
|
|
67
|
-
*
|
|
68
|
-
* @param {WatchHook} type - The type of hook to register the callback for; only HOOK_WATCH is supported
|
|
69
|
-
* @param {HookCallback} callback - The callback to register
|
|
70
|
-
* @returns {Cleanup} - A function to unregister the callback
|
|
71
|
-
*/
|
|
72
|
-
on(type: WatchHook, callback: HookCallback): Cleanup {
|
|
73
|
-
if (type === HOOK_WATCH) {
|
|
74
|
-
this.#watchHookCallbacks ||= new Set()
|
|
75
|
-
this.#watchHookCallbacks.add(callback)
|
|
76
|
-
return () => {
|
|
77
|
-
this.#watchHookCallbacks?.delete(callback)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
throw new InvalidHookError(TYPE_REF, type)
|
|
54
|
+
notifyOf(this)
|
|
81
55
|
}
|
|
82
56
|
}
|
|
83
57
|
|
|
@@ -88,7 +62,7 @@ class Ref<T extends {}> {
|
|
|
88
62
|
*
|
|
89
63
|
* @since 0.17.1
|
|
90
64
|
* @param {unknown} value - Value to check
|
|
91
|
-
* @returns {boolean} -
|
|
65
|
+
* @returns {boolean} - Whether the value is a Ref instance
|
|
92
66
|
*/
|
|
93
67
|
const isRef = /*#__PURE__*/ <T extends {}>(value: unknown): value is Ref<T> =>
|
|
94
68
|
isObjectOfType(value, TYPE_REF)
|
package/src/classes/state.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { isEqual } from '../diff'
|
|
2
|
+
import { validateCallback, validateSignalValue } from '../errors'
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
type Cleanup,
|
|
9
|
-
HOOK_WATCH,
|
|
10
|
-
type HookCallback,
|
|
11
|
-
notifyWatchers,
|
|
12
|
-
subscribeActiveWatcher,
|
|
4
|
+
type SignalOptions,
|
|
5
|
+
registerWatchCallbacks,
|
|
6
|
+
notifyOf,
|
|
7
|
+
subscribeTo,
|
|
13
8
|
UNSET,
|
|
14
|
-
|
|
15
|
-
type WatchHook,
|
|
9
|
+
unsubscribeAllFrom,
|
|
16
10
|
} from '../system'
|
|
17
11
|
import { isObjectOfType } from '../util'
|
|
18
12
|
|
|
@@ -26,23 +20,19 @@ const TYPE_STATE = 'State' as const
|
|
|
26
20
|
* Create a new state signal.
|
|
27
21
|
*
|
|
28
22
|
* @since 0.17.0
|
|
23
|
+
* @param {T} initialValue - Initial value of the state
|
|
24
|
+
* @throws {NullishSignalValueError} - If the initial value is null or undefined
|
|
25
|
+
* @throws {InvalidSignalValueError} - If the initial value is invalid
|
|
29
26
|
*/
|
|
30
27
|
class State<T extends {}> {
|
|
31
|
-
#watchers = new Set<Watcher>()
|
|
32
28
|
#value: T
|
|
33
|
-
#watchHookCallbacks: Set<HookCallback> | undefined
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
* @param {T} initialValue - Initial value of the state
|
|
39
|
-
* @throws {NullishSignalValueError} - If the initial value is null or undefined
|
|
40
|
-
* @throws {InvalidSignalValueError} - If the initial value is invalid
|
|
41
|
-
*/
|
|
42
|
-
constructor(initialValue: T) {
|
|
43
|
-
validateSignalValue(TYPE_STATE, initialValue)
|
|
30
|
+
constructor(initialValue: T, options?: SignalOptions<T>) {
|
|
31
|
+
validateSignalValue(TYPE_STATE, initialValue, options?.guard)
|
|
44
32
|
|
|
45
33
|
this.#value = initialValue
|
|
34
|
+
if (options?.watched)
|
|
35
|
+
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
46
36
|
}
|
|
47
37
|
|
|
48
38
|
get [Symbol.toStringTag](): string {
|
|
@@ -55,8 +45,7 @@ class State<T extends {}> {
|
|
|
55
45
|
* @returns {T} - Current value of the state
|
|
56
46
|
*/
|
|
57
47
|
get(): T {
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
subscribeTo(this)
|
|
60
49
|
return this.#value
|
|
61
50
|
}
|
|
62
51
|
|
|
@@ -73,10 +62,10 @@ class State<T extends {}> {
|
|
|
73
62
|
|
|
74
63
|
if (isEqual(this.#value, newValue)) return
|
|
75
64
|
this.#value = newValue
|
|
76
|
-
|
|
65
|
+
notifyOf(this)
|
|
77
66
|
|
|
78
67
|
// Setting to UNSET clears the watchers so the signal can be garbage collected
|
|
79
|
-
if (UNSET === this.#value) this
|
|
68
|
+
if (UNSET === this.#value) unsubscribeAllFrom(this)
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
/**
|
|
@@ -93,24 +82,6 @@ class State<T extends {}> {
|
|
|
93
82
|
|
|
94
83
|
this.set(updater(this.#value))
|
|
95
84
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Register a callback to be called when HOOK_WATCH is triggered.
|
|
99
|
-
*
|
|
100
|
-
* @param {WatchHook} type - The type of hook to register the callback for; only HOOK_WATCH is supported
|
|
101
|
-
* @param {HookCallback} callback - The callback to register
|
|
102
|
-
* @returns {Cleanup} - A function to unregister the callback
|
|
103
|
-
*/
|
|
104
|
-
on(type: WatchHook, callback: HookCallback): Cleanup {
|
|
105
|
-
if (type === HOOK_WATCH) {
|
|
106
|
-
this.#watchHookCallbacks ||= new Set()
|
|
107
|
-
this.#watchHookCallbacks.add(callback)
|
|
108
|
-
return () => {
|
|
109
|
-
this.#watchHookCallbacks?.delete(callback)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
throw new InvalidHookError(this.constructor.name, type)
|
|
113
|
-
}
|
|
114
85
|
}
|
|
115
86
|
|
|
116
87
|
/* === Functions === */
|