@zeix/cause-effect 0.14.0 → 0.14.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.14.0
3
+ Version 0.14.2
4
4
 
5
5
  **Cause & Effect** is a lightweight, reactive state management library for JavaScript applications. It uses fine-grained reactivity with signals to create predictable and efficient data flow in your app.
6
6
 
@@ -10,9 +10,9 @@ Version 0.14.0
10
10
 
11
11
  ### Core Concepts
12
12
 
13
- - **State signals**: Hold values that can be directly modified
14
- - **Computed signals**: Derive values from other signals (either `memo()` for sync or `task()` for async)
15
- - **Effects**: Run side effects when signals change
13
+ - **State signals**: Hold values that can be directly modified: `state()`
14
+ - **Computed signals**: Derive memoized values from other signals: `computed()`
15
+ - **Effects**: Run side effects when signals change: `effect()`
16
16
 
17
17
  ## Key Features
18
18
 
@@ -26,20 +26,17 @@ Version 0.14.0
26
26
  ## Quick Start
27
27
 
28
28
  ```js
29
- import { state, memo, effect } from '@zeix/cause-effect'
29
+ import { state, computed, effect } from '@zeix/cause-effect'
30
30
 
31
31
  // 1. Create state
32
32
  const user = state({ name: 'Alice', age: 30 })
33
33
 
34
34
  // 2. Create computed values
35
- const greeting = memo(() => `Hello ${user.get().name}!`)
35
+ const greeting = computed(() => `Hello ${user.get().name}!`)
36
36
 
37
37
  // 3. React to changes
38
- effect({
39
- signals: [user, greeting],
40
- ok: ({ age }, greet) => {
41
- console.log(`${greet} You are ${age} years old`)
42
- }
38
+ effect(() => {
39
+ console.log(`${greeting.get()} You are ${user.get().age} years old`)
43
40
  })
44
41
 
45
42
  // 4. Update state
@@ -76,61 +73,85 @@ document.querySelector('.increment').addEventListener('click', () => {
76
73
  // Click on button logs '25', '26', and so on
77
74
  ```
78
75
 
79
- ### Computed Signals: memo() and task()
76
+ ### Computed Signals vs. Functions
80
77
 
81
- #### Synchronous Computations with memo()
78
+ #### When to Use Computed Signals
82
79
 
83
- `memo()` creates a read-only computed signal for synchronous calculations. It automatically tracks dependencies and updates when they change.
80
+ `computed()` creates a memoized read-only signal that automatically tracks dependencies and updates only when those dependencies change.
84
81
 
85
82
  ```js
86
- import { state, memo, effect } from '@zeix/cause-effect'
83
+ import { state, computed, effect } from '@zeix/cause-effect'
87
84
 
88
85
  const count = state(42)
89
- const isOdd = memo(() => count.get() % 2)
90
- effect(() => console.log(isOdd.get())) // logs 'false'
86
+ const isEven = computed(() => !(count.get() % 2))
87
+ effect(() => console.log(isEven.get())) // logs 'true'
91
88
  count.set(24) // logs nothing because 24 is also an even number
92
89
  document.querySelector('button.increment').addEventListener('click', () => {
93
90
  count.update(v => ++v)
94
91
  })
95
- // Click on button logs 'true', 'false', and so on
92
+ // Click on button logs 'false', 'true', and so on
93
+ ```
94
+
95
+ #### When to Use Functions
96
+
97
+ **Performance tip**: For simple derivations, plain functions often outperform computed signals:
98
+
99
+ ```js
100
+ // More performant for simple calculations
101
+ const isEven = () => !(count.get() % 2)
96
102
  ```
97
103
 
98
- #### Asynchronous Computations with task()
104
+ **When to use which approach:**
99
105
 
100
- `task()` creates computed signals for asynchronous operations. It automatically manages promises, tracks dependencies, and handles cancellation through `AbortController`.
106
+ - **Use functions when**: The calculation is simple, inexpensive, or called infrequently
107
+ - **Use computed() when**:
108
+ - The calculation is expensive
109
+ - You need to share the result between multiple consumers
110
+ - You're working with asynchronous operations
111
+ - You need to track specific error states
101
112
 
