@zeix/cause-effect 0.15.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.15.1
3
+ * @version 0.16.0
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
7
7
  export {
8
8
  type Computed,
9
9
  type ComputedCallback,
10
- computed,
10
+ createComputed,
11
11
  isComputed,
12
12
  isComputedCallback,
13
13
  TYPE_COMPUTED,
@@ -20,9 +20,14 @@ export {
20
20
  type UnknownRecord,
21
21
  type UnknownRecordOrArray,
22
22
  } from './src/diff'
23
- export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
23
+ export {
24
+ createEffect,
25
+ type EffectCallback,
26
+ type MaybeCleanup,
27
+ } from './src/effect'
24
28
  export {
25
29
  CircularDependencyError,
30
+ InvalidCallbackError,
26
31
  InvalidSignalValueError,
27
32
  NullishSignalValueError,
28
33
  StoreKeyExistsError,
@@ -31,18 +36,6 @@ export {
31
36
  } from './src/errors'
32
37
  export { type MatchHandlers, match } from './src/match'
33
38
  export { type ResolveResult, resolve } from './src/resolve'
34
- export {
35
- batch,
36
- type Cleanup,
37
- enqueue,
38
- flush,
39
- notify,
40
- observe,
41
- subscribe,
42
- type Updater,
43
- type Watcher,
44
- watch,
45
- } from './src/scheduler'
46
39
  export {
47
40
  isMutableSignal,
48
41
  isSignal,
@@ -51,18 +44,24 @@ export {
51
44
  toSignal,
52
45
  type UnknownSignalRecord,
53
46
  } from './src/signal'
54
- export { isState, type State, state, TYPE_STATE } from './src/state'
47
+ export { createState, isState, type State, TYPE_STATE } from './src/state'
55
48
  export {
49
+ createStore,
56
50
  isStore,
57
51
  type Store,
58
- type StoreAddEvent,
59
- type StoreChangeEvent,
60
- type StoreEventMap,
61
- type StoreRemoveEvent,
62
- type StoreSortEvent,
63
- store,
52
+ type StoreChanges,
64
53
  TYPE_STORE,
65
54
  } from './src/store'
55
+ export {
56
+ batch,
57
+ type Cleanup,
58
+ createWatcher,
59
+ flush,
60
+ notify,
61
+ observe,
62
+ subscribe,
63
+ type Watcher,
64
+ } from './src/system'
66
65
  export {
67
66
  isAbortError,
68
67
  isAsyncFunction,
@@ -74,4 +73,5 @@ export {
74
73
  isSymbol,
75
74
  toError,
76
75
  UNSET,
76
+ valueString,
77
77
  } from './src/util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
package/src/computed.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  import { isEqual } from './diff'
2
- import { CircularDependencyError } from './errors'
3
2
  import {
3
+ CircularDependencyError,
4
+ InvalidCallbackError,
5
+ NullishSignalValueError,
6
+ } from './errors'
7
+ import {
8
+ createWatcher,
4
9
  flush,
5
10
  notify,
6
11
  observe,
7
12
  subscribe,
8
13
  type Watcher,
9
- watch,
10
- } from './scheduler'
14
+ } from './system'
11
15
  import {
12
16
  isAbortError,
13
17
  isAsyncFunction,
@@ -15,17 +19,18 @@ import {
15
19
  isObjectOfType,
16
20
  toError,
17
21
  UNSET,
22
+ valueString,
18
23
  } from './util'
19
24
 
20
25
  /* === Types === */
21
26
 
22
27
  type Computed<T extends {}> = {
23
- [Symbol.toStringTag]: 'Computed'
28
+ readonly [Symbol.toStringTag]: 'Computed'
24
29
  get(): T
25
30
  }
26
31
  type ComputedCallback<T extends {} & { then?: undefined }> =
27
- | ((abort: AbortSignal) => Promise<T>)
28
- | (() => T)
32
+ | ((oldValue: T, abort: AbortSignal) => Promise<T>)
33
+ | ((oldValue: T) => T)
29
34
 
30
35
  /* === Constants === */
31
36
 
@@ -37,14 +42,21 @@ const TYPE_COMPUTED = 'Computed'
37
42
  * Create a derived signal from existing signals
38
43
  *
39
44
  * @since 0.9.0
40
- * @param {ComputedCallback<T>} fn - computation callback function
45
+ * @param {ComputedCallback<T>} callback - Computation callback function
41
46
  * @returns {Computed<T>} - Computed signal
42
47
  */
43
- const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
48
+ const createComputed = <T extends {}>(
49
+ callback: ComputedCallback<T>,
50
+ initialValue: T = UNSET,
51
+ ): Computed<T> => {
52
+ if (!isComputedCallback(callback))
53
+ throw new InvalidCallbackError('computed', valueString(callback))
54
+ if (initialValue == null) throw new NullishSignalValueError('computed')
55
+
44
56
  const watchers: Set<Watcher> = new Set()
45
57
 
46
58
  // Internal state
47
- let value: T = UNSET
59
+ let value: T = initialValue
48
60
  let error: Error | undefined
49
61
  let controller: AbortController | undefined
50
62
  let dirty = true
@@ -75,22 +87,22 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
75
87
  error = newError
76
88
  }
77
89
  const settle =
78
- <T>(settleFn: (arg: T) => void) =>
90
+ <T>(fn: (arg: T) => void) =>
79
91
  (arg: T) => {
80
92
  computing = false
81
93
  controller = undefined
82
- settleFn(arg)
94
+ fn(arg)
83
95
  if (changed) notify(watchers)
84
96
  }
85
97
 
86
98
  // Own watcher: called when notified from sources (push)
87
- const mark = watch(() => {
99
+ const watcher = createWatcher(() => {
88
100
  dirty = true
89
101
  controller?.abort()
90
102
  if (watchers.size) notify(watchers)
91
- else mark.cleanup()
103
+ else watcher.cleanup()
92
104
  })
93
- mark.off(() => {
105
+ watcher.unwatch(() => {
94
106
  controller?.abort()
95
107
  })
96
108
 
@@ -99,7 +111,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
99
111
  observe(() => {
100
112
  if (computing) throw new CircularDependencyError('computed')
101
113
  changed = false
102
- if (isAsyncFunction(fn)) {
114
+ if (isAsyncFunction(callback)) {
103
115
  // Return current value until promise resolves
104
116
  if (controller) return value
105
117
  controller = new AbortController()
@@ -108,9 +120,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
108
120
  () => {
109
121
  computing = false
110
122
  controller = undefined
111
-
112
- // Retry computation with updated state
113
- compute()
123
+ compute() // Retry computation with updated state
114
124
  },
115
125
  {
116
126
  once: true,
@@ -120,7 +130,9 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
120
130
  let result: T | Promise<T>
121
131
  computing = true
122
132
  try {
123
- result = controller ? fn(controller.signal) : (fn as () => T)()
133
+ result = controller
134
+ ? callback(value, controller.signal)
135
+ : (callback as (oldValue: T) => T)(value)
124
136
  } catch (e) {
125
137
  if (isAbortError(e)) nil()
126
138
  else err(e)
@@ -131,16 +143,16 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
131
143
  else if (null == result || UNSET === result) nil()
132
144
  else ok(result)
133
145
  computing = false
134
- }, mark)
146
+ }, watcher)
135
147
 
136
- const c: Computed<T> = {
148
+ return {
137
149
  [Symbol.toStringTag]: TYPE_COMPUTED,
138
150
 
139
151
  /**
140
152
  * Get the current value of the computed
141
153
  *
142
154
  * @since 0.9.0
143
- * @returns {T} - current value of the computed
155
+ * @returns {T} - Current value of the computed
144
156
  */
145
157
  get: (): T => {
146
158
  subscribe(watchers)
@@ -150,15 +162,14 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
150
162
  return value
151
163
  },
152
164
  }
153
- return c
154
165
  }
155
166
 
156
167
  /**
157
- * Check if a value is a computed state
168
+ * Check if a value is a computed signal
158
169
  *
159
170
  * @since 0.9.0
160
- * @param {unknown} value - value to check
161
- * @returns {boolean} - true if value is a computed state, false otherwise
171
+ * @param {unknown} value - Value to check
172
+ * @returns {boolean} - true if value is a computed signal, false otherwise
162
173
  */
163
174
  const isComputed = /*#__PURE__*/ <T extends {}>(
164
175
  value: unknown,
@@ -168,18 +179,18 @@ const isComputed = /*#__PURE__*/ <T extends {}>(
168
179
  * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
169
180
  *
170
181
  * @since 0.12.0
171
- * @param {unknown} value - value to check
182
+ * @param {unknown} value - Value to check
172
183
  * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
173
184
  */
174
185
  const isComputedCallback = /*#__PURE__*/ <T extends {}>(
175
186
  value: unknown,
176
- ): value is ComputedCallback<T> => isFunction(value) && value.length < 2
187
+ ): value is ComputedCallback<T> => isFunction(value) && value.length < 3
177
188
 
178
189
  /* === Exports === */
179
190
 
180
191
  export {
181
192
  TYPE_COMPUTED,
182
- computed,
193
+ createComputed,
183
194
  isComputed,
184
195
  isComputedCallback,
185
196
  type Computed,
package/src/effect.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { CircularDependencyError } from './errors'
2
- import { type Cleanup, observe, watch } from './scheduler'
3
- import { isAbortError, isAsyncFunction, isFunction } from './util'
1
+ import { CircularDependencyError, InvalidCallbackError } from './errors'
2
+ import { type Cleanup, createWatcher, observe } from './system'
3
+ import { isAbortError, isAsyncFunction, isFunction, valueString } from './util'
4
4
 
5
5
  /* === Types === */
6
6
 
@@ -24,12 +24,15 @@ type EffectCallback =
24
24
  * @param {EffectCallback} callback - Synchronous or asynchronous effect callback
25
25
  * @returns {Cleanup} - Cleanup function for the effect
26
26
  */
27
- const effect = (callback: EffectCallback): Cleanup => {
28
- const isAsync = isAsyncFunction<MaybeCleanup>(callback)
27
+ const createEffect = (callback: EffectCallback): Cleanup => {
28
+ if (!isFunction(callback) || callback.length > 1)
29
+ throw new InvalidCallbackError('effect', valueString(callback))
30
+
31
+ const isAsync = isAsyncFunction(callback)
29
32
  let running = false
30
33
  let controller: AbortController | undefined
31
34
 
32
- const run = watch(() =>
35
+ const watcher = createWatcher(() =>
33
36
  observe(() => {
34
37
  if (running) throw new CircularDependencyError('effect')
35
38
  running = true
@@ -52,7 +55,7 @@ const effect = (callback: EffectCallback): Cleanup => {
52
55
  isFunction(cleanup) &&
53
56
  controller === currentController
54
57
  )
55
- run.off(cleanup)
58
+ watcher.unwatch(cleanup)
56
59
  })
57
60
  .catch(error => {
58
61
  if (!isAbortError(error))
@@ -60,7 +63,7 @@ const effect = (callback: EffectCallback): Cleanup => {
60
63
  })
61
64
  } else {
62
65
  cleanup = (callback as () => MaybeCleanup)()
63
- if (isFunction(cleanup)) run.off(cleanup)
66
+ if (isFunction(cleanup)) watcher.unwatch(cleanup)
64
67
  }
65
68
  } catch (error) {
66
69
  if (!isAbortError(error))
@@ -68,16 +71,16 @@ const effect = (callback: EffectCallback): Cleanup => {
68
71
  }
69
72
 
70
73
  running = false
71
- }, run),
74
+ }, watcher),
72
75
  )
73
76
 
74
- run()
77
+ watcher()
75
78
  return () => {
76
79
  controller?.abort()
77
- run.cleanup()
80
+ watcher.cleanup()
78
81
  }
79
82
  }
80
83
 
81
84
  /* === Exports === */
82
85
 
83
- export { type MaybeCleanup, type EffectCallback, effect }
86
+ export { type MaybeCleanup, type EffectCallback, createEffect }
package/src/errors.ts CHANGED
@@ -5,6 +5,13 @@ class CircularDependencyError extends Error {
5
5
  }
6
6
  }
7
7
 
8
+ class InvalidCallbackError extends TypeError {
9
+ constructor(where: string, value: string) {
10
+ super(`Invalid ${where} callback ${value}`)
11
+ this.name = 'InvalidCallbackError'
12
+ }
13
+ }
14
+
8
15
  class InvalidSignalValueError extends TypeError {
9
16
  constructor(where: string, value: string) {
10
17
  super(`Invalid signal value ${value} in ${where}`)
@@ -48,6 +55,7 @@ class StoreKeyReadonlyError extends Error {
48
55
 
49
56
  export {
50
57
  CircularDependencyError,
58
+ InvalidCallbackError,
51
59
  InvalidSignalValueError,
52
60
  NullishSignalValueError,
53
61
  StoreKeyExistsError,
package/src/signal.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  type Computed,
3
3
  type ComputedCallback,
4
- computed,
4
+ createComputed,
5
5
  isComputed,
6
6
  isComputedCallback,
7
7
  } from './computed'
8
- import { isState, type State, state } from './state'
9
- import { isStore, type Store, store } from './store'
8
+ import { createState, isState, type State } from './state'
9
+ import { createStore, isStore, type Store } from './store'
10
10
  import { isRecord } from './util'
11
11
 
12
12
  /* === Types === */
@@ -71,9 +71,9 @@ function toSignal<T extends {}>(
71
71
  : State<T>
72
72
  function toSignal<T extends {}>(value: T) {
73
73
  if (isSignal<T>(value)) return value
74
- if (isComputedCallback(value)) return computed(value)
75
- if (Array.isArray(value) || isRecord(value)) return store(value)
76
- return state(value)
74
+ if (isComputedCallback(value)) return createComputed(value)
75
+ if (Array.isArray(value) || isRecord(value)) return createStore(value)
76
+ return createState(value)
77
77
  }
78
78
 
79
79
  /* === Exports === */
package/src/state.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { isEqual } from './diff'
2
- import { NullishSignalValueError } from './errors'
3
- import { notify, subscribe, type Watcher } from './scheduler'
4
- import { isObjectOfType, UNSET } from './util'
2
+ import { InvalidCallbackError, NullishSignalValueError } from './errors'
3
+ import { notify, subscribe, type Watcher } from './system'
4
+ import { isFunction, isObjectOfType, UNSET, valueString } from './util'
5
5
 
6
6
  /* === Types === */
7
7
 
8
8
  type State<T extends {}> = {
9
- [Symbol.toStringTag]: 'State'
9
+ readonly [Symbol.toStringTag]: 'State'
10
10
  get(): T
11
- set(v: T): void
12
- update(fn: (v: T) => T): void
11
+ set(newValue: T): void
12
+ update(updater: (oldValue: T) => T): void
13
13
  }
14
14
 
15
15
  /* === Constants === */
@@ -25,18 +25,20 @@ const TYPE_STATE = 'State'
25
25
  * @param {T} initialValue - initial value of the state
26
26
  * @returns {State<T>} - new state signal
27
27
  */
28
- const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
28
+ const createState = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
29
+ if (initialValue == null) throw new NullishSignalValueError('state')
30
+
29
31
  const watchers: Set<Watcher> = new Set()
30
32
  let value: T = initialValue
31
33
 
32
- const s: State<T> = {
34
+ const state: State<T> = {
33
35
  [Symbol.toStringTag]: TYPE_STATE,
34
36
 
35
37
  /**
36
38
  * Get the current value of the state
37
39
  *
38
40
  * @since 0.9.0
39
- * @returns {T} - current value of the state
41
+ * @returns {T} - Current value of the state
40
42
  */
41
43
  get: (): T => {
42
44
  subscribe(watchers)
@@ -47,13 +49,13 @@ const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
47
49
  * Set a new value of the state
48
50
  *
49
51
  * @since 0.9.0
50
- * @param {T} v
52
+ * @param {T} newValue - New value of the state
51
53
  * @returns {void}
52
54
  */
53
- set: (v: T): void => {
54
- if (v == null) throw new NullishSignalValueError('state')
55
- if (isEqual(value, v)) return
56
- value = v
55
+ set: (newValue: T): void => {
56
+ if (newValue == null) throw new NullishSignalValueError('state')
57
+ if (isEqual(value, newValue)) return
58
+ value = newValue
57
59
  notify(watchers)
58
60
 
59
61
  // Setting to UNSET clears the watchers so the signal can be garbage collected
@@ -64,15 +66,20 @@ const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
64
66
  * Update the state with a new value using a function
65
67
  *
66
68
  * @since 0.10.0
67
- * @param {(v: T) => T} fn - function to update the state
68
- * @returns {void} - updates the state with the result of the function
69
+ * @param {(v: T) => T} updater - Function to update the state
70
+ * @returns {void}
69
71
  */
70
- update: (fn: (v: T) => T): void => {
71
- s.set(fn(value))
72
+ update: (updater: (oldValue: T) => T): void => {
73
+ if (!isFunction(updater))
74
+ throw new InvalidCallbackError(
75
+ 'state update',
76
+ valueString(updater),
77
+ )
78
+ state.set(updater(value))
72
79
  },
73
80
  }
74
81
 
75
- return s
82
+ return state
76
83
  }
77
84
 
78
85
  /**
@@ -88,4 +95,4 @@ const isState = /*#__PURE__*/ <T extends {}>(
88
95
 
89
96
  /* === Exports === */
90
97
 
91
- export { TYPE_STATE, isState, state, type State }
98
+ export { TYPE_STATE, isState, createState, type State }