@zeix/cause-effect 0.12.3 → 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 ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": false,
3
+ "useTabs": true,
4
+ "tabWidth": 4,
5
+ "singleQuote": true,
6
+ "arrowParens": "avoid"
7
+ }
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cause & Effect
2
2
 
3
- Version 0.12.3
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.3
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
- - 🚀 Efficient state management with automatic dependency tracking
22
- - Built-in support for async operations
23
- - 🧠 Memoized computed values
24
- - 🛡️ Type-safe and non-nullable signals
25
- - 🎭 Declarative error and pending state handling
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 Example
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 a state signal
33
- const count = state(0)
25
+ // 1. Create state
26
+ const user = state({ name: 'Alice', age: 30 })
34
27
 
35
- // Create a computed signal
36
- const doubleCount = count.map(v => v * 2)
28
+ // 2. Create computed values
29
+ const greeting = computed(() => `Hello ${user.get().name}!`)
37
30
 
38
- // Create an effect
39
- effect((c, d) => {
40
- console.log(`Count: ${c}, Double: ${d}`)
41
- }, count, doubleCount)
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 the state
44
- count.set(5) // Logs: "Count: 5, Double: 10"
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, effect } from '@zeix/cause-effect'
62
+ import { state } from '@zeix/cause-effect'
65
63
 
66
64
  const count = state(42)
67
- effect(() => console.log(count.get())) // logs '42'
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, effect } from '@zeix/cause-effect'
95
+ import { state } from '@zeix/cause-effect'
96
96
 
97
97
  const count = state(42)
98
- const isOdd = count.map(v => v % 2)
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, effect } from '@zeix/cause-effect'
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
- .addEventListener('click', () => entryId.update(v => ++v))
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
- ### Handling Unset Values and Errors in Effects
128
+ ## Error Handling
129
+
130
+ Cause & Effect provides three paths for robust error handling:
129
131
 
130
- Computations can fail and throw errors. Promises may not have resolved yet when you try to access their value. **Cause & Effect makes it easy to deal with errors and unresolved async functions.** Computed functions will catch errors and re-throw them when you access their values.
132
+ 1. **Ok**: Value is available
133
+ 2. **Nil**: Loading/Unset state (especially for async)
134
+ 3. **Err**: Error occurred
131
135
 
