@zeix/cause-effect 0.17.3 → 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.
Files changed (89) hide show
  1. package/.ai-context.md +163 -232
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +166 -116
  4. package/ARCHITECTURE.md +274 -0
  5. package/CLAUDE.md +199 -143
  6. package/COLLECTION_REFACTORING.md +161 -0
  7. package/GUIDE.md +298 -0
  8. package/README.md +232 -197
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/index.dev.js +1325 -997
  12. package/index.js +1 -1
  13. package/index.ts +58 -74
  14. package/package.json +4 -1
  15. package/src/errors.ts +118 -74
  16. package/src/graph.ts +601 -0
  17. package/src/nodes/collection.ts +474 -0
  18. package/src/nodes/effect.ts +149 -0
  19. package/src/nodes/list.ts +588 -0
  20. package/src/nodes/memo.ts +120 -0
  21. package/src/nodes/sensor.ts +139 -0
  22. package/src/nodes/state.ts +135 -0
  23. package/src/nodes/store.ts +383 -0
  24. package/src/nodes/task.ts +146 -0
  25. package/src/signal.ts +112 -66
  26. package/src/util.ts +26 -57
  27. package/test/batch.test.ts +96 -62
  28. package/test/benchmark.test.ts +473 -487
  29. package/test/collection.test.ts +466 -706
  30. package/test/effect.test.ts +293 -696
  31. package/test/list.test.ts +335 -592
  32. package/test/memo.test.ts +380 -0
  33. package/test/regression.test.ts +156 -0
  34. package/test/scope.test.ts +191 -0
  35. package/test/sensor.test.ts +454 -0
  36. package/test/signal.test.ts +220 -213
  37. package/test/state.test.ts +217 -265
  38. package/test/store.test.ts +346 -446
  39. package/test/task.test.ts +395 -0
  40. package/test/untrack.test.ts +167 -0
  41. package/types/index.d.ts +13 -15
  42. package/types/src/errors.d.ts +73 -17
  43. package/types/src/graph.d.ts +208 -0
  44. package/types/src/nodes/collection.d.ts +64 -0
  45. package/types/src/nodes/effect.d.ts +48 -0
  46. package/types/src/nodes/list.d.ts +65 -0
  47. package/types/src/nodes/memo.d.ts +57 -0
  48. package/types/src/nodes/sensor.d.ts +75 -0
  49. package/types/src/nodes/state.d.ts +78 -0
  50. package/types/src/nodes/store.d.ts +51 -0
  51. package/types/src/nodes/task.d.ts +73 -0
  52. package/types/src/signal.d.ts +43 -29
  53. package/types/src/util.d.ts +9 -16
  54. package/archive/benchmark.ts +0 -683
  55. package/archive/collection.ts +0 -253
  56. package/archive/composite.ts +0 -85
  57. package/archive/computed.ts +0 -195
  58. package/archive/list.ts +0 -483
  59. package/archive/memo.ts +0 -139
  60. package/archive/state.ts +0 -90
  61. package/archive/store.ts +0 -298
  62. package/archive/task.ts +0 -189
  63. package/src/classes/collection.ts +0 -245
  64. package/src/classes/computed.ts +0 -349
  65. package/src/classes/list.ts +0 -343
  66. package/src/classes/ref.ts +0 -70
  67. package/src/classes/state.ts +0 -102
  68. package/src/classes/store.ts +0 -262
  69. package/src/diff.ts +0 -138
  70. package/src/effect.ts +0 -93
  71. package/src/match.ts +0 -45
  72. package/src/resolve.ts +0 -49
  73. package/src/system.ts +0 -257
  74. package/test/computed.test.ts +0 -1108
  75. package/test/diff.test.ts +0 -955
  76. package/test/match.test.ts +0 -388
  77. package/test/ref.test.ts +0 -353
  78. package/test/resolve.test.ts +0 -154
  79. package/types/src/classes/collection.d.ts +0 -45
  80. package/types/src/classes/computed.d.ts +0 -94
  81. package/types/src/classes/list.d.ts +0 -43
  82. package/types/src/classes/ref.d.ts +0 -35
  83. package/types/src/classes/state.d.ts +0 -49
  84. package/types/src/classes/store.d.ts +0 -52
  85. package/types/src/diff.d.ts +0 -28
  86. package/types/src/effect.d.ts +0 -15
  87. package/types/src/match.d.ts +0 -21
  88. package/types/src/resolve.d.ts +0 -29
  89. package/types/src/system.d.ts +0 -78
