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