@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
package/src/classes/list.ts
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { type DiffResult, diff, isEqual, type UnknownArray } from '../diff'
|
|
2
|
-
import {
|
|
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,
|
|
15
|
-
} from '../system'
|
|
16
|
-
import { isFunction, isNumber, isObjectOfType, isString } from '../util'
|
|
17
|
-
import { type CollectionCallback, DerivedCollection } from './collection'
|
|
18
|
-
import { State } from './state'
|
|
19
|
-
|
|
20
|
-
/* === Types === */
|
|
21
|
-
|
|
22
|
-
type ArrayToRecord<T extends UnknownArray> = {
|
|
23
|
-
[key: string]: T extends Array<infer U extends {}> ? U : never
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type KeyConfig<T> = string | ((item: T) => string)
|
|
27
|
-
type ListOptions<T extends {}> = SignalOptions<T> & {
|
|
28
|
-
keyConfig?: KeyConfig<T>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/* === Constants === */
|
|
32
|
-
|
|
33
|
-
const TYPE_LIST = 'List' as const
|
|
34
|
-
|
|
35
|
-
/* === Class === */
|
|
36
|
-
|
|
37
|
-
class List<T extends {}> {
|
|
38
|
-
#signals = new Map<string, State<T>>()
|
|
39
|
-
#keys: string[] = []
|
|
40
|
-
#generateKey: (item: T) => string
|
|
41
|
-
#validate: (key: string, value: unknown) => value is T
|
|
42
|
-
|
|
43
|
-
constructor(initialValue: T[], options?: ListOptions<T>) {
|
|
44
|
-
validateSignalValue(TYPE_LIST, initialValue, Array.isArray)
|
|
45
|
-
|
|
46
|
-
let keyCounter = 0
|
|
47
|
-
const keyConfig = options?.keyConfig
|
|
48
|
-
this.#generateKey = isString(keyConfig)
|
|
49
|
-
? () => `${keyConfig}${keyCounter++}`
|
|
50
|
-
: isFunction<string>(keyConfig)
|
|
51
|
-
? (item: T) => keyConfig(item)
|
|
52
|
-
: () => String(keyCounter++)
|
|
53
|
-
|
|
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)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Convert array to record with stable keys
|
|
74
|
-
#toRecord(array: T[]): ArrayToRecord<T[]> {
|
|
75
|
-
const record = {} as Record<string, T>
|
|
76
|
-
|
|
77
|
-
for (let i = 0; i < array.length; i++) {
|
|
78
|
-
const value = array[i]
|
|
79
|
-
if (value === undefined) continue // Skip sparse array positions
|
|
80
|
-
|
|
81
|
-
let key = this.#keys[i]
|
|
82
|
-
if (!key) {
|
|
83
|
-
key = this.#generateKey(value)
|
|
84
|
-
this.#keys[i] = key
|
|
85
|
-
}
|
|
86
|
-
record[key] = value
|
|
87
|
-
}
|
|
88
|
-
return record
|
|
89
|
-
}
|
|
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
|
-
|
|
138
|
-
get #value(): T[] {
|
|
139
|
-
return this.#keys
|
|
140
|
-
.map(key => this.#signals.get(key)?.get())
|
|
141
|
-
.filter(v => v !== undefined) as T[]
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Public methods
|
|
145
|
-
get [Symbol.toStringTag](): 'List' {
|
|
146
|
-
return TYPE_LIST
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
get [Symbol.isConcatSpreadable](): true {
|
|
150
|
-
return true
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
*[Symbol.iterator](): IterableIterator<State<T>> {
|
|
154
|
-
for (const key of this.#keys) {
|
|
155
|
-
const signal = this.#signals.get(key)
|
|
156
|
-
if (signal) yield signal as State<T>
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
get length(): number {
|
|
161
|
-
subscribeTo(this)
|
|
162
|
-
return this.#keys.length
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
get(): T[] {
|
|
166
|
-
subscribeTo(this)
|
|
167
|
-
return this.#value
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
set(newValue: T[]): void {
|
|
171
|
-
if (UNSET === newValue) {
|
|
172
|
-
this.#signals.clear()
|
|
173
|
-
notifyOf(this)
|
|
174
|
-
unsubscribeAllFrom(this)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const changes = diff(
|
|
179
|
-
this.#toRecord(this.#value),
|
|
180
|
-
this.#toRecord(newValue),
|
|
181
|
-
)
|
|
182
|
-
if (this.#change(changes)) notifyOf(this)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
update(fn: (oldValue: T[]) => T[]): void {
|
|
186
|
-
this.set(fn(this.get()))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
at(index: number): State<T> | undefined {
|
|
190
|
-
return this.#signals.get(this.#keys[index])
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
keys(): IterableIterator<string> {
|
|
194
|
-
subscribeTo(this)
|
|
195
|
-
return this.#keys.values()
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
byKey(key: string): State<T> | undefined {
|
|
199
|
-
return this.#signals.get(key)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
keyAt(index: number): string | undefined {
|
|
203
|
-
return this.#keys[index]
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
indexOfKey(key: string): number {
|
|
207
|
-
return this.#keys.indexOf(key)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
add(value: T): string {
|
|
211
|
-
const key = this.#generateKey(value)
|
|
212
|
-
if (this.#signals.has(key))
|
|
213
|
-
throw new DuplicateKeyError('store', key, value)
|
|
214
|
-
|
|
215
|
-
if (!this.#keys.includes(key)) this.#keys.push(key)
|
|
216
|
-
const ok = this.#add(key, value)
|
|
217
|
-
if (ok) notifyOf(this)
|
|
218
|
-
return key
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
remove(keyOrIndex: string | number): void {
|
|
222
|
-
const key = isNumber(keyOrIndex) ? this.#keys[keyOrIndex] : keyOrIndex
|
|
223
|
-
const ok = this.#signals.delete(key)
|
|
224
|
-
if (ok) {
|
|
225
|
-
const index = isNumber(keyOrIndex)
|
|
226
|
-
? keyOrIndex
|
|
227
|
-
: this.#keys.indexOf(key)
|
|
228
|
-
if (index >= 0) this.#keys.splice(index, 1)
|
|
229
|
-
this.#keys = this.#keys.filter(() => true)
|
|
230
|
-
notifyOf(this)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
sort(compareFn?: (a: T, b: T) => number): void {
|
|
235
|
-
const entries = this.#keys
|
|
236
|
-
.map(key => [key, this.#signals.get(key)?.get()] as [string, T])
|
|
237
|
-
.sort(
|
|
238
|
-
isFunction(compareFn)
|
|
239
|
-
? (a, b) => compareFn(a[1], b[1])
|
|
240
|
-
: (a, b) => String(a[1]).localeCompare(String(b[1])),
|
|
241
|
-
)
|
|
242
|
-
const newOrder = entries.map(([key]) => key)
|
|
243
|
-
|
|
244
|
-
if (!isEqual(this.#keys, newOrder)) {
|
|
245
|
-
this.#keys = newOrder
|
|
246
|
-
notifyOf(this)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
splice(start: number, deleteCount?: number, ...items: T[]): T[] {
|
|
251
|
-
const length = this.#keys.length
|
|
252
|
-
const actualStart =
|
|
253
|
-
start < 0 ? Math.max(0, length + start) : Math.min(start, length)
|
|
254
|
-
const actualDeleteCount = Math.max(
|
|
255
|
-
0,
|
|
256
|
-
Math.min(
|
|
257
|
-
deleteCount ?? Math.max(0, length - Math.max(0, actualStart)),
|
|
258
|
-
length - actualStart,
|
|
259
|
-
),
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
const add = {} as Record<string, T>
|
|
263
|
-
const remove = {} as Record<string, T>
|
|
264
|
-
|
|
265
|
-
// Collect items to delete and their keys
|
|
266
|
-
for (let i = 0; i < actualDeleteCount; i++) {
|
|
267
|
-
const index = actualStart + i
|
|
268
|
-
const key = this.#keys[index]
|
|
269
|
-
if (key) {
|
|
270
|
-
const signal = this.#signals.get(key)
|
|
271
|
-
if (signal) remove[key] = signal.get() as T
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Build new order: items before splice point
|
|
276
|
-
const newOrder = this.#keys.slice(0, actualStart)
|
|
277
|
-
|
|
278
|
-
// Add new items
|
|
279
|
-
for (const item of items) {
|
|
280
|
-
const key = this.#generateKey(item)
|
|
281
|
-
newOrder.push(key)
|
|
282
|
-
add[key] = item as T
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Add items after splice point
|
|
286
|
-
newOrder.push(...this.#keys.slice(actualStart + actualDeleteCount))
|
|
287
|
-
|
|
288
|
-
const changed = !!(
|
|
289
|
-
Object.keys(add).length || Object.keys(remove).length
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
if (changed) {
|
|
293
|
-
this.#change({
|
|
294
|
-
add,
|
|
295
|
-
change: {} as Record<string, T>,
|
|
296
|
-
remove,
|
|
297
|
-
changed,
|
|
298
|
-
})
|
|
299
|
-
this.#keys = newOrder.filter(() => true) // Update order array
|
|
300
|
-
notifyOf(this)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return Object.values(remove)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
deriveCollection<R extends {}>(
|
|
307
|
-
callback: (sourceValue: T) => R,
|
|
308
|
-
options?: SignalOptions<R[]>,
|
|
309
|
-
): DerivedCollection<R, T>
|
|
310
|
-
deriveCollection<R extends {}>(
|
|
311
|
-
callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
|
|
312
|
-
options?: SignalOptions<R[]>,
|
|
313
|
-
): DerivedCollection<R, T>
|
|
314
|
-
deriveCollection<R extends {}>(
|
|
315
|
-
callback: CollectionCallback<R, T>,
|
|
316
|
-
options?: SignalOptions<R[]>,
|
|
317
|
-
): DerivedCollection<R, T> {
|
|
318
|
-
return new DerivedCollection(this, callback, options)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/* === Functions === */
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Check if the provided value is a List instance
|
|
326
|
-
*
|
|
327
|
-
* @since 0.15.0
|
|
328
|
-
* @param {unknown} value - Value to check
|
|
329
|
-
* @returns {boolean} - True if the value is a List instance, false otherwise
|
|
330
|
-
*/
|
|
331
|
-
const isList = <T extends {}>(value: unknown): value is List<T> =>
|
|
332
|
-
isObjectOfType(value, TYPE_LIST)
|
|
333
|
-
|
|
334
|
-
/* === Exports === */
|
|
335
|
-
|
|
336
|
-
export {
|
|
337
|
-
isList,
|
|
338
|
-
List,
|
|
339
|
-
TYPE_LIST,
|
|
340
|
-
type ArrayToRecord,
|
|
341
|
-
type KeyConfig,
|
|
342
|
-
type ListOptions,
|
|
343
|
-
}
|
package/src/classes/ref.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { validateSignalValue } from '../errors'
|
|
2
|
-
import {
|
|
3
|
-
notifyOf,
|
|
4
|
-
registerWatchCallbacks,
|
|
5
|
-
type SignalOptions,
|
|
6
|
-
subscribeTo,
|
|
7
|
-
} from '../system'
|
|
8
|
-
import { isObjectOfType } from '../util'
|
|
9
|
-
|
|
10
|
-
/* === Constants === */
|
|
11
|
-
|
|
12
|
-
const TYPE_REF = 'Ref'
|
|
13
|
-
|
|
14
|
-
/* === Class === */
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create a new ref signal.
|
|
18
|
-
*
|
|
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
|
|
24
|
-
*/
|
|
25
|
-
class Ref<T extends {}> {
|
|
26
|
-
#value: T
|
|
27
|
-
|
|
28
|
-
constructor(value: T, options?: SignalOptions<T>) {
|
|
29
|
-
validateSignalValue(TYPE_REF, value, options?.guard)
|
|
30
|
-
|
|
31
|
-
this.#value = value
|
|
32
|
-
if (options?.watched)
|
|
33
|
-
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get [Symbol.toStringTag](): string {
|
|
37
|
-
return TYPE_REF
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get the value of the ref signal.
|
|
42
|
-
*
|
|
43
|
-
* @returns {T} - Object reference
|
|
44
|
-
*/
|
|
45
|
-
get(): T {
|
|
46
|
-
subscribeTo(this)
|
|
47
|
-
return this.#value
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Notify watchers of relevant changes in the external reference.
|
|
52
|
-
*/
|
|
53
|
-
notify(): void {
|
|
54
|
-
notifyOf(this)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* === Functions === */
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Check if the provided value is a Ref instance
|
|
62
|
-
*
|
|
63
|
-
* @since 0.17.1
|
|
64
|
-
* @param {unknown} value - Value to check
|
|
65
|
-
* @returns {boolean} - Whether the value is a Ref instance
|
|
66
|
-
*/
|
|
67
|
-
const isRef = /*#__PURE__*/ <T extends {}>(value: unknown): value is Ref<T> =>
|
|
68
|
-
isObjectOfType(value, TYPE_REF)
|
|
69
|
-
|
|
70
|
-
export { TYPE_REF, Ref, isRef }
|
package/src/classes/state.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { isEqual } from '../diff'
|
|
2
|
-
import { validateCallback, validateSignalValue } from '../errors'
|
|
3
|
-
import {
|
|
4
|
-
type SignalOptions,
|
|
5
|
-
registerWatchCallbacks,
|
|
6
|
-
notifyOf,
|
|
7
|
-
subscribeTo,
|
|
8
|
-
UNSET,
|
|
9
|
-
unsubscribeAllFrom,
|
|
10
|
-
} from '../system'
|
|
11
|
-
import { isObjectOfType } from '../util'
|
|
12
|
-
|
|
13
|
-
/* === Constants === */
|
|
14
|
-
|
|
15
|
-
const TYPE_STATE = 'State' as const
|
|
16
|
-
|
|
17
|
-
/* === Class === */
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create a new state signal.
|
|
21
|
-
*
|
|
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
|
|
26
|
-
*/
|
|
27
|
-
class State<T extends {}> {
|
|
28
|
-
#value: T
|
|
29
|
-
|
|
30
|
-
constructor(initialValue: T, options?: SignalOptions<T>) {
|
|
31
|
-
validateSignalValue(TYPE_STATE, initialValue, options?.guard)
|
|
32
|
-
|
|
33
|
-
this.#value = initialValue
|
|
34
|
-
if (options?.watched)
|
|
35
|
-
registerWatchCallbacks(this, options.watched, options.unwatched)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get [Symbol.toStringTag](): string {
|
|
39
|
-
return TYPE_STATE
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get the current value of the state signal.
|
|
44
|
-
*
|
|
45
|
-
* @returns {T} - Current value of the state
|
|
46
|
-
*/
|
|
47
|
-
get(): T {
|
|
48
|
-
subscribeTo(this)
|
|
49
|
-
return this.#value
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Set the value of the state signal.
|
|
54
|
-
*
|
|
55
|
-
* @param {T} newValue - New value of the state
|
|
56
|
-
* @returns {void}
|
|
57
|
-
* @throws {NullishSignalValueError} - If the initial value is null or undefined
|
|
58
|
-
* @throws {InvalidSignalValueError} - If the initial value is invalid
|
|
59
|
-
*/
|
|
60
|
-
set(newValue: T): void {
|
|
61
|
-
validateSignalValue(TYPE_STATE, newValue)
|
|
62
|
-
|
|
63
|
-
if (isEqual(this.#value, newValue)) return
|
|
64
|
-
this.#value = newValue
|
|
65
|
-
notifyOf(this)
|
|
66
|
-
|
|
67
|
-
// Setting to UNSET clears the watchers so the signal can be garbage collected
|
|
68
|
-
if (UNSET === this.#value) unsubscribeAllFrom(this)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Update the value of the state signal.
|
|
73
|
-
*
|
|
74
|
-
* @param {Function} updater - Function that takes the current value and returns the new value
|
|
75
|
-
* @returns {void}
|
|
76
|
-
* @throws {InvalidCallbackError} - If the updater function is not a function
|
|
77
|
-
* @throws {NullishSignalValueError} - If the initial value is null or undefined
|
|
78
|
-
* @throws {InvalidSignalValueError} - If the initial value is invalid
|
|
79
|
-
*/
|
|
80
|
-
update(updater: (oldValue: T) => T): void {
|
|
81
|
-
validateCallback(`${TYPE_STATE} update`, updater)
|
|
82
|
-
|
|
83
|
-
this.set(updater(this.#value))
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/* === Functions === */
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Check if the provided value is a State instance
|
|
91
|
-
*
|
|
92
|
-
* @since 0.9.0
|
|
93
|
-
* @param {unknown} value - Value to check
|
|
94
|
-
* @returns {boolean} - True if the value is a State instance, false otherwise
|
|
95
|
-
*/
|
|
96
|
-
const isState = /*#__PURE__*/ <T extends {}>(
|
|
97
|
-
value: unknown,
|
|
98
|
-
): value is State<T> => isObjectOfType(value, TYPE_STATE)
|
|
99
|
-
|
|
100
|
-
/* === Exports === */
|
|
101
|
-
|
|
102
|
-
export { TYPE_STATE, isState, State }
|