@vielzeug/stateit 2.0.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.
Files changed (58) hide show
  1. package/README.md +337 -0
  2. package/dist/batch.cjs +2 -0
  3. package/dist/batch.cjs.map +1 -0
  4. package/dist/batch.d.ts +5 -0
  5. package/dist/batch.d.ts.map +1 -0
  6. package/dist/batch.js +28 -0
  7. package/dist/batch.js.map +1 -0
  8. package/dist/computed.cjs +2 -0
  9. package/dist/computed.cjs.map +1 -0
  10. package/dist/computed.d.ts +58 -0
  11. package/dist/computed.d.ts.map +1 -0
  12. package/dist/computed.js +65 -0
  13. package/dist/computed.js.map +1 -0
  14. package/dist/effect.cjs +2 -0
  15. package/dist/effect.cjs.map +1 -0
  16. package/dist/effect.d.ts +31 -0
  17. package/dist/effect.d.ts.map +1 -0
  18. package/dist/effect.js +53 -0
  19. package/dist/effect.js.map +1 -0
  20. package/dist/index.cjs +1 -0
  21. package/dist/index.d.ts +20 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +9 -0
  24. package/dist/runtime.cjs +2 -0
  25. package/dist/runtime.cjs.map +1 -0
  26. package/dist/runtime.d.ts +40 -0
  27. package/dist/runtime.d.ts.map +1 -0
  28. package/dist/runtime.js +47 -0
  29. package/dist/runtime.js.map +1 -0
  30. package/dist/signal.cjs +2 -0
  31. package/dist/signal.cjs.map +1 -0
  32. package/dist/signal.d.ts +26 -0
  33. package/dist/signal.d.ts.map +1 -0
  34. package/dist/signal.js +40 -0
  35. package/dist/signal.js.map +1 -0
  36. package/dist/stateit.cjs +2 -0
  37. package/dist/stateit.cjs.map +1 -0
  38. package/dist/stateit.js +270 -0
  39. package/dist/stateit.js.map +1 -0
  40. package/dist/store.cjs +2 -0
  41. package/dist/store.cjs.map +1 -0
  42. package/dist/store.d.ts +32 -0
  43. package/dist/store.d.ts.map +1 -0
  44. package/dist/store.js +51 -0
  45. package/dist/store.js.map +1 -0
  46. package/dist/types.cjs +2 -0
  47. package/dist/types.cjs.map +1 -0
  48. package/dist/types.d.ts +39 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +6 -0
  51. package/dist/types.js.map +1 -0
  52. package/dist/watch.cjs +2 -0
  53. package/dist/watch.cjs.map +1 -0
  54. package/dist/watch.d.ts +36 -0
  55. package/dist/watch.d.ts.map +1 -0
  56. package/dist/watch.js +32 -0
  57. package/dist/watch.js.map +1 -0
  58. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # @vielzeug/stateit
