@zeix/cause-effect 0.13.2 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/signal.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { type State } from "./state";
2
- import { type Computed } from "./computed";
3
- type Signal<T extends {}> = State<T> | Computed<T>;
4
- type ComputedCallback<T extends {}> = (abort?: AbortSignal) => T | Promise<T>;
5
- type MaybeSignal<T extends {}> = Signal<T> | T | ComputedCallback<T>;
1
+ import { type ComputedCallback, isComputedCallback } from './computed';
2
+ type Signal<T extends {}> = {
3
+ get(): T;
4
+ };
5
+ type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>;
6
+ type SignalValues<S extends Signal<{}>[]> = {
7
+ [K in keyof S]: S[K] extends Signal<infer T> ? T : never;
8
+ };
6
9
  declare const UNSET: any;
7
10
  /**
8
11
  * Check whether a value is a Signal or not
@@ -12,14 +15,6 @@ declare const UNSET: any;
12
15
  * @returns {boolean} - true if value is a Signal, false otherwise
13
16
  */
14
17
  declare const isSignal: <T extends {}>(value: unknown) => value is Signal<T>;
15
- /**
16
- * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
17
- *
18
- * @since 0.12.0
19
- * @param {unknown} value - value to check
20
- * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
21
- */
22
- declare const isComputedCallback: <T extends {}>(value: unknown) => value is ComputedCallback<T>;
23
18
  /**
24
19
  * Convert a value to a Signal if it's not already a Signal
25
20
  *
@@ -28,18 +23,4 @@ declare const isComputedCallback: <T extends {}>(value: unknown) => value is Com
28
23
  * @returns {Signal<T>} - converted Signal
29
24
  */
30
25
  declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
31
- /**
32
- * Resolve signals or functions using signals and apply callbacks based on the results
33
- *
34
- * @since 0.13.0
35
- * @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
36
- * @returns {R | Promise<R>} - result of the matched callback
37
- */
38
- declare const match: <S extends Signal<{}>[], R>(matcher: {
39
- signals: S;
40
- abort?: AbortSignal;
41
- ok: ((...values: { [K in keyof S]: S[K] extends Signal<infer T> ? T : never; }) => R | Promise<R>);
42
- err: ((...errors: Error[]) => R | Promise<R>);
43
- nil: (abort?: AbortSignal) => R | Promise<R>;
44
- }) => R | Promise<R>;
45
- export { type Signal, type MaybeSignal, type ComputedCallback, UNSET, isSignal, isComputedCallback, toSignal, match, };
26
+ export { type Signal, type MaybeSignal, type SignalValues, UNSET, isSignal, isComputedCallback, toSignal, };
package/src/signal.ts CHANGED
@@ -1,102 +1,63 @@
1
- import { type State, isState, state } from "./state"
2
- import { type Computed, computed, isComputed } from "./computed"
3
- import { isAbortError, isFunction, toError } from "./util"
1
+ import { isState, state } from './state'
2
+ import {
3
+ type ComputedCallback,
4
+ isComputed,
5
+ isComputedCallback,
6
+ computed,
7
+ } from './computed'
4
8
 
5
9
  /* === Types === */
6
10
 
7
- type Signal<T extends {}> = State<T> | Computed<T>
8
- type ComputedCallback<T extends {}> = (abort?: AbortSignal) => T | Promise<T>
9
- type MaybeSignal<T extends {}> = Signal<T> | T | ComputedCallback<T>
11
+ type Signal<T extends {}> = {
12
+ get(): T
13
+ }
14
+ type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>
15
+
16
+ type SignalValues<S extends Signal<{}>[]> = {
17
+ [K in keyof S]: S[K] extends Signal<infer T> ? T : never
18
+ }
10
19
 
11
20
  /* === Constants === */
12
21
 
13
22
  const UNSET: any = Symbol()
14
23
 
15
- /* === Exported Functions === */
24
+ /* === Functions === */
16
25
 
17
26
  /**
18
27
  * Check whether a value is a Signal or not
19
- *
28
+ *
20
29
  * @since 0.9.0
21
30
  * @param {unknown} value - value to check
22
31
  * @returns {boolean} - true if value is a Signal, false otherwise
23
32
  */