102
- **Note**: Task signals return `UNSET` while pending, which you can handle with the `nil` case in effects.
113
+ #### Asynchronous Computations with Automatic Cancellation
114
+
115
+ `computed()` seamlessly handles asynchronous operations with built-in cancellation support. When used with an async function, it:
116
+
117
+ 1. Provides an `abort` signal parameter you can pass to fetch or other cancelable APIs
118
+ 2. Automatically cancels pending operations when dependencies change
119
+ 3. Returns `UNSET` while the Promise is pending
120
+ 4. Properly handles errors from failed requests
103
121
 
104
122
  ```js
105
- import { state, task, effect } from '@zeix/cause-effect'
123
+ import { state, computed, effect } from '@zeix/cause-effect'
106
124
 
107
- const entryId = state(42)
108
- const entryData = task(async abort => {
109
- const response = await fetch(`/api/entry/${entryId.get()}`, { signal: abort })
125
+ const id = state(42)
126
+ const data = computed(async abort => {
127
+ // The abort signal is automatically managed by the computed signal
128
+ const response = await fetch(`/api/entries/${id.get()}`, { signal: abort })
110
129
  if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`)
111
130
  return response.json()
112
131
  })
113
132
 
114
- // Display data when available
133
+ // Handle all possible states
115
134
  effect({
116
- signals: [entryData],
117
- ok: data => console.log('Data loaded:', data),
135
+ signals: [data],
136
+ ok: json => console.log('Data loaded:', json),
118
137
  nil: () => console.log('Loading...'),
119
138
  err: error => console.error('Error:', error)
120
139
  })
121
140
 
122
- // Move to next entry, automatically triggers a new fetch
141
+ // When id changes, the previous request is automatically canceled
123
142
  document.querySelector('button.next').addEventListener('click', () => {
124
- entryId.update(v => ++v)
143
+ id.update(v => ++v)
125
144
  })
126
145
  ```
127
146
 
147
+ **Note**: Always use `computed()` (not plain functions) for async operations to benefit from automatic cancellation, memoization, and state management.
148
+
128
149
  ## Effects and Error Handling
129
150
 
130
151
  Cause & Effect provides a robust way to handle side effects and errors through the `effect()` function, with three distinct paths:
131
152
 
132
153
  1. **Ok**: When values are available
133
- 2. **Nil**: For loading/unset states (primarily with async tasks)
154
+ 2. **Nil**: For loading/unset states (with async tasks)
134
155
  3. **Err**: When errors occur during computation
135
156
 
136
157
  This allows for declarative handling of all possible states:
@@ -138,13 +159,13 @@ This allows for declarative handling of all possible states:
138
159
  ```js
139
160
  effect({
140
161
  signals: [data],
141
- ok: (value) => /* update UI */,
142
- nil: () => /* show loading */,
143
- err: (error) => /* show error */
162
+ ok: (value) => /* update UI when data is available */,
163
+ nil: () => /* show loading state while pending */,
164
+ err: (error) => /* show error message when computation fails */
144
165
  })
145
166
  ```
146
167
 
147
- Instead of using a single callback function, you can provide an object with an `ok` handler (required), plus optional `err` and `nil` handlers. Cause & Effect will automatically route to the appropriate handler based on the state of the signals.
168
+ Instead of using a single callback function, you can provide an object with an `ok` handler (required), plus optional `err` and `nil` handlers. Cause & Effect will automatically route to the appropriate handler based on the state of the signals. If not provided, Cause & Effect will assume `console.error` for `err` and a no-op for `nil`.
148
169
 
149
170
  ## DOM Updates
150
171
 
@@ -237,13 +258,13 @@ Using Symbols for deduplication provides:
237
258
  Use `batch()` to group multiple signal updates, ensuring effects run only once after all changes are applied:
238
259
 
239
260
  ```js
240
- import { state, memo, effect, batch } from '@zeix/cause-effect'
261
+ import { state, computed, effect, batch } from '@zeix/cause-effect'
241
262
 
242
263
  // State: define an array of State<number>
243
264
  const signals = [state(2), state(3), state(5)]
244
265
 
