@zeix/cause-effect 0.11.0 → 0.12.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cause & Effect
2
2
 
3
- Version 0.11.0
3
+ Version 0.12.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
 
@@ -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
 
@@ -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.0
4
4
  * @author Esther Brunner
5
5
  */
6
- export { State, state, isState } from './lib/state';
6
+ export { type Signal, type MaybeSignal, 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
- export { effect } from './lib/effect';
9
+ export { type EffectOkCallback, type EffectCallbacks, 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 j=(y)=>typeof y==="function";var O=(y)=>j(y)&&y.length<2,D=(y,x)=>Object.prototype.toString.call(y)===`[object ${x}]`,g=(y)=>(x)=>x instanceof y,A=g(Error),o=g(Promise),C=(y)=>A(y)?y:new Error(String(y)),k=(y,x)=>{if(!x)return!1;return y.name===x.name&&y.message===x.message};var X,P=new Set,_=0,S=new Map,M,b=()=>{M=void 0;for(let y of S.values()){for(let x of y.values())x();y.clear()}},v=()=>{if(M)cancelAnimationFrame(M);M=requestAnimationFrame(b)};queueMicrotask(b);var T=(y)=>{if(X&&!y.includes(X))y.push(X)},I=(y)=>{for(let x of y)_?P.add(x):x()},U=()=>{while(P.size){let y=Array.from(P);P.clear();for(let x of y)x()}},t=(y)=>{_++,y(),U(),_--},q=(y,x)=>{let z=X;X=x,y(),X=z},u=(y,x)=>new Promise((z,L)=>{let B=()=>{try{z(y())}catch(G){L(G)}};if(x){let[G,J]=x;if(!S.has(G))S.set(G,new Map);S.get(G).set(J,B)}v()});function R(y,...x){let z=j(y)?{ok:y}:y,L=()=>q(()=>{let B=F(x,z);if(A(B))console.error("Unhandled error in effect:",B)},L);L()}var p="Computed",Z=(y,...x)=>{let z=j(y)?{ok:y}:y,L=[],B=Q,G,J=!0,K=!1,H=!1,W=($)=>{if(!Object.is($,B))B=$,J=!1,G=void 0,K=!1},V=()=>{K=Q===B,B=Q,G=void 0},m=($)=>{let N=C($);K=k(N,G),B=Q,G=N},n=()=>{if(J=!0,!K)I(L)},i=()=>q(()=>{if(H)throw new Error("Circular dependency detected");K=!0,H=!0;let $=F(x,z);if(o($))V(),$.then((N)=>{W(N),I(L)}).catch(m);else if($==null||Q===$)V();else if(A($))m($);else W($);H=!1},n),Y={[Symbol.toStringTag]:p,get:()=>{if(T(L),U(),J)i();if(G)throw G;return B},map:($)=>Z(()=>$(Y.get())),match:($)=>{return R($,Y),Y}};return Y},f=(y)=>D(y,p);var h="State",w=(y)=>{let x=[],z=y,L={[Symbol.toStringTag]:h,get:()=>{return T(x),z},set:(B)=>{if(Object.is(z,B))return;if(z=B,I(x),Q===z)x.length=0},update:(B)=>{L.set(B(z))},map:(B)=>Z(()=>B(L.get())),match:(B)=>{return R(B,L),L}};return L},E=(y)=>D(y,h);var Q=Symbol(),d=(y)=>E(y)||f(y),c=(y)=>d(y)?y:O(y)?Z(y):w(y),F=(y,x)=>{let{ok:z,nil:L,err:B}=x,G=[],J=[],K=!1;for(let W of y)try{let V=W.get();if(V===Q)K=!0;G.push(V)}catch(V){J.push(C(V))}let H=void 0;try{if(K&&L)H=L();else if(J.length)H=B?B(...J):J[0];else if(!K)H=z(...G)}catch(W){if(H=C(W),B)H=B(H)}finally{return H}};export{q as watch,c as toSignal,w as state,E as isState,d as isSignal,f as isComputed,u as enqueue,R as effect,Z as computed,t as batch,Q as UNSET};
package/index.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.11.0
3
+ * @version 0.12.0
4
4
  * @author Esther Brunner
5
5
  */
6
- export { State, state, isState } from './lib/state'
6
+ export { type Signal, type MaybeSignal, 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 {
9
- type Signal, type MaybeSignal,
10
- UNSET, isSignal, toSignal, batch
11
- } from './lib/signal'
12
- export { effect } from './lib/effect'
9
+ export { type EffectOkCallback, type EffectCallbacks, effect } from './lib/effect'
10
+ export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler'
package/lib/computed.d.ts CHANGED
@@ -1,16 +1,29 @@
1
- export type Computed<T> = {
2
- [Symbol.toStringTag]: "Computed";
1
+ import { type SignalValue, type UnknownSignal } from './signal';
2
+ import { type EffectCallbacks } from './effect';
3
+ export type ComputedOkCallback<T extends {}, U extends UnknownSignal[]> = (...values: {
4
+ [K in keyof U]: SignalValue<U[K]>;
5
+ }) => T | Promise<T>;
6
+ export type ComputedCallbacks<T extends {}, U extends UnknownSignal[]> = {
7
+ ok: (...values: {
8
+ [K in keyof U]: SignalValue<U[K]>;
9
+ }) => T | Promise<T>;
10
+ nil?: () => T | Promise<T>;
11
+ err?: (...errors: Error[]) => T | Promise<T>;
12
+ };
13
+ export type Computed<T extends {}> = {
14
+ [Symbol.toStringTag]: 'Computed';
3
15
  get: () => T;
4
16
  map: <U extends {}>(fn: (value: T) => U) => Computed<U>;
17
+ match: (callbacks: EffectCallbacks<[Computed<T>]>) => void;
5
18
  };
6
19
  /**
7
20
  * Create a derived state from existing states
8
21
  *
9
22
  * @since 0.9.0
10
- * @param {() => T} fn - compute function to derive state
23
+ * @param {() => T} callbacksOrFn - compute function to derive state
11
24
  * @returns {Computed<T>} result of derived state
12
25
  */
13
- export declare const computed: <T extends {}>(fn: (v?: T) => T | Promise<T>) => Computed<T>;
26
+ export declare const computed: <T extends {}, U extends UnknownSignal[]>(callbacksOrFn: ComputedCallbacks<T, U> | ComputedOkCallback<T, U>, ...signals: U) => Computed<T>;
14
27
  /**
15
28
  * Check if a value is a computed state
16
29
  *
@@ -18,4 +31,4 @@ export declare const computed: <T extends {}>(fn: (v?: T) => T | Promise<T>) =>
18
31
  * @param {unknown} value - value to check
19
32
  * @returns {boolean} - true if value is a computed state, false otherwise
20
33
  */
21
- export declare const isComputed: <T>(value: unknown) => value is Computed<T>;
34
+ export declare const isComputed: <T extends {}>(value: unknown) => value is Computed<T>;
package/lib/computed.ts CHANGED
@@ -1,92 +1,140 @@
1
- import { type Watcher, subscribe, notify, watch, UNSET } from "./signal"
2
- import { isPromise, toError } from "./util"
1
+ import { resolveSignals, UNSET, type SignalValue, type UnknownSignal } from './signal'
2
+ import { isEquivalentError, isError, isFunction, isObjectOfType, isPromise, toError } from './util'
3
+ import { type Watcher, flush, notify, subscribe, watch } from './scheduler'
4
+ import { type EffectCallbacks, effect } from './effect'
3
5
 
4
6
  /* === Types === */
5
7
 
6
- export type Computed<T> = {
7
- [Symbol.toStringTag]: "Computed"
8
+ export type ComputedOkCallback<T extends {}, U extends UnknownSignal[]> = (
9
+ ...values: { [K in keyof U]: SignalValue<U[K]> }
10
+ ) => T | Promise<T>
11
+
12
+ export type ComputedCallbacks<T extends {}, U extends UnknownSignal[]> = {
13
+ ok: (...values: { [K in keyof U]: SignalValue<U[K]> }) => T | Promise<T>
14
+ nil?: () => T | Promise<T>
15
+ err?: (...errors: Error[]) => T | Promise<T>
16
+ }
17
+
18
+ export type Computed<T extends {}> = {
19
+ [Symbol.toStringTag]: 'Computed'
8
20
  get: () => T
9
21
  map: <U extends {}>(fn: (value: T) => U) => Computed<U>
22
+ match: (callbacks: EffectCallbacks<[Computed<T>]>) => void
10
23
  }
11
24
 
12
25
  /* === Constants === */
13
26
 
14
27
  const TYPE_COMPUTED = 'Computed'
15
- const MAX_ITERATIONS = 1000;
16
28
 
17
- /* === Namespace Computed === */
29
+ /* === Computed Factory === */
18
30
 
19
31
  /**
20
32
  * Create a derived state from existing states
21
33
  *
22
34
  * @since 0.9.0
23
- * @param {() => T} fn - compute function to derive state
35
+ * @param {() => T} callbacksOrFn - compute function to derive state
24
36
  * @returns {Computed<T>} result of derived state
25
37
  */
26
- export const computed = /*#__PURE__*/ <T extends {}>(
27
- fn: (v?: T) => T | Promise<T>,
38
+ export const computed = <T extends {}, U extends UnknownSignal[]>(
39
+ callbacksOrFn: ComputedCallbacks<T, U> | ComputedOkCallback<T, U>,
40
+ ...signals: U
28
41
  ): Computed<T> => {
42
+ const callbacks = isFunction(callbacksOrFn)
43
+ ? { ok: callbacksOrFn }
44
+ : callbacksOrFn
29
45
  const watchers: Watcher[] = []
30
46
  let value: T = UNSET
31
- let error: Error | null = null
47
+ let error: Error | undefined
32
48
  let dirty = true
33
49
  let unchanged = false
34
50
  let computing = false
35
- let iterations = 0
36
51
 
37
- const mark: Watcher = () => {
52
+ // Functions to update internal state
53
+ const ok = (v: T) => {
54
+ if (!Object.is(v, value)) {
55
+ value = v
56
+ dirty = false
57
+ error = undefined
58
+ unchanged = false
59
+ }
60
+ }
61
+ const nil = () => {
62
+ unchanged = (UNSET === value)
63
+ value = UNSET
64
+ error = undefined
65
+ }
66
+ const err = (e: unknown) => {
67
+ const newError = toError(e)
68
+ unchanged = isEquivalentError(newError, error)
69
+ value = UNSET
70
+ error = newError
71
+ }
72
+
73
+ // Called when notified from sources (push)
74
+ const mark = () => {
38
75
  dirty = true
39
76
  if (!unchanged) notify(watchers)
40
77
  }
41
78
 
79
+ // Called when requested by dependencies (pull)
42
80
  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
-
81
+ if (computing) throw new Error('Circular dependency detected')
82
+ unchanged = true
61
83
  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
- }
84
+ const result = resolveSignals(signals, callbacks as ComputedCallbacks<T, U>)
85
+ if (isPromise(result)) {
86
+ nil() // sync
87
+ result.then(v => {
88
+ ok(v) // async
89
+ notify(watchers)
90
+ }).catch(err)
91
+ } else if (null == result || UNSET === result) nil()
92
+ else if (isError(result)) err(result)
93
+ else ok(result)
94
+ computing = false
75
95
  }, mark)
76
96
 
77
97
  const c: Computed<T> = {
78
98
  [Symbol.toStringTag]: TYPE_COMPUTED,
79
- get: () => {
80
- if (iterations++ >= MAX_ITERATIONS) {
81
- throw new Error(`Circular dependency detected: exceeded ${MAX_ITERATIONS} iterations`)
82
- }
99
+
100
+ /**
101
+ * Get the current value of the computed
102
+ *
103
+ * @since 0.9.0
104
+ * @method of Computed<T>
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> =>
114
+
115
+ /**
116
+ * Create a computed signal from the current computed signal
117
+ *
118
+ * @since 0.9.0
119
+ * @method of Computed<T>
120
+ * @param {(value: T) => R} fn
121
+ * @returns {Computed<R>} - computed signal
122
+ */
123
+ map: <R extends {}>(fn: (value: T) => R): Computed<R> =>
89
124
  computed(() => fn(c.get())),
125
+
126
+ /**
127
+ * Case matching for the computed signal with effect callbacks
128
+ *
129
+ * @since 0.12.0
130
+ * @method of Computed<T>
131
+ * @param {EffectCallbacks[<T>]} callbacks
132
+ * @returns {Computed<T>} - self, for chaining effect callbacks
133
+ */
134
+ match: (callbacks: EffectCallbacks<[Computed<T>]>): Computed<T> => {
135
+ effect(callbacks, c)
136
+ return c
137
+ }
90
138
  }
91
139
  return c
92
140
  }
@@ -100,6 +148,5 @@ export const computed = /*#__PURE__*/ <T extends {}>(
100
148
  * @param {unknown} value - value to check
101
149
  * @returns {boolean} - true if value is a computed state, false otherwise
102
150
  */
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
151
+ export const isComputed = /*#__PURE__*/ <T extends {}>(value: unknown): value is Computed<T> =>
152
+ isObjectOfType(value, TYPE_COMPUTED)
package/lib/effect.d.ts CHANGED
@@ -1,7 +1,11 @@
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>;
1
+ import { type SignalValue, type UnknownSignal } from './signal';
2
+ export type EffectOkCallback<T extends UnknownSignal[]> = (...values: {
3
+ [K in keyof T]: SignalValue<T[K]>;
4
+ }) => void;
5
+ export type EffectCallbacks<T extends UnknownSignal[]> = {
6
+ ok: (...values: {
7
+ [K in keyof T]: SignalValue<T[K]>;
8
+ }) => void;
5
9
  nil?: () => void;
6
10
  err?: (...errors: Error[]) => void;
7
11
  };
@@ -9,7 +13,6 @@ export type EffectCallbacks<T extends {}[]> = {
9
13
  * Define what happens when a reactive state changes
10
14
  *
11
15
  * @since 0.1.0
12
- * @param {() => void} fn - callback function to be executed when a state changes
16
+ * @param {() => void} callbacksOrFn - callback function to be executed when a state changes
13
17
  */
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;
18
+ export declare function effect<T extends UnknownSignal[]>(callbacksOrFn: EffectCallbacks<T> | EffectOkCallback<T>, ...signals: T): void;
package/lib/effect.ts CHANGED
@@ -1,13 +1,16 @@
1
1
 
2
- import { type Signal, UNSET, type Watcher, watch } from "./signal"
3
- import { isFunction, toError } from "./util"
2
+ import { resolveSignals, type SignalValue, type UnknownSignal } from './signal'
3
+ import { isError, isFunction } from './util'
4
+ import { watch } from './scheduler'
4
5
 
5
6
  /* === Types === */
6
7
 
7
- export type EffectOkCallback<T extends {}[]> = (...values: T) => void
8
+ export type EffectOkCallback<T extends UnknownSignal[]> = (
9
+ ...values: { [K in keyof T]: SignalValue<T[K]> }
10
+ ) => void
8
11
 
9
- export type EffectCallbacks<T extends {}[]> = {
10
- ok: EffectOkCallback<T>
12
+ export type EffectCallbacks<T extends UnknownSignal[]> = {
13
+ ok: (...values: { [K in keyof T]: SignalValue<T[K]> }) => void
11
14
  nil?: () => void
12
15
  err?: (...errors: Error[]) => void
13
16
  }
@@ -18,48 +21,20 @@ export type EffectCallbacks<T extends {}[]> = {
18
21
  * Define what happens when a reactive state changes
19
22
  *
20
23
  * @since 0.1.0
21
- * @param {() => void} fn - callback function to be executed when a state changes
24
+ * @param {() => void} callbacksOrFn - callback function to be executed when a state changes
22
25
  */
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>[]
26
+ export function effect<T extends UnknownSignal[]>(
27
+ callbacksOrFn: EffectCallbacks<T> | EffectOkCallback<T>,
28
+ ...signals: T
35
29
  ): void {
36
30
  const callbacks = isFunction(callbacksOrFn)
37
31
  ? { 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
32
+ : callbacksOrFn
46
33
 
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
- }
34
+ const run = () => watch(() => {
35
+ const result = resolveSignals(signals, callbacks as EffectCallbacks<T>)
36
+ if (isError(result))
37
+ console.error('Unhandled error in effect:', result)
63
38
  }, run)
64
39
  run()
65
40
  }
@@ -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>;