package/archive/list.ts DELETED
@@ -1,483 +0,0 @@
1
- import { type DiffResult, diff, isEqual, type UnknownArray } from '../src/diff'
2
- import {
3
- DuplicateKeyError,
4
- InvalidSignalValueError,
5
- NullishSignalValueError,
6
- ReadonlySignalError,
7
- } from '../src/errors'
8
- import { isMutableSignal, type MutableSignal } from '../src/signal'
9
- import {
10
- batch,
11
- notifyWatchers,
12
- subscribeActiveWatcher,
13
- UNSET,
14
- type Watcher,
15
- } from '../src/system'
16
- import {
17
- isFunction,
18
- isNumber,
19
- isObjectOfType,
20
- isRecord,
21
- isString,
22
- isSymbol,
23
- } from '../src/util'
24
- import {
25
- type Collection,
26
- type CollectionCallback,
27
- createCollection,
28
- } from './collection'
29
- import { isComputed } from './computed'
30
- import { createState, isState } from './state'
31
- import { createStore, isStore } from './store'
32
-
33
- /* === Types === */
34
-
35
- type ArrayToRecord<T extends UnknownArray> = {
36
- [key: string]: T extends Array<infer U extends {}> ? U : never
37
- }
38
-
39
- type KeyConfig<T> = string | ((item: T) => string)
40
-
41
- type List<T extends {}> = {
42
- readonly [Symbol.toStringTag]: 'List'
43
- [Symbol.iterator](): IterableIterator<MutableSignal<T>>
44
- readonly [Symbol.isConcatSpreadable]: boolean
45
- [n: number]: MutableSignal<T>
46
- readonly length: number
47
- add(value: T): string
48
- byKey(key: string): MutableSignal<T> | undefined
49
- deriveCollection<U extends {}>(
50
- callback: CollectionCallback<U, T extends UnknownArray ? T : never>,
51
- ): Collection<U>
52
- get(): T
53
- keyAt(index: number): string | undefined
54
- indexOfKey(key: string): number
55
- set(value: T): void
56
- update(fn: (value: T) => T): void
57
- sort<U = T>(compareFn?: (a: U, b: U) => number): void
58
- splice(start: number, deleteCount?: number, ...items: T[]): T[]
59
- remove(index: number): void
60
- }
61
-
62
- /* === Constants === */
63
-
64
- const TYPE_LIST = 'List' as const
65
-
66
- /* === Functions === */
67
-
68
- /**
69
- * Create a new list with deeply nested reactive list items
70
- *
71
- * @since 0.16.2
72
- * @param {T} initialValue - Initial array of the list
73
- * @param {KeyConfig<T>} keyConfig - Optional key configuration:
74
- * - string: used as prefix for auto-incrementing IDs (e.g., "item" → "item0", "item1")
75
- * - function: computes key from array item at creation time
76
- * @returns {List<T>} - New list with reactive items of type T
77
- */
78
- const createList = <T extends {}>(
79
- initialValue: T[],
80
- keyConfig?: KeyConfig<T>,
81
- ): List<T> => {
82
- if (initialValue == null) throw new NullishSignalValueError('store')
83
-
84
- const watchers = new Set<Watcher>()
85
- const signals = new Map<string, MutableSignal<T>>()
86
- const ownWatchers = new Map<string, Watcher>()
87
-
88
- // Stable key support for lists
89
- let keyCounter = 0
90
- let order: string[] = []
91
-
92
- // Get signal by key or index
93
- const getSignal = (prop: string): MutableSignal<T> | undefined => {
94
- let key = prop
95
- const index = Number(prop)
96
- if (Number.isInteger(index) && index >= 0) key = order[index] ?? prop
97
- return signals.get(key)
98
- }
99
-
100
- // Generate stable key for array items
101
- const generateKey = (item: T): string => {
102
- const id = keyCounter++
103
- return isString(keyConfig)
104
- ? `${keyConfig}${id}`
105
- : isFunction<string>(keyConfig)
106
- ? keyConfig(item)
107
- : String(id)
108
- }
109
-
110
- // Convert array to record with stable keys
111
- const arrayToRecord = (array: T[]): ArrayToRecord<T[]> => {
112
- const record = {} as Record<string, T>
113
-
114
- for (let i = 0; i < array.length; i++) {
115
- const value = array[i]
116
- if (value === undefined) continue // Skip sparse array positions
117
-
118
- let key = order[i]
119
- if (!key) {
120
- key = generateKey(value)
121
- order[i] = key
122
- }
123
- record[key] = value
124
- }
125
- return record
126
- }
127
-
128
- // Get current record
129
- const current = (): T[] =>
130
- order
131
- .map(key => signals.get(key)?.get())
132
- .filter(v => v !== undefined) as T[]
133
-
134
- // Validate input
135
- const isValidValue = <T>(
136
- key: string,
137
- value: T,
138
- ): value is NonNullable<T> => {
139
- if (value == null)
140
- throw new NullishSignalValueError(`store for key "${key}"`)
141
- if (value === UNSET) return true
142
- if (isSymbol(value) || isFunction(value) || isComputed(value))
143
- throw new InvalidSignalValueError(`store for key "${key}"`, value)
144
- return true
145
- }
146
-
147
- // Add nested signal and own watcher
148
- const addProperty = <K extends keyof T & string>(
149
- key: K,
150
- value: T[K],
151
- single = false,
152
- ): boolean => {
153
- if (!isValidValue(key, value)) return false
154
-
155
- // Create signal for key
156
- // @ts-expect-error ignore
157
- const signal: MutableSignal<T[K] & {}> =
158
- isState(value) || isStore(value)
159
- ? (value as unknown as MutableSignal<T[K] & {}>)
160
- : isRecord(value) || Array.isArray(value)
161
- ? createStore(value)
162
- : createState(value)
163
-
164
- // Set internal states
165
- // @ts-expect-error ignore
166
- signals.set(key, signal)
167
- if (!order.includes(key)) order.push(key)
168
-
169
- if (single) {
170
- notifyWatchers(watchers)
171
- }
172
- return true
173
- }
174
-
175
- // Remove nested signal and effect
176
- const removeProperty = (key: string, single = false) => {
177
- // Remove signal for key
178
- const ok = signals.delete(key)
179
- if (!ok) return
180
-
181
- // Clean up internal states
182
- const index = order.indexOf(key)
183
- if (index >= 0) order.splice(index, 1)
184
- const watcher = ownWatchers.get(key)
185
- if (watcher) {
186
- watcher.stop()
187
- ownWatchers.delete(key)
188
- }
189
-
190
- if (single) {
191
- order = order.filter(() => true) // Compact array
192
- notifyWatchers(watchers)
193
- }
194
- }
195
-
196
- // Commit batched changes and emit notifications
197
- const batchChanges = (changes: DiffResult) => {
198
- // Additions
199
- if (Object.keys(changes.add).length) {
200
- for (const key in changes.add)
201
- // @ts-expect-error ignore
202
- addProperty(key, changes.add[key] as T, false)
203
- }
204
-
205
- // Changes
206
- if (Object.keys(changes.change).length) {
207
- batch(() => {
208
- for (const key in changes.change) {
209
- const value = changes.change[key] as T
210
- if (!isValidValue(key, value)) continue
211
-
212
- const signal = signals.get(key)
213
- if (isMutableSignal(signal)) signal.set(value)
214
- else throw new ReadonlySignalError(key, value)
215
- }
216
- })
217
- }
218
-
219
- // Removals
220
- if (Object.keys(changes.remove).length) {
221
- for (const key in changes.remove) removeProperty(key)
222
- order = order.filter(() => true)
223
- }
224
-
225
- return changes.changed
226
- }
227
-
228
- // Reconcile data and dispatch events
229
- const reconcile = (oldValue: T[], newValue: T[]): boolean =>
230
- batchChanges(diff(arrayToRecord(oldValue), arrayToRecord(newValue)))
231
-
232
- // Initialize data
233
- reconcile([] as T[], initialValue)
234
-
235
- // Methods and Properties
236
- const prototype: Record<PropertyKey, unknown> = {}
237
- Object.defineProperties(prototype, {
238
- [Symbol.toStringTag]: {
239
- value: TYPE_LIST,
240
- },
241
- [Symbol.isConcatSpreadable]: {
242
- value: true,
243
- },
244
- [Symbol.iterator]: {
245
- value: function* () {
246
- for (const key of order) {
247
- const signal = signals.get(key)
248
- if (signal) yield signal
249
- }
250
- },
251
- },
252
- length: {
253
- get(): number {
254
- subscribeActiveWatcher(watchers)
255
- return signals.size
256
- },
257
- },
258
- order: {
259
- get(): string[] {
260
- subscribeActiveWatcher(watchers)
261
- return order
262
- },
263
- },
264
- at: {
265
- value(index: number): MutableSignal<T> | undefined {
266
- return signals.get(order[index])
267
- },
268
- },
269
- byKey: {
270
- value: (key: string): MutableSignal<T> | undefined => {
271
- return getSignal(key)
272
- },
273
- },
274
- deriveCollection: {
275
- value: <U extends {}>(
276
- callback: CollectionCallback<U, T>,
277
- ): Collection<U> => {
278
- const collection = createCollection(list, callback)
279
- return collection
280
- },
281
- },
282
- keyAt: {
283
- value(index: number): string | undefined {
284
- return order[index]
285
- },
286
- },
287
- indexOfKey: {
288
- value(key: string): number {
289
- return order.indexOf(key)
290
- },
291
- },
292
- get: {
293
- value: (): T[] => {
294
- subscribeActiveWatcher(watchers)
295
- return current()
296
- },
297
- },
298
- set: {
299
- value: (newValue: T[]): void => {
300
- if (reconcile(current(), newValue)) {
301
- notifyWatchers(watchers)
302
- if (UNSET === newValue) watchers.clear()
303
- }
304
- },
305
- },
306
- update: {
307
- value: (fn: (oldValue: T[]) => T[]): void => {
308
- const oldValue = current()
309
- const newValue = fn(oldValue)
310
- if (reconcile(oldValue, newValue)) {
311
- notifyWatchers(watchers)
312
- if (UNSET === newValue) watchers.clear()
313
- }
314
- },
315
- },
316
- add: {
317
- value: (value: T): string => {
318
- const key = generateKey(value)
319
- if (!signals.has(key)) {
320
- // @ts-expect-error ignore
321
- addProperty(key, value, true)
322
- return key
323
- } else throw new DuplicateKeyError('store', key, value)
324
- },
325
- },
326
- remove: {
327
- value: (keyOrIndex: string | number): void => {
328
- const key = isNumber(keyOrIndex)
329
- ? order[keyOrIndex]
330
- : keyOrIndex
331
- if (key && signals.has(key)) removeProperty(key, true)
332
- },
333
- },
334
- sort: {
335
- value: (
336
- compareFn?: <
337
- U = T extends UnknownArray
338
- ? T
339
- : T[Extract<keyof T, string>],
340
- >(
341
- a: U,
342
- b: U,
343
- ) => number,
344
- ): void => {
345
- const entries = order
346
- .map(key => [key, signals.get(key)?.get()] as [string, T])
347
- .sort(
348
- compareFn
349
- ? (a, b) => compareFn(a[1], b[1])
350
- : (a, b) =>
351
- String(a[1]).localeCompare(String(b[1])),
352
- )
353
-
354
- // Set new order
355
- const newOrder = entries.map(([key]) => key)
356
- if (!isEqual(newOrder, order)) {
357
- order = newOrder
358
- notifyWatchers(watchers)
359
- }
360
- },
361
- },
362
- splice: {
363
- value: (
364
- start: number,
365
- deleteCount?: number,
366
- ...items: T[]
367
- ): T[] => {
368
- // Normalize start and deleteCount
369
- const length = signals.size
370
- const actualStart =
371
- start < 0
372
- ? Math.max(0, length + start)
373
- : Math.min(start, length)
374
- const actualDeleteCount = Math.max(
375
- 0,
376
- Math.min(
377
- deleteCount ??
378
- Math.max(0, length - Math.max(0, actualStart)),
379
- length - actualStart,
380
- ),
381
- )
382
-
383
- const add = {} as Record<string, T>
384
- const remove = {} as Record<string, T>
385
-
386
- // Collect items to delete and their keys
387
- for (let i = 0; i < actualDeleteCount; i++) {
388
- const index = actualStart + i
389
- const key = order[index]
390
- if (key) {
391
- const signal = signals.get(key)
392
- if (signal) remove[key] = signal.get() as T
393
- }
394
- }
395
-
396
- // Build new order: items before splice point
397
- const newOrder = order.slice(0, actualStart)
398
-
399
- // Add new items
400
- for (const item of items) {
401
- const key = generateKey(item)
402
- newOrder.push(key)
403
- add[key] = item as T
404
- }
405
-
406
- // Add items after splice point
407
- newOrder.push(...order.slice(actualStart + actualDeleteCount))
408
-
409
- // Update the order array
410
- order = newOrder.filter(() => true) // Compact array
411
-
412
- const changed = !!(
413
- Object.keys(add).length || Object.keys(remove).length
414
- )
415
-
416
- if (changed)
417
- batchChanges({
418
- add,
419
- change: {} as Record<string, T>,
420
- remove,
421
- changed,
422
- })
423
-
424
- notifyWatchers(watchers)
425
-
426
- return Object.values(remove) as T[]
427
- },
428
- },
429
- })
430
-
431
- // Return proxy directly with integrated signal methods
432
- const list = new Proxy(prototype as List<T>, {
433
- get(target, prop) {
434
- if (prop in target) return Reflect.get(target, prop)
435
- if (!isSymbol(prop)) return getSignal(prop)
436
- },
437
- has(target, prop) {
438
- if (prop in target) return true
439
- return signals.has(String(prop))
440
- },
441
- ownKeys(target) {
442
- const staticKeys = Reflect.ownKeys(target)
443
- return [...new Set([...order, ...staticKeys])]
444
- },
445
- getOwnPropertyDescriptor(target, prop) {
446
- if (prop in target)
447
- return Reflect.getOwnPropertyDescriptor(target, prop)
448
- if (isSymbol(prop)) return undefined
449
-
450
- const signal = getSignal(prop)
451
- return signal
452
- ? {
453
- enumerable: true,
454
- configurable: true,
455
- writable: true,
456
- value: signal,
457
- }
458
- : undefined
459
- },
460
- })
461
- return list
462
- }
463
-
464
- /**
465
- * Check if the provided value is a List instance
466
- *
467
- * @since 0.15.0
468
- * @param {unknown} value - value to check
469
- * @returns {boolean} - true if the value is a List instance, false otherwise
470
- */
471
- const isList = <T extends {}>(value: unknown): value is List<T> =>
472
- isObjectOfType(value, TYPE_LIST)
473
-
474
- /* === Exports === */
475
-
476
- export {
477
- TYPE_LIST,
478
- isList,
479
- createList,
480
- type ArrayToRecord,
481
- type List,
482
- type KeyConfig,
483
- }
package/archive/memo.ts DELETED
@@ -1,139 +0,0 @@
1
- import {
2
- CircularDependencyError,
3
- createError,
4
- InvalidCallbackError,
5
- NullishSignalValueError,
6
- } from '../src/errors'
7
- import {
8
- createWatcher,
9
- flush,
10
- notifyWatchers,
11
- subscribeActiveWatcher,
12
- UNSET,
13
- type Watcher,
14
- } from '../src/system'
15
- import { isObjectOfType, isSyncFunction } from '../src/util'
16
-
17
- /* === Types === */
18
-
19
- type Memo<T extends {}> = {
20
- readonly [Symbol.toStringTag]: 'Memo'
21
- get(): T
22
- }
23
-
24
- type MemoCallback<T extends {} & { then?: undefined }> = (oldValue: T) => T
25
-
26
- /* === Constants === */
27
-
28
- const TYPE_MEMO = 'Memo' as const
29
-
30
- /* === Functions === */
31
-
32
- /**
33
- * Create a derived signal from existing signals
34
- *
35
- * @since 0.9.0
36
- * @param {MemoCallback<T>} callback - Computation callback function
37
- * @returns {Memo<T>} - Computed signal
38
- */
39
- const createMemo = <T extends {}>(
40
- callback: MemoCallback<T>,
41
- initialValue: T = UNSET,
42
- ): Memo<T> => {
43
- if (!isMemoCallback(callback))
44
- throw new InvalidCallbackError('memo', callback)
45
- if (initialValue == null) throw new NullishSignalValueError('memo')
46
-
47
- const watchers: Set<Watcher> = new Set()
48
-
49
- // Internal state
50
- let value: T = initialValue
51
- let error: Error | undefined
52
- let dirty = true
53
- let computing = false
54
-
55
- // Own watcher: called when notified from sources (push)
56
- const watcher = createWatcher(
57
- () => {
58
- dirty = true
59
- if (watchers.size) notifyWatchers(watchers)
60
- else watcher.stop()
61
- },
62
- () => {
63
- if (computing) throw new CircularDependencyError('memo')
64
- let result: T
65
- computing = true
66
- try {
67
- result = callback(value)
68
- } catch (e) {
69
- // Err track
70
- value = UNSET
71
- error = createError(e)
72
- computing = false
73
- return
74
- }
75
-
76
- if (null == result || UNSET === result) {
77
- // Nil track
78
- value = UNSET
79
- error = undefined
80
- } else {
81
- // Ok track
82
- value = result
83
- error = undefined
84
- dirty = false
85
- }
86
- computing = false
87
- },
88
- )
89
-
90
- const memo: Record<PropertyKey, unknown> = {}
91
- Object.defineProperties(memo, {
92
- [Symbol.toStringTag]: {
93
- value: TYPE_MEMO,
94
- },
95
- get: {
96
- value: (): T => {
97
- subscribeActiveWatcher(watchers)
98
- flush()
99
-
100
- if (dirty) watcher.run()
101
- if (error) throw error
102
- return value
103
- },
104
- },
105
- })
106
- return memo as Memo<T>
107
- }
108
-
109
- /**
110
- * Check if a value is a memoized signal
111
- *
112
- * @since 0.9.0
113
- * @param {unknown} value - Value to check
114
- * @returns {boolean} - True if value is a memo signal, false otherwise
115
- */
116
- const isMemo = /*#__PURE__*/ <T extends {}>(value: unknown): value is Memo<T> =>
117
- isObjectOfType(value, TYPE_MEMO)
118
-
119
- /**
120
- * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
121
- *
122
- * @since 0.12.0
123
- * @param {unknown} value - Value to check
124
- * @returns {boolean} - True if value is a sync callback, false otherwise
125
- */
126
- const isMemoCallback = /*#__PURE__*/ <T extends {} & { then?: undefined }>(
127
- value: unknown,
128
- ): value is MemoCallback<T> => isSyncFunction(value) && value.length < 2
129
-
130
- /* === Exports === */
131
-
132
- export {
133
- TYPE_MEMO,
134
- createMemo,
135
- isMemo,
136
- isMemoCallback,
137
- type Memo,
138
- type MemoCallback,
139
- }
package/archive/state.ts DELETED
@@ -1,90 +0,0 @@
1
- import { isEqual } from '../src/diff'
2
- import { InvalidCallbackError, NullishSignalValueError } from '../src/errors'
3
- import {
4
- notifyWatchers,
5
- subscribeActiveWatcher,
6
- UNSET,
7
- type Watcher,
8
- } from '../src/system'
9
- import { isFunction, isObjectOfType } from '../src/util'
10
-
11
- /* === Types === */
12
-
13
- type State<T extends {}> = {
14
- readonly [Symbol.toStringTag]: 'State'
15
- get(): T
16
- set(newValue: T): void
17
- update(updater: (oldValue: T) => T): void
18
- }
19
-
20
- /* === Constants === */
21
-
22
- const TYPE_STATE = 'State' as const
23
-
24
- /* === Functions === */
25
-
26
- /**
27
- * Create a new state signal
28
- *
29
- * @since 0.9.0
30
- * @param {T} initialValue - initial value of the state
31
- * @returns {State<T>} - new state signal
32
- */
33
- const createState = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
34
- if (initialValue == null) throw new NullishSignalValueError('state')
35
-
36
- const watchers: Set<Watcher> = new Set()
37
- let value: T = initialValue
38
-
39
- const setValue = (newValue: T) => {
40
- if (newValue == null) throw new NullishSignalValueError('state')
41
- if (isEqual(value, newValue)) return
42
- value = newValue
43
- notifyWatchers(watchers)
44
-
45
- // Setting to UNSET clears the watchers so the signal can be garbage collected
46
- if (UNSET === value) watchers.clear()
47
- }
48
-
49
- const state: Record<PropertyKey, unknown> = {}
50
- Object.defineProperties(state, {
51
- [Symbol.toStringTag]: {
52
- value: TYPE_STATE,
53
- },
54
- get: {
55
- value: () => {
56
- subscribeActiveWatcher(watchers)
57
- return value
58
- },
59
- },
60
- set: {
61
- value: (newValue: T) => {
62
- setValue(newValue)
63
- },
64
- },
65
- update: {
66
- value: (updater: (oldValue: T) => T) => {
67
- if (!isFunction(updater))
68
- throw new InvalidCallbackError('state update', updater)
69
-
70
- setValue(updater(value))
71
- },
72
- },
73
- })
74
- return state as State<T>
75
- }
76
-
77
- /**
78
- * Check if the provided value is a State instance
79
- *
80
- * @since 0.9.0
81
- * @param {unknown} value - value to check
82
- * @returns {boolean} - true if the value is a State instance, false otherwise
83
- */
84
- const isState = /*#__PURE__*/ <T extends {}>(
85
- value: unknown,
86
- ): value is State<T> => isObjectOfType(value, TYPE_STATE)
87
-
88
- /* === Exports === */
89
-
90
- export { TYPE_STATE, isState, createState, type State }