2
+
3
+ > Tiny, zero-dependency reactive state — signals, computed values, effects, and typed object stores
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@vielzeug/stateit)](https://www.npmjs.com/package/@vielzeug/stateit) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ **Stateit** is a tiny, zero-dependency reactive library with two complementary primitives:
8
+
9
+ - **Signals** — fine-grained reactive atoms with `signal()`, `computed()`, `effect()`, `watch()`, `batch()`, and more
10
+ - **Stores** — typed reactive object containers with partial patching, updater functions, selectors, and freeze
11
+
12
+ A `Store<T>` extends `Signal<T>`, so every signal primitive — `effect`, `watch`, `computed`, `batch` — works seamlessly on stores.
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ pnpm add @vielzeug/stateit
18
+ # npm install @vielzeug/stateit
19
+ # yarn add @vielzeug/stateit
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Signals
25
+
26
+ ```typescript
27
+ import { signal, computed, effect, watch, batch } from '@vielzeug/stateit';
28
+
29
+ const count = signal(0);
30
+ const doubled = computed(() => count.value * 2);
31
+
32
+ // effect runs immediately and re-runs on every dependency change
33
+ const sub = effect(() => {
34
+ console.log(`count=${count.value}, doubled=${doubled.value}`);
35
+ });
36
+ // → count=0, doubled=0
37
+
38
+ count.value = 3;
39
+ // → count=3, doubled=6
40
+
41
+ // Update via function — shorthand for count.value = count.value + n
42
+ count.update((n) => n + 1);
43
+ // → count=4, doubled=8
44
+
45
+ // Watch explicitly (does not fire immediately by default)
46
+ const stopWatch = watch(count, (next, prev) => console.log(prev, '→', next));
47
+ count.value = 5; // → 4 → 5
48
+
49
+ // Batch coalesces notifications into one flush
50
+ batch(() => {
51
+ count.value = 10;
52
+ count.value = 20;
53
+ });
54
+ // One notification: → 5 → 20
55
+
56
+ sub.dispose(); // or: sub() — direct call also disposes
57
+ doubled.dispose();
58
+ stopWatch.dispose();
59
+ ```
60
+
61
+ ### Stores
62
+
63
+ ```typescript
64
+ import { store, watch, batch } from '@vielzeug/stateit';
65
+
66
+ const counter = store({ count: 0, user: null as User | null });
67
+
68
+ // Read
69
+ console.log(counter.value); // { count: 0, user: null }
70
+
71
+ // Partial patch
72
+ counter.patch({ count: 1 });
73
+
74
+ // Updater function
75
+ counter.update((s) => ({ ...s, count: s.count + 1 }));
76
+
77
+ // Watch a slice — compose store.select() with watch()
78
+ const countSignal = counter.select((s) => s.count);
79
+ const stopWatch = watch(countSignal, (count, prev) => {
80
+ console.log('count:', prev, '→', count);
81
+ });
82
+
83
+ // Batch updates into one notification
84
+ batch(() => {
85
+ counter.patch({ count: 10 });
86
+ counter.patch({ user: currentUser });
87
+ });
88
+
89
+ // Reset to initial state
90
+ counter.reset();
91
+
92
+ stopWatch.dispose();
93
+ counter.freeze();
94
+ ```
95
+
96
+ ## Features
97
+
98
+ - ✅ **Signals** — reactive atoms with `.value` getter/setter, `.update(fn)`, and `.peek()` for untracked reads
99
+ - ✅ **Computed** — lazy derived signals; call `.dispose()` to stop tracking dependencies
100
+ - ✅ **Effects** — run immediately and re-run when dependencies change; support cleanup returns; return a `Subscription`
101
+ - ✅ **Watch** — explicit subscriptions with `{ immediate?, once?, equals? }` options; returns a `Subscription`
102
+ - ✅ **Batch** — coalesce multiple writes into a single downstream notification
103
+ - ✅ **Untrack** — read signals without registering reactive dependencies
104
+ - ✅ **derived** — multi-source computed from an array of signals
105
+ - ✅ **nextValue** — `Promise` that resolves on the next matching signal emission
106
+ - ✅ **Custom equality** — `signal/computed/watch/store` all accept a custom `equals` function
107
+ - ✅ **Stores** — object state with `patch(partial)`, `update(fn)`, `reset()`, `select()`, and `freeze()`
108
+ - ✅ **Writable computed** — bidirectional computed with `writable(get, set)`
109
+ - ✅ **Symbol.dispose** — all dispose handles support `using` declarations (TC39 explicit resource management)
110
+ - ✅ **Type guards** — `isSignal(v)` and `isStore(v)`
111
+ - ✅ **Zero dependencies** — no supply-chain risk, minimal bundle size
112
+ - ✅ **Framework agnostic** — works in React, Vue, Svelte, or plain TypeScript
113
+
114
+ ## Usage
115
+
116
+ ### Signals
117
+
118
+ ```typescript
119
+ import { signal, readonly } from '@vielzeug/stateit';
120
+
121
+ const count = signal(0);
122
+ count.value = 5;
123
+ count.peek(); // read without tracking
124
+ count.update((n) => n + 1); // derive next value in place
125
+
126
+ // Narrow to read-only view (no proxy overhead)
127
+ const ro = readonly(count);
128
+ ```
129
+
130
+ ### Computed
131
+
132
+ ```typescript
133
+ import { signal, computed } from '@vielzeug/stateit';
134
+
135
+ const first = signal('Ada');
136
+ const last = signal('Lovelace');
137
+ const full = computed(() => `${first.value} ${last.value}`);
138
+
139
+ console.log(full.value); // 'Ada Lovelace'
140
+ full.stale; // false — just computed
141
+ first.value = 'Grace';
142
+ full.stale; // true — deps changed, not yet re-read
143
+
144
+ // Dispose (stop tracking dependencies)
145
+ full.dispose();
146
+ // or: using full = computed(...) — TC39 using declaration
147
+ ```
148
+
149
+ ### Effects
150
+
151
+ ```typescript
152
+ import { signal, effect, onCleanup } from '@vielzeug/stateit';
153
+
154
+ const name = signal('Ada');
155
+
156
+ const sub = effect(() => {
157
+ console.log('Hello,', name.value);
158
+ return () => console.log('cleanup'); // optional teardown
159
+ });
160
+
161
+ name.value = 'Grace'; // logs 'cleanup' then 'Hello, Grace'
162
+ sub.dispose(); // logs 'cleanup', effect is removed
163
+ ```
164
+
165
+ ### Watch
166
+
167
+ ```typescript
168
+ import { signal, store, watch } from '@vielzeug/stateit';
169
+
170
+ const count = signal(0);
171
+
172
+ // Plain watch
173
+ const sub = watch(count, (next, prev) => console.log(prev, '→', next));
174
+
175
+ // Slice watch — compose with store.select()
176
+ const user = store({ name: 'Ada', age: 30 });
177
+ const nameSig = user.select((s) => s.name);
178
+ watch(nameSig, (name) => console.log('name:', name));
179
+
180
+ // Options
181
+ watch(count, (v) => console.log(v), { immediate: true, once: true });
182
+
183
+ sub.dispose();
184
+ ```
185
+
186
+ ### Stores
187
+
188
+ ```typescript
189
+ import { store, watch, batch } from '@vielzeug/stateit';
190
+
191
+ const cart = store({ items: [] as string[], total: 0 });
192
+
193
+ // Partial patch
194
+ cart.patch({ total: 42 });
195
+
196
+ // Updater function
197
+ cart.update((s) => ({ ...s, items: [...s.items, 'apple'] }));
198
+
199
+ // Derived slice
200
+ const totalSignal = cart.select((s) => s.total);
201
+
202
+ // Watch slice
203
+ watch(totalSignal, (total) => console.log('total:', total));
204
+
205
+ // Batch
206
+ batch(() => {
207
+ cart.patch({ total: 0 });
208
+ cart.update((s) => ({ ...s, items: [] }));
209
+ });
210
+
211
+ cart.reset();
212
+ cart.freeze();
213
+ ```
214
+
215
+ ### Writable Computed
216
+
217
+ ```typescript
218
+ import { signal, writable } from '@vielzeug/stateit';
219
+
220
+ const celsius = signal(0);
221
+ const fahrenheit = writable(
222
+ () => (celsius.value * 9) / 5 + 32,
223
+ (f) => {
224
+ celsius.value = ((f - 32) * 5) / 9;
225
+ },
226
+ );
227
+
228
+ fahrenheit.value = 212;
229
+ console.log(celsius.value); // 100
230
+
231
+ fahrenheit.update((f) => f + 10); // increment via fn
232
+ fahrenheit.dispose();
233
+ ```
234
+
235
+ ### `nextValue` — Async Watch
236
+
237
+ ```typescript
238
+ import { signal, nextValue } from '@vielzeug/stateit';
239
+
240
+ const status = signal<'idle' | 'loading' | 'done'>('idle');
241
+
242
+ // Wait for the next change (any value)
243
+ const next = await nextValue(status);
244
+
245
+ // Wait for a specific condition
246
+ const done = await nextValue(status, (v) => v === 'done');
247
+
248
+ // Abortable wait
249
+ const controller = new AbortController();
250
+ const p = nextValue(status, (v) => v === 'done', { signal: controller.signal });
251
+ controller.abort(new Error('cancelled'));
252
+ ```
253
+
254
+ ### Custom Equality
255
+
256
+ ```typescript
257
+ import { signal, computed } from '@vielzeug/stateit';
258
+
259
+ // Signal only notifies when the array reference AND contents differ
260
+ const tags = signal(['ts'], { equals: (a, b) => JSON.stringify(a) === JSON.stringify(b) });
261
+
262
+ // Computed suppresses downstream when result is structurally unchanged
263
+ const sorted = computed(() => [...tags.value].sort(), { equals: (a, b) => a.join() === b.join() });
264
+ ```
265
+
266
+ ## API
267
+
268
+ ### Signal Functions
269
+
270
+ | Export | Signature | Returns |
271
+ | ------------------ | -------------------------------------------------------- | ------------------------ |
272
+ | `signal` | `signal(initial, options?)` | `Signal<T>` |
273
+ | `computed` | `computed(fn, options?)` | `ComputedSignal<T>` |
274
+ | `writable` | `writable(get, set, options?)` | `WritableSignal<T>` |
275
+ | `derived` | `derived(sources, fn, options?)` | `ComputedSignal<R>` |
276
+ | `effect` | `effect(fn, options?)` | `Subscription` |
277
+ | `watch` | `watch(source, cb, options?)` | `Subscription` |
278
+ | `nextValue` | `nextValue(source, predicate?, { signal? }?)` | `Promise<T>` |
279
+ | `batch` | `batch(fn)` | `T` |
280
+ | `untrack` | `untrack(fn)` | `T` |
281
+ | `onCleanup` | `onCleanup(fn)` | `void` |
282
+ | `readonly` | `readonly(sig)` | `ReadonlySignal<T>` |
283
+ | `toValue` | `toValue(v)` | `T` |
284
+ | `peekValue` | `peekValue(v)` | `T` |
285
+ | `isSignal` | `isSignal(v)` | `v is ReadonlySignal<T>` |
286
+ | `isStore` | `isStore(v)` | `v is Store<T>` |
287
+ | `shallowEqual` | `shallowEqual(a, b)` | `boolean` |
288
+ | `configureStateit` | `configureStateit(opts)` | `void` |
289
+ | `_resetContextForTesting` | `_resetContextForTesting()` | `void` |
290
+
291
+ ### Store Functions
292
+
293
+ | Export | Signature | Returns |
294
+ | ------- | -------------------------- | ---------- |
295
+ | `store` | `store(initial, options?)` | `Store<T>` |
296
+
297
+ ### `Store<T>` Methods
298
+
299
+ | Member | Description |
300
+ | ----------------------------- | ----------------------------------------------------------------------- |
301
+ | `.value` | Read the current state (tracked) |
302
+ | `.peek()` | Read the current state (untracked) |
303
+ | `.update(fn)` | Derive next state via `fn(copy) => next` |
304
+ | `.patch(partial)` | Shallow-merge a `Partial<T>` into state |
305
+ | `.reset()` | Restore the original initial state |
306
+ | `.select(selector, options?)` | Lazily derived `ComputedSignal<U>` from a slice; compose with `watch()` |
307
+ | `.frozen` | `true` after `freeze()` has been called |
308
+ | `.freeze()` | Freeze the store; further writes are silently ignored |
309
+
310
+ ### Types
311
+
312
+ | Type | Description |
313
+ | -------------------- | ------------------------------------------------------------------------------------ |
314
+ | `Signal<T>` | Readable/writable reactive atom with `.update(fn)` |
315
+ | `ReadonlySignal<T>` | Read-only signal (no setter) |
316
+ | `ComputedSignal<T>` | `ReadonlySignal<T> & Disposable` — has `.stale`, `.dispose()` |
317
+ | `WritableSignal<T>` | `Signal<T> & Disposable` — has `.stale`, `.update(fn)`, `.dispose()` |
318
+ | `Store<T>` | Object store extending `Signal<T>` |
319
+ | `Subscription` | Callable + `.dispose()` + `[Symbol.dispose]` — returned by `effect`/`watch` |
320
+ | `Disposable` | `.dispose()` + `[Symbol.dispose]` — implemented by `ComputedSignal`/`WritableSignal` |
321
+ | `CleanupFn` | `() => void` |
322
+ | `EffectCallback` | `() => CleanupFn \| void` |
323
+ | `EffectOptions` | `{ maxIterations?, onError? }` |
324
+ | `EqualityFn<T>` | `(a: T, b: T) => boolean` |
325
+ | `ReactiveOptions<T>` | `{ equals?: EqualityFn<T> }` |
326
+ | `WatchOptions<T>` | `{ immediate?, once?, equals? }` |
327
+ | `StoreOptions<T>` | `{ equals?: EqualityFn<T> }` |
328
+
329
+ ## Documentation
330
+
331
+ Full docs at **[vielzeug.dev/stateit](https://vielzeug.dev/stateit)**
332
+
333
+ | | |
334
+ | ------------------------------------------------- | -------------------------------------- |
335
+ | [Usage Guide](https://vielzeug.dev/stateit/usage) | Concepts, patterns, and best practices |
336
+ | [API Reference](https://vielzeug.dev/stateit/api) | Complete type signatures |
337
+ | [Examples](https://vielzeug.dev/stateit/examples) | Framework integrations and recipes |
package/dist/batch.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const e=require(`./runtime.cjs`);var t=()=>{let t=[...e.queue.pending];e.queue.pending.clear();let n=[];for(let e of t)try{e()}catch(e){n.push(e)}if(n.length)throw n.length===1?n[0]:AggregateError(n,`[stateit] batch errors`)},n=n=>{e.queue.depth++;try{let r=n();return--e.queue.depth===0&&t(),r}catch(n){if(--e.queue.depth===0)try{t()}catch{}throw n}};exports._flushPending=t,exports.batch=n;
2
+ //# sourceMappingURL=batch.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.cjs","names":[],"sources":["../src/batch.ts"],"sourcesContent":["import { _DEV, queue } from './runtime';\n\n/** @internal Drain and run all pending effects from the batch queue, collecting errors. */\nexport const _flushPending = (): void => {\n const toFlush = [...queue.pending];\n\n queue.pending.clear();\n\n const errors: unknown[] = [];\n\n for (const f of toFlush) {\n try {\n f();\n } catch (e) {\n errors.push(e);\n }\n }\n\n if (errors.length) throw errors.length === 1 ? errors[0] : new AggregateError(errors, '[stateit] batch errors');\n};\n\n/** Runs fn and defers all Signal notifications until fn returns, then flushes once. */\nexport const batch = <T>(fn: () => T): T => {\n queue.depth++;\n\n try {\n const result = fn();\n\n if (--queue.depth === 0) _flushPending();\n\n return result;\n } catch (e) {\n if (--queue.depth === 0) {\n try {\n _flushPending();\n } catch (flushErr) {\n if (_DEV)\n console.error(\n '[stateit] batch: a secondary flush error was suppressed (callback error takes precedence)',\n flushErr,\n );\n }\n }\n\n throw e;\n }\n};\n"],"mappings":"iCAGA,IAAa,MAA4B,CACvC,IAAM,EAAU,CAAC,GAAG,EAAA,MAAM,QAAQ,CAElC,EAAA,MAAM,QAAQ,OAAO,CAErB,IAAM,EAAoB,EAAE,CAE5B,IAAK,IAAM,KAAK,EACd,GAAI,CACF,GAAG,OACI,EAAG,CACV,EAAO,KAAK,EAAE,CAIlB,GAAI,EAAO,OAAQ,MAAM,EAAO,SAAW,EAAI,EAAO,GAAS,eAAe,EAAQ,yBAAyB,EAIpG,EAAY,GAAmB,CAC1C,EAAA,MAAM,QAEN,GAAI,CACF,IAAM,EAAS,GAAI,CAInB,MAFI,EAAE,EAAA,MAAM,QAAU,GAAG,GAAe,CAEjC,QACA,EAAG,CACV,GAAI,EAAE,EAAA,MAAM,QAAU,EACpB,GAAI,CACF,GAAe,MACE,EASrB,MAAM"}
@@ -0,0 +1,5 @@
1
+ /** @internal Drain and run all pending effects from the batch queue, collecting errors. */
2
+ export declare const _flushPending: () => void;
3
+ /** Runs fn and defers all Signal notifications until fn returns, then flushes once. */
4
+ export declare const batch: <T>(fn: () => T) => T;
5
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAEA,2FAA2F;AAC3F,eAAO,MAAM,aAAa,QAAO,IAgBhC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,IAAI,MAAM,CAAC,KAAG,CAwBtC,CAAC"}
package/dist/batch.js ADDED
@@ -0,0 +1,28 @@
1
+ import { queue as e } from "./runtime.js";
2
+ //#region src/batch.ts
3
+ var t = () => {
4
+ let t = [...e.pending];
5
+ e.pending.clear();
6
+ let n = [];
7
+ for (let e of t) try {
8
+ e();
9
+ } catch (e) {
10
+ n.push(e);
11
+ }
12
+ if (n.length) throw n.length === 1 ? n[0] : AggregateError(n, "[stateit] batch errors");
13
+ }, n = (n) => {
14
+ e.depth++;
15
+ try {
16
+ let r = n();
17
+ return --e.depth === 0 && t(), r;
18
+ } catch (n) {
19
+ if (--e.depth === 0) try {
20
+ t();
21
+ } catch {}
22
+ throw n;
23
+ }
24
+ };
25
+ //#endregion
26
+ export { t as _flushPending, n as batch };
27
+
28
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","names":[],"sources":["../src/batch.ts"],"sourcesContent":["import { _DEV, queue } from './runtime';\n\n/** @internal Drain and run all pending effects from the batch queue, collecting errors. */\nexport const _flushPending = (): void => {\n const toFlush = [...queue.pending];\n\n queue.pending.clear();\n\n const errors: unknown[] = [];\n\n for (const f of toFlush) {\n try {\n f();\n } catch (e) {\n errors.push(e);\n }\n }\n\n if (errors.length) throw errors.length === 1 ? errors[0] : new AggregateError(errors, '[stateit] batch errors');\n};\n\n/** Runs fn and defers all Signal notifications until fn returns, then flushes once. */\nexport const batch = <T>(fn: () => T): T => {\n queue.depth++;\n\n try {\n const result = fn();\n\n if (--queue.depth === 0) _flushPending();\n\n return result;\n } catch (e) {\n if (--queue.depth === 0) {\n try {\n _flushPending();\n } catch (flushErr) {\n if (_DEV)\n console.error(\n '[stateit] batch: a secondary flush error was suppressed (callback error takes precedence)',\n flushErr,\n );\n }\n }\n\n throw e;\n }\n};\n"],"mappings":";;AAGA,IAAa,UAA4B;CACvC,IAAM,IAAU,CAAC,GAAG,EAAM,QAAQ;AAElC,GAAM,QAAQ,OAAO;CAErB,IAAM,IAAoB,EAAE;AAE5B,MAAK,IAAM,KAAK,EACd,KAAI;AACF,KAAG;UACI,GAAG;AACV,IAAO,KAAK,EAAE;;AAIlB,KAAI,EAAO,OAAQ,OAAM,EAAO,WAAW,IAAI,EAAO,KAAS,eAAe,GAAQ,yBAAyB;GAIpG,KAAY,MAAmB;AAC1C,GAAM;AAEN,KAAI;EACF,IAAM,IAAS,GAAI;AAInB,SAFI,EAAE,EAAM,UAAU,KAAG,GAAe,EAEjC;UACA,GAAG;AACV,MAAI,EAAE,EAAM,UAAU,EACpB,KAAI;AACF,MAAe;UACE;AASrB,QAAM"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./runtime.cjs`),t=require(`./types.cjs`);var n=class extends e.ReactiveNode{[t._SIGNAL_BRAND]=!0;#e;#t=e._UNINITIALIZED;#n=!0;#r=!1;#i;#a=new Set;#o=()=>{this.#n||(this.#n=!0,this._notify())};constructor(e,t){super(),this.#e=e,this.#i=t?.equals??Object.is,t?.lazy||this.#s()}#s(){for(let e of this.#a)e();this.#a.clear();let t=e._withCtx(this.#o,this.#a,null,this.#e);this.#n=!1,(this.#t===e._UNINITIALIZED||!this.#i(this.#t,t))&&(this.#t=t)}get value(){return this.#r?this.#t:(this.#n&&this.#s(),this._track(),this.#t)}peek(){if(this.#t===e._UNINITIALIZED){if(this.#r)return;this.#s()}return this.#t}get stale(){return this.#n||this.#r}dispose(){if(!this.#r){this.#r=!0;for(let e of this.#a)e();this.#a.clear()}}[Symbol.dispose](){this.dispose()}},r=(e,t)=>new n(e,t),i=class extends n{#e;constructor(e,t,n){super(e,n),this.#e=t}get value(){return super.value}set value(e){this.#e(e)}update(e){this.#e(e(this.peek()))}},a=(e,t,n)=>new i(e,t,n),o=(e,t,n)=>r(()=>t(...e.map(e=>e.value)),n);exports.ComputedNode=n,exports.WritableNode=i,exports.computed=r,exports.derived=o,exports.writable=a;
2
+ //# sourceMappingURL=computed.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed.cjs","names":["#onDepChange","#dirty","#compute","#equals","#recompute","#deps","#value","#disposed","#set"],"sources":["../src/computed.ts"],"sourcesContent":["import { ReactiveNode, _DEV, _UNINITIALIZED, _withCtx } from './runtime';\nimport {\n type CleanupFn,\n type Disposable,\n type EffectCallback,\n type EqualityFn,\n type ReactiveOptions,\n type ReadonlySignal,\n type Signal,\n _SIGNAL_BRAND,\n} from './types';\n\n/** A derived read-only signal with an explicit dispose method. */\nexport interface ComputedSignal<T> extends ReadonlySignal<T>, Disposable {\n /** True when the computed value is stale (deps changed but not yet re-read) or disposed. */\n readonly stale: boolean;\n}\n\n/** @internal\n * Lazy computed node. Seeded once at construction so .peek() is immediately valid\n * (unless { lazy: true } is passed, in which case first compute defers to first read).\n * - On dep change: marks dirty, notifies downstream (no recompute yet).\n * - On .value read: recomputes if dirty, re-tracks fresh deps.\n * - After dispose: _track() is skipped to prevent leaking outer effects.\n */\nexport class ComputedNode<T> extends ReactiveNode implements ComputedSignal<T> {\n readonly [_SIGNAL_BRAND] = true as const;\n #compute: () => T;\n #value: T | typeof _UNINITIALIZED = _UNINITIALIZED;\n #dirty = true;\n #disposed = false;\n #equals: EqualityFn<T>;\n #deps = new Set<CleanupFn>();\n\n readonly #onDepChange: EffectCallback = () => {\n if (!this.#dirty) {\n this.#dirty = true;\n this._notify();\n }\n };\n\n constructor(compute: () => T, options?: ReactiveOptions<T> & { lazy?: boolean }) {\n super();\n this.#compute = compute;\n this.#equals = options?.equals ?? Object.is;\n\n if (!options?.lazy) this.#recompute(); // seed: ensures peek() is valid immediately\n }\n\n #recompute(): void {\n for (const unsub of this.#deps) unsub();\n this.#deps.clear();\n\n const result = _withCtx(this.#onDepChange, this.#deps, null, this.#compute);\n\n this.#dirty = false;\n\n if (this.#value === _UNINITIALIZED || !this.#equals(this.#value as T, result)) {\n this.#value = result;\n }\n }\n\n get value(): T {\n if (this.#disposed) {\n if (_DEV) console.warn('[stateit] Reading a disposed ComputedSignal returns a stale value.');\n\n return this.#value as T;\n }\n\n if (this.#dirty) this.#recompute();\n\n this._track();\n\n return this.#value as T;\n }\n\n peek(): T {\n if (this.#value === _UNINITIALIZED) {\n if (this.#disposed) {\n if (_DEV)\n console.warn(\n '[stateit] peek() called on a disposed lazy ComputedSignal that was never read — returning undefined.',\n );\n\n return undefined as unknown as T;\n }\n\n this.#recompute();\n }\n\n return this.#value as T;\n }\n\n get stale(): boolean {\n return this.#dirty || this.#disposed;\n }\n\n dispose(): void {\n if (this.#disposed) return;\n\n this.#disposed = true;\n for (const unsub of this.#deps) unsub();\n this.#deps.clear();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n/** Creates a derived read-only Signal whose value is recomputed lazily on `.value` read\n * when dependencies have changed. Call `.dispose()` to stop tracking and free dependencies.\n * Pass `{ lazy: true }` to defer the initial computation until the first `.value` read. */\nexport const computed = <T>(compute: () => T, options?: ReactiveOptions<T> & { lazy?: boolean }): ComputedSignal<T> =>\n new ComputedNode(compute, options);\n\n/** A bidirectional computed Signal with an explicit dispose method. */\nexport interface WritableSignal<T> extends Signal<T>, Disposable {\n /** True when the backing computed value is stale (deps changed but not yet re-read) or disposed. */\n readonly stale: boolean;\n}\n\n/** @internal */\nexport class WritableNode<T> extends ComputedNode<T> implements WritableSignal<T> {\n readonly #set: (v: T) => void;\n\n constructor(get: () => T, set: (v: T) => void, options?: ReactiveOptions<T>) {\n super(get, options);\n this.#set = set;\n }\n\n override get value(): T {\n return super.value;\n }\n\n set value(v: T) {\n this.#set(v);\n }\n\n update(fn: (current: T) => T): void {\n this.#set(fn(this.peek()));\n }\n}\n\n/** Creates a bidirectional computed Signal. Reads track the getter reactively;\n * writes are forwarded to `set`. Call `.dispose()` to stop tracking and free dependencies. */\nexport const writable = <T>(get: () => T, set: (value: T) => void, options?: ReactiveOptions<T>): WritableSignal<T> =>\n new WritableNode(get, set, options);\n\n/**\n * Creates a derived ComputedSignal by combining multiple source signals through a projector\n * function. Each source is passed as a positional argument to `fn`; the projector is\n * re-evaluated whenever any source changes.\n *\n * @example\n * const total = derived([price, quantity, discount], (p, q, d) => p * q * (1 - d));\n */\nexport const derived = <const Srcs extends ReadonlyArray<ReadonlySignal<unknown>>, R>(\n sources: Srcs,\n fn: (...values: { [K in keyof Srcs]: Srcs[K] extends ReadonlySignal<infer V> ? V : never }) => R,\n options?: ReactiveOptions<R>,\n): ComputedSignal<R> => computed(() => (fn as (...args: any[]) => R)(...sources.map((s) => s.value)), options);\n"],"mappings":"0DAyBA,IAAa,EAAb,cAAqC,EAAA,YAA0C,CAC7E,CAAU,EAAA,eAAiB,GAC3B,GACA,GAAoC,EAAA,eACpC,GAAS,GACT,GAAY,GACZ,GACA,GAAQ,IAAI,IAEZ,OAA8C,CACvC,MAAA,IACH,MAAA,EAAc,GACd,KAAK,SAAS,GAIlB,YAAY,EAAkB,EAAmD,CAC/E,OAAO,CACP,MAAA,EAAgB,EAChB,MAAA,EAAe,GAAS,QAAU,OAAO,GAEpC,GAAS,MAAM,MAAA,GAAiB,CAGvC,IAAmB,CACjB,IAAK,IAAM,KAAS,MAAA,EAAY,GAAO,CACvC,MAAA,EAAW,OAAO,CAElB,IAAM,EAAS,EAAA,SAAS,MAAA,EAAmB,MAAA,EAAY,KAAM,MAAA,EAAc,CAE3E,MAAA,EAAc,IAEV,MAAA,IAAgB,EAAA,gBAAkB,CAAC,MAAA,EAAa,MAAA,EAAkB,EAAO,IAC3E,MAAA,EAAc,GAIlB,IAAI,OAAW,CAWb,OAVI,MAAA,EAGK,MAAA,GAGL,MAAA,GAAa,MAAA,GAAiB,CAElC,KAAK,QAAQ,CAEN,MAAA,GAGT,MAAU,CACR,GAAI,MAAA,IAAgB,EAAA,eAAgB,CAClC,GAAI,MAAA,EAMF,OAGF,MAAA,GAAiB,CAGnB,OAAO,MAAA,EAGT,IAAI,OAAiB,CACnB,OAAO,MAAA,GAAe,MAAA,EAGxB,SAAgB,CACV,UAAA,EAEJ,OAAA,EAAiB,GACjB,IAAK,IAAM,KAAS,MAAA,EAAY,GAAO,CACvC,MAAA,EAAW,OAAO,EAGpB,CAAC,OAAO,UAAiB,CACvB,KAAK,SAAS,GAOL,GAAe,EAAkB,IAC5C,IAAI,EAAa,EAAS,EAAQ,CASvB,EAAb,cAAqC,CAA6C,CAChF,GAEA,YAAY,EAAc,EAAqB,EAA8B,CAC3E,MAAM,EAAK,EAAQ,CACnB,MAAA,EAAY,EAGd,IAAa,OAAW,CACtB,OAAO,MAAM,MAGf,IAAI,MAAM,EAAM,CACd,MAAA,EAAU,EAAE,CAGd,OAAO,EAA6B,CAClC,MAAA,EAAU,EAAG,KAAK,MAAM,CAAC,CAAC,GAMjB,GAAe,EAAc,EAAyB,IACjE,IAAI,EAAa,EAAK,EAAK,EAAQ,CAUxB,GACX,EACA,EACA,IACsB,MAAgB,EAA6B,GAAG,EAAQ,IAAK,GAAM,EAAE,MAAM,CAAC,CAAE,EAAQ"}
@@ -0,0 +1,58 @@
1
+ import { ReactiveNode } from './runtime';
2
+ import { type Disposable, type ReactiveOptions, type ReadonlySignal, type Signal, _SIGNAL_BRAND } from './types';
3
+ /** A derived read-only signal with an explicit dispose method. */
4
+ export interface ComputedSignal<T> extends ReadonlySignal<T>, Disposable {
5
+ /** True when the computed value is stale (deps changed but not yet re-read) or disposed. */
6
+ readonly stale: boolean;
7
+ }
8
+ /** @internal
9
+ * Lazy computed node. Seeded once at construction so .peek() is immediately valid
10
+ * (unless { lazy: true } is passed, in which case first compute defers to first read).
11
+ * - On dep change: marks dirty, notifies downstream (no recompute yet).
12
+ * - On .value read: recomputes if dirty, re-tracks fresh deps.
13
+ * - After dispose: _track() is skipped to prevent leaking outer effects.
14
+ */
15
+ export declare class ComputedNode<T> extends ReactiveNode implements ComputedSignal<T> {
16
+ #private;
17
+ readonly [_SIGNAL_BRAND]: true;
18
+ constructor(compute: () => T, options?: ReactiveOptions<T> & {
19
+ lazy?: boolean;
20
+ });
21
+ get value(): T;
22
+ peek(): T;
23
+ get stale(): boolean;
24
+ dispose(): void;
25
+ [Symbol.dispose](): void;
26
+ }
27
+ /** Creates a derived read-only Signal whose value is recomputed lazily on `.value` read
28
+ * when dependencies have changed. Call `.dispose()` to stop tracking and free dependencies.
29
+ * Pass `{ lazy: true }` to defer the initial computation until the first `.value` read. */
30
+ export declare const computed: <T>(compute: () => T, options?: ReactiveOptions<T> & {
31
+ lazy?: boolean;
32
+ }) => ComputedSignal<T>;
33
+ /** A bidirectional computed Signal with an explicit dispose method. */
34
+ export interface WritableSignal<T> extends Signal<T>, Disposable {
35
+ /** True when the backing computed value is stale (deps changed but not yet re-read) or disposed. */
36
+ readonly stale: boolean;
37
+ }
38
+ /** @internal */
39
+ export declare class WritableNode<T> extends ComputedNode<T> implements WritableSignal<T> {
40
+ #private;
41
+ constructor(get: () => T, set: (v: T) => void, options?: ReactiveOptions<T>);
42
+ get value(): T;
43
+ set value(v: T);
44
+ update(fn: (current: T) => T): void;
45
+ }
46
+ /** Creates a bidirectional computed Signal. Reads track the getter reactively;
47
+ * writes are forwarded to `set`. Call `.dispose()` to stop tracking and free dependencies. */
48
+ export declare const writable: <T>(get: () => T, set: (value: T) => void, options?: ReactiveOptions<T>) => WritableSignal<T>;
49
+ /**
50
+ * Creates a derived ComputedSignal by combining multiple source signals through a projector
51
+ * function. Each source is passed as a positional argument to `fn`; the projector is
52
+ * re-evaluated whenever any source changes.
53
+ *
54
+ * @example
55
+ * const total = derived([price, quantity, discount], (p, q, d) => p * q * (1 - d));
56
+ */
57
+ export declare const derived: <const Srcs extends ReadonlyArray<ReadonlySignal<unknown>>, R>(sources: Srcs, fn: (...values: { [K in keyof Srcs]: Srcs[K] extends ReadonlySignal<infer V> ? V : never; }) => R, options?: ReactiveOptions<R>) => ComputedSignal<R>;
58
+ //# sourceMappingURL=computed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAkC,MAAM,WAAW,CAAC;AACzE,OAAO,EAEL,KAAK,UAAU,EAGf,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,kEAAkE;AAClE,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC,EAAE,UAAU;IACtE,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,qBAAa,YAAY,CAAC,CAAC,CAAE,SAAQ,YAAa,YAAW,cAAc,CAAC,CAAC,CAAC;;IAC5E,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAG,IAAI,CAAU;gBAe7B,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE;IAqB/E,IAAI,KAAK,IAAI,CAAC,CAYb;IAED,IAAI,IAAI,CAAC;IAiBT,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,OAAO,IAAI,IAAI;IAQf,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB;AAED;;2FAE2F;AAC3F,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,SAAS,MAAM,CAAC,EAAE,UAAU,eAAe,CAAC,CAAC,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,KAAG,cAAc,CAAC,CAAC,CAC9E,CAAC;AAErC,uEAAuE;AACvE,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU;IAC9D,oGAAoG;IACpG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,gBAAgB;AAChB,qBAAa,YAAY,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAE,YAAW,cAAc,CAAC,CAAC,CAAC;;gBAGnE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;IAK3E,IAAa,KAAK,IAAI,CAAC,CAEtB;IAED,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,EAEb;IAED,MAAM,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI;CAGpC;AAED;8FAC8F;AAC9F,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,UAAU,eAAe,CAAC,CAAC,CAAC,KAAG,cAAc,CAAC,CAAC,CAC7E,CAAC;AAEtC;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,CAAC,IAAI,SAAS,aAAa,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAClF,SAAS,IAAI,EACb,IAAI,CAAC,GAAG,MAAM,EAAE,GAAG,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,GAAE,KAAK,CAAC,EAChG,UAAU,eAAe,CAAC,CAAC,CAAC,KAC3B,cAAc,CAAC,CAAC,CAA2F,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { ReactiveNode as e, _UNINITIALIZED as t, _withCtx as n } from "./runtime.js";
2
+ import { _SIGNAL_BRAND as r } from "./types.js";
3
+ //#region src/computed.ts
4
+ var i = class extends e {
5
+ [r] = !0;
6
+ #e;
7
+ #t = t;
8
+ #n = !0;
9
+ #r = !1;
10
+ #i;
11
+ #a = /* @__PURE__ */ new Set();
12
+ #o = () => {
13
+ this.#n || (this.#n = !0, this._notify());
14
+ };
15
+ constructor(e, t) {
16
+ super(), this.#e = e, this.#i = t?.equals ?? Object.is, t?.lazy || this.#s();
17
+ }
18
+ #s() {
19
+ for (let e of this.#a) e();
20
+ this.#a.clear();
21
+ let e = n(this.#o, this.#a, null, this.#e);
22
+ this.#n = !1, (this.#t === t || !this.#i(this.#t, e)) && (this.#t = e);
23
+ }
24
+ get value() {
25
+ return this.#r ? this.#t : (this.#n && this.#s(), this._track(), this.#t);
26
+ }
27
+ peek() {
28
+ if (this.#t === t) {
29
+ if (this.#r) return;
30
+ this.#s();
31
+ }
32
+ return this.#t;
33
+ }
34
+ get stale() {
35
+ return this.#n || this.#r;
36
+ }
37
+ dispose() {
38
+ if (!this.#r) {
39
+ this.#r = !0;
40
+ for (let e of this.#a) e();
41
+ this.#a.clear();
42
+ }
43
+ }
44
+ [Symbol.dispose]() {
45
+ this.dispose();
46
+ }
47
+ }, a = (e, t) => new i(e, t), o = class extends i {
48
+ #e;
49
+ constructor(e, t, n) {
50
+ super(e, n), this.#e = t;
51
+ }
52
+ get value() {
53
+ return super.value;
54
+ }
55
+ set value(e) {
56
+ this.#e(e);
57
+ }
58
+ update(e) {
59
+ this.#e(e(this.peek()));
60
+ }
61
+ }, s = (e, t, n) => new o(e, t, n), c = (e, t, n) => a(() => t(...e.map((e) => e.value)), n);
62
+ //#endregion
63
+ export { i as ComputedNode, o as WritableNode, a as computed, c as derived, s as writable };
64
+
65
+ //# sourceMappingURL=computed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed.js","names":["#onDepChange","#dirty","#compute","#equals","#recompute","#deps","#value","#disposed","#set"],"sources":["../src/computed.ts"],"sourcesContent":["import { ReactiveNode, _DEV, _UNINITIALIZED, _withCtx } from './runtime';\nimport {\n type CleanupFn,\n type Disposable,\n type EffectCallback,\n type EqualityFn,\n type ReactiveOptions,\n type ReadonlySignal,\n type Signal,\n _SIGNAL_BRAND,\n} from './types';\n\n/** A derived read-only signal with an explicit dispose method. */\nexport interface ComputedSignal<T> extends ReadonlySignal<T>, Disposable {\n /** True when the computed value is stale (deps changed but not yet re-read) or disposed. */\n readonly stale: boolean;\n}\n\n/** @internal\n * Lazy computed node. Seeded once at construction so .peek() is immediately valid\n * (unless { lazy: true } is passed, in which case first compute defers to first read).\n * - On dep change: marks dirty, notifies downstream (no recompute yet).\n * - On .value read: recomputes if dirty, re-tracks fresh deps.\n * - After dispose: _track() is skipped to prevent leaking outer effects.\n */\nexport class ComputedNode<T> extends ReactiveNode implements ComputedSignal<T> {\n readonly [_SIGNAL_BRAND] = true as const;\n #compute: () => T;\n #value: T | typeof _UNINITIALIZED = _UNINITIALIZED;\n #dirty = true;\n #disposed = false;\n #equals: EqualityFn<T>;\n #deps = new Set<CleanupFn>();\n\n readonly #onDepChange: EffectCallback = () => {\n if (!this.#dirty) {\n this.#dirty = true;\n this._notify();\n }\n };\n\n constructor(compute: () => T, options?: ReactiveOptions<T> & { lazy?: boolean }) {\n super();\n this.#compute = compute;\n this.#equals = options?.equals ?? Object.is;\n\n if (!options?.lazy) this.#recompute(); // seed: ensures peek() is valid immediately\n }\n\n #recompute(): void {\n for (const unsub of this.#deps) unsub();\n this.#deps.clear();\n\n const result = _withCtx(this.#onDepChange, this.#deps, null, this.#compute);\n\n this.#dirty = false;\n\n if (this.#value === _UNINITIALIZED || !this.#equals(this.#value as T, result)) {\n this.#value = result;\n }\n }\n\n get value(): T {\n if (this.#disposed) {\n if (_DEV) console.warn('[stateit] Reading a disposed ComputedSignal returns a stale value.');\n\n return this.#value as T;\n }\n\n if (this.#dirty) this.#recompute();\n\n this._track();\n\n return this.#value as T;\n }\n\n peek(): T {\n if (this.#value === _UNINITIALIZED) {\n if (this.#disposed) {\n if (_DEV)\n console.warn(\n '[stateit] peek() called on a disposed lazy ComputedSignal that was never read — returning undefined.',\n );\n\n return undefined as unknown as T;\n }\n\n this.#recompute();\n }\n\n return this.#value as T;\n }\n\n get stale(): boolean {\n return this.#dirty || this.#disposed;\n }\n\n dispose(): void {\n if (this.#disposed) return;\n\n this.#disposed = true;\n for (const unsub of this.#deps) unsub();\n this.#deps.clear();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n/** Creates a derived read-only Signal whose value is recomputed lazily on `.value` read\n * when dependencies have changed. Call `.dispose()` to stop tracking and free dependencies.\n * Pass `{ lazy: true }` to defer the initial computation until the first `.value` read. */\nexport const computed = <T>(compute: () => T, options?: ReactiveOptions<T> & { lazy?: boolean }): ComputedSignal<T> =>\n new ComputedNode(compute, options);\n\n/** A bidirectional computed Signal with an explicit dispose method. */\nexport interface WritableSignal<T> extends Signal<T>, Disposable {\n /** True when the backing computed value is stale (deps changed but not yet re-read) or disposed. */\n readonly stale: boolean;\n}\n\n/** @internal */\nexport class WritableNode<T> extends ComputedNode<T> implements WritableSignal<T> {\n readonly #set: (v: T) => void;\n\n constructor(get: () => T, set: (v: T) => void, options?: ReactiveOptions<T>) {\n super(get, options);\n this.#set = set;\n }\n\n override get value(): T {\n return super.value;\n }\n\n set value(v: T) {\n this.#set(v);\n }\n\n update(fn: (current: T) => T): void {\n this.#set(fn(this.peek()));\n }\n}\n\n/** Creates a bidirectional computed Signal. Reads track the getter reactively;\n * writes are forwarded to `set`. Call `.dispose()` to stop tracking and free dependencies. */\nexport const writable = <T>(get: () => T, set: (value: T) => void, options?: ReactiveOptions<T>): WritableSignal<T> =>\n new WritableNode(get, set, options);\n\n/**\n * Creates a derived ComputedSignal by combining multiple source signals through a projector\n * function. Each source is passed as a positional argument to `fn`; the projector is\n * re-evaluated whenever any source changes.\n *\n * @example\n * const total = derived([price, quantity, discount], (p, q, d) => p * q * (1 - d));\n */\nexport const derived = <const Srcs extends ReadonlyArray<ReadonlySignal<unknown>>, R>(\n sources: Srcs,\n fn: (...values: { [K in keyof Srcs]: Srcs[K] extends ReadonlySignal<infer V> ? V : never }) => R,\n options?: ReactiveOptions<R>,\n): ComputedSignal<R> => computed(() => (fn as (...args: any[]) => R)(...sources.map((s) => s.value)), options);\n"],"mappings":";;;AAyBA,IAAa,IAAb,cAAqC,EAA0C;CAC7E,CAAU,KAAiB;CAC3B;CACA,KAAoC;CACpC,KAAS;CACT,KAAY;CACZ;CACA,qBAAQ,IAAI,KAAgB;CAE5B,WAA8C;AAC5C,EAAK,MAAA,MACH,MAAA,IAAc,IACd,KAAK,SAAS;;CAIlB,YAAY,GAAkB,GAAmD;AAK/E,EAJA,OAAO,EACP,MAAA,IAAgB,GAChB,MAAA,IAAe,GAAS,UAAU,OAAO,IAEpC,GAAS,QAAM,MAAA,GAAiB;;CAGvC,KAAmB;AACjB,OAAK,IAAM,KAAS,MAAA,EAAY,IAAO;AACvC,QAAA,EAAW,OAAO;EAElB,IAAM,IAAS,EAAS,MAAA,GAAmB,MAAA,GAAY,MAAM,MAAA,EAAc;AAI3E,EAFA,MAAA,IAAc,KAEV,MAAA,MAAgB,KAAkB,CAAC,MAAA,EAAa,MAAA,GAAkB,EAAO,MAC3E,MAAA,IAAc;;CAIlB,IAAI,QAAW;AAWb,SAVI,MAAA,IAGK,MAAA,KAGL,MAAA,KAAa,MAAA,GAAiB,EAElC,KAAK,QAAQ,EAEN,MAAA;;CAGT,OAAU;AACR,MAAI,MAAA,MAAgB,GAAgB;AAClC,OAAI,MAAA,EAMF;AAGF,SAAA,GAAiB;;AAGnB,SAAO,MAAA;;CAGT,IAAI,QAAiB;AACnB,SAAO,MAAA,KAAe,MAAA;;CAGxB,UAAgB;AACV,aAAA,GAEJ;SAAA,IAAiB;AACjB,QAAK,IAAM,KAAS,MAAA,EAAY,IAAO;AACvC,SAAA,EAAW,OAAO;;;CAGpB,CAAC,OAAO,WAAiB;AACvB,OAAK,SAAS;;GAOL,KAAe,GAAkB,MAC5C,IAAI,EAAa,GAAS,EAAQ,EASvB,IAAb,cAAqC,EAA6C;CAChF;CAEA,YAAY,GAAc,GAAqB,GAA8B;AAE3E,EADA,MAAM,GAAK,EAAQ,EACnB,MAAA,IAAY;;CAGd,IAAa,QAAW;AACtB,SAAO,MAAM;;CAGf,IAAI,MAAM,GAAM;AACd,QAAA,EAAU,EAAE;;CAGd,OAAO,GAA6B;AAClC,QAAA,EAAU,EAAG,KAAK,MAAM,CAAC,CAAC;;GAMjB,KAAe,GAAc,GAAyB,MACjE,IAAI,EAAa,GAAK,GAAK,EAAQ,EAUxB,KACX,GACA,GACA,MACsB,QAAgB,EAA6B,GAAG,EAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,EAAE,EAAQ"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./runtime.cjs`);var t=(t,n)=>{let r,i=new Set,a=!1,o=!1,s=!1,c=n?.maxIterations??e._maxEffectIterations,l=()=>{r?.(),r=void 0;for(let e of i)e();i.clear()},u=()=>{o=!1,l();let a=[],c,u=!1;try{e._withCtx(d,i,a,()=>{let e=t();typeof e==`function`&&a.push(e)})}catch(e){if(n?.onError)u=!0,c=e;else throw e}return r=a.length>0?()=>{for(let e of a)e()}:void 0,u&&(l(),n.onError(c),s=!0),u},d=()=>{if(s||a){a&&(o=!0);return}a=!0;try{let e=0;do{if(++e>c)throw Error(`[stateit] effect: possible infinite reactive loop (> ${c} iterations)`);if(u())break}while(o)}finally{a=!1}};d();let f=()=>{s||(s=!0,l())};return Object.assign(f,{dispose:f,[Symbol.dispose]:f})},n=t=>e._withCtx(null,null,null,t),r=t=>{e.scope.cleanups?.push(t)};exports.effect=t,exports.onCleanup=r,exports.untrack=n;
2
+ //# sourceMappingURL=effect.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect.cjs","names":[],"sources":["../src/effect.ts"],"sourcesContent":["import { _DEV, _maxEffectIterations, _withCtx, scope } from './runtime';\nimport { type CleanupFn, type EffectCallback, type Subscription } from './types';\n\n/** Options for the `effect()` primitive. */\nexport type EffectOptions = {\n /**\n * Maximum re-entrant iterations before throwing to prevent infinite loops.\n * Overrides the global value set via `configureStateit` for this specific effect.\n */\n maxIterations?: number;\n /**\n * Called when the effect function throws. When provided, the effect is automatically\n * disposed after the first error — it won't re-run on subsequent dependency changes.\n */\n onError?: (error: unknown) => void;\n};\n\n/** Runs fn immediately and re-runs it whenever any Signal read inside it changes. Returns a dispose handle. */\nexport const effect = (fn: EffectCallback, options?: EffectOptions): Subscription => {\n let cleanup: CleanupFn | undefined;\n const deps = new Set<CleanupFn>();\n let running = false;\n let dirty = false;\n let disposed = false;\n const maxIter = options?.maxIterations ?? _maxEffectIterations;\n\n /** Tears down the current run: calls cleanup and unsubscribes all deps. */\n const teardown = (): void => {\n cleanup?.();\n cleanup = undefined;\n for (const unsub of deps) unsub();\n deps.clear();\n };\n\n /** Runs a single iteration: tears down previous deps/cleanup, re-executes fn, registers fresh deps.\n * Returns true if onError handled a throw (caller should break the loop). */\n const runIteration = (): boolean => {\n dirty = false;\n teardown();\n\n const cleanups: CleanupFn[] = [];\n let thrownError: unknown;\n let threw = false;\n\n try {\n _withCtx(runner, deps, cleanups, () => {\n const result = fn();\n\n if (typeof result === 'function') cleanups.push(result);\n });\n } catch (e) {\n if (options?.onError) {\n threw = true;\n thrownError = e;\n } else throw e;\n }\n\n cleanup =\n cleanups.length > 0\n ? () => {\n for (const c of cleanups) c();\n }\n : undefined;\n\n if (threw) {\n teardown(); // flush onCleanup registrations from the throwing run\n options!.onError!(thrownError);\n disposed = true;\n }\n\n return threw;\n };\n\n const runner: EffectCallback = () => {\n if (disposed || running) {\n if (running) dirty = true;\n\n return;\n }\n\n running = true;\n\n try {\n let iterations = 0;\n\n do {\n if (++iterations > maxIter)\n throw new Error(`[stateit] effect: possible infinite reactive loop (> ${maxIter} iterations)`);\n\n if (runIteration()) break;\n } while (dirty);\n } finally {\n running = false;\n }\n };\n\n runner();\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n teardown();\n };\n\n return Object.assign(dispose, { dispose, [Symbol.dispose]: dispose }) as Subscription;\n};\n\n/** Runs fn without registering any reactive dependencies. */\nexport const untrack = <T>(fn: () => T): T => _withCtx(null, null, null, fn);\n\n/**\n * Registers a cleanup function within the currently running effect.\n * Called before the effect re-runs and on final dispose.\n * Allows nested helpers to register teardown without needing the effect's return value.\n *\n * @example\n * effect(() => {\n * const id = setInterval(() => { ... }, 1000);\n * onCleanup(() => clearInterval(id));\n * });\n */\nexport const onCleanup = (fn: CleanupFn): void => {\n if (_DEV && scope.cleanups === null) {\n console.warn('[stateit] onCleanup() called outside of an active effect — the cleanup will never run.');\n }\n\n scope.cleanups?.push(fn);\n};\n"],"mappings":"iCAkBA,IAAa,GAAU,EAAoB,IAA0C,CACnF,IAAI,EACE,EAAO,IAAI,IACb,EAAU,GACV,EAAQ,GACR,EAAW,GACT,EAAU,GAAS,eAAiB,EAAA,qBAGpC,MAAuB,CAC3B,KAAW,CACX,EAAU,IAAA,GACV,IAAK,IAAM,KAAS,EAAM,GAAO,CACjC,EAAK,OAAO,EAKR,MAA8B,CAClC,EAAQ,GACR,GAAU,CAEV,IAAM,EAAwB,EAAE,CAC5B,EACA,EAAQ,GAEZ,GAAI,CACF,EAAA,SAAS,EAAQ,EAAM,MAAgB,CACrC,IAAM,EAAS,GAAI,CAEf,OAAO,GAAW,YAAY,EAAS,KAAK,EAAO,EACvD,OACK,EAAG,CACV,GAAI,GAAS,QACX,EAAQ,GACR,EAAc,OACT,MAAM,EAgBf,MAbA,GACE,EAAS,OAAS,MACR,CACJ,IAAK,IAAM,KAAK,EAAU,GAAG,EAE/B,IAAA,GAEF,IACF,GAAU,CACV,EAAS,QAAS,EAAY,CAC9B,EAAW,IAGN,GAGH,MAA+B,CACnC,GAAI,GAAY,EAAS,CACnB,IAAS,EAAQ,IAErB,OAGF,EAAU,GAEV,GAAI,CACF,IAAI,EAAa,EAEjB,EAAG,CACD,GAAI,EAAE,EAAa,EACjB,MAAU,MAAM,wDAAwD,EAAQ,cAAc,CAEhG,GAAI,GAAc,CAAE,YACb,UACD,CACR,EAAU,KAId,GAAQ,CAER,IAAM,MAAsB,CACtB,IAEJ,EAAW,GACX,GAAU,GAGZ,OAAO,OAAO,OAAO,EAAS,CAAE,WAAU,OAAO,SAAU,EAAS,CAAC,EAI1D,EAAc,GAAmB,EAAA,SAAS,KAAM,KAAM,KAAM,EAAG,CAa/D,EAAa,GAAwB,CAKhD,EAAA,MAAM,UAAU,KAAK,EAAG"}
@@ -0,0 +1,31 @@
1
+ import { type CleanupFn, type EffectCallback, type Subscription } from './types';
2
+ /** Options for the `effect()` primitive. */
3
+ export type EffectOptions = {
4
+ /**
5
+ * Maximum re-entrant iterations before throwing to prevent infinite loops.
6
+ * Overrides the global value set via `configureStateit` for this specific effect.
7
+ */
8
+ maxIterations?: number;
9
+ /**
10
+ * Called when the effect function throws. When provided, the effect is automatically
11
+ * disposed after the first error — it won't re-run on subsequent dependency changes.
12
+ */
13
+ onError?: (error: unknown) => void;
14
+ };
15
+ /** Runs fn immediately and re-runs it whenever any Signal read inside it changes. Returns a dispose handle. */
16
+ export declare const effect: (fn: EffectCallback, options?: EffectOptions) => Subscription;
17
+ /** Runs fn without registering any reactive dependencies. */
18
+ export declare const untrack: <T>(fn: () => T) => T;
19
+ /**
20
+ * Registers a cleanup function within the currently running effect.
21
+ * Called before the effect re-runs and on final dispose.
22
+ * Allows nested helpers to register teardown without needing the effect's return value.
23
+ *
24
+ * @example
25
+ * effect(() => {
26
+ * const id = setInterval(() => { ... }, 1000);
27
+ * onCleanup(() => clearInterval(id));
28
+ * });
29
+ */
30
+ export declare const onCleanup: (fn: CleanupFn) => void;
31
+ //# sourceMappingURL=effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEjF,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG;IAC1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,+GAA+G;AAC/G,eAAO,MAAM,MAAM,GAAI,IAAI,cAAc,EAAE,UAAU,aAAa,KAAG,YAwFpE,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,IAAI,MAAM,CAAC,KAAG,CAAmC,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GAAI,IAAI,SAAS,KAAG,IAMzC,CAAC"}