@zeix/cause-effect 0.13.2 → 0.14.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.
- package/README.md +160 -130
- package/index.d.ts +7 -5
- package/index.js +1 -1
- package/index.ts +6 -6
- package/package.json +1 -1
- package/src/computed.d.ts +15 -17
- package/src/computed.ts +26 -200
- package/src/effect.d.ts +9 -12
- package/src/effect.ts +54 -28
- package/src/memo.d.ts +13 -0
- package/src/memo.ts +91 -0
- package/src/scheduler.d.ts +15 -11
- package/src/scheduler.ts +32 -15
- package/src/signal.d.ts +6 -20
- package/src/signal.ts +34 -67
- package/src/state.d.ts +4 -7
- package/src/state.ts +9 -39
- package/src/task.d.ts +17 -0
- package/src/task.ts +153 -0
- package/src/util.ts +2 -0
- package/test/batch.test.ts +10 -14
- package/test/benchmark.test.ts +81 -69
- package/test/computed.test.ts +108 -123
- package/test/effect.test.ts +24 -16
- package/test/state.test.ts +2 -53
- package/test/util/dependency-graph.ts +2 -2
package/README.md
CHANGED
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
# Cause & Effect
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.14.0
|
|
4
4
|
|
|
5
|
-
**Cause & Effect** is a lightweight, reactive state management library for JavaScript applications. It uses
|
|
5
|
+
**Cause & Effect** is a lightweight, reactive state management library for JavaScript applications. It uses fine-grained reactivity with signals to create predictable and efficient data flow in your app.
|
|
6
6
|
|
|
7
7
|
## What is Cause & Effect?
|
|
8
8
|
|
|
9
|
-
**Cause & Effect** provides a simple way to manage application state using signals. Signals are containers for values that can change over time. When a signal's value changes, it automatically updates all
|
|
9
|
+
**Cause & Effect** provides a simple way to manage application state using signals. Signals are containers for values that can change over time. When a signal's value changes, it automatically updates all dependent computations and effects, ensuring your UI stays in sync with your data without manual intervention.
|
|
10
|
+
|
|
11
|
+
### Core Concepts
|
|
12
|
+
|
|
13
|
+
- **State signals**: Hold values that can be directly modified
|
|
14
|
+
- **Computed signals**: Derive values from other signals (either `memo()` for sync or `task()` for async)
|
|
15
|
+
- **Effects**: Run side effects when signals change
|
|
10
16
|
|
|
11
17
|
## Key Features
|
|
12
18
|
|
|
13
19
|
- ⚡ **Reactive States**: Automatic updates when dependencies change
|
|
14
|
-
- 🧩 **Composable**:
|
|
20
|
+
- 🧩 **Composable**: Create a complex signal graph with a minimal API
|
|
15
21
|
- ⏱️ **Async Ready**: Built-in `Promise` and `AbortController` support
|
|
16
22
|
- 🛡️ **Error Handling**: Declare handlers for errors and unset states in effects
|
|
17
23
|
- 🚀 **Performance**: Batching and efficient dependency tracking
|
|
18
|
-
- 📦 **Tiny**:
|
|
24
|
+
- 📦 **Tiny**: Around 1kB gzipped, zero dependencies
|
|
19
25
|
|
|
20
26
|
## Quick Start
|
|
21
27
|
|
|
22
28
|
```js
|
|
23
|
-
import { state,
|
|
29
|
+
import { state, memo, effect } from '@zeix/cause-effect'
|
|
24
30
|
|
|
25
31
|
// 1. Create state
|
|
26
32
|
const user = state({ name: 'Alice', age: 30 })
|
|
27
33
|
|
|
28
34
|
// 2. Create computed values
|
|
29
|
-
const greeting =
|
|
35
|
+
const greeting = memo(() => `Hello ${user.get().name}!`)
|
|
30
36
|
|
|
31
37
|
// 3. React to changes
|
|
32
38
|
effect({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
signals: [user, greeting],
|
|
40
|
+
ok: ({ age }, greet) => {
|
|
41
|
+
console.log(`${greet} You are ${age} years old`)
|
|
42
|
+
}
|
|
37
43
|
})
|
|
38
44
|
|
|
39
45
|
// 4. Update state
|
|
@@ -52,113 +58,97 @@ bun add @zeix/cause-effect
|
|
|
52
58
|
|
|
53
59
|
## Usage of Signals
|
|
54
60
|
|
|
55
|
-
###
|
|
56
|
-
|
|
57
|
-
`state()` creates a new state signal. To access the current value of the signal use the `.get()` method. To update the value of the signal use the `.set()` method with a new value or `.update()` with an updater function of the form `(v: T) => T`.
|
|
61
|
+
### State Signals
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
`state()` creates a mutable signal. Every signal has a `.get()` method to access its current value. State signals also provide `.set()` to directly assign a new value and `.update()` to modify the value with a function.
|
|
60
64
|
|
|
61
65
|
```js
|
|
62
|
-
import { state } from '@zeix/cause-effect'
|
|
66
|
+
import { state, effect } from '@zeix/cause-effect'
|
|
63
67
|
|
|
64
68
|
const count = state(42)
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
effect(() => {
|
|
70
|
+
console.log(count.get()) // logs '42'
|
|
67
71
|
})
|
|
68
72
|
count.set(24) // logs '24'
|
|
69
73
|
document.querySelector('.increment').addEventListener('click', () => {
|
|
70
|
-
|
|
74
|
+
count.update(v => ++v)
|
|
71
75
|
})
|
|
72
76
|
// Click on button logs '25', '26', and so on
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
###
|
|
79
|
+
### Computed Signals: memo() and task()
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
#### Synchronous Computations with memo()
|
|
82
|
+
|
|
83
|
+
`memo()` creates a read-only computed signal for synchronous calculations. It automatically tracks dependencies and updates when they change.
|
|
78
84
|
|
|
79
85
|
```js
|
|
80
|
-
import { state,
|
|
86
|
+
import { state, memo, effect } from '@zeix/cause-effect'
|
|
81
87
|
|
|
82
88
|
const count = state(42)
|
|
83
|
-
const isOdd =
|
|
89
|
+
const isOdd = memo(() => count.get() % 2)
|
|
84
90
|
effect(() => console.log(isOdd.get())) // logs 'false'
|
|
85
91
|
count.set(24) // logs nothing because 24 is also an even number
|
|
86
92
|
document.querySelector('button.increment').addEventListener('click', () => {
|
|
87
|
-
|
|
93
|
+
count.update(v => ++v)
|
|
88
94
|
})
|
|
89
95
|
// Click on button logs 'true', 'false', and so on
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
#### Asynchronous Computations with task()
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
import { state } from '@zeix/cause-effect'
|
|
100
|
+
`task()` creates computed signals for asynchronous operations. It automatically manages promises, tracks dependencies, and handles cancellation through `AbortController`.
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
count.map(v => v % 2).tap(v => console.log(v)) // logs 'false'
|
|
99
|
-
count.set(24) // logs nothing because 24 is also an even number
|
|
100
|
-
document.querySelector('.increment').addEventListener('click', () => {
|
|
101
|
-
count.update(v => ++v)
|
|
102
|
-
})
|
|
103
|
-
// Click on button logs 'true', 'false', and so on
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Async Computed Signal
|
|
107
|
-
|
|
108
|
-
Async computed signals are as straight forward as their sync counterparts. Just create the computed signal with an async function.
|
|
109
|
-
|
|
110
|
-
**Caution**: Async computed signals will return a Symbol `UNSET` until the Promise is resolved.
|
|
102
|
+
**Note**: Task signals return `UNSET` while pending, which you can handle with the `nil` case in effects.
|
|
111
103
|
|
|
112
104
|
```js
|
|
113
|
-
import { state } from '@zeix/cause-effect'
|
|
105
|
+
import { state, task, effect } from '@zeix/cause-effect'
|
|
114
106
|
|
|
115
107
|
const entryId = state(42)
|
|
116
|
-
const entryData =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
108
|
+
const entryData = task(async abort => {
|
|
109
|
+
const response = await fetch(`/api/entry/${entryId.get()}`, { signal: abort })
|
|
110
|
+
if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`)
|
|
111
|
+
return response.json()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Display data when available
|
|
115
|
+
effect({
|
|
116
|
+
signals: [entryData],
|
|
117
|
+
ok: data => console.log('Data loaded:', data),
|
|
118
|
+
nil: () => console.log('Loading...'),
|
|
119
|
+
err: error => console.error('Error:', error)
|
|
120
120
|
})
|
|
121
|
-
|
|
121
|
+
|
|
122
|
+
// Move to next entry, automatically triggers a new fetch
|
|
122
123
|
document.querySelector('button.next').addEventListener('click', () => {
|
|
123
|
-
|
|
124
|
+
entryId.update(v => ++v)
|
|
124
125
|
})
|
|
125
|
-
// Click on button updates h1 and p of the entry as soon as fetched data for the next entry is loaded
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
## Error Handling
|
|
128
|
+
## Effects and Error Handling
|
|
129
129
|
|
|
130
|
-
Cause & Effect provides
|
|
130
|
+
Cause & Effect provides a robust way to handle side effects and errors through the `effect()` function, with three distinct paths:
|
|
131
131
|
|
|
132
|
-
1. **Ok**:
|
|
133
|
-
2. **Nil**:
|
|
134
|
-
3. **Err**:
|
|
132
|
+
1. **Ok**: When values are available
|
|
133
|
+
2. **Nil**: For loading/unset states (primarily with async tasks)
|
|
134
|
+
3. **Err**: When errors occur during computation
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
This allows for declarative handling of all possible states:
|
|
137
137
|
|
|
138
138
|
```js
|
|
139
139
|
effect({
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
signals: [data],
|
|
141
|
+
ok: (value) => /* update UI */,
|
|
142
|
+
nil: () => /* show loading */,
|
|
143
|
+
err: (error) => /* show error */
|
|
144
144
|
})
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
Instead of a single callback function, provide an object with `ok` (required), `err` and `nil`
|
|
148
|
-
|
|
149
|
-
If you want an effect based on a single signal, there's a shorthand too: The `.tap()` method on either `State` or `Computed`. You can use it for easy debugging, for example:
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
signal.tap({
|
|
153
|
-
ok: v => console.log('Value:', v),
|
|
154
|
-
nil: () => console.warn('Not ready'),
|
|
155
|
-
err: e => console.error('Error:', e)
|
|
156
|
-
})
|
|
157
|
-
```
|
|
147
|
+
Instead of using a single callback function, you can provide an object with an `ok` handler (required), plus optional `err` and `nil` handlers. Cause & Effect will automatically route to the appropriate handler based on the state of the signals.
|
|
158
148
|
|
|
159
149
|
## DOM Updates
|
|
160
150
|
|
|
161
|
-
The `enqueue()` function allows you to schedule DOM updates to be executed on the next animation frame. It returns a `Promise`, which makes it easy to
|
|
151
|
+
The `enqueue()` function allows you to schedule DOM updates to be executed on the next animation frame. It returns a `Promise`, which makes it easy to track when updates are applied or handle errors.
|
|
162
152
|
|
|
163
153
|
```js
|
|
164
154
|
import { enqueue } from '@zeix/cause-effect'
|
|
@@ -171,7 +161,11 @@ enqueue(() => {
|
|
|
171
161
|
.catch(error => console.error('Update failed:', error))
|
|
172
162
|
```
|
|
173
163
|
|
|
174
|
-
|
|
164
|
+
### Deduplication with Symbols
|
|
165
|
+
|
|
166
|
+
A powerful feature of `enqueue()` is deduplication, which ensures that only the most recent update for a specific operation is applied when multiple updates occur within a single animation frame. This is particularly useful for high-frequency events like typing, dragging, or scrolling.
|
|
167
|
+
|
|
168
|
+
Deduplication is controlled using JavaScript Symbols:
|
|
175
169
|
|
|
176
170
|
```js
|
|
177
171
|
import { state, effect, enqueue } from '@zeix/cause-effect'
|
|
@@ -179,57 +173,95 @@ import { state, effect, enqueue } from '@zeix/cause-effect'
|
|
|
179
173
|
// Define a signal and update it in an event handler
|
|
180
174
|
const name = state('')
|
|
181
175
|
document.querySelector('input[name="name"]').addEventListener('input', e => {
|
|
182
|
-
|
|
176
|
+
name.set(e.target.value) // Triggers an update on every keystroke
|
|
183
177
|
})
|
|
184
178
|
|
|
185
179
|
// Define an effect to react to signal changes
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
180
|
+
effect(text => {
|
|
181
|
+
// Create a Symbol for a specific update operation
|
|
182
|
+
const NAME_UPDATE = Symbol('name-update')
|
|
183
|
+
const text = name.get()
|
|
184
|
+
const nameSpan = document.querySelector('.greeting .name')
|
|
185
|
+
enqueue(() => {
|
|
186
|
+
nameSpan.textContent = text
|
|
187
|
+
return text
|
|
188
|
+
}, NAME_UPDATE) // Using the Symbol for deduplication
|
|
189
|
+
.then(result => console.log(`Name was updated to ${result}`))
|
|
190
|
+
.catch(error => console.error('Failed to update name:', error))
|
|
194
191
|
})
|
|
195
192
|
```
|
|
196
193
|
|
|
197
|
-
In this example, as the user types
|
|
194
|
+
In this example, as the user types "Jane" quickly, the intermediate values ('J', 'Ja', 'Jan') are deduplicated, and only the final value 'Jane' is applied to the DOM. Only the Promise for the final update is resolved.
|
|
195
|
+
|
|
196
|
+
### How Deduplication Works
|
|
197
|
+
|
|
198
|
+
When multiple `enqueue` calls use the same Symbol before the next animation frame:
|
|
199
|
+
|
|
200
|
+
1. Only the last call will be executed
|
|
201
|
+
2. Previous calls are superseded
|
|
202
|
+
3. Only the Promise of the last call will be resolved
|
|
203
|
+
|
|
204
|
+
This "last-write-wins" behavior optimizes DOM updates and prevents unnecessary work when many updates happen rapidly.
|
|
205
|
+
|
|
206
|
+
### Optional Deduplication
|
|
207
|
+
|
|
208
|
+
The deduplication Symbol is optional. When not provided, a unique Symbol is created automatically, ensuring the update is always executed:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
// No deduplication - always executed
|
|
212
|
+
enqueue(() => document.title = 'New Page Title')
|
|
213
|
+
|
|
214
|
+
// Create symbols for different types of updates
|
|
215
|
+
const COLOR_UPDATE = Symbol('color-update')
|
|
216
|
+
const SIZE_UPDATE = Symbol('size-update')
|
|
217
|
+
|
|
218
|
+
// These won't interfere with each other (different symbols)
|
|
219
|
+
enqueue(() => element.style.color = 'red', COLOR_UPDATE)
|
|
220
|
+
enqueue(() => element.style.fontSize = '16px', SIZE_UPDATE)
|
|
221
|
+
|
|
222
|
+
// This will replace the previous color update (same symbol)
|
|
223
|
+
enqueue(() => element.style.color = 'blue', COLOR_UPDATE)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Using Symbols for deduplication provides:
|
|
198
227
|
|
|
199
|
-
|
|
228
|
+
- Clear semantic meaning for update operations
|
|
229
|
+
- Type safety in TypeScript
|
|
230
|
+
- Simple mechanism to control which updates should overwrite each other
|
|
231
|
+
- Flexibility to run every update when needed
|
|
200
232
|
|
|
201
233
|
## Advanced Usage
|
|
202
234
|
|
|
203
|
-
### Batching
|
|
235
|
+
### Batching Updates
|
|
204
236
|
|
|
205
|
-
|
|
237
|
+
Use `batch()` to group multiple signal updates, ensuring effects run only once after all changes are applied:
|
|
206
238
|
|
|
207
239
|
```js
|
|
208
|
-
import { state,
|
|
240
|
+
import { state, memo, effect, batch } from '@zeix/cause-effect'
|
|
209
241
|
|
|
210
242
|
// State: define an array of State<number>
|
|
211
243
|
const signals = [state(2), state(3), state(5)]
|
|
212
244
|
|
|
213
|
-
//
|
|
214
|
-
const sum =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return v
|
|
245
|
+
// Compute the sum of all signals
|
|
246
|
+
const sum = memo(() => {
|
|
247
|
+
const v = signals.reduce((total, signal) => total + signal.get(), 0)
|
|
248
|
+
// Validate the result
|
|
249
|
+
if (!Number.isFinite(v)) throw new Error('Invalid value')
|
|
250
|
+
return v
|
|
220
251
|
})
|
|
221
252
|
|
|
222
|
-
// Effect:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
253
|
+
// Effect: handle the result
|
|
254
|
+
effect({
|
|
255
|
+
signals: [sum],
|
|
256
|
+
ok: v => console.log('Sum:', v),
|
|
257
|
+
err: error => console.error('Error:', error)
|
|
226
258
|
})
|
|
227
259
|
|
|
228
260
|
// Batch: apply changes to all signals in a single transaction
|
|
229
261
|
document.querySelector('.double-all').addEventListener('click', () => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
262
|
+
batch(() => {
|
|
263
|
+
signals.forEach(signal => signal.update(v => v * 2))
|
|
264
|
+
})
|
|
233
265
|
})
|
|
234
266
|
// Click on button logs '20' only once
|
|
235
267
|
// (instead of first '12', then '15' and then '20' without batch)
|
|
@@ -238,31 +270,28 @@ document.querySelector('.double-all').addEventListener('click', () => {
|
|
|
238
270
|
signals[0].set(NaN)
|
|
239
271
|
```
|
|
240
272
|
|
|
241
|
-
|
|
273
|
+
The Cause & Effect library is designed around these principles:
|
|
242
274
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
5. **Flexibility and Integration**: Seamlessly integrates with DOM manipulation and event listeners, fitting into any JavaScript application or framework.
|
|
275
|
+
- **Minimal API**: Core primitives with a small but powerful interface
|
|
276
|
+
- **Automatic Dependency Tracking**: Fine-grained reactivity with minimal boilerplate
|
|
277
|
+
- **Tree-Shakable**: Import only what you need for optimal bundle size
|
|
278
|
+
- **Flexible Integration**: Works with any JavaScript application or framework
|
|
248
279
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
### Cleanup
|
|
280
|
+
### Cleanup Functions
|
|
252
281
|
|
|
253
282
|
Effects return a cleanup function. When executed, it will unsubscribe from signals and run cleanup functions returned by effect callbacks, for example to remove event listeners.
|
|
254
283
|
|
|
255
284
|
```js
|
|
256
|
-
import { state,
|
|
285
|
+
import { state, memo, effect } from '@zeix/cause-effect'
|
|
257
286
|
|
|
258
287
|
const user = state({ name: 'Alice', age: 30 })
|
|
259
|
-
const greeting =
|
|
288
|
+
const greeting = memo(() => `Hello ${user.get().name}!`)
|
|
260
289
|
const cleanup = effect({
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
290
|
+
signals: [user, greeting],
|
|
291
|
+
ok: ({ age }, greet) => {
|
|
292
|
+
console.log(`${greet} You are ${age} years old`)
|
|
293
|
+
return () => console.log('Cleanup') // Cleanup function
|
|
294
|
+
}
|
|
266
295
|
})
|
|
267
296
|
|
|
268
297
|
// When you no longer need the effect, execute the cleanup function
|
|
@@ -271,34 +300,35 @@ cleanup() // Logs: 'Cleanup' and unsubscribes from signals `user` and `greeting`
|
|
|
271
300
|
user.set({ name: 'Bob', age: 28 }) // Won't trigger the effect anymore
|
|
272
301
|
```
|
|
273
302
|
|
|
274
|
-
### Abort
|
|
303
|
+
### Automatic Abort Control
|
|
275
304
|
|
|
276
|
-
For asynchronous
|
|
305
|
+
For asynchronous operations, `task()` automatically manages cancellation when dependencies change, providing an `abort` signal parameter:
|
|
277
306
|
|
|
278
307
|
```js
|
|
279
|
-
import { state,
|
|
308
|
+
import { state, task, effect } from '@zeix/cause-effect'
|
|
280
309
|
|
|
281
310
|
const id = state(42)
|
|
282
|
-
const url =
|
|
283
|
-
const data =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
311
|
+
const url = memo(v => `https://example.com/api/entries/${id.get()}`)
|
|
312
|
+
const data = task(async abort => {
|
|
313
|
+
const response = await fetch(url.get(), { signal: abort })
|
|
314
|
+
if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`)
|
|
315
|
+
return response.json()
|
|
287
316
|
})
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
317
|
+
effect({
|
|
318
|
+
signals: [data],
|
|
319
|
+
ok: v => console.log('Value:', v),
|
|
320
|
+
nil: () => console.warn('Not ready'),
|
|
321
|
+
err: e => console.error('Error:', e)
|
|
292
322
|
})
|
|
293
323
|
|
|
294
324
|
// User switches to another entry
|
|
295
|
-
id.set(24) // Cancels
|
|
325
|
+
id.set(24) // Cancels the previous fetch request and starts a new one
|
|
296
326
|
```
|
|
297
327
|
|
|
298
328
|
## Contributing & License
|
|
299
329
|
|
|
300
330
|
Feel free to contribute, report issues, or suggest improvements.
|
|
301
331
|
|
|
302
|
-
|
|
332
|
+
License: [MIT](LICENSE)
|
|
303
333
|
|
|
304
334
|
(c) 2025 [Zeix AG](https://zeix.com)
|
package/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.14.0
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
export { CircularDependencyError } from './src/util';
|
|
7
|
-
export { type Signal, type MaybeSignal,
|
|
7
|
+
export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal, } from './src/signal';
|
|
8
8
|
export { type State, state, isState } from './src/state';
|
|
9
|
-
export { type Computed, type
|
|
10
|
-
export { type
|
|
11
|
-
export { type
|
|
9
|
+
export { type Computed, type ComputedCallback, computed, isComputed, } from './src/computed';
|
|
10
|
+
export { type MemoCallback, memo } from './src/memo';
|
|
11
|
+
export { type TaskCallback, task } from './src/task';
|
|
12
|
+
export { type EffectMatcher, effect } from './src/effect';
|
|
13
|
+
export { batch, watch, enqueue } from './src/scheduler';
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var O=($)=>typeof $==="function",p=($)=>O($)&&$.constructor.name==="AsyncFunction",N=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,c=($)=>$ instanceof Error,q=($)=>$ instanceof DOMException&&$.name==="AbortError",d=($)=>$ instanceof Promise,M=($)=>c($)?$:Error(String($));class x extends Error{constructor($){super(`Circular dependency in ${$} detected`);return this}}var V,k=new Set,U=0,b=new Map,_,h=()=>{_=void 0;let $=Array.from(b.values());b.clear();for(let B of $)B()},n=()=>{if(_)cancelAnimationFrame(_);_=requestAnimationFrame(h)};queueMicrotask(h);var Y=($)=>{if(V&&!$.has(V)){let B=V;$.add(B),V.cleanups.add(()=>{$.delete(B)})}},A=($)=>{for(let B of $)if(U)k.add(B);else B()},C=()=>{while(k.size){let $=Array.from(k);k.clear();for(let B of $)B()}},l=($)=>{U++;try{$()}finally{C(),U--}},R=($,B)=>{let z=V;V=B;try{$()}finally{V=z}},u=($,B)=>new Promise((z,F)=>{b.set(B||Symbol(),()=>{try{z($())}catch(L){F(L)}}),n()});var v="State",S=($)=>{let B=new Set,z=$,F={[Symbol.toStringTag]:v,get:()=>{return Y(B),z},set:(L)=>{if(Object.is(z,L))return;if(z=L,A(B),K===z)B.clear()},update:(L)=>{F.set(L(z))}};return F},T=($)=>N($,v);var w=($)=>{let B=new Set,z=K,F,L=!0,X=!1,H=()=>{if(L=!0,B.size)A(B);else H.cleanups.forEach((W)=>W()),H.cleanups.clear()};H.cleanups=new Set;let Q=()=>R(()=>{if(X)throw new x("memo");X=!0;try{let W=$();if(W==null||K===W)z=K,F=void 0;else z=W,L=!1,F=void 0}catch(W){z=K,F=W instanceof Error?W:new Error(String(W))}finally{X=!1}},H);return{[Symbol.toStringTag]:y,get:()=>{if(Y(B),C(),L)Q();if(F)throw F;return z}}};var g=($)=>{let B=new Set,z=K,F,L=!0,X=!1,H=!1,Q,j=(J)=>{if(!Object.is(J,z))z=J,L=!1,F=void 0,X=!0},W=()=>{X=K!==z,z=K,F=void 0},D=(J)=>{let I=M(J);X=!(F&&I.name===F.name&&I.message===F.message),z=K,F=I},G=(J)=>{if(H=!1,Q=void 0,j(J),X)A(B)},Z=(J)=>{if(H=!1,Q=void 0,D(J),X)A(B)},i=()=>{H=!1,Q=void 0,m()},P=()=>{if(L=!0,Q?.abort("Aborted because source signal changed"),B.size)A(B);else P.cleanups.forEach((J)=>J()),P.cleanups.clear()};P.cleanups=new Set;let m=()=>R(()=>{if(H)throw new x("task");X=!1,Q=new AbortController,Q.signal.addEventListener("abort",i,{once:!0});let J;H=!0;try{J=$(Q.signal)}catch(I){if(q(I))W();else D(I);H=!1;return}if(d(J))J.then(G,Z);else if(J==null||K===J)W();else j(J);H=!1},P);return{[Symbol.toStringTag]:y,get:()=>{if(Y(B),C(),L)m();if(F)throw F;return z}}};var y="Computed",E=($)=>p($)?g($):w($),f=($)=>N($,y);var K=Symbol(),o=($)=>T($)||f($),s=($)=>O($)&&$.length<2,t=($)=>o($)?$:s($)?E($):S($);function r($){let{signals:B,ok:z,err:F=console.error,nil:L=()=>{}}=O($)?{signals:[],ok:$}:$,X=!1,H=()=>R(()=>{if(X)throw new x("effect");X=!0;let Q=void 0;try{let j=[],W=!1,D=B.map((G)=>{try{let Z=G.get();if(Z===K)W=!0;return Z}catch(Z){if(q(Z))throw Z;return j.push(M(Z)),K}});try{Q=W?L():j.length?F(...j):z(...D)}catch(G){if(q(G))throw G;let Z=M(G);Q=F(Z)}}catch(j){F(M(j))}if(O(Q))H.cleanups.add(Q);X=!1},H);return H.cleanups=new Set,H(),()=>{H.cleanups.forEach((Q)=>Q()),H.cleanups.clear()}}export{R as watch,t as toSignal,g as task,S as state,w as memo,T as isState,o as isSignal,s as isComputedCallback,f as isComputed,u as enqueue,r as effect,E as computed,l as batch,K as UNSET,x as CircularDependencyError};
|
package/index.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.14.0
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
export { CircularDependencyError } from './src/util'
|
|
7
7
|
export {
|
|
8
8
|
type Signal,
|
|
9
9
|
type MaybeSignal,
|
|
10
|
-
type ComputedCallback,
|
|
11
10
|
UNSET,
|
|
12
11
|
isSignal,
|
|
13
12
|
isComputedCallback,
|
|
14
13
|
toSignal,
|
|
15
14
|
} from './src/signal'
|
|
16
|
-
|
|
17
15
|
export { type State, state, isState } from './src/state'
|
|
18
16
|
export {
|
|
19
17
|
type Computed,
|
|
20
|
-
type
|
|
18
|
+
type ComputedCallback,
|
|
21
19
|
computed,
|
|
22
20
|
isComputed,
|
|
23
21
|
} from './src/computed'
|
|
24
|
-
export { type
|
|
25
|
-
export { type
|
|
22
|
+
export { type MemoCallback, memo } from './src/memo'
|
|
23
|
+
export { type TaskCallback, task } from './src/task'
|
|
24
|
+
export { type EffectMatcher, effect } from './src/effect'
|
|
25
|
+
export { batch, watch, enqueue } from './src/scheduler'
|
package/package.json
CHANGED
package/src/computed.d.ts
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
3
|
-
|
|
4
|
-
signals: S;
|
|
5
|
-
abort?: AbortSignal;
|
|
6
|
-
ok: (...values: {
|
|
7
|
-
[K in keyof S]: S[K] extends Signal<infer T> ? T : never;
|
|
8
|
-
}) => R | Promise<R>;
|
|
9
|
-
err?: (...errors: Error[]) => R | Promise<R>;
|
|
10
|
-
nil?: () => R | Promise<R>;
|
|
11
|
-
};
|
|
12
|
-
export type Computed<T extends {}> = {
|
|
1
|
+
import { type MemoCallback } from './memo';
|
|
2
|
+
import { type TaskCallback } from './task';
|
|
3
|
+
type Computed<T extends {}> = {
|
|
13
4
|
[Symbol.toStringTag]: 'Computed';
|
|
14
5
|
get(): T;
|
|
15
|
-
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>;
|
|
16
|
-
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void;
|
|
17
6
|
};
|
|
7
|
+
type ComputedCallback<T extends {} & {
|
|
8
|
+
then?: void;
|
|
9
|
+
}> = TaskCallback<T> | MemoCallback<T>;
|
|
10
|
+
declare const TYPE_COMPUTED = "Computed";
|
|
18
11
|
/**
|
|
19
12
|
* Create a derived signal from existing signals
|
|
20
13
|
*
|
|
14
|
+
* This function delegates to either memo() for synchronous computations
|
|
15
|
+
* or task() for asynchronous computations, providing better performance
|
|
16
|
+
* for each case.
|
|
17
|
+
*
|
|
21
18
|
* @since 0.9.0
|
|
22
|
-
* @param {
|
|
19
|
+
* @param {ComputedCallback<T>} fn - computation callback function
|
|
23
20
|
* @returns {Computed<T>} - Computed signal
|
|
24
21
|
*/
|
|
25
|
-
|
|
22
|
+
declare const computed: <T extends {}>(fn: ComputedCallback<T>) => Computed<T>;
|
|
26
23
|
/**
|
|
27
24
|
* Check if a value is a computed state
|
|
28
25
|
*
|
|
@@ -30,4 +27,5 @@ export declare const computed: <T extends {}, S extends Signal<{}>[] = []>(match
|
|
|
30
27
|
* @param {unknown} value - value to check
|
|
31
28
|
* @returns {boolean} - true if value is a computed state, false otherwise
|
|
32
29
|
*/
|
|
33
|
-
|
|
30
|
+
declare const isComputed: <T extends {}>(value: unknown) => value is Computed<T>;
|
|
31
|
+
export { type Computed, type ComputedCallback, TYPE_COMPUTED, computed, isComputed, };
|