@zeix/cause-effect 0.12.2 → 0.12.4

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.12.2
3
+ Version 0.12.4
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
 
@@ -54,7 +54,7 @@ npm install @zeix/cause-effect
54
54
  bun add @zeix/cause-effect
55
55
  ```
56
56
 
57
- ## Basic Usage
57
+ ## Usage
58
58
 
59
59
  ### Single State Signal
60
60
 
@@ -214,3 +214,45 @@ This example showcases several powerful features of Cause & Effect:
214
214
  5. **Flexibility and Integration**: Seamlessly integrates with DOM manipulation and event listeners, fitting into any JavaScript application or framework.
215
215
 
216
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.
217
+
218
+ ### Effects and DOM Updates
219
+
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.
221
+
222
+ ```js
223
+ import { enqueue } from '@zeix/cause-effect'
224
+
225
+ // Schedule a DOM update
226
+ enqueue(() => {
227
+ document.getElementById('myElement').textContent = 'Updated content'
228
+ })
229
+ .then(() => console.log('Update applied successfully'))
230
+ .catch(error => console.error('Update failed:', error))
231
+ ```
232
+
233
+ You can also use the deduplication feature to ensure that only the latest update for a specific element and operation is applied:
234
+
235
+ ```js
236
+ import { state, effect, enqueue } from '@zeix/cause-effect'
237
+
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
242
+ })
243
+
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)
254
+ ```
255
+
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).
257
+
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.
package/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.12.2
3
+ * @version 0.12.4
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 { type Signal, type MaybeSignal, type InferSignalType, type ComputedCallbacks, type EffectCallbacks, UNSET, isSignal, isComputedCallbacks, toSignal } from './lib/signal';
7
7
  export { type State, state, isState } from './lib/state';
8
8
  export { type Computed, computed, isComputed } from './lib/computed';
9
9
  export { effect } from './lib/effect';
package/index.js CHANGED
@@ -1 +1 @@
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=!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 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 in computed 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};
1
+ var q=(x)=>typeof x==="function";var M=(x,B)=>Object.prototype.toString.call(x)===`[object ${B}]`,f=(x)=>(B)=>B instanceof x,I=f(Error),m=f(Promise),V=(x)=>I(x)?x:new Error(String(x));var Z,R=new Set,T=0,w=new Map,N,g=()=>{N=void 0;let x=Array.from(w.values());w.clear();for(let B of x)B()},v=()=>{if(N)cancelAnimationFrame(N);N=requestAnimationFrame(g)};queueMicrotask(g);var D=(x)=>{if(Z&&!x.includes(Z))x.push(Z)},j=(x)=>{for(let B of x)if(T)R.add(B);else B()},S=()=>{while(R.size){let x=Array.from(R);R.clear();for(let B of x)B()}},n=(x)=>{T++;try{x()}finally{S(),T--}},C=(x,B)=>{let z=Z;Z=B;try{x()}finally{Z=z}},i=(x,B)=>new Promise((z,$)=>{let L=()=>{try{z(x())}catch(Q){$(Q)}};if(B)w.set(B,L);v()});function O(x,...B){let z=!1,$=()=>C(()=>{if(z)throw new Error("Circular dependency in effect detected");z=!0;let L=_(B,x);if(I(L))console.error("Unhandled error in effect:",L);z=!1},$);$()}var p="Computed",c=(x,B)=>{if(!B)return!1;return x.name===B.name&&x.message===B.message},A=(x,...B)=>{let z=[],$=J,L,Q=!0,G=!1,W=!1,H=(F)=>{if(!Object.is(F,$))$=F,Q=!1,L=void 0,G=!1},K=()=>{G=J===$,$=J,L=void 0},Y=(F)=>{let P=V(F);G=c(P,L),$=J,L=P},X=()=>{if(Q=!0,!G)j(z)},d=()=>C(()=>{if(W)throw new Error("Circular dependency in computed detected");G=!0,W=!0;let F=_(B,x);if(m(F))K(),F.then((P)=>{H(P),j(z)}).catch(Y);else if(F==null||J===F)K();else if(I(F))Y(F);else H(F);W=!1},X),y={[Symbol.toStringTag]:p,get:()=>{if(D(z),S(),Q)d();if(L)throw L;return $},map:(F)=>A(F,y),match:(F)=>{return O(F,y),y}};return y},U=(x)=>M(x,p);var b="State",k=(x)=>{let B=[],z=x,$={[Symbol.toStringTag]:b,get:()=>{return D(B),z},set:(L)=>{if(Object.is(z,L))return;if(z=L,j(B),J===z)B.length=0},update:(L)=>{$.set(L(z))},map:(L)=>A(L,$),match:(L)=>{return O(L,$),$}};return $},E=(x)=>M(x,b);var J=Symbol(),h=(x)=>E(x)||U(x),o=(x)=>q(x)&&!x.length||typeof x==="object"&&x!==null&&("ok"in x)&&q(x.ok),t=(x)=>h(x)?x:o(x)?A(x):k(x),_=(x,B)=>{let{ok:z,nil:$,err:L}=q(B)?{ok:B}:B,Q=[],G=[],W=!1;for(let K=0;K<x.length;K++){let Y=x[K];try{let X=Y.get();if(X===J)W=!0;Q[K]=X}catch(X){G.push(V(X))}}let H=void 0;try{if(W&&$)H=$();else if(G.length)H=L?L(...G):G[0];else if(!W)H=z(...Q)}catch(K){if(H=V(K),L)H=L(H)}return H};export{C as watch,t as toSignal,k as state,E as isState,h as isSignal,o as isComputedCallbacks,U as isComputed,i as enqueue,O as effect,A as computed,n as batch,J as UNSET};
package/index.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.12.2
3
+ * @version 0.12.4
4
4
  * @author Esther Brunner
5
5
  */
6
6
  export {
7
- type Signal, type MaybeSignal, type InferMaybeSignalType,
7
+ type Signal, type MaybeSignal, type InferSignalType,
8
8
  type ComputedCallbacks, type EffectCallbacks,
9
- UNSET, isSignal, toSignal
9
+ UNSET, isSignal, isComputedCallbacks, toSignal
10
10
  } from './lib/signal'
11
11
 
12
12
  export { type State, state, isState } from './lib/state'
package/lib/computed.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type MaybeSignal, type EffectCallbacks, type ComputedCallbacks } from './signal';
1
+ import { type Signal, type EffectCallbacks, type ComputedCallbacks } from './signal';
2
2
  export type Computed<T extends {}> = {
3
3
  [Symbol.toStringTag]: 'Computed';
4
4
  get: () => T;
@@ -13,7 +13,7 @@ export type Computed<T extends {}> = {
13
13
  * @param {U} maybeSignals - signals of functions using signals this values depends on
14
14
  * @returns {Computed<T>} - Computed signal
15
15
  */
16
- export declare const computed: <T extends {}, U extends MaybeSignal<{}>[]>(cb: ComputedCallbacks<T, U>, ...maybeSignals: U) => Computed<T>;
16
+ export declare const computed: <T extends {}, U extends Signal<{}>[]>(cb: ComputedCallbacks<T, U>, ...maybeSignals: U) => Computed<T>;
17
17
  /**
18
18
  * Check if a value is a computed state
19
19
  *
package/lib/computed.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {
2
- type MaybeSignal, type EffectCallbacks, type ComputedCallbacks,
2
+ type Signal, type EffectCallbacks, type ComputedCallbacks,
3
3
  resolve, UNSET
4
4
  } from './signal'
5
5
  import { isError, isObjectOfType, isPromise, toError } from './util'
@@ -39,7 +39,7 @@ const isEquivalentError = /*#__PURE__*/ (
39
39
  * @param {U} maybeSignals - signals of functions using signals this values depends on
40
40
  * @returns {Computed<T>} - Computed signal
41
41
  */
42
- export const computed = <T extends {}, U extends MaybeSignal<{}>[]>(
42
+ export const computed = <T extends {}, U extends Signal<{}>[]>(
43
43
  cb: ComputedCallbacks<T, U>,
44
44
  ...maybeSignals: U
45
45
  ): Computed<T> => {
package/lib/effect.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type EffectCallbacks, type MaybeSignal } from './signal';
1
+ import { type EffectCallbacks, type Signal } from './signal';
2
2
  /**
3
3
  * Define what happens when a reactive state changes
4
4
  *
@@ -6,4 +6,4 @@ import { type EffectCallbacks, type MaybeSignal } from './signal';
6
6
  * @param {() => void} cb - effect callback or object of ok, nil, err callbacks to be executed when a state changes
7
7
  * @param {U} maybeSignals - signals of functions using signals that should trigger the effect
8
8
  */
9
- export declare function effect<U extends MaybeSignal<{}>[]>(cb: EffectCallbacks<U>, ...maybeSignals: U): void;
9
+ export declare function effect<U extends Signal<{}>[]>(cb: EffectCallbacks<U>, ...maybeSignals: U): void;
package/lib/effect.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { type EffectCallbacks, type MaybeSignal, resolve } from './signal'
2
+ import { type EffectCallbacks, type Signal, resolve } from './signal'
3
3
  import { isError } from './util'
4
4
  import { watch } from './scheduler'
5
5
 
@@ -12,7 +12,7 @@ import { watch } from './scheduler'
12
12
  * @param {() => void} cb - effect callback or object of ok, nil, err callbacks to be executed when a state changes
13
13
  * @param {U} maybeSignals - signals of functions using signals that should trigger the effect
14
14
  */
15
- export function effect<U extends MaybeSignal<{}>[]>(
15
+ export function effect<U extends Signal<{}>[]>(
16
16
  cb: EffectCallbacks<U>,
17
17
  ...maybeSignals: U
18
18
  ): void {
@@ -1,6 +1,6 @@
1
1
  export type EnqueueDedupe = [Element, string];
2
2
  export type Watcher = () => void;
3
- export type Updater = <T>() => T;
3
+ export type Updater = <T>() => T | boolean | void;
4
4
  /**
5
5
  * Add active watcher to the array of watchers
6
6
  *
@@ -27,14 +27,13 @@ export declare const batch: (fn: () => void) => void;
27
27
  * Run a function in a reactive context
28
28
  *
29
29
  * @param {() => void} run - function to run the computation or effect
30
- * @param {Watcher} mark - function to be called when the state changes
30
+ * @param {Watcher} mark - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
31
31
  */
32
- export declare const watch: (run: () => void, mark: Watcher) => void;
32
+ export declare const watch: (run: () => void, mark?: Watcher) => void;
33
33
  /**
34
34
  * Enqueue a function to be executed on the next animation frame
35
35
  *
36
- * @param callback
37
- * @param dedupe
38
- * @returns
36
+ * @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
37
+ * @param {EnqueueDedupe} dedupe - [element, operation] pair for deduplication
39
38
  */
40
- export declare const enqueue: <T>(update: Updater, dedupe?: EnqueueDedupe) => Promise<T>;
39
+ export declare const enqueue: <T>(fn: Updater, dedupe?: EnqueueDedupe) => Promise<boolean | void | T>;
package/lib/scheduler.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  export type EnqueueDedupe = [Element, string]
4
4
 
5
5
  export type Watcher = () => void
6
- export type Updater = <T>() => T
6
+ export type Updater = <T>() => T | boolean | void
7
7
 
8
8
  /* === Internal === */
9
9
 
@@ -15,16 +15,15 @@ const pending = new Set<Watcher>()
15
15
  let batchDepth = 0
16
16
 
17
17
  // Map of DOM elements to update functions
18
- const updateMap = new Map<Element, Map<string, () => void>>()
18
+ const updateMap = new Map<EnqueueDedupe, () => void>()
19
19
  let requestId: number | undefined
20
20
 
21
21
  const updateDOM = () => {
22
22
  requestId = undefined
23
- for (const elementMap of updateMap.values()) {
24
- for (const fn of elementMap.values()) {
25
- fn()
26
- }
27
- elementMap.clear()
23
+ const updates = Array.from(updateMap.values())
24
+ updateMap.clear()
25
+ for (const fn of updates) {
26
+ fn()
28
27
  }
29
28
  }
30
29
 
@@ -57,7 +56,8 @@ export const subscribe = (watchers: Watcher[]) => {
57
56
  */
58
57
  export const notify = (watchers: Watcher[]) => {
59
58
  for (const mark of watchers) {
60
- batchDepth ? pending.add(mark) : mark()
59
+ if (batchDepth) pending.add(mark)
60
+ else mark()
61
61
  }
62
62
  }
63
63
 
@@ -81,47 +81,49 @@ export const flush = () => {
81
81
  */
82
82
  export const batch = (fn: () => void) => {
83
83
  batchDepth++
84
- fn()
85
- flush()
86
- batchDepth--
84
+ try {
85
+ fn()
86
+ } finally {
87
+ flush()
88
+ batchDepth--
89
+ }
87
90
  }
88
91
 
89
92
  /**
90
93
  * Run a function in a reactive context
91
94
  *
92
95
  * @param {() => void} run - function to run the computation or effect
93
- * @param {Watcher} mark - function to be called when the state changes
96
+ * @param {Watcher} mark - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
94
97
  */
95
- export const watch = (run: () => void, mark: Watcher): void => {
98
+ export const watch = (run: () => void, mark?: Watcher): void => {
96
99
  const prev = active
97
100
  active = mark
98
- run()
99
- active = prev
101
+ try {
102
+ run()
103
+ } finally {
104
+ active = prev
105
+ }
100
106
  }
101
107
 
102
108
  /**
103
109
  * Enqueue a function to be executed on the next animation frame
104
110
  *
105
- * @param callback
106
- * @param dedupe
107
- * @returns
111
+ * @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
112
+ * @param {EnqueueDedupe} dedupe - [element, operation] pair for deduplication
108
113
  */
109
114
  export const enqueue = <T>(
110
- update: Updater,
115
+ fn: Updater,
111
116
  dedupe?: EnqueueDedupe
112
- ) => new Promise<T>((resolve, reject) => {
117
+ ) => new Promise<T | boolean | void>((resolve, reject) => {
113
118
  const wrappedCallback = () => {
114
119
  try {
115
- resolve(update())
120
+ resolve(fn())
116
121
  } catch (error) {
117
122
  reject(error)
118
123
  }
119
124
  }
120
125
  if (dedupe) {
121
- const [el, op] = dedupe
122
- if (!updateMap.has(el)) updateMap.set(el, new Map())
123
- const elementMap = updateMap.get(el)!
124
- elementMap.set(op, wrappedCallback)
126
+ updateMap.set(dedupe, wrappedCallback)
125
127
  }
126
128
  requestTick()
127
129
  })
package/lib/signal.d.ts CHANGED
@@ -2,18 +2,18 @@ import { type State } from "./state";
2
2
  import { type Computed } from "./computed";
3
3
  type Signal<T extends {}> = State<T> | Computed<T>;
4
4
  type MaybeSignal<T extends {}> = Signal<T> | T | (() => T | Promise<T>);
5
- type InferMaybeSignalType<T> = T extends Signal<infer U> ? U : T extends (() => infer U) ? U : T;
6
- type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
7
- [K in keyof U]: InferMaybeSignalType<U[K]>;
5
+ type InferSignalType<T> = T extends Signal<infer U> ? U : never;
6
+ type OkCallback<T, U extends Signal<{}>[]> = (...values: {
7
+ [K in keyof U]: InferSignalType<U[K]>;
8
8
  }) => T | Promise<T> | Error;
9
9
  type NilCallback<T> = () => T | Promise<T> | Error;
10
10
  type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error;
11
- type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
11
+ type ComputedCallbacks<T extends {}, U extends Signal<{}>[]> = OkCallback<T, U> | {
12
12
  ok: OkCallback<T, U>;
13
13
  nil?: NilCallback<T>;
14
14
  err?: ErrCallback<T>;
15
15
  };
16
- type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
16
+ type EffectCallbacks<U extends Signal<{}>[]> = OkCallback<void, U> | {
17
17
  ok: OkCallback<void, U>;
18
18
  nil?: NilCallback<void>;
19
19
  err?: ErrCallback<void>;
@@ -24,10 +24,18 @@ declare const UNSET: any;
24
24
  * Check whether a value is a Signal or not
25
25
  *
26
26
  * @since 0.9.0
27
- * @param {any} value - value to check
27
+ * @param {unknown} value - value to check
28
28
  * @returns {boolean} - true if value is a Signal, false otherwise
29
29
  */
30
30
  declare const isSignal: <T extends {}>(value: any) => value is Signal<T>;
31
+ /**
32
+ * Check if the provided value is a callback or callbacks object of { ok, nil?, err? } that may be used as input for toSignal() to derive a computed state
33
+ *
34
+ * @since 0.12.4
35
+ * @param {unknown} value - value to check
36
+ * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
37
+ */
38
+ declare const isComputedCallbacks: <T extends {}>(value: unknown) => value is ComputedCallbacks<T, []>;
31
39
  /**
32
40
  * Convert a value to a Signal if it's not already a Signal
33
41
  *
@@ -45,9 +53,9 @@ declare const toSignal: <T extends {}>(value: MaybeSignal<T> | ComputedCallbacks
45
53
  * @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
46
54
  * @returns {CallbackReturnType<T>} - result of chosen callback
47
55
  */
48
- declare const resolve: <T, U extends MaybeSignal<{}>[]>(maybeSignals: U, cb: OkCallback<T | Promise<T>, U> | {
56
+ declare const resolve: <T, U extends Signal<{}>[]>(maybeSignals: U, cb: OkCallback<T | Promise<T>, U> | {
49
57
  ok: OkCallback<T | Promise<T>, U>;
50
58
  nil?: NilCallback<T>;
51
59
  err?: ErrCallback<T>;
52
60
  }) => CallbackReturnType<T>;
53
- export { type Signal, type MaybeSignal, type InferMaybeSignalType, type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType, UNSET, isSignal, toSignal, resolve, };
61
+ export { type Signal, type MaybeSignal, type InferSignalType, type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType, UNSET, isSignal, isComputedCallbacks, toSignal, resolve, };
package/lib/signal.ts CHANGED
@@ -6,23 +6,21 @@ import { isFunction, toError } from "./util"
6
6
 
7
7
  type Signal<T extends {}> = State<T> | Computed<T>
8
8
  type MaybeSignal<T extends {}> = Signal<T> | T | (() => T | Promise<T>)
9
- type InferMaybeSignalType<T> = T extends Signal<infer U> ? U :
10
- T extends (() => infer U) ? U :
11
- T
9
+ type InferSignalType<T> = T extends Signal<infer U> ? U : never
12
10
 
13
- type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
14
- [K in keyof U]: InferMaybeSignalType<U[K]>
11
+ type OkCallback<T, U extends Signal<{}>[]> = (...values: {
12
+ [K in keyof U]: InferSignalType<U[K]>
15
13
  }) => T | Promise<T> | Error
16
14
  type NilCallback<T> = () => T | Promise<T> | Error
17
15
  type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error
18
16
 
19
- type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
17
+ type ComputedCallbacks<T extends {}, U extends Signal<{}>[]> = OkCallback<T, U> | {
20
18
  ok: OkCallback<T, U>,
21
19
  nil?: NilCallback<T>,
22
20
  err?: ErrCallback<T>
23
21
  }
24
22
 
25
- type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
23
+ type EffectCallbacks<U extends Signal<{}>[]> = OkCallback<void, U> | {
26
24
  ok: OkCallback<void, U>,
27
25
  nil?: NilCallback<void>,
28
26
  err?: ErrCallback<void>
@@ -34,24 +32,29 @@ type CallbackReturnType<T> = T | Promise<T> | Error | void
34
32
 
35
33
  const UNSET: any = Symbol()
36
34
 
37
- /* === Private Functions === */
38
-
39
- const isComputedCallbacks = /*#__PURE__*/ <T extends {}>(value: unknown): value is ComputedCallbacks<T, []> =>
40
- (isFunction(value) && !value.length)
41
- || (typeof value === 'object' && value !== null && 'ok' in value && isFunction(value.ok))
42
-
43
35
  /* === Exported Functions === */
44
36
 
45
37
  /**
46
38
  * Check whether a value is a Signal or not
47
39
  *
48
40
  * @since 0.9.0
49
- * @param {any} value - value to check
41
+ * @param {unknown} value - value to check
50
42
  * @returns {boolean} - true if value is a Signal, false otherwise
51
43
  */
52
44
  const isSignal = /*#__PURE__*/ <T extends {}>(value: any): value is Signal<T> =>
53
45
  isState(value) || isComputed(value)
54
46
 
47
+ /**
48
+ * Check if the provided value is a callback or callbacks object of { ok, nil?, err? } that may be used as input for toSignal() to derive a computed state
49
+ *
50
+ * @since 0.12.4
51
+ * @param {unknown} value - value to check
52
+ * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
53
+ */
54
+ const isComputedCallbacks = /*#__PURE__*/ <T extends {}>(value: unknown): value is ComputedCallbacks<T, []> =>
55
+ (isFunction(value) && !value.length)
56
+ || (typeof value === 'object' && value !== null && 'ok' in value && isFunction(value.ok))
57
+
55
58
  /**
56
59
  * Convert a value to a Signal if it's not already a Signal
57
60
  *
@@ -76,7 +79,7 @@ const toSignal = /*#__PURE__*/ <T extends {}>(
76
79
  * @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
77
80
  * @returns {CallbackReturnType<T>} - result of chosen callback
78
81
  */
79
- const resolve = <T, U extends MaybeSignal<{}>[]>(
82
+ const resolve = <T, U extends Signal<{}>[]>(
80
83
  maybeSignals: U,
81
84
  cb: OkCallback<T | Promise<T>, U> | {
82
85
  ok: OkCallback<T | Promise<T>, U>
@@ -92,7 +95,7 @@ const resolve = <T, U extends MaybeSignal<{}>[]>(
92
95
  err?: ErrCallback<T>
93
96
  }
94
97
  const values = [] as {
95
- [K in keyof U]: InferMaybeSignalType<U[K]>
98
+ [K in keyof U]: InferSignalType<U[K]>
96
99
  }
97
100
  const errors: Error[] = []
98
101
  let hasUnset = false
@@ -100,9 +103,9 @@ const resolve = <T, U extends MaybeSignal<{}>[]>(
100
103
  for (let i = 0; i < maybeSignals.length; i++) {
101
104
  const s = maybeSignals[i]
102
105
  try {
103
- const value = isSignal(s) ? s.get() : isFunction(s) ? s() : s
106
+ const value = s.get()
104
107
  if (value === UNSET) hasUnset = true
105
- values[i] = value as InferMaybeSignalType<typeof s>
108
+ values[i] = value as InferSignalType<typeof s>
106
109
  } catch (e) {
107
110
  errors.push(toError(e))
108
111
  }
@@ -121,7 +124,7 @@ const resolve = <T, U extends MaybeSignal<{}>[]>(
121
124
  }
122
125
 
123
126
  export {
124
- type Signal, type MaybeSignal, type InferMaybeSignalType,
127
+ type Signal, type MaybeSignal, type InferSignalType,
125
128
  type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType,
126
- UNSET, isSignal, toSignal, resolve,
129
+ UNSET, isSignal, isComputedCallbacks, toSignal, resolve,
127
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",