245
266
  // Compute the sum of all signals
246
- const sum = memo(() => {
267
+ const sum = computed(() => {
247
268
  const v = signals.reduce((total, signal) => total + signal.get(), 0)
248
269
  // Validate the result
249
270
  if (!Number.isFinite(v)) throw new Error('Invalid value')
@@ -260,7 +281,9 @@ effect({
260
281
  // Batch: apply changes to all signals in a single transaction
261
282
  document.querySelector('.double-all').addEventListener('click', () => {
262
283
  batch(() => {
263
- signals.forEach(signal => signal.update(v => v * 2))
284
+ signals.forEach(signal => {
285
+ signal.update(v => v * 2)
286
+ })
264
287
  })
265
288
  })
266
289
  // Click on button logs '20' only once
@@ -274,6 +297,7 @@ The Cause & Effect library is designed around these principles:
274
297
 
275
298
  - **Minimal API**: Core primitives with a small but powerful interface
276
299
  - **Automatic Dependency Tracking**: Fine-grained reactivity with minimal boilerplate
300
+ - **Performance-Focused**: Choose the right tool (functions vs computed) for optimal speed
277
301
  - **Tree-Shakable**: Import only what you need for optimal bundle size
278
302
  - **Flexible Integration**: Works with any JavaScript application or framework
279
303
 
@@ -282,49 +306,21 @@ The Cause & Effect library is designed around these principles:
282
306
  Effects return a cleanup function. When executed, it will unsubscribe from signals and run cleanup functions returned by effect callbacks, for example to remove event listeners.
283
307
 
284
308
  ```js
285
- import { state, memo, effect } from '@zeix/cause-effect'
309
+ import { state, computed, effect } from '@zeix/cause-effect'
286
310
 
287
311
  const user = state({ name: 'Alice', age: 30 })
288
- const greeting = memo(() => `Hello ${user.get().name}!`)
289
- const cleanup = effect({
290
- signals: [user, greeting],
291
- ok: ({ age }, greet) => {
292
- console.log(`${greet} You are ${age} years old`)
293
- return () => console.log('Cleanup') // Cleanup function
294
- }
312
+ const greeting = () => `Hello ${user.get().name}!`
313
+ const cleanup = effect(() => {
314
+ console.log(`${greeting()} You are ${user.get().age} years old`)
315
+ return () => console.log('Cleanup') // Cleanup function
295
316
  })
296
317
 
297
318
  // When you no longer need the effect, execute the cleanup function
298
- cleanup() // Logs: 'Cleanup' and unsubscribes from signals `user` and `greeting`
319
+ cleanup() // Logs: 'Cleanup' and unsubscribes from signal `user`
299
320
 
300
321
  user.set({ name: 'Bob', age: 28 }) // Won't trigger the effect anymore
301
322
  ```
302
323
 
303
- ### Automatic Abort Control
304
-
305
- For asynchronous operations, `task()` automatically manages cancellation when dependencies change, providing an `abort` signal parameter:
306
-
307
- ```js
308
- import { state, task, effect } from '@zeix/cause-effect'
309
-
310
- const id = state(42)
311
- const url = memo(v => `https://example.com/api/entries/${id.get()}`)
312
- const data = task(async abort => {
313
- const response = await fetch(url.get(), { signal: abort })
314
- if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`)
315
- return response.json()
316
- })
317
- effect({
318
- signals: [data],
319
- ok: v => console.log('Value:', v),
320
- nil: () => console.warn('Not ready'),
321
- err: e => console.error('Error:', e)
322
- })
323
-
324
- // User switches to another entry
325
- id.set(24) // Cancels the previous fetch request and starts a new one
326
- ```
327
-
328
324
  ## Contributing & License
329
325
 
330
326
  Feel free to contribute, report issues, or suggest improvements.
package/biome.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
3
+ "vcs": {
4
+ "enabled": false,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": ["**", "!index.js", "!index.dev.js", "!**/*.d.ts"]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "tab"
15
+ },
16
+ "linter": {
17
+ "enabled": true,
18
+ "rules": {
19
+ "recommended": true
20
+ }
21
+ },
22
+ "javascript": {
23
+ "formatter": {
24
+ "quoteStyle": "double"
25
+ }
26
+ },
27
+ "assist": {
28
+ "enabled": true,
29
+ "actions": {
30
+ "source": {
31
+ "organizeImports": "on"
32
+ }
33
+ }
34
+ }
35
+ }
package/index.d.ts CHANGED
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.14.0
3
+ * @version 0.14.2
4
4
  * @author Esther Brunner
5
5
  */
6
- export { CircularDependencyError } from './src/util';
7
- export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal, } from './src/signal';
8
- export { type State, state, isState } from './src/state';
9
- export { type Computed, type ComputedCallback, computed, isComputed, } from './src/computed';
10
- export { type MemoCallback, memo } from './src/memo';
11
- export { type TaskCallback, task } from './src/task';
6
+ export { type Computed, type ComputedCallback, computed, isComputed, TYPE_COMPUTED, } from './src/computed';
12
7
  export { type EffectMatcher, effect } from './src/effect';
13
- export { batch, watch, enqueue } from './src/scheduler';
8
+ export { batch, type Cleanup, enqueue, flush, notify, observe, subscribe, type Updater, type Watcher, watch, } from './src/scheduler';
9
+ export { isComputedCallback, isSignal, type MaybeSignal, type Signal, type SignalValues, toSignal, UNSET, } from './src/signal';
10
+ export { isState, type State, state, TYPE_STATE } from './src/state';
11
+ export { CircularDependencyError, isFunction } from './src/util';
package/index.dev.js ADDED
@@ -0,0 +1,294 @@
1
+ // src/scheduler.ts
2
+ var active;
3
+ var pending = new Set;
4
+ var batchDepth = 0;
5
+ var updateMap = new Map;
6
+ var requestId;
7
+ var updateDOM = () => {
8
+ requestId = undefined;
9
+ const updates = Array.from(updateMap.values());
10
+ updateMap.clear();
11
+ for (const update of updates) {
12
+ update();
13
+ }
14
+ };
15
+ var requestTick = () => {
16
+ if (requestId)
17
+ cancelAnimationFrame(requestId);
18
+ requestId = requestAnimationFrame(updateDOM);
19
+ };
20
+ queueMicrotask(updateDOM);
21
+ var watch = (notice) => {
22
+ const cleanups = new Set;
23
+ const w = notice;
24
+ w.off = (on) => {
25
+ cleanups.add(on);
26
+ };
27
+ w.cleanup = () => {
28
+ for (const cleanup of cleanups) {
29
+ cleanup();
30
+ }
31
+ cleanups.clear();
32
+ };
33
+ return w;
34
+ };
35
+ var subscribe = (watchers) => {
36
+ if (active && !watchers.has(active)) {
37
+ const watcher = active;
38
+ watchers.add(watcher);
39
+ active.off(() => {
40
+ watchers.delete(watcher);
41
+ });
42
+ }
43
+ };
44
+ var notify = (watchers) => {
45
+ for (const watcher of watchers) {
46
+ if (batchDepth)
47
+ pending.add(watcher);
48
+ else
49
+ watcher();
50
+ }
51
+ };
52
+ var flush = () => {
53
+ while (pending.size) {
54
+ const watchers = Array.from(pending);
55
+ pending.clear();
56
+ for (const watcher of watchers) {
57
+ watcher();
58
+ }
59
+ }
60
+ };
61
+ var batch = (fn) => {
62
+ batchDepth++;
63
+ try {
64
+ fn();
65
+ } finally {
66
+ flush();
67
+ batchDepth--;
68
+ }
69
+ };
70
+ var observe = (run, watcher) => {
71
+ const prev = active;
72
+ active = watcher;
73
+ try {
74
+ run();
75
+ } finally {
76
+ active = prev;
77
+ }
78
+ };
79
+ var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
80
+ updateMap.set(dedupe || Symbol(), () => {
81
+ try {
82
+ resolve(fn());
83
+ } catch (error) {
84
+ reject(error);
85
+ }
86
+ });
87
+ requestTick();
88
+ });
89
+
90
+ // src/util.ts
91
+ var isFunction = (value) => typeof value === "function";
92
+ var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
93
+ var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
94
+
95
+ class CircularDependencyError extends Error {
96
+ constructor(where) {
97
+ super(`Circular dependency in ${where} detected`);
98
+ this.name = "CircularDependencyError";
99
+ }
100
+ }
101
+
102
+ // src/state.ts
103
+ var TYPE_STATE = "State";
104
+ var state = (initialValue) => {
105
+ const watchers = new Set;
106
+ let value = initialValue;
107
+ const s = {
108
+ [Symbol.toStringTag]: TYPE_STATE,
109
+ get: () => {
110
+ subscribe(watchers);
111
+ return value;
112
+ },
113
+ set: (v) => {
114
+ if (Object.is(value, v))
115
+ return;
116
+ value = v;
117
+ notify(watchers);
118
+ if (UNSET === value)
119
+ watchers.clear();
120
+ },
121
+ update: (fn) => {
122
+ s.set(fn(value));
123
+ }
124
+ };
125
+ return s;
126
+ };
127
+ var isState = (value) => isObjectOfType(value, TYPE_STATE);
128
+
129
+ // src/signal.ts
130
+ var UNSET = Symbol();
131
+ var isSignal = (value) => isState(value) || isComputed(value);
132
+ var toSignal = (value) => isSignal(value) ? value : isComputedCallback(value) ? computed(value) : state(value);
133
+
134
+ // src/computed.ts
135
+ var TYPE_COMPUTED = "Computed";
136
+ var computed = (fn) => {
137
+ const watchers = new Set;
138
+ let value = UNSET;
139
+ let error;
140
+ let controller;
141
+ let dirty = true;
142
+ let changed = false;
143
+ let computing = false;
144
+ const ok = (v) => {
145
+ if (!Object.is(v, value)) {
146
+ value = v;
147
+ changed = true;
148
+ }
149
+ error = undefined;
150
+ dirty = false;
151
+ };
152
+ const nil = () => {
153
+ changed = UNSET !== value;
154
+ value = UNSET;
155
+ error = undefined;
156
+ };
157
+ const err = (e) => {
158
+ const newError = toError(e);
159
+ changed = !error || newError.name !== error.name || newError.message !== error.message;
160
+ value = UNSET;
161
+ error = newError;
162
+ };
163
+ const settle = (settleFn) => (arg) => {
164
+ computing = false;
165
+ controller = undefined;
166
+ settleFn(arg);
167
+ if (changed)
168
+ notify(watchers);
169
+ };
170
+ const mark = watch(() => {
171
+ dirty = true;
172
+ controller?.abort("Aborted because source signal changed");
173
+ if (watchers.size)
174
+ notify(watchers);
175
+ else
176
+ mark.cleanup();
177
+ });
178
+ const compute = () => observe(() => {
179
+ if (computing)
180
+ throw new CircularDependencyError("computed");
181
+ changed = false;
182
+ if (isFunction(fn) && fn.constructor.name === "AsyncFunction") {
183
+ if (controller)
184
+ return value;
185
+ controller = new AbortController;
186
+ controller.signal.addEventListener("abort", () => {
187
+ computing = false;
188
+ controller = undefined;
189
+ compute();
190
+ }, {
191
+ once: true
192
+ });
193
+ }
194
+ let result;
195
+ computing = true;
196
+ try {
197
+ result = controller ? fn(controller.signal) : fn();
198
+ } catch (e) {
199
+ if (e instanceof DOMException && e.name === "AbortError")
200
+ nil();
201
+ else
202
+ err(e);
203
+ computing = false;
204
+ return;
205
+ }
206
+ if (result instanceof Promise)
207
+ result.then(settle(ok), settle(err));
208
+ else if (result == null || UNSET === result)
209
+ nil();
210
+ else
211
+ ok(result);
212
+ computing = false;
213
+ }, mark);
214
+ const c = {
215
+ [Symbol.toStringTag]: TYPE_COMPUTED,
216
+ get: () => {
217
+ subscribe(watchers);
218
+ flush();
219
+ if (dirty)
220
+ compute();
221
+ if (error)
222
+ throw error;
223
+ return value;
224
+ }
225
+ };
226
+ return c;
227
+ };
228
+ var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
229
+ var isComputedCallback = (value) => isFunction(value) && value.length < 2;
230
+ // src/effect.ts
231
+ function effect(matcher) {
232
+ const {
233
+ signals,
234
+ ok,
235
+ err = (error) => {
236
+ console.error(error);
237
+ },
238
+ nil = () => {
239
+ }
240
+ } = isFunction(matcher) ? { signals: [], ok: matcher } : matcher;
241
+ let running = false;
242
+ const run = watch(() => observe(() => {
243
+ if (running)
244
+ throw new CircularDependencyError("effect");
245
+ running = true;
246
+ const errors = [];
247
+ let pending2 = false;
248
+ const values = signals.map((signal) => {
249
+ try {
250
+ const value = signal.get();
251
+ if (value === UNSET)
252
+ pending2 = true;
253
+ return value;
254
+ } catch (e) {
255
+ errors.push(toError(e));
256
+ return UNSET;
257
+ }
258
+ });
259
+ let cleanup;
260
+ try {
261
+ cleanup = pending2 ? nil() : errors.length ? err(...errors) : ok(...values);
262
+ } catch (e) {
263
+ cleanup = err(toError(e));
264
+ } finally {
265
+ if (isFunction(cleanup))
266
+ run.off(cleanup);
267
+ }
268
+ running = false;
269
+ }, run));
270
+ run();
271
+ return () => run.cleanup();
272
+ }
273
+ export {
274
+ watch,
275
+ toSignal,
276
+ subscribe,
277
+ state,
278
+ observe,
279
+ notify,
280
+ isState,
281
+ isSignal,
282
+ isFunction,
283
+ isComputedCallback,
284
+ isComputed,
285
+ flush,
286
+ enqueue,
287
+ effect,
288
+ computed,
289
+ batch,
290
+ UNSET,
291
+ TYPE_STATE,
292
+ TYPE_COMPUTED,
293
+ CircularDependencyError
294
+ };
package/index.js CHANGED
@@ -1 +1 @@
1
- var O=($)=>typeof $==="function",p=($)=>O($)&&$.constructor.name==="AsyncFunction",N=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,c=($)=>$ instanceof Error,q=($)=>$ instanceof DOMException&&$.name==="AbortError",d=($)=>$ instanceof Promise,M=($)=>c($)?$:Error(String($));class x extends Error{constructor($){super(`Circular dependency in ${$} detected`);return this}}var V,k=new Set,U=0,b=new Map,_,h=()=>{_=void 0;let $=Array.from(b.values());b.clear();for(let B of $)B()},n=()=>{if(_)cancelAnimationFrame(_);_=requestAnimationFrame(h)};queueMicrotask(h);var Y=($)=>{if(V&&!$.has(V)){let B=V;$.add(B),V.cleanups.add(()=>{$.delete(B)})}},A=($)=>{for(let B of $)if(U)k.add(B);else B()},C=()=>{while(k.size){let $=Array.from(k);k.clear();for(let B of $)B()}},l=($)=>{U++;try{$()}finally{C(),U--}},R=($,B)=>{let z=V;V=B;try{$()}finally{V=z}},u=($,B)=>new Promise((z,F)=>{b.set(B||Symbol(),()=>{try{z($())}catch(L){F(L)}}),n()});var v="State",S=($)=>{let B=new Set,z=$,F={[Symbol.toStringTag]:v,get:()=>{return Y(B),z},set:(L)=>{if(Object.is(z,L))return;if(z=L,A(B),K===z)B.clear()},update:(L)=>{F.set(L(z))}};return F},T=($)=>N($,v);var w=($)=>{let B=new Set,z=K,F,L=!0,X=!1,H=()=>{if(L=!0,B.size)A(B);else H.cleanups.forEach((W)=>W()),H.cleanups.clear()};H.cleanups=new Set;let Q=()=>R(()=>{if(X)throw new x("memo");X=!0;try{let W=$();if(W==null||K===W)z=K,F=void 0;else z=W,L=!1,F=void 0}catch(W){z=K,F=W instanceof Error?W:new Error(String(W))}finally{X=!1}},H);return{[Symbol.toStringTag]:y,get:()=>{if(Y(B),C(),L)Q();if(F)throw F;return z}}};var g=($)=>{let B=new Set,z=K,F,L=!0,X=!1,H=!1,Q,j=(J)=>{if(!Object.is(J,z))z=J,L=!1,F=void 0,X=!0},W=()=>{X=K!==z,z=K,F=void 0},D=(J)=>{let I=M(J);X=!(F&&I.name===F.name&&I.message===F.message),z=K,F=I},G=(J)=>{if(H=!1,Q=void 0,j(J),X)A(B)},Z=(J)=>{if(H=!1,Q=void 0,D(J),X)A(B)},i=()=>{H=!1,Q=void 0,m()},P=()=>{if(L=!0,Q?.abort("Aborted because source signal changed"),B.size)A(B);else P.cleanups.forEach((J)=>J()),P.cleanups.clear()};P.cleanups=new Set;let m=()=>R(()=>{if(H)throw new x("task");X=!1,Q=new AbortController,Q.signal.addEventListener("abort",i,{once:!0});let J;H=!0;try{J=$(Q.signal)}catch(I){if(q(I))W();else D(I);H=!1;return}if(d(J))J.then(G,Z);else if(J==null||K===J)W();else j(J);H=!1},P);return{[Symbol.toStringTag]:y,get:()=>{if(Y(B),C(),L)m();if(F)throw F;return z}}};var y="Computed",E=($)=>p($)?g($):w($),f=($)=>N($,y);var K=Symbol(),o=($)=>T($)||f($),s=($)=>O($)&&$.length<2,t=($)=>o($)?$:s($)?E($):S($);function r($){let{signals:B,ok:z,err:F=console.error,nil:L=()=>{}}=O($)?{signals:[],ok:$}:$,X=!1,H=()=>R(()=>{if(X)throw new x("effect");X=!0;let Q=void 0;try{let j=[],W=!1,D=B.map((G)=>{try{let Z=G.get();if(Z===K)W=!0;return Z}catch(Z){if(q(Z))throw Z;return j.push(M(Z)),K}});try{Q=W?L():j.length?F(...j):z(...D)}catch(G){if(q(G))throw G;let Z=M(G);Q=F(Z)}}catch(j){F(M(j))}if(O(Q))H.cleanups.add(Q);X=!1},H);return H.cleanups=new Set,H(),()=>{H.cleanups.forEach((Q)=>Q()),H.cleanups.clear()}}export{R as watch,t as toSignal,g as task,S as state,w as memo,T as isState,o as isSignal,s as isComputedCallback,f as isComputed,u as enqueue,r as effect,E as computed,l as batch,K as UNSET,x as CircularDependencyError};
1
+ var V,M=new Set,U=0,k=new Map,D,f=()=>{D=void 0;let $=Array.from(k.values());k.clear();for(let B of $)B()},d=()=>{if(D)cancelAnimationFrame(D);D=requestAnimationFrame(f)};queueMicrotask(f);var Y=($)=>{let B=new Set,W=$;return W.off=(z)=>{B.add(z)},W.cleanup=()=>{for(let z of B)z();B.clear()},W},I=($)=>{if(V&&!$.has(V)){let B=V;$.add(B),V.off(()=>{$.delete(B)})}},F=($)=>{for(let B of $)if(U)M.add(B);else B()},N=()=>{while(M.size){let $=Array.from(M);M.clear();for(let B of $)B()}},h=($)=>{U++;try{$()}finally{N(),U--}},O=($,B)=>{let W=V;V=B;try{$()}finally{V=W}},v=($,B)=>new Promise((W,z)=>{k.set(B||Symbol(),()=>{try{W($())}catch(G){z(G)}}),d()});var j=($)=>typeof $==="function",P=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,C=($)=>$ instanceof Error?$:Error(String($));class R extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var _="State",S=($)=>{let B=new Set,W=$,z={[Symbol.toStringTag]:_,get:()=>{return I(B),W},set:(G)=>{if(Object.is(W,G))return;if(W=G,F(B),K===W)B.clear()},update:(G)=>{z.set(G(W))}};return z},T=($)=>P($,_);var K=Symbol(),p=($)=>T($)||b($),o=($)=>p($)?$:g($)?E($):S($);var m="Computed",E=($)=>{let B=new Set,W=K,z,G,X=!0,L=!1,J=!1,x=(H)=>{if(!Object.is(H,W))W=H,L=!0;z=void 0,X=!1},q=()=>{L=K!==W,W=K,z=void 0},Z=(H)=>{let Q=C(H);L=!z||Q.name!==z.name||Q.message!==z.message,W=K,z=Q},A=(H)=>(Q)=>{if(J=!1,G=void 0,H(Q),L)F(B)},y=Y(()=>{if(X=!0,G?.abort("Aborted because source signal changed"),B.size)F(B);else y.cleanup()}),w=()=>O(()=>{if(J)throw new R("computed");if(L=!1,j($)&&$.constructor.name==="AsyncFunction"){if(G)return W;G=new AbortController,G.signal.addEventListener("abort",()=>{J=!1,G=void 0,w()},{once:!0})}let H;J=!0;try{H=G?$(G.signal):$()}catch(Q){if(Q instanceof DOMException&&Q.name==="AbortError")q();else Z(Q);J=!1;return}if(H instanceof Promise)H.then(A(x),A(Z));else if(H==null||K===H)q();else x(H);J=!1},y);return{[Symbol.toStringTag]:m,get:()=>{if(I(B),N(),X)w();if(z)throw z;return W}}},b=($)=>P($,m),g=($)=>j($)&&$.length<2;function s($){let{signals:B,ok:W,err:z=(J)=>{console.error(J)},nil:G=()=>{}}=j($)?{signals:[],ok:$}:$,X=!1,L=Y(()=>O(()=>{if(X)throw new R("effect");X=!0;let J=[],x=!1,q=B.map((A)=>{try{let y=A.get();if(y===K)x=!0;return y}catch(y){return J.push(C(y)),K}}),Z;try{Z=x?G():J.length?z(...J):W(...q)}catch(A){Z=z(C(A))}finally{if(j(Z))L.off(Z)}X=!1},L));return L(),()=>L.cleanup()}export{Y as watch,o as toSignal,I as subscribe,S as state,O as observe,F as notify,T as isState,p as isSignal,j as isFunction,g as isComputedCallback,b as isComputed,N as flush,v as enqueue,s as effect,E as computed,h as batch,K as UNSET,_ as TYPE_STATE,m as TYPE_COMPUTED,R as CircularDependencyError};
package/index.ts CHANGED
@@ -1,25 +1,37 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.14.0
3
+ * @version 0.14.2
4
4
  * @author Esther Brunner
5
5
  */
6
- export { CircularDependencyError } from './src/util'
7
- export {
8
- type Signal,
9
- type MaybeSignal,
10
- UNSET,
11
- isSignal,
12
- isComputedCallback,
13
- toSignal,
14
- } from './src/signal'
15
- export { type State, state, isState } from './src/state'
6
+
16
7
  export {
17
8
  type Computed,
18
9
  type ComputedCallback,
19
10
  computed,
20
11
  isComputed,
12
+ TYPE_COMPUTED,
21
13
  } from './src/computed'
22
- export { type MemoCallback, memo } from './src/memo'
23
- export { type TaskCallback, task } from './src/task'
24
14
  export { type EffectMatcher, effect } from './src/effect'
25
- export { batch, watch, enqueue } from './src/scheduler'
15
+ export {
16
+ batch,
17
+ type Cleanup,
18
+ enqueue,
19
+ flush,
20
+ notify,
21
+ observe,
22
+ subscribe,
23
+ type Updater,
24
+ type Watcher,
25
+ watch,
26
+ } from './src/scheduler'
27
+ export {
28
+ isComputedCallback,
29
+ isSignal,
30
+ type MaybeSignal,
31
+ type Signal,
32
+ type SignalValues,
33
+ toSignal,
34
+ UNSET,
35
+ } from './src/signal'
36
+ export { isState, type State, state, TYPE_STATE } from './src/state'
37
+ export { CircularDependencyError, isFunction } from './src/util'