@zeix/cause-effect 0.11.0 → 0.12.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cause & Effect
2
2
 
3
- Version 0.11.0
3
+ Version 0.12.1
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
 
@@ -14,7 +14,7 @@ Version 0.11.0
14
14
  - **Performance**: Efficient updates that only recompute what's necessary.
15
15
  - **Type Safety**: Full TypeScript support for robust applications.
16
16
  - **Flexibility**: Works well with any UI framework or vanilla JavaScript.
17
- - **Lightweight**: Around 1kB gzipped over the wire.
17
+ - **Lightweight**: Dependency-free, only 1kB gzipped over the wire.
18
18
 
19
19
  ## Key Features
20
20
 
@@ -27,18 +27,18 @@ Version 0.11.0
27
27
  ## Quick Example
28
28
 
29
29
  ```js
30
- import { state, computed, effect } from '@zeix/cause-effect'
30
+ import { state, effect } from '@zeix/cause-effect'
31
31
 
32
32
  // Create a state signal
33
33
  const count = state(0)
34
34
 
35
35
  // Create a computed signal
36
- const doubleCount = computed(() => count.get() * 2)
36
+ const doubleCount = count.map(v => v * 2)
37
37
 
38
38
  // Create an effect
39
- effect(() => {
40
- console.log(`Count: ${count.get()}, Double: ${doubleCount.get()}`)
41
- })
39
+ effect((c, d) => {
40
+ console.log(`Count: ${c}, Double: ${d}`)
41
+ }, count, doubleCount)
42
42
 
43
43
  // Update the state
44
44
  count.set(5) // Logs: "Count: 5, Double: 10"
@@ -66,8 +66,9 @@ import { state, effect } from '@zeix/cause-effect'
66
66
  const count = state(42)
67
67
  effect(() => console.log(count.get())) // logs '42'
68
68
  count.set(24) // logs '24'
69
- document.querySelector('button.increment')
70
- .addEventListener('click', () => count.update(v => ++v))
69
+ document.querySelector('.increment').addEventListener('click', () => {
70
+ count.update(v => ++v)
71
+ })
71
72
  // Click on button logs '25', '26', and so on
72
73
  ```
73
74
 
@@ -82,8 +83,9 @@ const count = state(42)
82
83
  const isOdd = computed(() => count.get() % 2)
83
84
  effect(() => console.log(isOdd.get())) // logs 'false'
84
85
  count.set(24) // logs nothing because 24 is also an even number
85
- document.querySelector('button.increment')
86
- .addEventListener('click', () => count.update(v => ++v))
86
+ document.querySelector('button.increment').addEventListener('click', () => {
87
+ count.update(v => ++v)
88
+ })
87
89
  // Click on button logs 'true', 'false', and so on
88
90
  ```
89
91
 
@@ -96,8 +98,9 @@ const count = state(42)
96
98
  const isOdd = count.map(v => v % 2)
97
99
  effect(() => console.log(isOdd.get())) // logs 'false'
98
100
  count.set(24) // logs nothing because 24 is also an even number
99
- document.querySelector('button.increment')
100
- .addEventListener('click', () => count.set(v => ++v))
101
+ document.querySelector('.increment').addEventListener('click', () => {
102
+ count.update(v => ++v)
103
+ })
101
104
  // Click on button logs 'true', 'false', and so on
102
105
  ```
103
106
 
@@ -105,14 +108,14 @@ document.querySelector('button.increment')
105
108
 
106
109
  Async computed signals are as straight forward as their sync counterparts. Just create the computed signal with an async function.
107
110
 
108
- **Caution**: You can't use the `.map()` method to create an async computed signal. And async computed signals will return a Symbol `UNSET` until the Promise is resolved.
111
+ **Caution**: Async computed signals will return a Symbol `UNSET` until the Promise is resolved.
109
112
 
110
113
  ```js
111
- import { state, computed, effect } from '@zeix/cause-effect'
114
+ import { state, effect } from '@zeix/cause-effect'
112
115
 
113
116
  const entryId = state(42)
114
- const entryData = computed(async () => {
115
- const response = await fetch(`/api/entry/${entryId.get()}`)
117
+ const entryData = entryId.map(async id => {
118
+ const response = await fetch(`/api/entry/${id}`)
116
119
  if (!response.ok) return new Error(`Failed to fetch data: ${response.statusText}`)
117
120
  return response.json()
118
121
  })
@@ -154,23 +157,60 @@ effect({
154
157
 
155
158
  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()`.
