@zeix/cause-effect 0.12.4 → 0.13.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/.prettierrc +7 -0
- package/README.md +140 -94
- package/index.d.ts +5 -4
- package/index.js +1 -1
- package/index.ts +6 -6
- package/lib/computed.d.ts +16 -7
- package/lib/computed.ts +100 -44
- package/lib/effect.d.ts +17 -4
- package/lib/effect.ts +49 -17
- package/lib/scheduler.d.ts +9 -6
- package/lib/scheduler.ts +16 -8
- package/lib/signal.d.ts +16 -33
- package/lib/signal.ts +48 -77
- package/lib/state.d.ts +3 -3
- package/lib/state.ts +29 -22
- package/lib/util.d.ts +8 -5
- package/lib/util.ts +18 -11
- package/package.json +3 -3
- package/test/batch.test.ts +43 -42
- package/test/benchmark.test.ts +315 -319
- package/test/computed.test.ts +341 -242
- package/test/effect.test.ts +136 -119
- package/test/state.test.ts +126 -118
package/.prettierrc
ADDED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cause & Effect
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.13.0
|
|
4
4
|
|
|
5
5
|
**Cause & Effect** is a lightweight, reactive state management library for JavaScript applications. It uses the concept of signals to create a predictable and efficient data flow in your app.
|
|
6
6
|
|
|
@@ -8,40 +8,36 @@ Version 0.12.4
|
|
|
8
8
|
|
|
9
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 parts of your app that depend on it, ensuring your UI stays in sync with your data.
|
|
10
10
|
|
|
11
|
-
## Why Cause & Effect?
|
|
12
|
-
|
|
13
|
-
- **Simplicity**: Easy to learn and use, with a small API surface.
|
|
14
|
-
- **Performance**: Efficient updates that only recompute what's necessary.
|
|
15
|
-
- **Type Safety**: Full TypeScript support for robust applications.
|
|
16
|
-
- **Flexibility**: Works well with any UI framework or vanilla JavaScript.
|
|
17
|
-
- **Lightweight**: Dependency-free, only 1kB gzipped over the wire.
|
|
18
|
-
|
|
19
11
|
## Key Features
|
|
20
12
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- 🛡️
|
|
25
|
-
-
|
|
13
|
+
- ⚡ **Reactive States**: Automatic updates when dependencies change
|
|
14
|
+
- 🧩 **Composable**: Chain signals with `.map()` and `.tap()`
|
|
15
|
+
- ⏱️ **Async Ready**: Built-in `Promise` and `AbortController` support
|
|
16
|
+
- 🛡️ **Error Handling**: Declare handlers for errors and unset states in effects
|
|
17
|
+
- 🚀 **Performance**: Batching and efficient dependency tracking
|
|
18
|
+
- 📦 **Tiny**: ~1kB gzipped, zero dependencies
|
|
26
19
|
|
|
27
|
-
## Quick
|
|
20
|
+
## Quick Start
|
|
28
21
|
|
|
29
22
|
```js
|
|
30
|
-
import { state, effect } from '@zeix/cause-effect'
|
|
23
|
+
import { state, computed, effect } from '@zeix/cause-effect'
|
|
31
24
|
|
|
32
|
-
// Create
|
|
33
|
-
const
|
|
25
|
+
// 1. Create state
|
|
26
|
+
const user = state({ name: 'Alice', age: 30 })
|
|
34
27
|
|
|
35
|
-
// Create
|
|
36
|
-
const
|
|
28
|
+
// 2. Create computed values
|
|
29
|
+
const greeting = computed(() => `Hello ${user.get().name}!`)
|
|
37
30
|
|
|
38
|
-
//
|
|
39
|
-
effect(
|
|
40
|
-
|
|
41
|
-
},
|
|
31
|
+
// 3. React to changes
|
|
32
|
+
effect({
|
|
33
|
+
signals: [user, greeting],
|
|
34
|
+
ok: ({ age }, greet) => {
|
|
35
|
+
console.log(`${greet} You are ${age} years old`)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
42
38
|
|
|
43
|
-
// Update
|
|
44
|
-
|
|
39
|
+
// 4. Update state
|
|
40
|
+
user.update(u => ({ ...u, age: 31 })) // Logs: "Hello Alice! You are 31 years old"
|
|
45
41
|
```
|
|
46
42
|
|
|
47
43
|
## Installation
|
|
@@ -54,17 +50,21 @@ npm install @zeix/cause-effect
|
|
|
54
50
|
bun add @zeix/cause-effect
|
|
55
51
|
```
|
|
56
52
|
|
|
57
|
-
## Usage
|
|
53
|
+
## Usage of Signals
|
|
58
54
|
|
|
59
55
|
### Single State Signal
|
|
60
56
|
|
|
61
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`.
|
|
62
58
|
|
|
59
|
+
The `.tap()` method on either `State` or `Computed` is a shorthand for creating an effect on the signal.
|
|
60
|
+
|
|
63
61
|
```js
|
|
64
|
-
import { state
|
|
62
|
+
import { state } from '@zeix/cause-effect'
|
|
65
63
|
|
|
66
64
|
const count = state(42)
|
|
67
|
-
|
|
65
|
+
count.tap(v => {
|
|
66
|
+
console.log(v) // logs '42'
|
|
67
|
+
})
|
|
68
68
|
count.set(24) // logs '24'
|
|
69
69
|
document.querySelector('.increment').addEventListener('click', () => {
|
|
70
70
|
count.update(v => ++v)
|
|
@@ -92,11 +92,10 @@ document.querySelector('button.increment').addEventListener('click', () => {
|
|
|
92
92
|
If you want to derive a computed signal from a single other signal you can use the `.map()` method on either `State` or `Computed`. This does the same as the snippet above:
|
|
93
93
|
|
|
94
94
|
```js
|
|
95
|
-
import { state
|
|
95
|
+
import { state } from '@zeix/cause-effect'
|
|
96
96
|
|
|
97
97
|
const count = state(42)
|
|
98
|
-
|
|
99
|
-
effect(() => console.log(isOdd.get())) // logs 'false'
|
|
98
|
+
count.map(v => v % 2).tap(v => console.log(v)) // logs 'false'
|
|
100
99
|
count.set(24) // logs nothing because 24 is also an even number
|
|
101
100
|
document.querySelector('.increment').addEventListener('click', () => {
|
|
102
101
|
count.update(v => ++v)
|
|
@@ -111,7 +110,7 @@ Async computed signals are as straight forward as their sync counterparts. Just
|
|
|
111
110
|
**Caution**: Async computed signals will return a Symbol `UNSET` until the Promise is resolved.
|
|
112
111
|
|
|
113
112
|
```js
|
|
114
|
-
import { state
|
|
113
|
+
import { state } from '@zeix/cause-effect'
|
|
115
114
|
|
|
116
115
|
const entryId = state(42)
|
|
117
116
|
const entryData = entryId.map(async id => {
|
|
@@ -120,56 +119,90 @@ const entryData = entryId.map(async id => {
|
|
|
120
119
|
return response.json()
|
|
121
120
|
})
|
|
122
121
|
// Updates h1 and p of the entry as soon as fetched data for entry becomes available
|
|
123
|
-
document.querySelector('button.next')
|
|
124
|
-
|
|
122
|
+
document.querySelector('button.next').addEventListener('click', () => {
|
|
123
|
+
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
|
-
|
|
128
|
+
## Error Handling
|
|
129
|
+
|
|
130
|
+
Cause & Effect provides three paths for robust error handling:
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
1. **Ok**: Value is available
|
|
133
|
+
2. **Nil**: Loading/Unset state (especially for async)
|
|
134
|
+
3. **Err**: Error occurred
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
Handle all cases declaratively:
|
|
133
137
|
|
|
134
138
|
```js
|
|
135
|
-
const h2 = document.querySelector('.entry h2')
|
|
136
|
-
const p = document.querySelector('.entry p')
|
|
137
139
|
effect({
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
nil: () =>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Handle errors
|
|
145
|
-
err: (error) => {
|
|
146
|
-
h2.textContent = 'Oops, Something Went Wrong'
|
|
147
|
-
p.textContent = error.message
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
// Happy path, data is entryData.get()
|
|
151
|
-
ok: (data) => {
|
|
152
|
-
h2.textContent = data.title
|
|
153
|
-
p.textContent = data.description
|
|
154
|
-
}
|
|
155
|
-
}, entryData) // assuming an `entryData` async computed signal as in the example above
|
|
140
|
+
signals: [data],
|
|
141
|
+
ok: (value) => /* update UI */,
|
|
142
|
+
nil: () => /* show loading */,
|
|
143
|
+
err: (error) => /* show error */
|
|
144
|
+
})
|
|
156
145
|
```
|
|
157
146
|
|
|
158
147
|
Instead of a single callback function, provide an object with `ok` (required), `err` and `nil` keys (both optional) and Cause & Effect will take care of anything that might go wrong with the listed signals in the rest parameters of `effect()`.
|
|
159
148
|
|
|
160
|
-
If you want an effect based on a single signal, there's a shorthand too: The `.
|
|
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:
|
|
161
150
|
|
|
162
151
|
```js
|
|
163
|
-
signal.
|
|
152
|
+
signal.tap({
|
|
164
153
|
ok: v => console.log('Value:', v),
|
|
165
154
|
nil: () => console.warn('Not ready'),
|
|
166
155
|
err: e => console.error('Error:', e)
|
|
167
156
|
})
|
|
168
157
|
```
|
|
169
158
|
|
|
170
|
-
|
|
159
|
+
## DOM Updates
|
|
160
|
+
|
|
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 detect when updates are applied or if they fail.
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
import { enqueue } from '@zeix/cause-effect'
|
|
165
|
+
|
|
166
|
+
// Schedule a DOM update
|
|
167
|
+
enqueue(() => {
|
|
168
|
+
document.getElementById('myElement').textContent = 'Updated content'
|
|
169
|
+
})
|
|
170
|
+
.then(() => console.log('Update applied successfully'))
|
|
171
|
+
.catch(error => console.error('Update failed:', error))
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You can also use the deduplication feature to ensure that only the latest update for a specific element and operation is applied:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
import { state, effect, enqueue } from '@zeix/cause-effect'
|
|
178
|
+
|
|
179
|
+
// Define a signal and update it in an event handler
|
|
180
|
+
const name = state('')
|
|
181
|
+
document.querySelector('input[name="name"]').addEventListener('input', e => {
|
|
182
|
+
name.set(e.target.value) // Triggers an update on every keystroke
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Define an effect to react to signal changes
|
|
186
|
+
name.tap(text => {
|
|
187
|
+
const nameSpan = document.querySelector('.greeting .name')
|
|
188
|
+
enqueue(() => {
|
|
189
|
+
nameSpan.textContent = text
|
|
190
|
+
return text
|
|
191
|
+
}, [nameSpan, 'setName']) // For deduplication
|
|
192
|
+
.then(result => console.log(`Name was updated to ${result}`))
|
|
193
|
+
.catch(error => console.error('Failed to update name:', error))
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
In this example, as the user types in the input field only 'Jane' will be applied to the DOM. 'J', 'Ja', 'Jan' were superseded by more recent updates and deduplicated (if typing was fast enough).
|
|
198
|
+
|
|
199
|
+
When multiple `enqueue` calls are made with the same deduplication key before the next animation frame, only the last call will be executed. Previous calls are superseded and their Promises will not be resolved or rejected. This "last-write-wins" behavior ensures that only the most recent update is applied, which is typically desirable for UI updates and state changes.
|
|
200
|
+
|
|
201
|
+
## Advanced Usage
|
|
171
202
|
|
|
172
|
-
|
|
203
|
+
### Batching
|
|
204
|
+
|
|
205
|
+
Effects run synchronously as soon as source signals update. If you need to set multiple signals you can batch them together to ensure dependent effects are executed simultanously and only once.
|
|
173
206
|
|
|
174
207
|
```js
|
|
175
208
|
import { state, computed, batch } from '@zeix/cause-effect'
|
|
@@ -178,16 +211,16 @@ import { state, computed, batch } from '@zeix/cause-effect'
|
|
|
178
211
|
const signals = [state(2), state(3), state(5)]
|
|
179
212
|
|
|
180
213
|
// Computed: derive a calculation ...
|
|
181
|
-
const sum = computed(
|
|
182
|
-
|
|
183
|
-
...
|
|
184
|
-
).map(v => { // ... perform validation and handle errors
|
|
214
|
+
const sum = computed({
|
|
215
|
+
signals,
|
|
216
|
+
ok: (...values) => values.reduce((total, v) => total + v, 0),
|
|
217
|
+
}).map(v => { // ... perform validation and handle errors
|
|
185
218
|
if (!Number.isFinite(v)) throw new Error('Invalid value')
|
|
186
219
|
return v
|
|
187
220
|
})
|
|
188
221
|
|
|
189
222
|
// Effect: switch cases for the result
|
|
190
|
-
sum.
|
|
223
|
+
sum.tap({
|
|
191
224
|
ok: v => console.log('Sum:', v),
|
|
192
225
|
err: error => console.error('Error:', error)
|
|
193
226
|
})
|
|
@@ -215,44 +248,57 @@ This example showcases several powerful features of Cause & Effect:
|
|
|
215
248
|
|
|
216
249
|
These principles enable developers to create complex, reactive applications with clear data flow, efficient updates, and robust error handling, while promoting code reuse and modularity.
|
|
217
250
|
|
|
218
|
-
###
|
|
251
|
+
### Cleanup
|
|
219
252
|
|
|
220
|
-
|
|
253
|
+
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.
|
|
221
254
|
|
|
222
255
|
```js
|
|
223
|
-
import {
|
|
256
|
+
import { state, computed, effect } from '@zeix/cause-effect'
|
|
224
257
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
258
|
+
const user = state({ name: 'Alice', age: 30 })
|
|
259
|
+
const greeting = computed(() => `Hello ${user.get().name}!`)
|
|
260
|
+
const cleanup = effect({
|
|
261
|
+
signals: [user, greeting],
|
|
262
|
+
ok: ({ age }, greet) => {
|
|
263
|
+
console.log(`${greet} You are ${age} years old`)
|
|
264
|
+
return () => console.log('Cleanup') // Cleanup function
|
|
265
|
+
}
|
|
228
266
|
})
|
|
229
|
-
|
|
230
|
-
|
|
267
|
+
|
|
268
|
+
// When you no longer need the effect, execute the cleanup function
|
|
269
|
+
cleanup() // Logs: 'Cleanup' and unsubscribes from signals `user` and `greeting`
|
|
270
|
+
|
|
271
|
+
user.set({ name: 'Bob', age: 28 }) // Won't trigger the effect anymore
|
|
231
272
|
```
|
|
232
273
|
|
|
233
|
-
|
|
274
|
+
### Abort Controller
|
|
234
275
|
|
|
235
|
-
|
|
236
|
-
import { state, effect, enqueue } from '@zeix/cause-effect'
|
|
276
|
+
For asynchronous computed signals, Cause & Effect uses an `AbortController` to cancel pending promises when source signals update. You can use the `abort` parameter in `computed()` callbacks and pass it on to other AbortController aware APIs like `fetch()`:
|
|
237
277
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
278
|
+
```js
|
|
279
|
+
import { state, computed } from '@zeix/cause-effect'
|
|
280
|
+
|
|
281
|
+
const id = state(42)
|
|
282
|
+
const url = id.map(v => `https://example.com/api/entries/${v}`)
|
|
283
|
+
const data = computed(async abort => {
|
|
284
|
+
const response = await fetch(url.get(), { signal: abort })
|
|
285
|
+
if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`)
|
|
286
|
+
return response.json()
|
|
287
|
+
})
|
|
288
|
+
data.tap({
|
|
289
|
+
ok: v => console.log('Value:', v),
|
|
290
|
+
nil: () => console.warn('Not ready'),
|
|
291
|
+
err: e => console.error('Error:', e)
|
|
242
292
|
})
|
|
243
293
|
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
const nameSpan = document.querySelector('.greeting .name')
|
|
247
|
-
enqueue(() => {
|
|
248
|
-
nameSpan.textContent = text
|
|
249
|
-
return text
|
|
250
|
-
}, [nameSpan, 'setName']) // For deduplication
|
|
251
|
-
.then(result => console.log(`Name was updated to ${result}`))
|
|
252
|
-
.catch(error => console.error('Failed to update name:', error))
|
|
253
|
-
}, name)
|
|
294
|
+
// User switches to another entry
|
|
295
|
+
id.set(24) // Cancels or ignores the previous fetch request and starts a new one
|
|
254
296
|
```
|
|
255
297
|
|
|
256
|
-
|
|
298
|
+
## Contributing & License
|
|
299
|
+
|
|
300
|
+
Feel free to contribute, report issues, or suggest improvements.
|
|
301
|
+
|
|
302
|
+
Licence: [MIT](LICENCE.md)
|
|
257
303
|
|
|
258
|
-
|
|
304
|
+
(c) 2025 [Zeix AG](https://zeix.com)
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.13.0
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
6
|
+
export { CircularDependencyError } from './lib/util';
|
|
7
|
+
export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal } from './lib/signal';
|
|
7
8
|
export { type State, state, isState } from './lib/state';
|
|
8
|
-
export { type Computed, computed, isComputed } from './lib/computed';
|
|
9
|
-
export { effect } from './lib/effect';
|
|
9
|
+
export { type Computed, type ComputedMatcher, computed, isComputed } from './lib/computed';
|
|
10
|
+
export { type EffectMatcher, type TapMatcher, effect } from './lib/effect';
|
|
10
11
|
export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler';
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var X=(x)=>typeof x==="function",d=(x)=>X(x)&&x.constructor.name==="AsyncFunction",P=(x,y)=>Object.prototype.toString.call(x)===`[object ${y}]`,c=(x)=>x instanceof Error,I=(x)=>x instanceof DOMException&&x.name==="AbortError",m=(x)=>x instanceof Promise,j=(x)=>c(x)?x:Error(String(x));class V extends Error{constructor(x){super(`Circular dependency in ${x} detected`);return this}}var A,R=new Set,U=0,_=new Map,D,v=()=>{D=void 0;let x=Array.from(_.values());_.clear();for(let y of x)y()},t=()=>{if(D)cancelAnimationFrame(D);D=requestAnimationFrame(v)};queueMicrotask(v);var N=(x)=>{if(A&&!x.has(A)){let y=A;x.add(y),A.cleanups.add(()=>{x.delete(y)})}},F=(x)=>{for(let y of x)if(U)R.add(y);else y()},k=()=>{while(R.size){let x=Array.from(R);R.clear();for(let y of x)y()}},a=(x)=>{U++;try{x()}finally{k(),U--}},M=(x,y)=>{let z=A;A=y;try{x()}finally{A=z}},r=(x,y)=>new Promise((z,J)=>{let B=()=>{try{z(x())}catch(K){J(K)}};if(y)_.set(y,B);t()});function Y(x){let{signals:y,ok:z,err:J=console.error,nil:B=()=>{}}=X(x)?{signals:[],ok:x}:x,K=!1,L=()=>M(()=>{if(K)throw new V("effect");K=!0;let G=void 0;try{G=S({signals:y,ok:z,err:J,nil:B})}catch(Q){J(j(Q))}if(X(G))L.cleanups.add(G);K=!1},L);return L.cleanups=new Set,L(),()=>{L.cleanups.forEach((G)=>G()),L.cleanups.clear()}}var o="Computed",e=(x,y)=>{if(!y)return!1;return x.name===y.name&&x.message===y.message},O=(x)=>{let y=new Set,z=X(x)?void 0:{nil:()=>Z,err:(...$)=>{if($.length>1)throw new AggregateError($);else throw $[0]},...x},J=z?z.ok:x,B=Z,K,L=!0,G=!1,Q=!1,H,W=($)=>{if(!Object.is($,B))B=$,L=!1,K=void 0,G=!0},g=()=>{G=Z!==B,B=Z,K=void 0},f=($)=>{let C=j($);G=!e(C,K),B=Z,K=C},n=($)=>{if(Q=!1,H=void 0,W($),G)F(y)},u=($)=>{if(Q=!1,H=void 0,f($),G)F(y)},l=()=>{Q=!1,H=void 0,p()},q=()=>{if(L=!0,H?.abort("Aborted because source signal changed"),y.size){if(G)F(y)}else q.cleanups.forEach(($)=>$()),q.cleanups.clear()};q.cleanups=new Set;let p=()=>M(()=>{if(Q)throw new V("computed");if(G=!1,d(J)){if(H)return B;if(H=new AbortController,z)z.abort=z.abort instanceof AbortSignal?AbortSignal.any([z.abort,H.signal]):H.signal;H.signal.addEventListener("abort",l,{once:!0})}let $;Q=!0;try{$=z&&z.signals.length?S(z):J(H?.signal)}catch(C){I(C)?g():f(C),Q=!1;return}if(m($))$.then(n,u);else if($==null||Z===$)g();else W($);Q=!1},q),T={[Symbol.toStringTag]:o,get:()=>{if(N(y),k(),L)p();if(K)throw K;return B},map:($)=>O({signals:[T],ok:$}),tap:($)=>Y({signals:[T],...X($)?{ok:$}:$})};return T},b=(x)=>P(x,o);var i="State",E=(x)=>{let y=new Set,z=x,J={[Symbol.toStringTag]:i,get:()=>{return N(y),z},set:(B)=>{if(Object.is(z,B))return;if(z=B,F(y),Z===z)y.clear()},update:(B)=>{J.set(B(z))},map:(B)=>O({signals:[J],ok:B}),tap:(B)=>Y({signals:[J],...X(B)?{ok:B}:B})};return J},w=(x)=>P(x,i);var Z=Symbol(),h=(x)=>w(x)||b(x),s=(x)=>X(x)&&x.length<2,xx=(x)=>h(x)?x:s(x)?O(x):E(x),S=(x)=>{let{signals:y,abort:z,ok:J,err:B,nil:K}=x,L=[],G=!1,Q=y.map((H)=>{try{let W=H.get();if(W===Z)G=!0;return W}catch(W){if(I(W))throw W;L.push(j(W))}});try{return G?K(z):L.length?B(...L):J(...Q)}catch(H){if(I(H))throw H;let W=j(H);return B(W)}};export{M as watch,xx as toSignal,E as state,w as isState,h as isSignal,s as isComputedCallback,b as isComputed,r as enqueue,Y as effect,O as computed,a as batch,Z as UNSET,V as CircularDependencyError};
|
package/index.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.13.0
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
|
+
export { CircularDependencyError } from './lib/util'
|
|
6
7
|
export {
|
|
7
|
-
type Signal, type MaybeSignal,
|
|
8
|
-
|
|
9
|
-
UNSET, isSignal, isComputedCallbacks, toSignal
|
|
8
|
+
type Signal, type MaybeSignal,
|
|
9
|
+
UNSET, isSignal, isComputedCallback, toSignal
|
|
10
10
|
} from './lib/signal'
|
|
11
11
|
|
|
12
12
|
export { type State, state, isState } from './lib/state'
|
|
13
|
-
export { type Computed, computed, isComputed } from './lib/computed'
|
|
14
|
-
export { effect } from './lib/effect'
|
|
13
|
+
export { type Computed, type ComputedMatcher, computed, isComputed } from './lib/computed'
|
|
14
|
+
export { type EffectMatcher, type TapMatcher, effect } from './lib/effect'
|
|
15
15
|
export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler'
|
package/lib/computed.d.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import { type Signal
|
|
1
|
+
import { type Signal } from './signal';
|
|
2
|
+
import { type TapMatcher } from './effect';
|
|
3
|
+
export type ComputedMatcher<S extends Signal<{}>[], R extends {}> = {
|
|
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
|
+
};
|
|
2
12
|
export type Computed<T extends {}> = {
|
|
3
13
|
[Symbol.toStringTag]: 'Computed';
|
|
4
|
-
get
|
|
5
|
-
map
|
|
6
|
-
|
|
14
|
+
get(): T;
|
|
15
|
+
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>;
|
|
16
|
+
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void;
|
|
7
17
|
};
|
|
8
18
|
/**
|
|
9
19
|
* Create a derived signal from existing signals
|
|
10
20
|
*
|
|
11
21
|
* @since 0.9.0
|
|
12
|
-
* @param {() => T}
|
|
13
|
-
* @param {U} maybeSignals - signals of functions using signals this values depends on
|
|
22
|
+
* @param {ComputedMatcher<S, T> | ((abort?: AbortSignal) => T | Promise<T>)} matcher - computed matcher or callback
|
|
14
23
|
* @returns {Computed<T>} - Computed signal
|
|
15
24
|
*/
|
|
16
|
-
export declare const computed: <T extends {},
|
|
25
|
+
export declare const computed: <T extends {}, S extends Signal<{}>[] = []>(matcher: ComputedMatcher<S, T> | ((abort?: AbortSignal) => T | Promise<T>)) => Computed<T>;
|
|
17
26
|
/**
|
|
18
27
|
* Check if a value is a computed state
|
|
19
28
|
*
|