@zeix/cause-effect 0.12.4 → 0.13.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/lib/state.ts CHANGED
@@ -1,18 +1,18 @@
1
- import { UNSET, type ComputedCallbacks, type EffectCallbacks } from './signal'
1
+ import { UNSET } from './signal'
2
2
  import { type Computed, computed } from './computed'
3
- import { isObjectOfType } from './util';
3
+ import { isFunction, isObjectOfType } from './util'
4
4
  import { type Watcher, notify, subscribe } from './scheduler'
5
- import { effect } from './effect';
5
+ import { type TapMatcher, type EffectMatcher, effect } from './effect'
6
6
 
7
7
  /* === Types === */
8
8
 
9
9
  export type State<T extends {}> = {
10
- [Symbol.toStringTag]: 'State';
11
- get(): T;
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
10
+ [Symbol.toStringTag]: 'State'
11
+ get(): T
12
+ set(v: T): void
13
+ 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
16
  }
17
17
 
18
18
  /* === Constants === */
@@ -29,7 +29,7 @@ const TYPE_STATE = 'State'
29
29
  * @returns {State<T>} - new state signal
30
30
  */
31
31
  export const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
32
- const watchers: Watcher[] = []
32
+ const watchers: Set<Watcher> = new Set()
33
33
  let value: T = initialValue
34
34
 
35
35
  const s: State<T> = {
@@ -59,7 +59,7 @@ export const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> =>
59
59
  notify(watchers)
60
60
 
61
61
  // Setting to UNSET clears the watchers so the signal can be garbage collected
62
- if (UNSET === value) watchers.length = 0 // head = tail = undefined
62
+ if (UNSET === value) watchers.clear()
63
63
  },
64
64
 
65
65
  /**
@@ -77,24 +77,31 @@ export const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> =>
77
77
  * Create a computed signal from the current state signal
78
78
  *
79
79
  * @since 0.9.0
80
- * @param {ComputedCallbacks<U, [State<T>]>} cb - compute callback or object of ok, nil, err callbacks to map this value to new computed
80
+ * @param {(v: T) => U | Promise<U>} fn - computed callback
81
81
  * @returns {Computed<U>} - computed signal
82
82
  */
83
- map: <U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U> =>
84
- computed(cb, s),
83
+ map: <U extends {}>(
84
+ fn: (v: T) => U | Promise<U>
85
+ ): Computed<U> =>
86
+ computed({
87
+ signals: [s],
88
+ ok: fn
89
+ }),
85
90
 
86
91
  /**
87
92
  * Case matching for the state signal with effect callbacks
88
93
  *
89
- * @since 0.12.0
90
- * @method of State<T>
91
- * @param {EffectCallbacks<[State<T>]>} cb - effect callback or object of ok, nil, err callbacks to be executed when the state changes
92
- * @returns {State<T>} - self, for chaining effect callbacks
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
93
97
  */
94
- match: (cb: EffectCallbacks<[State<T>]>): State<T> => {
95
- effect(cb, s)
96
- return s
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>]>)
98
105
  }
99
106
 
100
107
  return s
package/lib/util.d.ts CHANGED
@@ -1,8 +1,11 @@
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> | PromiseLike<T>;
2
+ declare const isAsyncFunction: <T>(value: unknown) => value is (...args: unknown[]) => Promise<T>;
3
3
  declare const isObjectOfType: <T>(value: unknown, type: string) => value is T;
4
- declare const isInstanceOf: <T>(type: new (...args: any[]) => T) => (value: unknown) => value is T;
5
4
  declare const isError: (value: unknown) => value is Error;
6
- declare const isPromise: (value: unknown) => value is Promise<unknown>;
7
- declare const toError: (value: unknown) => Error;
8
- export { isFunction, isAsyncFunction, isObjectOfType, isInstanceOf, isError, isPromise, toError };
5
+ declare const isAbortError: (value: unknown) => value is DOMException;
6
+ declare const isPromise: <T>(value: unknown) => value is Promise<T>;
7
+ declare const toError: (reason: unknown) => Error;
8
+ declare class CircularDependencyError extends Error {
9
+ constructor(where: string);
10
+ }
11
+ export { isFunction, isAsyncFunction, isObjectOfType, isError, isAbortError, isPromise, toError, CircularDependencyError };
package/lib/util.ts CHANGED
@@ -3,23 +3,30 @@
3
3
  const isFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown[]) => T =>
4
4
  typeof value === 'function'
5
5
 
6
- const isAsyncFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown[]) => Promise<T> | PromiseLike<T> =>
7
- isFunction(value) && /^async\s+/.test(value.toString())
6
+ const isAsyncFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown[]) => Promise<T> =>
7
+ isFunction(value) && value.constructor.name === 'AsyncFunction'
8
8
 
9
9
  const isObjectOfType = /*#__PURE__*/ <T>(value: unknown, type: string): value is T =>
10
10
  Object.prototype.toString.call(value) === `[object ${type}]`
11
11
 
12
- const isInstanceOf = /*#__PURE__*/ <T>(type: new (...args: any[]) => T) =>
13
- (value: unknown): value is T =>
14
- value instanceof type
12
+ const isError = /*#__PURE__*/ (value: unknown): value is Error =>
13
+ value instanceof Error
14
+ const isAbortError = /*#__PURE__*/ (value: unknown): value is DOMException =>
15
+ value instanceof DOMException && value.name === 'AbortError'
16
+ const isPromise = /*#__PURE__*/ <T>(value: unknown): value is Promise<T> =>
17
+ value instanceof Promise
18
+ const toError = (reason: unknown): Error =>
19
+ isError(reason) ? reason : Error(String(reason))
15
20
 