156
159
 
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:
161
+
162
+ ```js
163
+ signal.match({
164
+ ok: v => console.log('Value:', v),
165
+ nil: () => console.warn('Not ready'),
166
+ err: e => console.error('Error:', e)
167
+ })
168
+ ```
169
+
157
170
  ### Effects and Batching
158
171
 
159
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.
160
173
 
161
174
  ```js
162
- import { state, computed, effect, batch } from '@zeix/cause-effect'
163
-
164
- const a = state(3)
165
- const b = state(4)
166
- const sum = computed(() => a.get() + b.get())
167
- effect(() => console.log(sum.get())) // logs '7'
168
- document.querySelector('button.double-all')
169
- .addEventListener('click', () =>
170
- batch(() => {
171
- a.update(v => v * 2)
172
- b.update(v => v * 2)
173
- }
174
- ))
175
- // Click on button logs '14' only once (instead of first '10' and then '14' without batch)
176
- ```
175
+ import { state, computed, batch } from '@zeix/cause-effect'
176
+
177
+ // State: define an array of State<number>
178
+ const signals = [state(2), state(3), state(5)]
179
+
180
+ // 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
185
+ if (!Number.isFinite(v)) throw new Error('Invalid value')
186
+ return v
187
+ })
188
+
189
+ // Effect: switch cases for the result
190
+ sum.match({
191
+ ok: v => console.log('Sum:', v),
192
+ err: error => console.error('Error:', error)
193
+ })
194
+
195
+ // Batch: apply changes to all signals in a single transaction
196
+ document.querySelector('.double-all').addEventListener('click', () => {
197
+ batch(() => {
198
+ signals.forEach(signal => signal.update(v => v * 2))
199
+ })
200
+ })
201
+ // Click on button logs '20' only once
202
+ // (instead of first '12', then '15' and then '20' without batch)
203
+
204
+ // Provoke an error - but no worries: it will be handled fine
205
+ signals[0].set(NaN)
206
+ ```
207
+
208
+ This example showcases several powerful features of Cause & Effect:
209
+
210
+ 1. **Composability and Declarative Computations**: Easily compose multiple signals into a single computed value, declaring how values should be calculated based on other signals.
211
+ 2. **Automatic Dependency Tracking and Efficient Updates**: The library tracks dependencies between signals and computed values, ensuring efficient propagation of changes.
212
+ 3. **Robust Error Handling**: Built-in error handling at computation level and reactive error management allow for graceful handling of unexpected situations.
213
+ 4. **Performance Optimization through Batching**: Group multiple state changes to ensure dependent computations and effects run only once after all changes are applied.
214
+ 5. **Flexibility and Integration**: Seamlessly integrates with DOM manipulation and event listeners, fitting into any JavaScript application or framework.
215
+
216
+ 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.
package/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.11.0
3
+ * @version 0.12.1
4
4
  * @author Esther Brunner
5
5
  */
6
- export { State, state, isState } from './lib/state';
6
+ export { type Signal, type MaybeSignal, type InferMaybeSignalType, type ComputedCallbacks, type EffectCallbacks, UNSET, isSignal, toSignal } from './lib/signal';
7
+ export { type State, state, isState } from './lib/state';
7
8
  export { type Computed, computed, isComputed } from './lib/computed';
8
- export { type Signal, type MaybeSignal, UNSET, isSignal, toSignal, batch } from './lib/signal';
9
9
  export { effect } from './lib/effect';
10
+ export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler';
package/index.js CHANGED
@@ -1 +1 @@
1
- var N=(x)=>typeof x==="function";var U=(x)=>N(x)&&x.length<2,O=(x)=>(y)=>y instanceof x,o=O(Error),g=O(Promise),$=(x)=>o(x)?x:new Error(String(x));var w="Computed",f=1000,V=(x)=>{let y=[],j=B,z=null,G=!0,q=!1,H=!1,X=0,J=()=>{if(G=!0,!q)C(y)},Z=()=>I(()=>{if(!G||H)return;let T=(L)=>{if(!Object.is(L,j))j=L,G=!1,z=null,q=!1;else q=!0},E=(L)=>{let Y=$(L);q=Object.is(Y,z),z=Y};H=!0;try{let L=x(j);g(L)?L.then((Y)=>{T(Y),C(y)}).catch(E):T(L)}catch(L){E(L)}finally{H=!1}},J),K={[Symbol.toStringTag]:w,get:()=>{if(X++>=f)throw new Error(`Circular dependency detected: exceeded ${f} iterations`);if(A(y),Z(),z)throw z;return j},map:(T)=>V(()=>T(K.get()))};return K},R=(x)=>!!x&&typeof x==="object"&&x[Symbol.toStringTag]===w;var W,M=0,P=new Set,S=new Set,p=()=>{while(P.size||S.size)P.forEach((x)=>x()),P.clear(),S.forEach((x)=>x()),S.clear()},B=Symbol(),_=(x)=>Q(x)||R(x),d=(x)=>_(x)?x:U(x)?V(x):D(x),A=(x)=>{if(W&&!x.includes(W))x.push(W)},C=(x)=>{x.forEach((y)=>M?P.add(y):y())},I=(x,y)=>{let j=W;W=y,x(),W=j},m=(x)=>{if(M++,x(),M--,!M)p()};class F{x;watchers=[];constructor(x){this.value=x}get(){return A(this.watchers),this.value}set(x){if(Object.is(this.value,x))return;if(this.value=x,C(this.watchers),B===x)this.watchers=[]}update(x){this.set(x(this.value))}map(x){return V(()=>x(this.get()))}}var D=(x)=>new F(x),Q=(x)=>x instanceof F;function b(x,...y){let j=N(x)?{ok:x}:x,{ok:z,nil:G,err:q}=j,H=()=>I(()=>{let X=[],J=[],Z=!1;for(let K of y)try{let T=K.get();if(T===B)Z=!0;X.push(T)}catch(T){J.push($(T))}try{if(!Z&&!J.length)z(...X);else if(J.length&&q)q(...J);else if(Z&&G)G()}catch(K){q?.($(K))}},H);H()}export{d as toSignal,D as state,Q as isState,_ as isSignal,R as isComputed,b as effect,V as computed,m as batch,B as UNSET,F as State};
1
+ var V=(x)=>typeof x==="function";var R=(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,D=new Set,w=0,N=new Map,M,p=()=>{M=void 0;for(let x of N.values()){for(let B of x.values())B();x.clear()}},d=()=>{if(M)cancelAnimationFrame(M);M=requestAnimationFrame(p)};queueMicrotask(p);var T=(x)=>{if(F&&!x.includes(F))x.push(F)},C=(x)=>{for(let B of x)w?D.add(B):B()},U=()=>{while(D.size){let x=Array.from(D);D.clear();for(let B of x)B()}},n=(x)=>{w++,x(),U(),w--},O=(x,B)=>{let z=F;F=B,x(),F=z},v=(x,B)=>new Promise((z,$)=>{let L=()=>{try{z(x())}catch(H){$(H)}};if(B){let[H,J]=B;if(!N.has(H))N.set(H,new Map);N.get(H).set(J,L)}d()});function Y(x,...B){let z=()=>O(()=>{let $=_(B,x);if(j($))console.error("Unhandled error in effect:",$)},z);z()}var o="Computed",i=(x,B)=>{if(!B)return!1;return x.name===B.name&&x.message===B.message},I=(x,...B)=>{let z=[],$=Q,L,H=!0,J=!1,X=!1,K=(G)=>{if(!Object.is(G,$))$=G,H=!1,L=void 0,J=!1},W=()=>{J=Q===$,$=Q,L=void 0},Z=(G)=>{let P=y(G);J=i(P,L),$=Q,L=P},A=()=>{if(H=!0,!J)C(z)},h=()=>O(()=>{if(X)throw new Error("Circular dependency detected");J=!0,X=!0;let G=_(B,x);if(g(G))W(),G.then((P)=>{K(P),C(z)}).catch(Z);else if(G==null||Q===G)W();else if(j(G))Z(G);else K(G);X=!1},A),q={[Symbol.toStringTag]:o,get:()=>{if(T(z),U(),H)h();if(L)throw L;return $},map:(G)=>I(G,q),match:(G)=>{return Y(G,q),q}};return q},S=(x)=>R(x,o);var b="State",E=(x)=>{let B=[],z=x,$={[Symbol.toStringTag]:b,get:()=>{return T(B),z},set:(L)=>{if(Object.is(z,L))return;if(z=L,C(B),Q===z)B.length=0},update:(L)=>{$.set(L(z))},map:(L)=>I(L,$),match:(L)=>{return Y(L,$),$}};return $},k=(x)=>R(x,b);var Q=Symbol(),c=(x)=>V(x)&&!x.length||typeof x==="object"&&x!==null&&("ok"in x)&&V(x.ok),f=(x)=>k(x)||S(x),t=(x)=>f(x)?x:c(x)?I(x):E(x),_=(x,B)=>{let{ok:z,nil:$,err:L}=V(B)?{ok:B}:B,H=[],J=[],X=!1;for(let W=0;W<x.length;W++){let Z=x[W];try{let A=f(Z)?Z.get():V(Z)?Z():Z;if(A===Q)X=!0;H[W]=A}catch(A){J.push(y(A))}}let K=void 0;try{if(X&&$)K=$();else if(J.length)K=L?L(...J):J[0];else if(!X)K=z(...H)}catch(W){if(K=y(W),L)K=L(K)}return K};export{O as watch,t as toSignal,E as state,k as isState,f as isSignal,S as isComputed,v as enqueue,Y as effect,I as computed,n as batch,Q as UNSET};
package/index.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.11.0
3
+ * @version 0.12.1
4
4
  * @author Esther Brunner
5
5
  */
6
- export { State, state, isState } from './lib/state'
7
- export { type Computed, computed, isComputed } from './lib/computed'
8
6
  export {
9
- type Signal, type MaybeSignal,
10
- UNSET, isSignal, toSignal, batch
7
+ type Signal, type MaybeSignal, type InferMaybeSignalType,
8
+ type ComputedCallbacks, type EffectCallbacks,
9
+ UNSET, isSignal, toSignal
11
10
  } from './lib/signal'
12
- export { effect } from './lib/effect'
11
+
12
+ export { type State, state, isState } from './lib/state'
13
+ export { type Computed, computed, isComputed } from './lib/computed'
14
+ export { effect } from './lib/effect'
15
+ export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler'
package/lib/computed.d.ts CHANGED
@@ -1,16 +1,19 @@
1
- export type Computed<T> = {
2
- [Symbol.toStringTag]: "Computed";
1
+ import { type MaybeSignal, type EffectCallbacks, type ComputedCallbacks } from './signal';
2
+ export type Computed<T extends {}> = {
3
+ [Symbol.toStringTag]: 'Computed';
3
4
  get: () => T;
4
- map: <U extends {}>(fn: (value: T) => U) => Computed<U>;
5
+ map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>) => Computed<U>;
6
+ match: (cb: EffectCallbacks<[Computed<T>]>) => void;
5
7
  };
6
8
  /**
7
- * Create a derived state from existing states
9
+ * Create a derived signal from existing signals
8
10
  *
9
11
  * @since 0.9.0
10
- * @param {() => T} fn - compute function to derive state
11
- * @returns {Computed<T>} result of derived state
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
14
+ * @returns {Computed<T>} - Computed signal
12
15
  */
13
- export declare const computed: <T extends {}>(fn: (v?: T) => T | Promise<T>) => Computed<T>;
16
+ export declare const computed: <T extends {}, U extends MaybeSignal<{}>[]>(cb: ComputedCallbacks<T, U>, ...maybeSignals: U) => Computed<T>;
14
17
  /**
15
18
  * Check if a value is a computed state
16
19
  *
@@ -18,4 +21,4 @@ export declare const computed: <T extends {}>(fn: (v?: T) => T | Promise<T>) =>
18
21
  * @param {unknown} value - value to check
19
22
  * @returns {boolean} - true if value is a computed state, false otherwise
20
23
  */
21
- export declare const isComputed: <T>(value: unknown) => value is Computed<T>;
24
+ export declare const isComputed: <T extends {}>(value: unknown) => value is Computed<T>;
package/lib/computed.ts CHANGED
@@ -1,92 +1,138 @@
1
- import { type Watcher, subscribe, notify, watch, UNSET } from "./signal"
2
- import { isPromise, toError } from "./util"
1
+ import {
2
+ type MaybeSignal, type EffectCallbacks, type ComputedCallbacks,
3
+ resolve, UNSET
4
+ } from './signal'
5
+ import { isError, isObjectOfType, isPromise, toError } from './util'
6
+ import { type Watcher, flush, notify, subscribe, watch } from './scheduler'
7
+ import { effect } from './effect'
3
8
 
4
9
  /* === Types === */
5
10
 
6
- export type Computed<T> = {
7
- [Symbol.toStringTag]: "Computed"
11
+ export type Computed<T extends {}> = {
12
+ [Symbol.toStringTag]: 'Computed'
8
13
  get: () => T
9
- map: <U extends {}>(fn: (value: T) => U) => Computed<U>
14
+ map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>) => Computed<U>
15
+ match: (cb: EffectCallbacks<[Computed<T>]>) => void
10
16
  }
11
17
 
12
18
  /* === Constants === */
13
19
 
14
20
  const TYPE_COMPUTED = 'Computed'
15
- const MAX_ITERATIONS = 1000;
16
21
 
17
- /* === Namespace Computed === */
22
+ /* === Private Functions === */
23
+
24
+ const isEquivalentError = /*#__PURE__*/ (
25
+ error1: Error,
26
+ error2: Error | undefined
27
+ ): boolean => {
28
+ if (!error2) return false
29
+ return error1.name === error2.name && error1.message === error2.message
30
+ }
31
+
32
+ /* === Computed Factory === */
18
33
 
19
34
  /**
20
- * Create a derived state from existing states
35
+ * Create a derived signal from existing signals
21
36
  *
22
37
  * @since 0.9.0
23
- * @param {() => T} fn - compute function to derive state
24
- * @returns {Computed<T>} result of derived state
38
+ * @param {() => T} cb - compute callback or object of ok, nil, err callbacks to derive state
39
+ * @param {U} maybeSignals - signals of functions using signals this values depends on
40
+ * @returns {Computed<T>} - Computed signal
25
41
  */
26
- export const computed = /*#__PURE__*/ <T extends {}>(
27
- fn: (v?: T) => T | Promise<T>,
42
+ export const computed = <T extends {}, U extends MaybeSignal<{}>[]>(
43
+ cb: ComputedCallbacks<T, U>,
44
+ ...maybeSignals: U
28
45
  ): Computed<T> => {
29
46
  const watchers: Watcher[] = []
30
47
  let value: T = UNSET
31
- let error: Error | null = null
48
+ let error: Error | undefined
32
49
  let dirty = true
33
50
  let unchanged = false
34
51
  let computing = false
35
- let iterations = 0
36
52
 
37
- const mark: Watcher = () => {
53
+ // Functions to update internal state
54
+ const ok = (v: T) => {
55
+ if (!Object.is(v, value)) {
56
+ value = v
57
+ dirty = false
58
+ error = undefined
59
+ unchanged = false
60
+ }
61
+ }
62
+ const nil = () => {
63
+ unchanged = (UNSET === value)
64
+ value = UNSET
65
+ error = undefined
66
+ }
67
+ const err = (e: unknown) => {
68
+ const newError = toError(e)
69
+ unchanged = isEquivalentError(newError, error)
70
+ value = UNSET
71
+ error = newError
72
+ }
73
+
74
+ // Called when notified from sources (push)
75
+ const mark = () => {
38
76
  dirty = true
39
77
  if (!unchanged) notify(watchers)
40
78
  }
41
79
 
80
+ // Called when requested by dependencies (pull)
42
81
  const compute = () => watch(() => {
43
- if (!dirty || computing) return
44
-
45
- const ok = (v: T) => {
46
- if (!Object.is(v, value)) {
47
- value = v
48
- dirty = false
49
- error = null
50
- unchanged = false
51
- } else {
52
- unchanged = true
53
- }
54
- }
55
- const err = (e: unknown) => {
56
- const newError = toError(e)
57
- unchanged = Object.is(newError, error)
58
- error = newError
59
- }
60
-
82
+ if (computing) throw new Error('Circular dependency detected')
83
+ unchanged = true
61
84
  computing = true
62
- try {
63
- const res = fn(value)
64
- isPromise(res)
65
- ? res.then(v => {
66
- ok(v)
67
- notify(watchers)
68
- }).catch(err)
69
- : ok(res)
70
- } catch (e) {
71
- err(e)
72
- } finally {
73
- computing = false
74
- }
85
+ const result = resolve(maybeSignals, cb)
86
+ if (isPromise(result)) {
87
+ nil() // sync
88
+ result.then(v => {
89
+ ok(v) // async
90
+ notify(watchers)
91
+ }).catch(err)
92
+ } else if (null == result || UNSET === result) nil()
93
+ else if (isError(result)) err(result)
94
+ else ok(result)
95
+ computing = false
75
96
  }, mark)
76
97
 
77
98
  const c: Computed<T> = {
78
99
  [Symbol.toStringTag]: TYPE_COMPUTED,
79
- get: () => {
80
- if (iterations++ >= MAX_ITERATIONS) {
81
- throw new Error(`Circular dependency detected: exceeded ${MAX_ITERATIONS} iterations`)
82
- }
100
+
101
+ /**
102
+ * Get the current value of the computed
103
+ *
104
+ * @since 0.9.0
105
+ * @returns {T} - current value of the computed
106
+ */
107
+ get: (): T => {
83
108
  subscribe(watchers)
84
- compute()
109
+ flush()
110
+ if (dirty) compute()
85
111
  if (error) throw error
86
112
  return value
87
113
  },
88
- map: <U extends {}>(fn: (value: T) => U): Computed<U> =>
89
- computed(() => fn(c.get())),
114
+
115
+ /**
116
+ * Create a computed signal from the current computed signal
117
+ *
118
+ * @since 0.9.0
119
+ * @param {ComputedCallbacks<U, [Computed<T>]>} cb - compute callback or object of ok, nil, err callbacks to map this value to new computed
120
+ * @returns {Computed<U>} - computed signal
121
+ */
122
+ map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>): Computed<U> =>
123
+ computed(cb, c),
124
+
125
+ /**
126
+ * Case matching for the computed signal with effect callbacks
127
+ *
128
+ * @since 0.12.0
129
+ * @param {EffectCallbacks[Computed<T>]} cb - effect callback or object of ok, nil, err callbacks to be executed when the computed changes
130
+ * @returns {Computed<T>} - self, for chaining effect callbacks
131
+ */
132
+ match: (cb: EffectCallbacks<[Computed<T>]>): Computed<T> => {
133
+ effect(cb, c)
134
+ return c
135
+ }
90
136
  }
91
137
  return c
92
138
  }
@@ -100,6 +146,5 @@ export const computed = /*#__PURE__*/ <T extends {}>(
100
146
  * @param {unknown} value - value to check
101
147
  * @returns {boolean} - true if value is a computed state, false otherwise
102
148
  */
103
- export const isComputed = /*#__PURE__*/ <T>(value: unknown): value is Computed<T> =>
104
- !!value && typeof value === 'object'
105
- && (value as { [key in typeof Symbol.toStringTag]: string })[Symbol.toStringTag] === TYPE_COMPUTED
149
+ export const isComputed = /*#__PURE__*/ <T extends {}>(value: unknown): value is Computed<T> =>
150
+ isObjectOfType(value, TYPE_COMPUTED)
package/lib/effect.d.ts CHANGED
@@ -1,15 +1,9 @@
1
- import { type Signal } from "./signal";
2
- export type EffectOkCallback<T extends {}[]> = (...values: T) => void;
3
- export type EffectCallbacks<T extends {}[]> = {
4
- ok: EffectOkCallback<T>;
5
- nil?: () => void;
6
- err?: (...errors: Error[]) => void;
7
- };
1
+ import { type EffectCallbacks, type MaybeSignal } from './signal';
8
2
  /**
9
3
  * Define what happens when a reactive state changes
10
4
  *
11
5
  * @since 0.1.0
12
- * @param {() => void} fn - callback function to be executed when a state changes
6
+ * @param {() => void} cb - effect callback or object of ok, nil, err callbacks to be executed when a state changes
7
+ * @param {U} maybeSignals - signals of functions using signals that should trigger the effect
13
8
  */
14
- export declare function effect<T extends {}>(ok: EffectOkCallback<T[]>, ...signals: Signal<T>[]): void;
15
- export declare function effect<T extends {}>(callbacks: EffectCallbacks<T[]>, ...signals: Signal<T>[]): void;
9
+ export declare function effect<U extends MaybeSignal<{}>[]>(cb: EffectCallbacks<U>, ...maybeSignals: U): void;
package/lib/effect.ts CHANGED
@@ -1,16 +1,7 @@
1
1
 
2
- import { type Signal, UNSET, type Watcher, watch } from "./signal"
3
- import { isFunction, toError } from "./util"
4
-
5
- /* === Types === */
6
-
7
- export type EffectOkCallback<T extends {}[]> = (...values: T) => void
8
-
9
- export type EffectCallbacks<T extends {}[]> = {
10
- ok: EffectOkCallback<T>
11
- nil?: () => void
12
- err?: (...errors: Error[]) => void
13
- }
2
+ import { type EffectCallbacks, type MaybeSignal, resolve } from './signal'
3
+ import { isError } from './util'
4
+ import { watch } from './scheduler'
14
5
 
15
6
  /* === Exported Function === */
16
7
 
@@ -18,48 +9,17 @@ export type EffectCallbacks<T extends {}[]> = {
18
9
  * Define what happens when a reactive state changes
19
10
  *
20
11
  * @since 0.1.0
21
- * @param {() => void} fn - callback function to be executed when a state changes
12
+ * @param {() => void} cb - effect callback or object of ok, nil, err callbacks to be executed when a state changes
13
+ * @param {U} maybeSignals - signals of functions using signals that should trigger the effect
22
14
  */
23
-
24
- export function effect<T extends {}>(
25
- ok: EffectOkCallback<T[]>,
26
- ...signals: Signal<T>[]
27
- ): void
28
- export function effect<T extends {}>(
29
- callbacks: EffectCallbacks<T[]>,
30
- ...signals: Signal<T>[]
31
- ): void
32
- export function effect<T extends {}>(
33
- callbacksOrFn: EffectCallbacks<T[]> | EffectOkCallback<T[]>,
34
- ...signals: Signal<T>[]
15
+ export function effect<U extends MaybeSignal<{}>[]>(
16
+ cb: EffectCallbacks<U>,
17
+ ...maybeSignals: U
35
18
  ): void {
36
- const callbacks = isFunction(callbacksOrFn)
37
- ? { ok: callbacksOrFn }
38
- : callbacksOrFn as EffectCallbacks<T[]>
39
-
40
- const { ok, nil, err } = callbacks
41
-
42
- const run: Watcher = () => watch(() => {
43
- const values: T[] = []
44
- const errors: Error[] = []
45
- let hasUnset = false
46
-
47
- for (const signal of signals) {
48
- try {
49
- const value = signal.get()
50
- if (value === UNSET) hasUnset = true
51
- values.push(value)
52
- } catch (error) {
53
- errors.push(toError(error))
54
- }
55
- }
56
- try {
57
- if (!hasUnset && !errors.length) ok(...values)
58
- else if (errors.length && err) err(...errors)
59
- else if (hasUnset && nil) nil()
60
- } catch (error) {
61
- err?.(toError(error))
62
- }
19
+ const run = () => watch(() => {
20
+ const result = resolve(maybeSignals, cb)
21
+ if (isError(result))
22
+ console.error('Unhandled error in effect:', result)
63
23
  }, run)
64
24
  run()
65
25
  }
@@ -0,0 +1,40 @@
1
+ export type EnqueueDedupe = [Element, string];
2
+ export type Watcher = () => void;
3
+ export type Updater = <T>() => T;
4
+ /**
5
+ * Add active watcher to the array of watchers
6
+ *
7
+ * @param {Watcher[]} watchers - watchers of the signal
8
+ */
9
+ export declare const subscribe: (watchers: Watcher[]) => void;
10
+ /**
11
+ * Add watchers to the pending set of change notifications
12
+ *
13
+ * @param {Watcher[]} watchers - watchers of the signal
14
+ */
15
+ export declare const notify: (watchers: Watcher[]) => void;
16
+ /**
17
+ * Flush all pending changes to notify watchers
18
+ */
19
+ export declare const flush: () => void;
20
+ /**
21
+ * Batch multiple changes in a single signal graph and DOM update cycle
22
+ *
23
+ * @param {() => void} fn - function with multiple signal writes to be batched
24
+ */
25
+ export declare const batch: (fn: () => void) => void;
26
+ /**
27
+ * Run a function in a reactive context
28
+ *
29
+ * @param {() => void} run - function to run the computation or effect
30
+ * @param {Watcher} mark - function to be called when the state changes
31
+ */
32
+ export declare const watch: (run: () => void, mark: Watcher) => void;
33
+ /**
34
+ * Enqueue a function to be executed on the next animation frame
35
+ *
36
+ * @param callback
37
+ * @param dedupe
38
+ * @returns
39
+ */
40
+ export declare const enqueue: <T>(update: Updater, dedupe?: EnqueueDedupe) => Promise<T>;