@zeix/cause-effect 0.12.0 → 0.12.2

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.0
3
+ Version 0.12.2
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
 
@@ -108,14 +108,14 @@ document.querySelector('.increment').addEventListener('click', () => {
108
108
 
109
109
  Async computed signals are as straight forward as their sync counterparts. Just create the computed signal with an async function.
110
110
 
111
- **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.
112
112
 
113
113
  ```js
114
- import { state, computed, effect } from '@zeix/cause-effect'
114
+ import { state, effect } from '@zeix/cause-effect'
115
115
 
116
116
  const entryId = state(42)
117
- const entryData = computed(async () => {
118
- const response = await fetch(`/api/entry/${entryId.get()}`)
117
+ const entryData = entryId.map(async id => {
118
+ const response = await fetch(`/api/entry/${id}`)
119
119
  if (!response.ok) return new Error(`Failed to fetch data: ${response.statusText}`)
120
120
  return response.json()
121
121
  })
package/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.12.0
3
+ * @version 0.12.2
4
4
  * @author Esther Brunner
5
5
  */
6
- export { type Signal, type MaybeSignal, UNSET, isSignal, toSignal } from './lib/signal';
6
+ export { type Signal, type MaybeSignal, type InferMaybeSignalType, type ComputedCallbacks, type EffectCallbacks, UNSET, isSignal, 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
- export { type EffectOkCallback, type EffectCallbacks, effect } from './lib/effect';
9
+ export { effect } from './lib/effect';
10
10
  export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler';
package/index.js CHANGED
@@ -1 +1 @@
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};
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};
package/index.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.12.0
3
+ * @version 0.12.2
4
4
  * @author Esther Brunner
5
5
  */
6
- export { type Signal, type MaybeSignal, UNSET, isSignal, toSignal } from './lib/signal'
6
+ export {
7
+ type Signal, type MaybeSignal, type InferMaybeSignalType,
8
+ type ComputedCallbacks, type EffectCallbacks,
9
+ UNSET, isSignal, toSignal
10
+ } from './lib/signal'
11
+
7
12
  export { type State, state, isState } from './lib/state'
8
13
  export { type Computed, computed, isComputed } from './lib/computed'
9
- export { type EffectOkCallback, type EffectCallbacks, effect } from './lib/effect'
14
+ export { effect } from './lib/effect'
10
15
  export { type EnqueueDedupe, batch, watch, enqueue } from './lib/scheduler'
package/lib/computed.d.ts CHANGED
@@ -1,29 +1,19 @@
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
- };
1
+ import { type MaybeSignal, type EffectCallbacks, type ComputedCallbacks } from './signal';
13
2
  export type Computed<T extends {}> = {
14
3
  [Symbol.toStringTag]: 'Computed';
15
4
  get: () => T;
16
- map: <U extends {}>(fn: (value: T) => U) => Computed<U>;
17
- match: (callbacks: EffectCallbacks<[Computed<T>]>) => void;
5
+ map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>) => Computed<U>;
6
+ match: (cb: EffectCallbacks<[Computed<T>]>) => void;
18
7
  };
19
8
  /**
20
- * Create a derived state from existing states
9
+ * Create a derived signal from existing signals
21
10
  *
22
11
  * @since 0.9.0
23
- * @param {() => T} callbacksOrFn - compute function to derive state
24
- * @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
25
15
  */
26
- export declare const computed: <T extends {}, U extends UnknownSignal[]>(callbacksOrFn: ComputedCallbacks<T, U> | ComputedOkCallback<T, U>, ...signals: U) => Computed<T>;
16
+ export declare const computed: <T extends {}, U extends MaybeSignal<{}>[]>(cb: ComputedCallbacks<T, U>, ...maybeSignals: U) => Computed<T>;
27
17
  /**
28
18
  * Check if a value is a computed state
29
19
  *
package/lib/computed.ts CHANGED
@@ -1,47 +1,48 @@
1
- import { resolveSignals, UNSET, type SignalValue, type UnknownSignal } from './signal'
2
- import { isEquivalentError, isError, isFunction, isObjectOfType, 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'
3
6
  import { type Watcher, flush, notify, subscribe, watch } from './scheduler'
4
- import { type EffectCallbacks, effect } from './effect'
7
+ import { effect } from './effect'
5
8
 
6
9
  /* === Types === */
7
10
 
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
11
  export type Computed<T extends {}> = {
19
12
  [Symbol.toStringTag]: 'Computed'
20
13
  get: () => T
21
- map: <U extends {}>(fn: (value: T) => U) => Computed<U>
22
- match: (callbacks: EffectCallbacks<[Computed<T>]>) => void
14
+ map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>) => Computed<U>
15
+ match: (cb: EffectCallbacks<[Computed<T>]>) => void
23
16
  }
24
17
 
25
18
  /* === Constants === */
26
19
 
27
20
  const TYPE_COMPUTED = 'Computed'
28
21
 
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
+
29
32
  /* === Computed Factory === */
30
33
 
31
34
  /**
32
- * Create a derived state from existing states
35
+ * Create a derived signal from existing signals
33
36
  *
34
37
  * @since 0.9.0
35
- * @param {() => T} callbacksOrFn - compute function to derive state
36
- * @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
37
41
  */
38
- export const computed = <T extends {}, U extends UnknownSignal[]>(
39
- callbacksOrFn: ComputedCallbacks<T, U> | ComputedOkCallback<T, U>,
40
- ...signals: U
42
+ export const computed = <T extends {}, U extends MaybeSignal<{}>[]>(
43
+ cb: ComputedCallbacks<T, U>,
44
+ ...maybeSignals: U
41
45
  ): Computed<T> => {
42
- const callbacks = isFunction(callbacksOrFn)
43
- ? { ok: callbacksOrFn }
44
- : callbacksOrFn
45
46
  const watchers: Watcher[] = []
46
47
  let value: T = UNSET
47
48
  let error: Error | undefined
@@ -78,10 +79,10 @@ export const computed = <T extends {}, U extends UnknownSignal[]>(
78
79
 
79
80
  // Called when requested by dependencies (pull)
80
81
  const compute = () => watch(() => {
81
- if (computing) throw new Error('Circular dependency detected')
82
+ if (computing) throw new Error('Circular dependency in computed detected')
82
83
  unchanged = true
83
84
  computing = true
84
- const result = resolveSignals(signals, callbacks as ComputedCallbacks<T, U>)
85
+ const result = resolve(maybeSignals, cb)
85
86
  if (isPromise(result)) {
86
87
  nil() // sync
87
88
  result.then(v => {
@@ -101,7 +102,6 @@ export const computed = <T extends {}, U extends UnknownSignal[]>(
101
102
  * Get the current value of the computed
102
103
  *
103
104
  * @since 0.9.0
104
- * @method of Computed<T>
105
105
  * @returns {T} - current value of the computed
106
106
  */
107
107
  get: (): T => {
@@ -116,23 +116,21 @@ export const computed = <T extends {}, U extends UnknownSignal[]>(
116
116
  * Create a computed signal from the current computed signal
117
117
  *
118
118
  * @since 0.9.0
119
- * @method of Computed<T>
120
- * @param {(value: T) => R} fn
121
- * @returns {Computed<R>} - computed signal
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
122
121
  */
123
- map: <R extends {}>(fn: (value: T) => R): Computed<R> =>
124
- computed(() => fn(c.get())),
122
+ map: <U extends {}>(cb: ComputedCallbacks<U, [Computed<T>]>): Computed<U> =>
123
+ computed(cb, c),
125
124
 
126
125
  /**
127
126
  * Case matching for the computed signal with effect callbacks
128
127
  *
129
128
  * @since 0.12.0
130
- * @method of Computed<T>
131
- * @param {EffectCallbacks[<T>]} callbacks
129
+ * @param {EffectCallbacks[Computed<T>]} cb - effect callback or object of ok, nil, err callbacks to be executed when the computed changes
132
130
  * @returns {Computed<T>} - self, for chaining effect callbacks
133
131
  */
134
- match: (callbacks: EffectCallbacks<[Computed<T>]>): Computed<T> => {
135
- effect(callbacks, c)
132
+ match: (cb: EffectCallbacks<[Computed<T>]>): Computed<T> => {
133
+ effect(cb, c)
136
134
  return c
137
135
  }
138
136
  }
package/lib/effect.d.ts CHANGED
@@ -1,18 +1,9 @@
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;
9
- nil?: () => void;
10
- err?: (...errors: Error[]) => void;
11
- };
1
+ import { type EffectCallbacks, type MaybeSignal } from './signal';
12
2
  /**
13
3
  * Define what happens when a reactive state changes
14
4
  *
15
5
  * @since 0.1.0
16
- * @param {() => void} callbacksOrFn - 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
17
8
  */
18
- export declare function effect<T extends UnknownSignal[]>(callbacksOrFn: EffectCallbacks<T> | EffectOkCallback<T>, ...signals: T): void;
9
+ export declare function effect<U extends MaybeSignal<{}>[]>(cb: EffectCallbacks<U>, ...maybeSignals: U): void;
package/lib/effect.ts CHANGED
@@ -1,40 +1,29 @@
1
1
 
2
- import { resolveSignals, type SignalValue, type UnknownSignal } from './signal'
3
- import { isError, isFunction } from './util'
2
+ import { type EffectCallbacks, type MaybeSignal, resolve } from './signal'
3
+ import { isError } from './util'
4
4
  import { watch } from './scheduler'
5
5
 
6
- /* === Types === */
7
-
8
- export type EffectOkCallback<T extends UnknownSignal[]> = (
9
- ...values: { [K in keyof T]: SignalValue<T[K]> }
10
- ) => void
11
-
12
- export type EffectCallbacks<T extends UnknownSignal[]> = {
13
- ok: (...values: { [K in keyof T]: SignalValue<T[K]> }) => void
14
- nil?: () => void
15
- err?: (...errors: Error[]) => void
16
- }
17
-
18
6
  /* === Exported Function === */
19
7
 
20
8
  /**
21
9
  * Define what happens when a reactive state changes
22
10
  *
23
11
  * @since 0.1.0
24
- * @param {() => void} callbacksOrFn - 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
25
14
  */
26
- export function effect<T extends UnknownSignal[]>(
27
- callbacksOrFn: EffectCallbacks<T> | EffectOkCallback<T>,
28
- ...signals: T
15
+ export function effect<U extends MaybeSignal<{}>[]>(
16
+ cb: EffectCallbacks<U>,
17
+ ...maybeSignals: U
29
18
  ): void {
30
- const callbacks = isFunction(callbacksOrFn)
31
- ? { ok: callbacksOrFn }
32
- : callbacksOrFn
33
-
19
+ let running = false
34
20
  const run = () => watch(() => {
35
- const result = resolveSignals(signals, callbacks as EffectCallbacks<T>)
21
+ if (running) throw new Error('Circular dependency in effect detected')
22
+ running = true
23
+ const result = resolve(maybeSignals, cb)
36
24
  if (isError(result))
37
25
  console.error('Unhandled error in effect:', result)
26
+ running = false
38
27
  }, run)
39
28
  run()
40
29
  }
package/lib/signal.d.ts CHANGED
@@ -1,10 +1,25 @@
1
1
  import { type State } from "./state";
2
2
  import { type Computed } from "./computed";
3
3
  type Signal<T extends {}> = State<T> | Computed<T>;
4
- type UnknownSignal = Signal<{}>;
5
- type MaybeSignal<T extends {}> = Signal<T> | T | (() => T);
6
- type SignalValue<T> = T extends Signal<infer U> ? U : never;
7
- export declare const UNSET: any;
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]>;
8
+ }) => T | Promise<T> | Error;
9
+ type NilCallback<T> = () => T | Promise<T> | Error;
10
+ type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error;
11
+ type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
12
+ ok: OkCallback<T, U>;
13
+ nil?: NilCallback<T>;
14
+ err?: ErrCallback<T>;
15
+ };
16
+ type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
17
+ ok: OkCallback<void, U>;
18
+ nil?: NilCallback<void>;
19
+ err?: ErrCallback<void>;
20
+ };
21
+ type CallbackReturnType<T> = T | Promise<T> | Error | void;
22
+ declare const UNSET: any;
8
23
  /**
9
24
  * Check whether a value is a Signal or not
10
25
  *
@@ -21,18 +36,18 @@ declare const isSignal: <T extends {}>(value: any) => value is Signal<T>;
21
36
  * @param memo
22
37
  * @returns {Signal<T>} - converted Signal
23
38
  */
24
- declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
39
+ declare const toSignal: <T extends {}>(value: MaybeSignal<T> | ComputedCallbacks<T, []>) => Signal<T>;
25
40
  /**
26
- * Resolve signals and apply callbacks based on the results
41
+ * Resolve signals or functions using signals and apply callbacks based on the results
27
42
  *
28
43
  * @since 0.12.0
29
- * @param {U} signals - dependency signals
30
- * @param {Record<string, (...args) => T | Promise<T> | Error | void>} callbacks - ok, nil, err callbacks
31
- * @returns {T | Promise<T> | Error | void} - result of chosen callback
44
+ * @param {U} maybeSignals - dependency signals (or functions using signals)
45
+ * @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
46
+ * @returns {CallbackReturnType<T>} - result of chosen callback
32
47
  */
33
- declare const resolveSignals: <T extends {}, U extends UnknownSignal[]>(signals: U, callbacks: {
34
- ok: (...values: { [K in keyof U]: SignalValue<U[K]>; }) => T | Promise<T> | Error | void;
35
- nil?: () => T | Promise<T> | Error | void;
36
- err?: (...errors: Error[]) => T | Promise<T> | Error | void;
37
- }) => T | Promise<T> | Error | void;
38
- export { type Signal, type UnknownSignal, type SignalValue, type MaybeSignal, isSignal, toSignal, resolveSignals, };
48
+ declare const resolve: <T, U extends MaybeSignal<{}>[]>(maybeSignals: U, cb: OkCallback<T | Promise<T>, U> | {
49
+ ok: OkCallback<T | Promise<T>, U>;
50
+ nil?: NilCallback<T>;
51
+ err?: ErrCallback<T>;
52
+ }) => CallbackReturnType<T>;
53
+ export { type Signal, type MaybeSignal, type InferMaybeSignalType, type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType, UNSET, isSignal, toSignal, resolve, };
package/lib/signal.ts CHANGED
@@ -1,18 +1,44 @@
1
1
  import { type State, isState, state } from "./state"
2
- import { computed, type Computed, isComputed } from "./computed"
3
- import { isComputeFunction, toError } from "./util"
2
+ import { type Computed, computed, isComputed } from "./computed"
3
+ import { isFunction, toError } from "./util"
4
4
 
5
5
  /* === Types === */
6
6
 
7
7
  type Signal<T extends {}> = State<T> | Computed<T>
8
- type UnknownSignal = Signal<{}>
9
- type MaybeSignal<T extends {}> = Signal<T> | T | (() => T)
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
10
12
 
11
- type SignalValue<T> = T extends Signal<infer U> ? U : never
13
+ type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
14
+ [K in keyof U]: InferMaybeSignalType<U[K]>
15
+ }) => T | Promise<T> | Error
16
+ type NilCallback<T> = () => T | Promise<T> | Error
17
+ type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error
18
+
19
+ type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
20
+ ok: OkCallback<T, U>,
21
+ nil?: NilCallback<T>,
22
+ err?: ErrCallback<T>
23
+ }
24
+
25
+ type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
26
+ ok: OkCallback<void, U>,
27
+ nil?: NilCallback<void>,
28
+ err?: ErrCallback<void>
29
+ }
30
+
31
+ type CallbackReturnType<T> = T | Promise<T> | Error | void
12
32
 
13
33
  /* === Constants === */
14
34
 
15
- export const UNSET: any = Symbol()
35
+ const UNSET: any = Symbol()
36
+
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))
16
42
 
17
43
  /* === Exported Functions === */
18
44
 
@@ -35,58 +61,67 @@ const isSignal = /*#__PURE__*/ <T extends {}>(value: any): value is Signal<T> =>
35
61
  * @returns {Signal<T>} - converted Signal
36
62
  */
37
63
  const toSignal = /*#__PURE__*/ <T extends {}>(
38
- value: MaybeSignal<T>
64
+ value: MaybeSignal<T> | ComputedCallbacks<T, []>
39
65
  ): Signal<T> =>
40
66
  isSignal<T>(value) ? value
41
- : isComputeFunction<T>(value) ? computed(value)
42
- : state(value)
67
+ : isComputedCallbacks<T>(value) ? computed(value)
68
+ : state(value as T)
43
69
 
44
70
 
45
71
  /**
46
- * Resolve signals and apply callbacks based on the results
72
+ * Resolve signals or functions using signals and apply callbacks based on the results
47
73
  *
48
74
  * @since 0.12.0
49
- * @param {U} signals - dependency signals
50
- * @param {Record<string, (...args) => T | Promise<T> | Error | void>} callbacks - ok, nil, err callbacks
51
- * @returns {T | Promise<T> | Error | void} - result of chosen callback
75
+ * @param {U} maybeSignals - dependency signals (or functions using signals)
76
+ * @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
77
+ * @returns {CallbackReturnType<T>} - result of chosen callback
52
78
  */
53
- const resolveSignals = <T extends {}, U extends UnknownSignal[]>(
54
- signals: U,
55
- callbacks: {
56
- ok: (...values: { [K in keyof U]: SignalValue<U[K]> }) => T | Promise<T> | Error | void
57
- nil?: () => T | Promise<T> | Error | void
58
- err?: (...errors: Error[]) => T | Promise<T> | Error | void
79
+ const resolve = <T, U extends MaybeSignal<{}>[]>(
80
+ maybeSignals: U,
81
+ cb: OkCallback<T | Promise<T>, U> | {
82
+ ok: OkCallback<T | Promise<T>, U>
83
+ nil?: NilCallback<T>
84
+ err?: ErrCallback<T>
85
+ }
86
+ ): CallbackReturnType<T> => {
87
+ const { ok, nil, err } = isFunction(cb)
88
+ ? { ok: cb }
89
+ : cb as {
90
+ ok: OkCallback<T | Promise<T>, U>
91
+ nil?: NilCallback<T>
92
+ err?: ErrCallback<T>
93
+ }
94
+ const values = [] as {
95
+ [K in keyof U]: InferMaybeSignalType<U[K]>
59
96
  }
60
- ): T | Promise<T> | Error | void => {
61
- const { ok, nil, err } = callbacks
62
- const values = [] as { [K in keyof U]: SignalValue<U[K]> }
63
97
  const errors: Error[] = []
64
98
  let hasUnset = false
65
99
 
66
- for (const signal of signals) {
100
+ for (let i = 0; i < maybeSignals.length; i++) {
101
+ const s = maybeSignals[i]
67
102
  try {
68
- const value = signal.get()
103
+ const value = isSignal(s) ? s.get() : isFunction(s) ? s() : s
69
104
  if (value === UNSET) hasUnset = true
70
- values.push(value)
105
+ values[i] = value as InferMaybeSignalType<typeof s>
71
106
  } catch (e) {
72
107
  errors.push(toError(e))
73
108
  }
74
109
  }
75
110
 
76
- let result: T | Promise<T> | Error | void = undefined
111
+ let result: CallbackReturnType<T> = undefined
77
112
  try {
78
113
  if (hasUnset && nil) result = nil()
79
114
  else if (errors.length) result = err ? err(...errors) : errors[0]
80
- else if (!hasUnset) result = ok(...values)
115
+ else if (!hasUnset) result = ok(...values) as CallbackReturnType<T>
81
116
  } catch (e) {
82
117
  result = toError(e)
83
118
  if (err) result = err(result)
84
- } finally {
85
- return result
86
- }
119
+ }
120
+ return result
87
121
  }
88
122
 
89
123
  export {
90
- type Signal, type UnknownSignal, type SignalValue, type MaybeSignal,
91
- isSignal, toSignal, resolveSignals,
124
+ type Signal, type MaybeSignal, type InferMaybeSignalType,
125
+ type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType,
126
+ UNSET, isSignal, toSignal, resolve,
92
127
  }
package/lib/state.d.ts CHANGED
@@ -1,12 +1,12 @@
1
+ import { type ComputedCallbacks, type EffectCallbacks } from './signal';
1
2
  import { type Computed } from './computed';
2
- import { type EffectCallbacks } from './effect';
3
3
  export type State<T extends {}> = {
4
4
  [Symbol.toStringTag]: 'State';
5
5
  get(): T;
6
- set(value: T): void;
7
- update(fn: (value: T) => T): void;
8
- map<U extends {}>(fn: (value: T) => U): Computed<U>;
9
- match: (callbacks: EffectCallbacks<[State<T>]>) => void;
6
+ set(v: T): void;
7
+ update(fn: (v: T) => T): void;
8
+ map<U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U>;
9
+ match: (cb: EffectCallbacks<[State<T>]>) => void;
10
10
  };
11
11
  /**
12
12
  * Create a new state signal
@@ -15,7 +15,7 @@ export type State<T extends {}> = {
15
15
  * @param {T} initialValue - initial value of the state
16
16
  * @returns {State<T>} - new state signal
17
17
  */
18
- export declare const state: <T extends {}>(v: T) => State<T>;
18
+ export declare const state: <T extends {}>(initialValue: T) => State<T>;
19
19
  /**
20
20
  * Check if the provided value is a State instance
21
21
  *
package/lib/state.ts CHANGED
@@ -1,18 +1,18 @@
1
- import { UNSET } from './signal'
1
+ import { UNSET, type ComputedCallbacks, type EffectCallbacks } from './signal'
2
2
  import { type Computed, computed } from './computed'
3
3
  import { isObjectOfType } from './util';
4
4
  import { type Watcher, notify, subscribe } from './scheduler'
5
- import { type EffectCallbacks, effect } from './effect';
5
+ import { effect } from './effect';
6
6
 
7
7
  /* === Types === */
8
8
 
9
9
  export type State<T extends {}> = {
10
10
  [Symbol.toStringTag]: 'State';
11
11
  get(): T;
12
- set(value: T): void;
13
- update(fn: (value: T) => T): void;
14
- map<U extends {}>(fn: (value: T) => U): Computed<U>;
15
- match: (callbacks: EffectCallbacks<[State<T>]>) => void
12
+ set(v: T): void;
13
+ update(fn: (v: T) => T): void;
14
+ map<U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U>;
15
+ match: (cb: EffectCallbacks<[State<T>]>) => void
16
16
  }
17
17
 
18
18
  /* === Constants === */
@@ -28,9 +28,9 @@ const TYPE_STATE = 'State'
28
28
  * @param {T} initialValue - initial value of the state
29
29
  * @returns {State<T>} - new state signal
30
30
  */
31
- export const state = /*#__PURE__*/ <T extends {}>(v: T): State<T> => {
31
+ export const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
32
32
  const watchers: Watcher[] = []
33
- let value: T = v
33
+ let value: T = initialValue
34
34
 
35
35
  const s: State<T> = {
36
36
  [Symbol.toStringTag]: TYPE_STATE,
@@ -39,7 +39,6 @@ export const state = /*#__PURE__*/ <T extends {}>(v: T): State<T> => {
39
39
  * Get the current value of the state
40
40
  *
41
41
  * @since 0.9.0
42
- * @method of State<T>
43
42
  * @returns {T} - current value of the state
44
43
  */
45
44
  get: (): T => {
@@ -51,7 +50,6 @@ export const state = /*#__PURE__*/ <T extends {}>(v: T): State<T> => {
51
50
  * Set a new value of the state
52
51
  *
53
52
  * @since 0.9.0
54
- * @method of State<T>
55
53
  * @param {T} v
56
54
  * @returns {void}
57
55
  */
@@ -68,8 +66,7 @@ export const state = /*#__PURE__*/ <T extends {}>(v: T): State<T> => {
68
66
  * Update the state with a new value using a function
69
67
  *
70
68
  * @since 0.10.0
71
- * @method of State<T>
72
- * @param {(v: T) => T} fn
69
+ * @param {(v: T) => T} fn - function to update the state
73
70
  * @returns {void} - updates the state with the result of the function
74
71
  */
75
72
  update: (fn: (v: T) => T): void => {
@@ -80,23 +77,22 @@ export const state = /*#__PURE__*/ <T extends {}>(v: T): State<T> => {
80
77
  * Create a computed signal from the current state signal
81
78
  *
82
79
  * @since 0.9.0
83
- * @method of State<T>
84
- * @param {(v: T) => R} fn
85
- * @returns {Computed<R>} - computed signal
80
+ * @param {ComputedCallbacks<U, [State<T>]>} cb - compute callback or object of ok, nil, err callbacks to map this value to new computed
81
+ * @returns {Computed<U>} - computed signal
86
82
  */
87
- map: <R extends {}>(fn: (v: T) => R): Computed<R> =>
88
- computed(() => fn(s.get())),
83
+ map: <U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U> =>
84
+ computed(cb, s),
89
85
 
90
86
  /**
91
87
  * Case matching for the state signal with effect callbacks
92
88
  *
93
89
  * @since 0.12.0
94
90
  * @method of State<T>
95
- * @param {EffectCallbacks[<T>]} callbacks
91
+ * @param {EffectCallbacks<[State<T>]>} cb - effect callback or object of ok, nil, err callbacks to be executed when the state changes
96
92
  * @returns {State<T>} - self, for chaining effect callbacks
97
93
  */
98
- match: (callbacks: EffectCallbacks<[State<T>]>): State<T> => {
99
- effect(callbacks, s)
94
+ match: (cb: EffectCallbacks<[State<T>]>): State<T> => {
95
+ effect(cb, s)
100
96
  return s
101
97
  }
102
98
  }
package/lib/util.d.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  declare const isFunction: <T>(value: unknown) => value is (...args: unknown[]) => T;
2
2
  declare const isAsyncFunction: <T>(value: unknown) => value is (...args: unknown[]) => Promise<T> | PromiseLike<T>;
3
- declare const isComputeFunction: <T>(value: unknown) => value is ((old?: T) => T);
4
3
  declare const isObjectOfType: <T>(value: unknown, type: string) => value is T;
5
4
  declare const isInstanceOf: <T>(type: new (...args: any[]) => T) => (value: unknown) => value is T;
6
5
  declare const isError: (value: unknown) => value is Error;
7
6
  declare const isPromise: (value: unknown) => value is Promise<unknown>;
8
7
  declare const toError: (value: unknown) => Error;
9
- declare const isEquivalentError: (error1: Error, error2: Error | undefined) => boolean;
10
- export { isFunction, isAsyncFunction, isComputeFunction, isObjectOfType, isInstanceOf, isError, isPromise, toError, isEquivalentError };
8
+ export { isFunction, isAsyncFunction, isObjectOfType, isInstanceOf, isError, isPromise, toError };
package/lib/util.ts CHANGED
@@ -6,10 +6,7 @@ const isFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown
6
6
  const isAsyncFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown[]) => Promise<T> | PromiseLike<T> =>
7
7
  isFunction(value) && /^async\s+/.test(value.toString())
8
8
 
9
- const isComputeFunction = /*#__PURE__*/ <T>(value: unknown): value is ((old?: T) => T) =>
10
- isFunction(value) && value.length < 2
11
-
12
- const isObjectOfType = <T>(value: unknown, type: string): value is T =>
9
+ const isObjectOfType = /*#__PURE__*/ <T>(value: unknown, type: string): value is T =>
13
10
  Object.prototype.toString.call(value) === `[object ${type}]`
14
11
 
15
12
  const isInstanceOf = /*#__PURE__*/ <T>(type: new (...args: any[]) => T) =>
@@ -22,15 +19,7 @@ const isPromise = /*#__PURE__*/ isInstanceOf(Promise)
22
19
  const toError = (value: unknown): Error =>
23
20
  isError(value) ? value : new Error(String(value))
24
21
 
25
- const isEquivalentError = /*#__PURE__*/ (
26
- error1: Error,
27
- error2: Error | undefined
28
- ): boolean => {
29
- if (!error2) return false
30
- return error1.name === error2.name && error1.message === error2.message
31
- }
32
-
33
22
  export {
34
- isFunction, isAsyncFunction, isComputeFunction,
35
- isObjectOfType, isInstanceOf, isError, isPromise, toError, isEquivalentError
23
+ isFunction, isAsyncFunction,
24
+ isObjectOfType, isInstanceOf, isError, isPromise, toError
36
25
  }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
7
7
  "devDependencies": {
8
- "@types/bun": "latest"
8
+ "@types/bun": "latest",
9
+ "random": "^5.3.0"
9
10
  },
10
11
  "peerDependencies": {
11
12
  "typescript": "^5.6.3"
@@ -26,8 +27,5 @@
26
27
  "test": "bun test"
27
28
  },
28
29
  "type": "module",
29
- "types": "index.d.ts",
30
- "dependencies": {
31
- "random": "^5.3.0"
32
- }
30
+ "types": "index.d.ts"
33
31
  }
@@ -216,36 +216,45 @@ describe('Computed', function () {
216
216
  const c = computed(() => b.get() + a.get());
217
217
  expect(() => {
218
218
  b.get(); // This should trigger the circular dependency
219
- }).toThrow('Circular dependency detected');
219
+ }).toThrow('Circular dependency in computed detected');
220
220
  expect(a.get()).toBe(1);
221
221
  });
222
222
 
223
- /* test('should propagate error if an error occurred', function() {
224
- let count = 0;
223
+ test('should propagate error if an error occurred', function() {
224
+ let okCount = 0;
225
+ let errCount = 0;
225
226
  const x = state(0);
226
- const a = computed(() => {
227
- if (x.get() === 1) throw new Error('Calculation error');
227
+ const a = x.map(v => {
228
+ if (v === 1) throw new Error('Calculation error');
228
229
  return 1;
229
230
  });
230
- const b = a.map(v => v ? 'success' : 'pending');
231
- const c = computed(() => {
232
- count++;
233
- return `c: ${b.get()}`;
231
+ const b = a.map({
232
+ ok: v => v ? 'success' : 'failure',
233
+ err: _e => {
234
+ errCount++;
235
+ // console.error(e);
236
+ return `recovered`;
237
+ }
238
+ });
239
+ const c = b.map(v => {
240
+ okCount++;
241
+ return `c: ${v}`;
234
242
  });
235
243
  expect(a.get()).toBe(1);
236
244
  expect(c.get()).toBe('c: success');
237
- expect(count).toBe(1);
238
- x.set(1)
245
+ expect(okCount).toBe(1);
239
246
  try {
247
+ x.set(1)
240
248
  expect(a.get()).toBe(1);
241
249
  expect(true).toBe(false); // This line should not be reached
242
250
  } catch (error) {
243
251
  expect(error.message).toBe('Calculation error');
244
252
  } finally {
245
- expect(c.get()).toBe('c: success');
246
- expect(count).toBe(2);
253
+ expect(c.get()).toBe('c: recovered');
254
+ expect(okCount).toBe(2);
255
+ expect(errCount).toBe(1);
247
256
  }
248
- }); */
257
+ });
249
258
 
250
259
  test('should return a computed signal with .map()', function() {
251
260
  const cause = state(42);
@@ -308,7 +317,7 @@ describe('Computed', function () {
308
317
  return 0
309
318
  },
310
319
  err: (_e) => { // error path
311
- console.error('Error:', _e);
320
+ // console.error('Error:', _e);
312
321
  errCount++;
313
322
  return -1
314
323
  },
@@ -318,7 +327,7 @@ describe('Computed', function () {
318
327
  toggleState.set(!!(i % 2));
319
328
  await wait(10);
320
329
  result = complexComputed.get();
321
- console.log(`i: ${i}, result: ${result}`);
330
+ // console.log(`i: ${i}, result: ${result}`);
322
331
  }
323
332
 
324
333
  expect(nilCount).toBeGreaterThanOrEqual(4);
@@ -154,4 +154,28 @@ describe('Effect', function () {
154
154
  console.error = originalConsoleError;
155
155
  }
156
156
  });
157
+
158
+ test('should detect and throw error for circular dependencies in effects', () => {
159
+ let okCount = 0
160
+ let errCount = 0
161
+ const count = state(0)
162
+
163
+ effect({
164
+ ok: () => {
165
+ okCount++
166
+ // This effect updates the signal it depends on, creating a circular dependency
167
+ count.update(v => ++v)
168
+ },
169
+ err: e => {
170
+ errCount++
171
+ expect(e).toBeInstanceOf(Error)
172
+ expect(e.message).toBe('Circular dependency in effect detected')
173
+ }
174
+ }, count)
175
+
176
+ // Verify that the count was changed only once due to the circular dependency error
177
+ expect(count.get()).toBe(1)
178
+ expect(okCount).toBe(1)
179
+ expect(errCount).toBe(1)
180
+ })
157
181
  });
@@ -1,6 +1,10 @@
1
1
  import { describe, test, expect } from 'bun:test'
2
2
  import { state, isComputed, UNSET } from '../'
3
3
 
4
+ /* === Utility Functions === */
5
+
6
+ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
7
+
4
8
  /* === Tests === */
5
9
 
6
10
  describe('State', function () {
@@ -166,6 +170,18 @@ describe('State', function () {
166
170
  expect(double.get()).toBe(84);
167
171
  });
168
172
 
173
+ test('should return a computed signal for an async function', async function() {
174
+ const cause = state(42);
175
+ const asyncDouble = cause.map(async v => {
176
+ await wait(100);
177
+ return v * 2;
178
+ });
179
+ expect(isComputed(asyncDouble)).toBe(true);
180
+ expect(asyncDouble.get()).toBe(UNSET);
181
+ await wait(110);
182
+ expect(asyncDouble.get()).toBe(84);
183
+ });
184
+
169
185
  });
170
186
 
171
187
  describe('Match method', function () {
@@ -174,7 +190,7 @@ describe('State', function () {
174
190
  const cause = state(42);
175
191
  let okCount = 0;
176
192
  let nilCount = 0;
177
- let result: number = 0;
193
+ let result = 0;
178
194
  cause.match({
179
195
  ok: v => {
180
196
  result = v;