24
- const isSignal = /*#__PURE__*/ <T extends {}>(value: unknown): value is Signal<T> =>
25
- isState(value) || isComputed(value)
26
-
27
- /**
28
- * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
29
- *
30
- * @since 0.12.0
31
- * @param {unknown} value - value to check
32
- * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
33
- */
34
- const isComputedCallback = /*#__PURE__*/ <T extends {}>(
35
- value: unknown
36
- ): value is ComputedCallback<T> =>
37
- isFunction(value) && value.length < 2
33
+ const isSignal = /*#__PURE__*/ <T extends {}>(
34
+ value: unknown,
35
+ ): value is Signal<T> => isState(value) || isComputed(value)
38
36
 
39
37
  /**
40
38
  * Convert a value to a Signal if it's not already a Signal
41
- *
39
+ *
42
40
  * @since 0.9.6
43
41
  * @param {MaybeSignal<T>} value - value to convert to a Signal
44
42
  * @returns {Signal<T>} - converted Signal
45
43
  */
46
44
  const toSignal = /*#__PURE__*/ <T extends {}>(
47
- value: MaybeSignal<T>
48
- ): Signal<T> => isSignal<T>(value) ? value
49
- : isComputedCallback<T>(value) ? computed(value)
50
- : state(value as T)
45
+ value: MaybeSignal<T>,
46
+ ): Signal<T> =>
47
+ isSignal<T>(value)
48
+ ? value
49
+ : isComputedCallback<T>(value)
50
+ ? computed(value)
51
+ : state(value as T)
51
52
 
52
-
53
- /**
54
- * Resolve signals or functions using signals and apply callbacks based on the results
55
- *
56
- * @since 0.13.0
57
- * @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
58
- * @returns {R | Promise<R>} - result of the matched callback
59
- */
60
- const match = <S extends Signal<{}>[], R>(
61
- matcher: {
62
- signals: S,
63
- abort?: AbortSignal,
64
- ok: ((...values: {
65
- [K in keyof S]: S[K] extends Signal<infer T> ? T : never
66
- }) => R | Promise<R>),
67
- err: ((...errors: Error[]) => R | Promise<R>),
68
- nil: (abort?: AbortSignal) => R | Promise<R>
69
- }
70
- ): R | Promise<R> => {
71
- const { signals, abort, ok, err, nil } = matcher
72
-
73
- const errors: Error[] = []
74
- let suspense = false
75
- const values = signals.map(signal => {
76
- try {
77
- const value = signal.get()
78
- if (value === UNSET) suspense = true
79
- return value
80
- } catch (e) {
81
- if (isAbortError(e)) throw e
82
- errors.push(toError(e))
83
- }
84
- }) as {
85
- [K in keyof S]: S[K] extends Signal<infer T extends {}> ? T : never
86
- }
87
-
88
- try {
89
- return suspense ? nil(abort)
90
- : errors.length ? err(...errors)
91
- : ok(...values)
92
- } catch (e) {
93
- if (isAbortError(e)) throw e
94
- const error = toError(e)
95
- return err(error)
96
- }
97
- }
53
+ /* === Exports === */
98
54
 