16
- const isError = /*#__PURE__*/ isInstanceOf(Error)
17
- const isPromise = /*#__PURE__*/ isInstanceOf(Promise)
18
-
19
- const toError = (value: unknown): Error =>
20
- isError(value) ? value : new Error(String(value))
21
+ class CircularDependencyError extends Error {
22
+ constructor(where: string) {
23
+ super(`Circular dependency in ${where} detected`)
24
+ return this
25
+ }
26
+ }
21
27
 
22
28
  export {
23
29
  isFunction, isAsyncFunction,
24
- isObjectOfType, isInstanceOf, isError, isPromise, toError
30
+ isObjectOfType, isError, isAbortError, isPromise, toError,
31
+ CircularDependencyError
25
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.12.4",
3
+ "version": "0.13.1",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
@@ -28,4 +28,4 @@
28
28
  },
29
29
  "type": "module",
30
30
  "types": "index.d.ts"
31
- }
31
+ }
@@ -10,41 +10,44 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
10
10
  describe('Batch', function () {
11
11
 
12
12
  test('should be triggered only once after repeated state change', function() {
13
- const cause = state(0);
14
- let result = 0;
15
- let count = 0;
16
- effect((res) => {
17
- result = res;
18
- count++;
19
- }, cause);
13
+ const cause = state(0)
14
+ let result = 0
15
+ let count = 0
16
+ cause.tap(res => {
17
+ result = res
18
+ count++
19
+ })
20
20
  batch(() => {
21
21
  for (let i = 1; i <= 10; i++) {
22
- cause.set(i);
22
+ cause.set(i)
23
23
  }
24
- });
25
- expect(result).toBe(10);
24
+ })
25
+ expect(result).toBe(10)
26
26
  expect(count).toBe(2); // + 1 for effect initialization
27
- });
27
+ })
28
28
 
29
29
  test('should be triggered only once when multiple signals are set', function() {
30
- const a = state(3);
31
- const b = state(4);
32
- const c = state(5);
33
- const sum = computed(() => a.get() + b.get() + c.get());
34
- let result = 0;
35
- let count = 0;
36
- effect((res) => {
37
- result = res;
38
- count++;
39
- }, sum);
30
+ const a = state(3)
31
+ const b = state(4)
32
+ const c = state(5)
33
+ const sum = computed(() => a.get() + b.get() + c.get())
34
+ let result = 0
35
+ let count = 0
36
+ sum.tap({
37
+ ok: res => {
38
+ result = res
39
+ count++
40
+ },
41
+ err: () => {},
42
+ })
40
43
  batch(() => {
41
- a.set(6);
42
- b.set(8);
43
- c.set(10);
44
- });
45
- expect(result).toBe(24);
44
+ a.set(6)
45
+ b.set(8)
46
+ c.set(10)
47
+ })
48
+ expect(result).toBe(24)
46
49
  expect(count).toBe(2); // + 1 for effect initialization
47
- });
50
+ })
48
51
 
49
52
  test('should prove example from README works', function() {
50
53
 
@@ -52,20 +55,18 @@ describe('Batch', function () {
52
55
  const signals = [state(2), state(3), state(5)]
53
56
 
54
57
  // Computed: derive a calculation ...
55
- const sum = computed(
56
- (...values) => values.reduce((total, v) => total + v, 0),
57
- ...signals
58
- ).map(v => { // ... perform validation and handle errors
59
- if (!Number.isFinite(v)) throw new Error('Invalid value')
60
- return v
61
- })
58
+ const sum = computed(() => signals.reduce((total, v) => total + v.get(), 0))
59
+ .map(v => { // ... perform validation and handle errors
60
+ if (!Number.isFinite(v)) throw new Error('Invalid value')
61
+ return v
62
+ })
62
63
 
63
64
  let result = 0
64
65
  let okCount = 0
65
66
  let errCount = 0
66
67
 
67
68
  // Effect: switch cases for the result
68
- sum.match({
69
+ sum.tap({
69
70
  ok: v => {
70
71
  result = v
71
72
  okCount++
@@ -77,23 +78,23 @@ describe('Batch', function () {
77
78
  }
78
79
  })
79
80
 
80
- expect(okCount).toBe(1);
81
- expect(result).toBe(10);
81
+ expect(okCount).toBe(1)
82
+ expect(result).toBe(10)
82
83
 
83
84
  // Batch: apply changes to all signals in a single transaction
84
85
  batch(() => {
85
86
  signals.forEach(signal => signal.update(v => v * 2))
86
87
  })
87
88
 
88
- expect(okCount).toBe(2);
89
- expect(result).toBe(20);
89
+ expect(okCount).toBe(2)
90
+ expect(result).toBe(20)
90
91
 
91
92
  // Provoke an error
92
93
  signals[0].set(NaN)
93
94
 
94
- expect(errCount).toBe(1);
95
+ expect(errCount).toBe(1)
95
96
  expect(okCount).toBe(2); // should not have changed due to error
96
97
  expect(result).toBe(20); // should not have changed due to error
97
- });
98
+ })
98
99
 
99
- });
100
+ })