132
- **Effects** are where you handle different cases:
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
- // Handle pending states while fetching data
140
- nil: () => {
141
- h2.textContent = 'Loading...'
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 `.match()` method on either `State` or `Computed`. You can use it for easy debugging, for example:
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.match({
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
- ### Effects and Batching
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
+ ```
171
173
 
172
- Effects run synchronously as soon as source signals update. If you need to set multiple signals you can batch them together to ensure dependents are executed only once.
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
202
+
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
- (...values) => values.reduce((total, v) => total + v, 0),
183
- ...signals
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.match({
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
- ### Effects and DOM Updates
251
+ ### Cleanup
219
252
 
220
- The `enqueue()` function allows you to schedule DOM updates to be executed on the next animation frame. This function returns a `Promise`, which makes it easy to detect when updates are applied or if they fail.
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 { enqueue } from '@zeix/cause-effect'
256
+ import { state, computed, effect } from '@zeix/cause-effect'
224
257
 
225
- // Schedule a DOM update
226
- enqueue(() => {
227
- document.getElementById('myElement').textContent = 'Updated content'
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
- .then(() => console.log('Update applied successfully'))
230
- .catch(error => console.error('Update failed:', error))
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
- You can also use the deduplication feature to ensure that only the latest update for a specific element and operation is applied:
274
+ ### Abort Controller
234
275
 
235
- ```js
236
- import { 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
- // Define a signal and update it in an event handler
239
- const name = state('')
240
- document.querySelector('input[name="name"]').addEventListener('input', e => {
241
- state.set(e.target.value) // Triggers an update on every keystroke
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
- // Define an effect to react to signal changes
245
- effect(text => {
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
- 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).
298
+ ## Contributing & License
299
+
300
+ Feel free to contribute, report issues, or suggest improvements.
301
+
302
+ Licence: [MIT](LICENCE.md)
257
303
 
258
- 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.
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.12.3
3
+ * @version 0.13.0
4
4
  * @author Esther Brunner
5
5
  */
6
- export { type Signal, type MaybeSignal, type InferMaybeSignalType, type ComputedCallbacks, type EffectCallbacks, UNSET, isSignal, toSignal } from './lib/signal';
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 V=(x)=>typeof x==="function";var q=(x,B)=>Object.prototype.toString.call(x)===`[object ${B}]`,m=(x)=>(B)=>B instanceof x,j=m(Error),g=m(Promise),y=(x)=>j(x)?x:new Error(String(x));var F,R=new Set,T=0,w=new Map,N,p=()=>{N=void 0;let x=Array.from(w.values());w.clear();for(let B of x)B()},o=()=>{if(N)cancelAnimationFrame(N);N=requestAnimationFrame(p)};queueMicrotask(p);var D=(x)=>{if(F&&!x.includes(F))x.push(F)},C=(x)=>{for(let B of x)T?R.add(B):B()},S=()=>{while(R.size){let x=Array.from(R);R.clear();for(let B of x)B()}},v=(x)=>{T++;try{x()}finally{S(),T--}},O=(x,B)=>{let z=F;F=B;try{x()}finally{F=z}},n=(x,B)=>new Promise((z,$)=>{let L=()=>{try{z(x())}catch(W){$(W)}};if(B)w.set(B,L);o()});function Y(x,...B){let z=!1,$=()=>O(()=>{if(z)throw new Error("Circular dependency in effect detected");z=!0;let L=_(B,x);if(j(L))console.error("Unhandled error in effect:",L);z=!1},$);$()}var b="Computed",i=(x,B)=>{if(!B)return!1;return x.name===B.name&&x.message===B.message},I=(x,...B)=>{let z=[],$=K,L,W=!0,H=!1,Z=!1,J=(G)=>{if(!Object.is(G,$))$=G,W=!1,L=void 0,H=!1},Q=()=>{H=K===$,$=K,L=void 0},X=(G)=>{let P=y(G);H=i(P,L),$=K,L=P},A=()=>{if(W=!0,!H)C(z)},d=()=>O(()=>{if(Z)throw new Error("Circular dependency in computed detected");H=!0,Z=!0;let G=_(B,x);if(g(G))Q(),G.then((P)=>{J(P),C(z)}).catch(X);else if(G==null||K===G)Q();else if(j(G))X(G);else J(G);Z=!1},A),M={[Symbol.toStringTag]:b,get:()=>{if(D(z),S(),W)d();if(L)throw L;return $},map:(G)=>I(G,M),match:(G)=>{return Y(G,M),M}};return M},U=(x)=>q(x,b);var h="State",k=(x)=>{let B=[],z=x,$={[Symbol.toStringTag]:h,get:()=>{return D(B),z},set:(L)=>{if(Object.is(z,L))return;if(z=L,C(B),K===z)B.length=0},update:(L)=>{$.set(L(z))},map:(L)=>I(L,$),match:(L)=>{return Y(L,$),$}};return $},E=(x)=>q(x,h);var K=Symbol(),c=(x)=>V(x)&&!x.length||typeof x==="object"&&x!==null&&("ok"in x)&&V(x.ok),f=(x)=>E(x)||U(x),t=(x)=>f(x)?x:c(x)?I(x):k(x),_=(x,B)=>{let{ok:z,nil:$,err:L}=V(B)?{ok:B}:B,W=[],H=[],Z=!1;for(let Q=0;Q<x.length;Q++){let X=x[Q];try{let A=f(X)?X.get():V(X)?X():X;if(A===K)Z=!0;W[Q]=A}catch(A){H.push(y(A))}}let J=void 0;try{if(Z&&$)J=$();else if(H.length)J=L?L(...H):H[0];else if(!Z)J=z(...W)}catch(Q){if(J=y(Q),L)J=L(J)}return J};export{O as watch,t as toSignal,k as state,E as isState,f as isSignal,U as isComputed,n as enqueue,Y as effect,I as computed,v as batch,K as UNSET};
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.12.3
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, type InferMaybeSignalType,
8
- type ComputedCallbacks, type EffectCallbacks,
9
- UNSET, isSignal, 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 MaybeSignal, type EffectCallbacks, type ComputedCallbacks } from './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: () => T;
5
- map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>) => Computed<U>;
6
- match: (cb: EffectCallbacks<[Computed<T>]>) => void;
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} cb - compute callback or object of ok, nil, err callbacks to derive state
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 {}, U extends MaybeSignal<{}>[]>(cb: ComputedCallbacks<T, U>, ...maybeSignals: U) => Computed<T>;
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
  *