@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.
Files changed (94) hide show
  1. package/.ai-context.md +169 -227
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +176 -116
  4. package/ARCHITECTURE.md +276 -0
  5. package/CHANGELOG.md +29 -0
  6. package/CLAUDE.md +201 -143
  7. package/GUIDE.md +298 -0
  8. package/README.md +246 -193
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/context7.json +4 -0
  12. package/examples/events-sensor.ts +187 -0
  13. package/examples/selector-sensor.ts +173 -0
  14. package/index.dev.js +1390 -1008
  15. package/index.js +1 -1
  16. package/index.ts +60 -74
  17. package/package.json +5 -2
  18. package/skills/changelog-keeper/SKILL.md +59 -0
  19. package/skills/changelog-keeper/agents/openai.yaml +4 -0
  20. package/src/errors.ts +118 -74
  21. package/src/graph.ts +612 -0
  22. package/src/nodes/collection.ts +512 -0
  23. package/src/nodes/effect.ts +149 -0
  24. package/src/nodes/list.ts +589 -0
  25. package/src/nodes/memo.ts +148 -0
  26. package/src/nodes/sensor.ts +149 -0
  27. package/src/nodes/state.ts +135 -0
  28. package/src/nodes/store.ts +378 -0
  29. package/src/nodes/task.ts +174 -0
  30. package/src/signal.ts +112 -66
  31. package/src/util.ts +26 -57
  32. package/test/batch.test.ts +96 -62
  33. package/test/benchmark.test.ts +473 -487
  34. package/test/collection.test.ts +456 -707
  35. package/test/effect.test.ts +293 -696
  36. package/test/list.test.ts +335 -592
  37. package/test/memo.test.ts +574 -0
  38. package/test/regression.test.ts +156 -0
  39. package/test/scope.test.ts +191 -0
  40. package/test/sensor.test.ts +454 -0
  41. package/test/signal.test.ts +220 -213
  42. package/test/state.test.ts +217 -265
  43. package/test/store.test.ts +346 -446
  44. package/test/task.test.ts +529 -0
  45. package/test/untrack.test.ts +167 -0
  46. package/types/index.d.ts +13 -15
  47. package/types/src/errors.d.ts +73 -17
  48. package/types/src/graph.d.ts +218 -0
  49. package/types/src/nodes/collection.d.ts +69 -0
  50. package/types/src/nodes/effect.d.ts +48 -0
  51. package/types/src/nodes/list.d.ts +66 -0
  52. package/types/src/nodes/memo.d.ts +63 -0
  53. package/types/src/nodes/sensor.d.ts +81 -0
  54. package/types/src/nodes/state.d.ts +78 -0
  55. package/types/src/nodes/store.d.ts +51 -0
  56. package/types/src/nodes/task.d.ts +79 -0
  57. package/types/src/signal.d.ts +43 -29
  58. package/types/src/util.d.ts +9 -16
  59. package/archive/benchmark.ts +0 -683
  60. package/archive/collection.ts +0 -253
  61. package/archive/composite.ts +0 -85
  62. package/archive/computed.ts +0 -195
  63. package/archive/list.ts +0 -483
  64. package/archive/memo.ts +0 -139
  65. package/archive/state.ts +0 -90
  66. package/archive/store.ts +0 -298
  67. package/archive/task.ts +0 -189
  68. package/src/classes/collection.ts +0 -245
  69. package/src/classes/computed.ts +0 -349
  70. package/src/classes/list.ts +0 -343
  71. package/src/classes/ref.ts +0 -70
  72. package/src/classes/state.ts +0 -102
  73. package/src/classes/store.ts +0 -262
  74. package/src/diff.ts +0 -138
  75. package/src/effect.ts +0 -93
  76. package/src/match.ts +0 -45
  77. package/src/resolve.ts +0 -49
  78. package/src/system.ts +0 -257
  79. package/test/computed.test.ts +0 -1108
  80. package/test/diff.test.ts +0 -955
  81. package/test/match.test.ts +0 -388
  82. package/test/ref.test.ts +0 -353
  83. package/test/resolve.test.ts +0 -154
  84. package/types/src/classes/collection.d.ts +0 -45
  85. package/types/src/classes/computed.d.ts +0 -94
  86. package/types/src/classes/list.d.ts +0 -43
  87. package/types/src/classes/ref.d.ts +0 -35
  88. package/types/src/classes/state.d.ts +0 -49
  89. package/types/src/classes/store.d.ts +0 -52
  90. package/types/src/diff.d.ts +0 -28
  91. package/types/src/effect.d.ts +0 -15
  92. package/types/src/match.d.ts +0 -21
  93. package/types/src/resolve.d.ts +0 -29
  94. package/types/src/system.d.ts +0 -78