99
55
  export {
100
- type Signal, type MaybeSignal, type ComputedCallback,
101
- UNSET, isSignal, isComputedCallback, toSignal, match,
102
- }
56
+ type Signal,
57
+ type MaybeSignal,
58
+ type SignalValues,
59
+ UNSET,
60
+ isSignal,
61
+ isComputedCallback,
62
+ toSignal,
63
+ }
package/src/state.d.ts CHANGED
@@ -1,13 +1,10 @@
1
- import { type Computed } from './computed';
2
- import { type TapMatcher } from './effect';
3
- export type State<T extends {}> = {
1
+ type State<T extends {}> = {
4
2
  [Symbol.toStringTag]: 'State';
5
3
  get(): T;
6
4
  set(v: T): void;
7
5
  update(fn: (v: T) => T): void;
8
- map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>;
9
- tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void;
10
6
  };
7
+ declare const TYPE_STATE = "State";
11
8
  /**
12
9
  * Create a new state signal
13
10
  *
@@ -15,7 +12,7 @@ export type State<T extends {}> = {
15
12
  * @param {T} initialValue - initial value of the state
16
13
  * @returns {State<T>} - new state signal
17
14
  */
18
- export declare const state: <T extends {}>(initialValue: T) => State<T>;
15
+ declare const state: <T extends {}>(initialValue: T) => State<T>;
19
16
  /**
20
17
  * Check if the provided value is a State instance
21
18
  *
@@ -23,4 +20,5 @@ export declare const state: <T extends {}>(initialValue: T) => State<T>;
23
20
  * @param {unknown} value - value to check
24
21
  * @returns {boolean} - true if the value is a State instance, false otherwise
25
22
  */
26
- export declare const isState: <T extends {}>(value: unknown) => value is State<T>;
23
+ declare const isState: <T extends {}>(value: unknown) => value is State<T>;
24
+ export { type State, TYPE_STATE, state, isState };
package/src/state.ts CHANGED
@@ -1,25 +1,21 @@
1
1
  import { UNSET } from './signal'
2
- import { type Computed, computed } from './computed'
3
- import { isFunction, isObjectOfType } from './util'
2
+ import { isObjectOfType } from './util'
4
3
  import { type Watcher, notify, subscribe } from './scheduler'
5
- import { type TapMatcher, type EffectMatcher, effect } from './effect'
6
4
 
7
5
  /* === Types === */
8
6
 
9
- export type State<T extends {}> = {
7
+ type State<T extends {}> = {
10
8
  [Symbol.toStringTag]: 'State'
11
9
  get(): T
12
10
  set(v: T): void
13
11
  update(fn: (v: T) => T): void
14
- map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>
15
- tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void
16
12
  }
17
13
 
18
14
  /* === Constants === */
19
15
 
20
16
  const TYPE_STATE = 'State'
21
17
 
22
- /* === State Factory === */
18
+ /* === Functions === */
23
19
 
24
20
  /**
25
21
  * Create a new state signal
@@ -28,9 +24,7 @@ const TYPE_STATE = 'State'
28
24
  * @param {T} initialValue - initial value of the state
29
25
  * @returns {State<T>} - new state signal
30
26
  */
31
- export const state = /*#__PURE__*/ <T extends {}>(
32
- initialValue: T,
33
- ): State<T> => {
27
+ const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
34
28
  const watchers: Set<Watcher> = new Set()
35
29
  let value: T = initialValue
36
30
 
@@ -74,34 +68,6 @@ export const state = /*#__PURE__*/ <T extends {}>(
74
68
  update: (fn: (v: T) => T): void => {
75
69
  s.set(fn(value))
76
70
  },
77
-
78
- /**
79
- * Create a computed signal from the current state signal
80
- *
81
- * @since 0.9.0
82
- * @param {(v: T) => U | Promise<U>} fn - computed callback
83
- * @returns {Computed<U>} - computed signal
84
- */
85
- map: <U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U> =>
86
- computed({
87
- signals: [s],
88
- ok: fn,
89
- }),
90
-
91
- /**
92
- * Case matching for the state signal with effect callbacks
93
- *
94
- * @since 0.13.0
95
- * @param {TapMatcher<T> | ((v: T) => void | (() => void))} matcher - tap matcher or effect callback
96
- * @returns {() => void} - cleanup function for the effect
97
- */
98
- tap: (
99
- matcher: TapMatcher<T> | ((v: T) => void | (() => void)),
100
- ): (() => void) =>
101
- effect({
102
- signals: [s],
103
- ...(isFunction(matcher) ? { ok: matcher } : matcher),
104
- } as EffectMatcher<[State<T>]>),
105
71
  }
106
72
 
107
73
  return s
@@ -114,6 +80,10 @@ export const state = /*#__PURE__*/ <T extends {}>(
114
80
  * @param {unknown} value - value to check
115
81
  * @returns {boolean} - true if the value is a State instance, false otherwise
116
82
  */
117
- export const isState = /*#__PURE__*/ <T extends {}>(
83
+ const isState = /*#__PURE__*/ <T extends {}>(
118
84
  value: unknown,
119
85
  ): value is State<T> => isObjectOfType(value, TYPE_STATE)
86
+
87
+ /* === Exports === */
88
+
89
+ export { type State, TYPE_STATE, state, isState }
package/src/util.d.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  declare const isFunction: <T>(value: unknown) => value is (...args: unknown[]) => T;
2
- declare const isAsyncFunction: <T>(value: unknown) => value is (...args: unknown[]) => Promise<T>;
3
2
  declare const isObjectOfType: <T>(value: unknown, type: string) => value is T;
4
- declare const isError: (value: unknown) => value is Error;
5
- declare const isAbortError: (value: unknown) => value is DOMException;
6
- declare const isPromise: <T>(value: unknown) => value is Promise<T>;
7
3
  declare const toError: (reason: unknown) => Error;
8
4
  declare class CircularDependencyError extends Error {
9
5
  constructor(where: string);
10
6
  }
11
- export { isFunction, isAsyncFunction, isObjectOfType, isError, isAbortError, isPromise, toError, CircularDependencyError, };
7
+ export { isFunction, isObjectOfType, toError, CircularDependencyError };
package/src/util.ts CHANGED
@@ -4,24 +4,13 @@ const isFunction = /*#__PURE__*/ <T>(
4
4
  value: unknown,
5
5
  ): value is (...args: unknown[]) => T => typeof value === 'function'
6
6
 
7
- const isAsyncFunction = /*#__PURE__*/ <T>(
8
- value: unknown,
9
- ): value is (...args: unknown[]) => Promise<T> =>
10
- isFunction(value) && value.constructor.name === 'AsyncFunction'
11
-
12
7
  const isObjectOfType = /*#__PURE__*/ <T>(
13
8
  value: unknown,
14
9
  type: string,
15
10
  ): value is T => Object.prototype.toString.call(value) === `[object ${type}]`
16
11
 
17
- const isError = /*#__PURE__*/ (value: unknown): value is Error =>
18
- value instanceof Error
19
- const isAbortError = /*#__PURE__*/ (value: unknown): value is DOMException =>
20
- value instanceof DOMException && value.name === 'AbortError'
21
- const isPromise = /*#__PURE__*/ <T>(value: unknown): value is Promise<T> =>
22
- value instanceof Promise
23
12
  const toError = (reason: unknown): Error =>
24
- isError(reason) ? reason : Error(String(reason))
13
+ reason instanceof Error ? reason : Error(String(reason))
25
14
 
26
15
  class CircularDependencyError extends Error {
27
16
  constructor(where: string) {
@@ -30,13 +19,6 @@ class CircularDependencyError extends Error {
30
19
  }
31
20
  }
32
21
 
33
- export {
34
- isFunction,
35
- isAsyncFunction,
36
- isObjectOfType,
37
- isError,
38
- isAbortError,
39
- isPromise,
40
- toError,
41
- CircularDependencyError,
42
- }
22
+ /* === Exports === */
23
+
24
+ export { isFunction, isObjectOfType, toError, CircularDependencyError }
@@ -1,9 +1,5 @@
1
1
  import { describe, test, expect } from 'bun:test'
2
- import { state, computed, batch } from '../'
3
-
4
- /* === Utility Functions === */
5
-
6
- const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
2
+ import { state, computed, batch, effect } from '../'
7
3
 
8
4
  /* === Tests === */
9
5
 
@@ -12,8 +8,8 @@ describe('Batch', function () {
12
8
  const cause = state(0)
13
9
  let result = 0
14
10
  let count = 0
15
- cause.tap(res => {
16
- result = res
11
+ effect(() => {
12
+ result = cause.get()
17
13
  count++
18
14
  })
19
15
  batch(() => {
@@ -32,7 +28,8 @@ describe('Batch', function () {
32
28
  const sum = computed(() => a.get() + b.get() + c.get())
33
29
  let result = 0
34
30
  let count = 0
35
- sum.tap({
31
+ effect({
32
+ signals: [sum],
36
33
  ok: res => {
37
34
  result = res
38
35
  count++
@@ -53,10 +50,8 @@ describe('Batch', function () {
53
50
  const signals = [state(2), state(3), state(5)]
54
51
 
55
52
  // Computed: derive a calculation ...
56
- const sum = computed(() =>
57
- signals.reduce((total, v) => total + v.get(), 0),
58
- ).map(v => {
59
- // ... perform validation and handle errors
53
+ const sum = computed(() => {
54
+ const v = signals.reduce((total, v) => total + v.get(), 0)
60
55
  if (!Number.isFinite(v)) throw new Error('Invalid value')
61
56
  return v
62
57
  })
@@ -66,7 +61,8 @@ describe('Batch', function () {
66
61
  let errCount = 0
67
62
 
68
63
  // Effect: switch cases for the result
69
- sum.tap({
64
+ effect({
65
+ signals: [sum],
70
66
  ok: v => {
71
67
  result = v
72
68
  okCount++