@@ -1,253 +0,0 @@
1
- import type { UnknownArray } from '../src/diff'
2
- import { match } from '../src/match'
3
- import { resolve } from '../src/resolve'
4
- import type { Signal } from '../src/signal'
5
- import {
6
- createWatcher,
7
- notifyWatchers,
8
- subscribeActiveWatcher,
9
- UNSET,
10
- type Watcher,
11
- } from '../src/system'
12
- import { isAsyncFunction, isObjectOfType, isSymbol } from '../src/util'
13
- import { type Computed, createComputed } from './computed'
14
- import type { List } from './list'
15
-
16
- /* === Types === */
17
-
18
- type CollectionKeySignal<T extends {}> = T extends UnknownArray
19
- ? Collection<T>
20
- : Computed<T>
21
-
22
- type CollectionCallback<T extends {} & { then?: undefined }, O extends {}> =
23
- | ((originValue: O, abort: AbortSignal) => Promise<T>)
24
- | ((originValue: O) => T)
25
-
26
- type Collection<T extends {}> = {
27
- readonly [Symbol.toStringTag]: typeof TYPE_COLLECTION
28
- readonly [Symbol.isConcatSpreadable]: boolean
29
- [Symbol.iterator](): IterableIterator<CollectionKeySignal<T>>
30
- readonly [n: number]: CollectionKeySignal<T>
31
- readonly length: number
32
-
33
- byKey(key: string): CollectionKeySignal<T> | undefined
34
- get(): T[]
35
- keyAt(index: number): string | undefined
36
- indexOfKey(key: string): number
37
- sort(compareFn?: (a: T, b: T) => number): void
38
- }
39
-
40
- /* === Constants === */
41
-
42
- const TYPE_COLLECTION = 'Collection' as const
43
-
44
- /* === Exported Functions === */
45
-
46
- /**
47
- * Collections - Read-Only Derived Array-Like Stores
48
- *
49
- * Collections are the read-only, derived counterpart to array-like Stores.
50
- * They provide reactive, memoized, and lazily-evaluated array transformations
51
- * while maintaining the familiar array-like store interface.
52
- *
53
- * @since 0.16.2
54
- * @param {List<O> | Collection<O>} origin - Origin of collection to derive values from
55
- * @param {ComputedCallback<ArrayItem<T>>} callback - Callback function to transform array items
56
- * @returns {Collection<T>} - New collection with reactive properties that preserves the original type T
57
- */
58
- const createCollection = <T extends {}, O extends {}>(
59
- origin: List<O> | Collection<O>,
60
- callback: CollectionCallback<T, O>,
61
- ): Collection<T> => {
62
- const watchers = new Set<Watcher>()
63
- const signals = new Map<string, Signal<T>>()
64
- const signalWatchers = new Map<string, Watcher>()
65
-
66
- let order: string[] = []
67
-
68
- // Add nested signal and effect
69
- const addProperty = (key: string): boolean => {
70
- const computedCallback = isAsyncFunction(callback)
71
- ? async (_: T, abort: AbortSignal) => {
72
- const originSignal = origin.byKey(key)
73
- if (!originSignal) return UNSET
74
-
75
- let result = UNSET
76
- match(resolve({ originSignal }), {
77
- ok: async ({ originSignal: originValue }) => {
78
- result = await callback(originValue, abort)
79
- },
80
- err: (errors: readonly Error[]) => {
81
- console.log(errors)
82
- },
83
- })
84
- return result
85
- }
86
- : () => {
87
- const originSignal = origin.byKey(key)
88
- if (!originSignal) return UNSET
89
-
90
- let result = UNSET
91
- match(resolve({ originSignal }), {
92
- ok: ({ originSignal: originValue }) => {
93
- result = (callback as (originValue: O) => T)(
94
- originValue as unknown as O,
95
- )
96
- },
97
- err: (errors: readonly Error[]) => {
98
- console.log(errors)
99
- },
100
- })
101
- return result
102
- }
103
-
104
- const signal = createComputed(computedCallback)
105
-
106
- // Set internal states
107
- signals.set(key, signal)
108
- if (!order.includes(key)) order.push(key)
109
- const watcher = createWatcher(
110
- () => {
111
- signal.get() // Subscribe to the signal
112
- },
113
- () => {},
114
- )
115
- watcher()
116
- signalWatchers.set(key, watcher)
117
- return true
118
- }
119
-
120
- // Initialize properties
121
- for (let i = 0; i < origin.length; i++) {
122
- const key = origin.keyAt(i)
123
- if (!key) continue
124
- addProperty(key)
125
- }
126
-
127
- // Get signal by key or index
128
- const getSignal = (prop: string): Signal<T> | undefined => {
129
- let key = prop
130
- const index = Number(prop)
131
- if (Number.isInteger(index) && index >= 0) key = order[index] ?? prop
132
- return signals.get(key)
133
- }
134
-
135
- // Get current array
136
- const current = (): T =>
137
- order
138
- .map(key => signals.get(key)?.get())
139
- .filter(v => v !== UNSET) as unknown as T
140
-
141
- // Methods and Properties
142
- const collection: Record<PropertyKey, unknown> = {}
143
- Object.defineProperties(collection, {
144
- [Symbol.toStringTag]: {
145
- value: TYPE_COLLECTION,
146
- },
147
- [Symbol.isConcatSpreadable]: {
148
- value: true,
149
- },
150
- [Symbol.iterator]: {
151
- value: function* () {
152
- for (const key of order) {
153
- const signal = signals.get(key)
154
- if (signal) yield signal
155
- }
156
- },
157
- },
158
- byKey: {
159
- value(key: string) {
160
- return getSignal(key)
161
- },
162
- },
163
- keyAt: {
164
- value(index: number): string | undefined {
165
- return order[index]
166
- },
167
- },
168
- indexOfKey: {
169
- value(key: string): number {
170
- return order.indexOf(key)
171
- },
172
- },
173
- get: {
174
- value: (): T => {
175
- subscribeActiveWatcher(watchers)
176
- return current()
177
- },
178
- },
179
- sort: {
180
- value: (compareFn?: (a: T, b: T) => number): void => {
181
- const entries = order
182
- .map((key, index) => {
183
- const signal = signals.get(key)
184
- return [
185
- index,
186
- key,
187
- signal ? signal.get() : undefined,
188
- ] as [number, string, T]
189
- })
190
- .sort(
191
- compareFn
192
- ? (a, b) => compareFn(a[2], b[2])
193
- : (a, b) =>
194
- String(a[2]).localeCompare(String(b[2])),
195
- )
196
-
197
- // Set new order
198
- order = entries.map(([_, key]) => key)
199
-
200
- notifyWatchers(watchers)
201
- },
202
- },
203
- length: {
204
- get(): number {
205
- subscribeActiveWatcher(watchers)
206
- return signals.size
207
- },
208
- },
209
- })
210
-
211
- // Return proxy directly with integrated signal methods
212
- return new Proxy(collection as Collection<T>, {
213
- get(target, prop) {
214
- if (prop in target) return Reflect.get(target, prop)
215
- if (!isSymbol(prop)) return getSignal(prop)
216
- },
217
- has(target, prop) {
218
- if (prop in target) return true
219
- return signals.has(String(prop))
220
- },
221
- ownKeys(target) {
222
- const staticKeys = Reflect.ownKeys(target)
223
- return [...new Set([...order, ...staticKeys])]
224
- },
225
- getOwnPropertyDescriptor(target, prop) {
226
- if (prop in target)
227
- return Reflect.getOwnPropertyDescriptor(target, prop)
228
- if (isSymbol(prop)) return undefined
229
-
230
- const signal = getSignal(prop)
231
- return signal
232
- ? {
233
- enumerable: true,
234
- configurable: true,
235
- writable: true,
236
- value: signal,
237
- }
238
- : undefined
239
- },
240
- })
241
- }
242
-
243
- const isCollection = /*#__PURE__*/ <T extends UnknownArray>(
244
- value: unknown,
245
- ): value is Collection<T> => isObjectOfType(value, TYPE_COLLECTION)
246
-
247
- export {
248
- type Collection,
249
- type CollectionCallback,
250
- createCollection,
251
- isCollection,
252
- TYPE_COLLECTION,
253
- }
@@ -1,85 +0,0 @@
1
- import type { DiffResult, UnknownRecord } from '../src/diff'
2
- import { guardMutableSignal } from '../src/errors'
3
- import type { Signal } from '../src/signal'
4
- import { batch } from '../src/system'
5
-
6
- /* === Class Definitions === */
7
-
8
- class Composite<T extends UnknownRecord, S extends Signal<T[keyof T] & {}>> {
9
- signals = new Map<string, S>()
10
- #validate: <K extends keyof T & string>(
11
- key: K,
12
- value: unknown,
13
- ) => value is T[K] & {}
14
- #create: <V extends T[keyof T] & {}>(value: V) => S
15
-
16
- constructor(
17
- values: T,
18
- validate: <K extends keyof T & string>(
19
- key: K,
20
- value: unknown,
21
- ) => value is T[K] & {},
22
- create: <V extends T[keyof T] & {}>(value: V) => S,
23
- ) {
24
- this.#validate = validate
25
- this.#create = create
26
- this.change({
27
- add: values,
28
- change: {},
29
- remove: {},
30
- changed: true,
31
- })
32
- }
33
-
34
- add<K extends keyof T & string>(key: K, value: T[K]): boolean {
35
- if (!this.#validate(key, value)) return false
36
-
37
- this.signals.set(key, this.#create(value))
38
- return true
39
- }
40
-
41
- remove<K extends keyof T & string>(key: K): boolean {
42
- return this.signals.delete(key)
43
- }
44
-
45
- change(changes: DiffResult): boolean {
46
- // Additions
47
- if (Object.keys(changes.add).length) {
48
- for (const key in changes.add)
49
- this.add(
50
- key as Extract<keyof T, string>,
51
- changes.add[key] as T[Extract<keyof T, string>] & {},
52
- )
53
- }
54
-
55
- // Changes
56
- if (Object.keys(changes.change).length) {
57
- batch(() => {
58
- for (const key in changes.change) {
59
- const value = changes.change[key]
60
- if (!this.#validate(key as keyof T & string, value))
61
- continue
62
-
63
- const signal = this.signals.get(key)
64
- if (guardMutableSignal(`list item "${key}"`, value, signal))
65
- signal.set(value)
66
- }
67
- })
68
- }
69
-
70
- // Removals
71
- if (Object.keys(changes.remove).length) {
72
- for (const key in changes.remove)
73
- this.remove(key as keyof T & string)
74
- }
75
-
76
- return changes.changed
77
- }
78
-
79
- clear(): boolean {
80
- this.signals.clear()
81
- return true
82
- }
83
- }
84
-
85
- export { Composite }
@@ -1,195 +0,0 @@
1
- import { isEqual } from '../src/diff'
2
- import {
3
- CircularDependencyError,
4
- createError,
5
- InvalidCallbackError,
6
- NullishSignalValueError,
7
- } from '../src/errors'
8
- import {
9
- createWatcher,
10
- flush,
11
- notifyWatchers,
12
- subscribeActiveWatcher,
13
- UNSET,
14
- type Watcher,
15
- } from '../src/system'
16
- import {
17
- isAbortError,
18
- isAsyncFunction,
19
- isFunction,
20
- isObjectOfType,
21
- } from '../src/util'
22
-
23
- /* === Types === */
24
-
25
- type Computed<T extends {}> = {
26
- readonly [Symbol.toStringTag]: 'Computed'
27
- get(): T
28
- }
29
-
30
- type ComputedCallback<T extends {} & { then?: undefined }> =
31
- | ((oldValue: T, abort: AbortSignal) => Promise<T>)
32
- | ((oldValue: T) => T)
33
-
34
- /* === Constants === */
35
-
36
- const TYPE_COMPUTED = 'Computed' as const
37
-
38
- /* === Functions === */
39
-
40
- /**
41
- * Create a derived signal from existing signals
42
- *
43
- * @since 0.9.0
44
- * @param {ComputedCallback<T>} callback - Computation callback function
45
- * @returns {Computed<T>} - Computed signal
46
- */
47
- const createComputed = <T extends {}>(
48
- callback: ComputedCallback<T>,
49
- initialValue: T = UNSET,
50
- ): Computed<T> => {
51
- if (!isComputedCallback(callback))
52
- throw new InvalidCallbackError('computed', callback)
53
- if (initialValue == null) throw new NullishSignalValueError('computed')
54
-
55
- const watchers: Set<Watcher> = new Set()
56
-
57
- // Internal state
58
- let value: T = initialValue
59
- let error: Error | undefined
60
- let controller: AbortController | undefined
61
- let dirty = true
62
- let changed = false
63
- let computing = false
64
-
65
- // Functions to update internal state
66
- const ok = (v: T): undefined => {
67
- if (!isEqual(v, value)) {
68
- value = v
69
- changed = true
70
- }
71
- error = undefined
72
- dirty = false
73
- }
74
- const nil = (): undefined => {
75
- changed = UNSET !== value
76
- value = UNSET
77
- error = undefined
78
- }
79
- const err = (e: unknown): undefined => {
80
- const newError = createError(e)
81
- changed =
82
- !error ||
83
- newError.name !== error.name ||
84
- newError.message !== error.message
85
- value = UNSET
86
- error = newError
87
- }
88
- const settle =
89
- <T>(fn: (arg: T) => void) =>
90
- (arg: T) => {
91
- computing = false
92
- controller = undefined
93
- fn(arg)
94
- if (changed) notifyWatchers(watchers)
95
- }
96
-
97
- // Own watcher: called when notified from sources (push)
98
- const watcher = createWatcher(
99
- () => {
100
- dirty = true
101
- controller?.abort()
102
- if (watchers.size) notifyWatchers(watchers)
103
- else watcher.stop()
104
- },
105
- () => {
106
- if (computing) throw new CircularDependencyError('computed')
107
- changed = false
108
- if (isAsyncFunction(callback)) {
109
- // Return current value until promise resolves
110
- if (controller) return value
111
- controller = new AbortController()
112
- controller.signal.addEventListener(
113
- 'abort',
114
- () => {
115
- computing = false
116
- controller = undefined
117
- watcher.run() // Retry computation with updated state
118
- },
119
- {
120
- once: true,
121
- },
122
- )
123
- }
124
- let result: T | Promise<T>
125
- computing = true
126
- try {
127
- result = controller
128
- ? callback(value, controller.signal)
129
- : (callback as (oldValue: T) => T)(value)
130
- } catch (e) {
131
- if (isAbortError(e)) nil()
132
- else err(e)
133
- computing = false
134
- return
135
- }
136
- if (result instanceof Promise) result.then(settle(ok), settle(err))
137
- else if (null == result || UNSET === result) nil()
138
- else ok(result)
139
- computing = false
140
- },
141
- )
142
- watcher.onCleanup(() => {
143
- controller?.abort()
144
- })
145
-
146
- const computed: Record<PropertyKey, unknown> = {}
147
- Object.defineProperties(computed, {
148
- [Symbol.toStringTag]: {
149
- value: TYPE_COMPUTED,
150
- },
151
- get: {
152
- value: (): T => {
153
- subscribeActiveWatcher(watchers)
154
- flush()
155
- if (dirty) watcher.run()
156
- if (error) throw error
157
- return value
158
- },
159
- },
160
- })
161
- return computed as Computed<T>
162
- }
163
-
164
- /**
165
- * Check if a value is a computed signal
166
- *
167
- * @since 0.9.0
168
- * @param {unknown} value - Value to check
169
- * @returns {boolean} - true if value is a computed signal, false otherwise
170
- */
171
- const isComputed = /*#__PURE__*/ <T extends {}>(
172
- value: unknown,
173
- ): value is Computed<T> => isObjectOfType(value, TYPE_COMPUTED)
174
-
175
- /**
176
- * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
177
- *
178
- * @since 0.12.0
179
- * @param {unknown} value - Value to check
180
- * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
181
- */
182
- const isComputedCallback = /*#__PURE__*/ <T extends {}>(
183
- value: unknown,
184
- ): value is ComputedCallback<T> => isFunction(value) && value.length < 3
185
-
186
- /* === Exports === */
187
-
188
- export {
189
- TYPE_COMPUTED,
190
- createComputed,
191
- isComputed,
192
- isComputedCallback,
193
- type Computed,
194
- type ComputedCallback,
195
- }