@vielzeug/ripple 1.2.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 (123) hide show
  1. package/README.md +101 -0
  2. package/dist/_dev.cjs +2 -0
  3. package/dist/_dev.cjs.map +1 -0
  4. package/dist/_dev.d.ts +2 -0
  5. package/dist/_dev.d.ts.map +1 -0
  6. package/dist/_dev.js +9 -0
  7. package/dist/_dev.js.map +1 -0
  8. package/dist/_error-utils.cjs +2 -0
  9. package/dist/_error-utils.cjs.map +1 -0
  10. package/dist/_error-utils.d.ts +24 -0
  11. package/dist/_error-utils.d.ts.map +1 -0
  12. package/dist/_error-utils.js +20 -0
  13. package/dist/_error-utils.js.map +1 -0
  14. package/dist/_virtual/_rolldown/runtime.js +7 -0
  15. package/dist/async-computed.cjs +2 -0
  16. package/dist/async-computed.cjs.map +1 -0
  17. package/dist/async-computed.d.ts +36 -0
  18. package/dist/async-computed.d.ts.map +1 -0
  19. package/dist/async-computed.js +58 -0
  20. package/dist/async-computed.js.map +1 -0
  21. package/dist/computed.cjs +2 -0
  22. package/dist/computed.cjs.map +1 -0
  23. package/dist/computed.d.ts +26 -0
  24. package/dist/computed.d.ts.map +1 -0
  25. package/dist/computed.js +113 -0
  26. package/dist/computed.js.map +1 -0
  27. package/dist/devtools-hook.cjs +2 -0
  28. package/dist/devtools-hook.cjs.map +1 -0
  29. package/dist/devtools-hook.d.ts +4 -0
  30. package/dist/devtools-hook.d.ts.map +1 -0
  31. package/dist/devtools-hook.js +8 -0
  32. package/dist/devtools-hook.js.map +1 -0
  33. package/dist/devtools.d.ts +39 -0
  34. package/dist/devtools.d.ts.map +1 -0
  35. package/dist/effect.cjs +2 -0
  36. package/dist/effect.cjs.map +1 -0
  37. package/dist/effect.d.ts +104 -0
  38. package/dist/effect.d.ts.map +1 -0
  39. package/dist/effect.js +173 -0
  40. package/dist/effect.js.map +1 -0
  41. package/dist/errors.cjs +2 -0
  42. package/dist/errors.cjs.map +1 -0
  43. package/dist/errors.d.ts +24 -0
  44. package/dist/errors.d.ts.map +1 -0
  45. package/dist/errors.js +13 -0
  46. package/dist/errors.js.map +1 -0
  47. package/dist/index.cjs +1 -0
  48. package/dist/index.d.ts +12 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +12 -0
  51. package/dist/reactive-base.cjs +2 -0
  52. package/dist/reactive-base.cjs.map +1 -0
  53. package/dist/reactive-base.d.ts +60 -0
  54. package/dist/reactive-base.d.ts.map +1 -0
  55. package/dist/reactive-base.js +56 -0
  56. package/dist/reactive-base.js.map +1 -0
  57. package/dist/ripple.cjs +2 -0
  58. package/dist/ripple.cjs.map +1 -0
  59. package/dist/ripple.iife.js +2 -0
  60. package/dist/ripple.iife.js.map +1 -0
  61. package/dist/ripple.js +2 -0
  62. package/dist/ripple.js.map +1 -0
  63. package/dist/scheduling.cjs +2 -0
  64. package/dist/scheduling.cjs.map +1 -0
  65. package/dist/scheduling.d.ts +5 -0
  66. package/dist/scheduling.d.ts.map +1 -0
  67. package/dist/scheduling.js +57 -0
  68. package/dist/scheduling.js.map +1 -0
  69. package/dist/signal.cjs +2 -0
  70. package/dist/signal.cjs.map +1 -0
  71. package/dist/signal.d.ts +24 -0
  72. package/dist/signal.d.ts.map +1 -0
  73. package/dist/signal.js +54 -0
  74. package/dist/signal.js.map +1 -0
  75. package/dist/src/devtools.cjs +2 -0
  76. package/dist/src/devtools.cjs.map +1 -0
  77. package/dist/src/devtools.js +36 -0
  78. package/dist/src/devtools.js.map +1 -0
  79. package/dist/src/ssr/index.cjs +2 -0
  80. package/dist/src/ssr/index.cjs.map +1 -0
  81. package/dist/src/ssr/index.js +38 -0
  82. package/dist/src/ssr/index.js.map +1 -0
  83. package/dist/ssr/index.d.ts +56 -0
  84. package/dist/ssr/index.d.ts.map +1 -0
  85. package/dist/store-history.cjs +2 -0
  86. package/dist/store-history.cjs.map +1 -0
  87. package/dist/store-history.d.ts +42 -0
  88. package/dist/store-history.d.ts.map +1 -0
  89. package/dist/store-history.js +60 -0
  90. package/dist/store-history.js.map +1 -0
  91. package/dist/store.cjs +2 -0
  92. package/dist/store.cjs.map +1 -0
  93. package/dist/store.d.ts +45 -0
  94. package/dist/store.d.ts.map +1 -0
  95. package/dist/store.js +204 -0
  96. package/dist/store.js.map +1 -0
  97. package/dist/subscription.cjs +2 -0
  98. package/dist/subscription.cjs.map +1 -0
  99. package/dist/subscription.d.ts +22 -0
  100. package/dist/subscription.d.ts.map +1 -0
  101. package/dist/subscription.js +47 -0
  102. package/dist/subscription.js.map +1 -0
  103. package/dist/symbols.cjs +2 -0
  104. package/dist/symbols.cjs.map +1 -0
  105. package/dist/symbols.d.ts +6 -0
  106. package/dist/symbols.d.ts.map +1 -0
  107. package/dist/symbols.js +6 -0
  108. package/dist/symbols.js.map +1 -0
  109. package/dist/tracking.cjs +2 -0
  110. package/dist/tracking.cjs.map +1 -0
  111. package/dist/tracking.d.ts +82 -0
  112. package/dist/tracking.d.ts.map +1 -0
  113. package/dist/tracking.js +76 -0
  114. package/dist/tracking.js.map +1 -0
  115. package/dist/types.d.ts +315 -0
  116. package/dist/types.d.ts.map +1 -0
  117. package/dist/utilities.cjs +2 -0
  118. package/dist/utilities.cjs.map +1 -0
  119. package/dist/utilities.d.ts +55 -0
  120. package/dist/utilities.d.ts.map +1 -0
  121. package/dist/utilities.js +25 -0
  122. package/dist/utilities.js.map +1 -0
  123. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @vielzeug/ripple
2
+
3
+ > Tiny, type-safe reactive primitives — signals, effects, computed values, and object stores. Zero dependencies, works everywhere.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@vielzeug/ripple)](https://www.npmjs.com/package/@vielzeug/ripple) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ <details>
8
+ <summary>Quick Reference</summary>
9
+
10
+ **Package:** `@vielzeug/ripple` &nbsp;·&nbsp; **Category:** State
11
+
12
+ **Key exports:** `signal`, `computed`, `effect`, `effectAsync`, `resource`, `watch`, `batch`, `store`, `storeWithHistory`, `untrack`, `scope`, `onCleanup`, `readonly`, `isSignal`, `isComputed`, `isStore`
13
+
14
+ **When to use:** Fine-grained reactivity without a framework. Powers Ore templates. Works in any TS/JS environment including Node, Deno, and SSR.
15
+
16
+ **Related:** [@vielzeug/ore](https://vielzeug.dev/ore/) · [@vielzeug/forge](https://vielzeug.dev/forge/) · [@vielzeug/herald](https://vielzeug.dev/herald/)
17
+
18
+ </details>
19
+
20
+ `@vielzeug/ripple` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output.
21
+
22
+ ## Installation
23
+
24
+ ```sh
25
+ # pnpm
26
+ pnpm add @vielzeug/ripple
27
+ # npm
28
+ npm install @vielzeug/ripple
29
+ # yarn
30
+ yarn add @vielzeug/ripple
31
+ ```
32
+
33
+ ## Sub-paths
34
+
35
+ | Import | Purpose |
36
+ | --------------------------- | ------------------------------------------------------------------------ |
37
+ | `@vielzeug/ripple` | Core primitives and types |
38
+ | `@vielzeug/ripple/devtools` | `installDevTools`, `debugEffect` — dev-only, tree-shaken from production |
39
+ | `@vielzeug/ripple/ssr` | SSR tracking isolation helpers (`setTrackingProvider`, `createAsyncProvider`, `withProvider`, `runWithProvider`). Node.js only — do not import in browser builds. |
40
+
41
+ ## Quick Start
42
+
43
+ ```ts
44
+ import { signal, computed, effect, store, watch, batch } from '@vielzeug/ripple';
45
+
46
+ // Signals
47
+ const count = signal(0);
48
+ const doubled = computed(() => count.value * 2); // Computed<number>
49
+
50
+ const sub = effect(() => {
51
+ console.log('doubled:', doubled.value); // re-runs when count changes
52
+ });
53
+
54
+ count.value = 5; // → logs "doubled: 10"
55
+ sub.dispose();
56
+ doubled.dispose();
57
+
58
+ // Stores with typed lenses
59
+ const cart = store({ items: 0, label: 'empty' });
60
+ const items = cart.lens('items'); // Signal<number> — cached, path-scoped
61
+
62
+ items.value = 3;
63
+ console.log(cart.value); // { items: 3, label: 'empty' }
64
+
65
+ cart.replace((s) => ({ ...s, label: 'checkout' })); // replace entire state via fn
66
+ cart.reset();
67
+
68
+ // Effect options — scheduler, name
69
+ const stop = effect(() => console.log('items:', items.value), { scheduler: 'microtask', name: 'cart-logger' });
70
+ stop.dispose();
71
+
72
+ // Async resource — tracks deps and re-runs on change
73
+ import { resource } from '@vielzeug/ripple';
74
+
75
+ const userId = signal('u1');
76
+ const userState = resource(async (signal) => {
77
+ const id = userId.value; // tracked dep
78
+ return fetch(`/users/${id}`, { signal }).then((r) => r.json());
79
+ });
80
+ // userState.value → { status: 'loading' | 'ready' | 'error', data, error }
81
+
82
+ // Store with undo/redo history
83
+ import { storeWithHistory } from '@vielzeug/ripple';
84
+
85
+ const editor = storeWithHistory({ text: '' }, { maxHistory: 50 });
86
+ editor.patch({ text: 'hello' });
87
+ editor.patch({ text: 'hello world' });
88
+ editor.undo(); // back to 'hello'
89
+ editor.redo(); // forward to 'hello world'
90
+ ```
91
+
92
+ ## Documentation
93
+
94
+ - [Overview](https://vielzeug.dev/ripple/)
95
+ - [Usage Guide](https://vielzeug.dev/ripple/usage)
96
+ - [API Reference](https://vielzeug.dev/ripple/api)
97
+ - [Examples](https://vielzeug.dev/ripple/examples)
98
+
99
+ ## License
100
+
101
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
package/dist/_dev.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var e=!globalThis.__RIPPLE_PROD__;function t(t){e&&console.warn(`[@vielzeug/ripple] ${t}`)}exports.warn=t;
2
+ //# sourceMappingURL=_dev.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __RIPPLE_PROD__?: boolean }).__RIPPLE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ripple] ${msg}`);\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA6C,gBAG7D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,sBAAsB,GAAK,CACrD"}
package/dist/_dev.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
package/dist/_dev.js ADDED
@@ -0,0 +1,9 @@
1
+ //#region src/_dev.ts
2
+ var e = !globalThis.__RIPPLE_PROD__;
3
+ function t(t) {
4
+ e && console.warn(`[@vielzeug/ripple] ${t}`);
5
+ }
6
+ //#endregion
7
+ export { t as warn };
8
+
9
+ //# sourceMappingURL=_dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __RIPPLE_PROD__?: boolean }).__RIPPLE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ripple] ${msg}`);\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA6C;AAG7D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,sBAAsB,GAAK;AACrD"}
@@ -0,0 +1,2 @@
1
+ var e=e=>e instanceof Error?e:Error(`Non-Error thrown: ${String(e)}`),t=t=>{let n=[];for(let r of t)try{r()}catch(t){n.push(e(t))}return n},n=(e,n)=>{let r=t(e);if(r.length===1)throw r[0];if(r.length>0)throw AggregateError(r,n)},r=(t,n,r)=>{throw n.length===0?t:AggregateError([e(t),...n],r,{cause:e(t)})};exports.collectErrors=t,exports.ensureError=e,exports.rethrowWith=r,exports.runAll=n;
2
+ //# sourceMappingURL=_error-utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_error-utils.cjs","names":[],"sources":["../src/_error-utils.ts"],"sourcesContent":["import type { CleanupFn } from './types';\n\n/**\n * Coerces any thrown value to an `Error`.\n * Non-Error throws (strings, numbers, etc.) are wrapped in a plain `Error`\n * with a descriptive message — they are NOT mis-branded as `RippleError`.\n */\nexport const ensureError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(`Non-Error thrown: ${String(error)}`);\n\n/**\n * Runs each fn in `fns`, collecting any thrown errors. Does NOT re-throw.\n */\nexport const collectErrors = (fns: Iterable<CleanupFn>): Error[] => {\n const errors: Error[] = [];\n\n for (const fn of fns) {\n try {\n fn();\n } catch (e) {\n errors.push(ensureError(e));\n }\n }\n\n return errors;\n};\n\n/**\n * Runs all `fns`, collecting errors.\n * - Single error: re-throws the original error directly (preserves type + stack).\n * - Multiple errors: wraps in AggregateError with the given context label.\n */\nexport const runAll = (fns: Iterable<CleanupFn>, context: string): void => {\n const errors = collectErrors(fns);\n\n if (errors.length === 1) throw errors[0]!;\n\n if (errors.length > 0) {\n throw new AggregateError(errors, context);\n }\n};\n\n/**\n * Re-throws `primary`, augmented with any extra errors as an AggregateError.\n * If `extras` is empty, throws `primary` directly (preserves original type + stack).\n * Otherwise, `primary` is set as the `.cause` so callers can identify the root failure.\n */\nexport const rethrowWith = (primary: unknown, extras: Error[], context: string): never => {\n if (extras.length === 0) throw primary;\n\n throw new AggregateError([ensureError(primary), ...extras], context, {\n cause: ensureError(primary),\n });\n};\n"],"mappings":"AAOA,IAAa,EAAe,GAC1B,aAAiB,MAAQ,EAAY,MAAM,qBAAqB,OAAO,CAAK,GAAG,EAKpE,EAAiB,GAAsC,CAClE,IAAM,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAM,EACf,GAAI,CACF,EAAG,CACL,OAAS,EAAG,CACV,EAAO,KAAK,EAAY,CAAC,CAAC,CAC5B,CAGF,OAAO,CACT,EAOa,GAAU,EAA0B,IAA0B,CACzE,IAAM,EAAS,EAAc,CAAG,EAEhC,GAAI,EAAO,SAAW,EAAG,MAAM,EAAO,GAEtC,GAAI,EAAO,OAAS,EAClB,MAAU,eAAe,EAAQ,CAAO,CAE5C,EAOa,GAAe,EAAkB,EAAiB,IAA2B,CAGxF,MAFI,EAAO,SAAW,EAAS,EAErB,eAAe,CAAC,EAAY,CAAO,EAAG,GAAG,CAAM,EAAG,EAAS,CACnE,MAAO,EAAY,CAAO,CAC5B,CAAC,CACH"}
@@ -0,0 +1,24 @@
1
+ import type { CleanupFn } from './types';
2
+ /**
3
+ * Coerces any thrown value to an `Error`.
4
+ * Non-Error throws (strings, numbers, etc.) are wrapped in a plain `Error`
5
+ * with a descriptive message — they are NOT mis-branded as `RippleError`.
6
+ */
7
+ export declare const ensureError: (error: unknown) => Error;
8
+ /**
9
+ * Runs each fn in `fns`, collecting any thrown errors. Does NOT re-throw.
10
+ */
11
+ export declare const collectErrors: (fns: Iterable<CleanupFn>) => Error[];
12
+ /**
13
+ * Runs all `fns`, collecting errors.
14
+ * - Single error: re-throws the original error directly (preserves type + stack).
15
+ * - Multiple errors: wraps in AggregateError with the given context label.
16
+ */
17
+ export declare const runAll: (fns: Iterable<CleanupFn>, context: string) => void;
18
+ /**
19
+ * Re-throws `primary`, augmented with any extra errors as an AggregateError.
20
+ * If `extras` is empty, throws `primary` directly (preserves original type + stack).
21
+ * Otherwise, `primary` is set as the `.cause` so callers can identify the root failure.
22
+ */
23
+ export declare const rethrowWith: (primary: unknown, extras: Error[], context: string) => never;
24
+ //# sourceMappingURL=_error-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_error-utils.d.ts","sourceRoot":"","sources":["../src/_error-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KACqC,CAAC;AAEnF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,QAAQ,CAAC,SAAS,CAAC,KAAG,KAAK,EAY7D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,KAAK,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,KAAG,IAQlE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,OAAO,EAAE,QAAQ,KAAK,EAAE,EAAE,SAAS,MAAM,KAAG,KAMhF,CAAC"}
@@ -0,0 +1,20 @@
1
+ //#region src/_error-utils.ts
2
+ var e = (e) => e instanceof Error ? e : /* @__PURE__ */ Error(`Non-Error thrown: ${String(e)}`), t = (t) => {
3
+ let n = [];
4
+ for (let r of t) try {
5
+ r();
6
+ } catch (t) {
7
+ n.push(e(t));
8
+ }
9
+ return n;
10
+ }, n = (e, n) => {
11
+ let r = t(e);
12
+ if (r.length === 1) throw r[0];
13
+ if (r.length > 0) throw AggregateError(r, n);
14
+ }, r = (t, n, r) => {
15
+ throw n.length === 0 ? t : AggregateError([e(t), ...n], r, { cause: e(t) });
16
+ };
17
+ //#endregion
18
+ export { t as collectErrors, e as ensureError, r as rethrowWith, n as runAll };
19
+
20
+ //# sourceMappingURL=_error-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_error-utils.js","names":[],"sources":["../src/_error-utils.ts"],"sourcesContent":["import type { CleanupFn } from './types';\n\n/**\n * Coerces any thrown value to an `Error`.\n * Non-Error throws (strings, numbers, etc.) are wrapped in a plain `Error`\n * with a descriptive message — they are NOT mis-branded as `RippleError`.\n */\nexport const ensureError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(`Non-Error thrown: ${String(error)}`);\n\n/**\n * Runs each fn in `fns`, collecting any thrown errors. Does NOT re-throw.\n */\nexport const collectErrors = (fns: Iterable<CleanupFn>): Error[] => {\n const errors: Error[] = [];\n\n for (const fn of fns) {\n try {\n fn();\n } catch (e) {\n errors.push(ensureError(e));\n }\n }\n\n return errors;\n};\n\n/**\n * Runs all `fns`, collecting errors.\n * - Single error: re-throws the original error directly (preserves type + stack).\n * - Multiple errors: wraps in AggregateError with the given context label.\n */\nexport const runAll = (fns: Iterable<CleanupFn>, context: string): void => {\n const errors = collectErrors(fns);\n\n if (errors.length === 1) throw errors[0]!;\n\n if (errors.length > 0) {\n throw new AggregateError(errors, context);\n }\n};\n\n/**\n * Re-throws `primary`, augmented with any extra errors as an AggregateError.\n * If `extras` is empty, throws `primary` directly (preserves original type + stack).\n * Otherwise, `primary` is set as the `.cause` so callers can identify the root failure.\n */\nexport const rethrowWith = (primary: unknown, extras: Error[], context: string): never => {\n if (extras.length === 0) throw primary;\n\n throw new AggregateError([ensureError(primary), ...extras], context, {\n cause: ensureError(primary),\n });\n};\n"],"mappings":";AAOA,IAAa,KAAe,MAC1B,aAAiB,QAAQ,IAAQ,gBAAI,MAAM,qBAAqB,OAAO,CAAK,GAAG,GAKpE,KAAiB,MAAsC;CAClE,IAAM,IAAkB,CAAC;CAEzB,KAAK,IAAM,KAAM,GACf,IAAI;EACF,EAAG;CACL,SAAS,GAAG;EACV,EAAO,KAAK,EAAY,CAAC,CAAC;CAC5B;CAGF,OAAO;AACT,GAOa,KAAU,GAA0B,MAA0B;CACzE,IAAM,IAAS,EAAc,CAAG;CAEhC,IAAI,EAAO,WAAW,GAAG,MAAM,EAAO;CAEtC,IAAI,EAAO,SAAS,GAClB,MAAU,eAAe,GAAQ,CAAO;AAE5C,GAOa,KAAe,GAAkB,GAAiB,MAA2B;CAGxF,MAFI,EAAO,WAAW,IAAS,IAErB,eAAe,CAAC,EAAY,CAAO,GAAG,GAAG,CAAM,GAAG,GAAS,EACnE,OAAO,EAAY,CAAO,EAC5B,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ //#region \0rolldown/runtime.js
2
+ var e = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
3
+ if (typeof require < "u") return require.apply(this, arguments);
4
+ throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
5
+ });
6
+ //#endregion
7
+ export { e as __require };
@@ -0,0 +1,2 @@
1
+ const e=require("./tracking.cjs"),t=require("./symbols.cjs"),n=require("./effect.cjs"),r=require("./signal.cjs");var i=(i,a)=>{let o=a?.name,s=r.signal({data:a?.initialValue,status:`loading`},o?{name:o}:void 0),c=r.signal(0),l=null,u=!1,d=n.effect(()=>{c.value,l?.abort(),l=new AbortController;let{signal:e}=l,t=s.peek(),n=`data`in t?t.data:void 0;s.value={data:n,status:`loading`},(async()=>{try{let t=await i(e);!e.aborted&&!u&&(s.value={data:t,status:`ready`})}catch(t){!e.aborted&&!u&&(s.value={data:n,error:t,status:`error`})}})()},o?{name:o}:void 0),f=()=>{u||(c.value=c.peek()+1)},p=()=>{u||(u=!0,l?.abort(),d.dispose(),s.dispose(),c.dispose())};return e.autoRegisterDisposal(p),{dispose:p,get disposed(){return u},[t.IS_COMPUTED]:!0,[t.IS_SIGNAL]:!0,get name(){return s.name},peek:()=>s.peek(),refresh:f,subscribe:e=>s.subscribe(e),[Symbol.dispose]:p,get value(){return s.value}}};exports.resource=i;
2
+ //# sourceMappingURL=async-computed.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-computed.cjs","names":[],"sources":["../src/async-computed.ts"],"sourcesContent":["import type { Resource, ResourceOptions, ResourceState } from './types';\n\nimport { effect } from './effect';\nimport { signal } from './signal';\nimport { IS_COMPUTED, IS_SIGNAL } from './symbols';\nimport { autoRegisterDisposal } from './tracking';\n\n/**\n * Creates a reactive async resource that emits a `ResourceState<T>` discriminated union.\n *\n * Dependencies are tracked synchronously in the factory (before the first `await`).\n * The factory receives an `AbortSignal` that fires when dependencies change or\n * the resource is disposed, cancelling any in-flight work.\n *\n * If `resource()` is created inside an active `effect()` or `scope.run()` context,\n * it is automatically registered for cleanup and disposed with that context —\n * matching `computed()`'s auto-disposal behavior.\n *\n * Call `refresh()` to force the factory to re-run immediately, even if no tracked\n * dependency changed — useful for manual \"retry\"/\"refetch\" actions.\n *\n * @example\n * ```ts\n * const userId = signal('u1');\n * const user = resource(async (signal) => {\n * const id = userId.value; // tracked dep — re-runs when userId changes\n * return fetchUser(id, { signal });\n * });\n *\n * effect(() => {\n * const s = user.value;\n * if (s.status === 'loading') return showSpinner();\n * if (s.status === 'error') return showError(s.error);\n * renderUser(s.data);\n * });\n *\n * user.refresh(); // force a re-fetch without changing userId\n * user.dispose();\n * ```\n */\nexport const resource = <T>(\n factory: (abortSignal: AbortSignal) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> => {\n const name = options?.name;\n const state = signal<ResourceState<T>>(\n { data: options?.initialValue, status: 'loading' },\n name ? { name } : undefined,\n );\n // Internal-only trigger — never exposed. Read (but never written to a meaningful\n // value) inside the effect purely so it becomes a tracked dep; refresh() bumps it\n // to force a re-run without requiring a real dependency to change.\n const epoch = signal(0);\n\n let controller: AbortController | null = null;\n let disposed = false;\n\n const stop = effect(\n () => {\n void epoch.value;\n\n controller?.abort();\n controller = new AbortController();\n\n const { signal: abortSignal } = controller;\n\n const current = state.peek();\n const prevData = 'data' in current ? current.data : undefined;\n\n state.value = { data: prevData, status: 'loading' };\n\n void (async () => {\n try {\n const result = await factory(abortSignal);\n\n if (!abortSignal.aborted && !disposed) {\n state.value = { data: result, status: 'ready' };\n }\n } catch (err) {\n if (!abortSignal.aborted && !disposed) {\n state.value = { data: prevData, error: err, status: 'error' };\n }\n }\n })();\n },\n name ? { name } : undefined,\n );\n\n const refresh = (): void => {\n if (disposed) return;\n\n epoch.value = epoch.peek() + 1;\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n controller?.abort();\n stop.dispose();\n state.dispose();\n epoch.dispose();\n };\n\n autoRegisterDisposal(dispose);\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n [IS_COMPUTED]: true as const,\n [IS_SIGNAL]: true as const,\n get name() {\n return state.name;\n },\n peek: () => state.peek(),\n refresh,\n subscribe: (listener: () => void) => state.subscribe(listener),\n [Symbol.dispose]: dispose,\n get value() {\n return state.value;\n },\n } as Resource<T>;\n};\n"],"mappings":"iHAwCA,IAAa,GACX,EACA,IACgB,CAChB,IAAM,EAAO,GAAS,KAChB,EAAQ,EAAA,OACZ,CAAE,KAAM,GAAS,aAAc,OAAQ,SAAU,EACjD,EAAO,CAAE,MAAK,EAAI,IAAA,EACpB,EAIM,EAAQ,EAAA,OAAO,CAAC,EAElB,EAAqC,KACrC,EAAW,GAET,EAAO,EAAA,WACL,CACJ,EAAW,MAEX,GAAY,MAAM,EAClB,EAAa,IAAI,gBAEjB,GAAM,CAAE,OAAQ,GAAgB,EAE1B,EAAU,EAAM,KAAK,EACrB,EAAW,SAAU,EAAU,EAAQ,KAAO,IAAA,GAEpD,EAAM,MAAQ,CAAE,KAAM,EAAU,OAAQ,SAAU,GAE5C,SAAY,CAChB,GAAI,CACF,IAAM,EAAS,MAAM,EAAQ,CAAW,EAEpC,CAAC,EAAY,SAAW,CAAC,IAC3B,EAAM,MAAQ,CAAE,KAAM,EAAQ,OAAQ,OAAQ,EAElD,OAAS,EAAK,CACR,CAAC,EAAY,SAAW,CAAC,IAC3B,EAAM,MAAQ,CAAE,KAAM,EAAU,MAAO,EAAK,OAAQ,OAAQ,EAEhE,CACF,EAAA,CAAG,CACL,EACA,EAAO,CAAE,MAAK,EAAI,IAAA,EACpB,EAEM,MAAsB,CACtB,IAEJ,EAAM,MAAQ,EAAM,KAAK,EAAI,EAC/B,EAEM,MAAsB,CACtB,IAEJ,EAAW,GACX,GAAY,MAAM,EAClB,EAAK,QAAQ,EACb,EAAM,QAAQ,EACd,EAAM,QAAQ,EAChB,EAIA,OAFA,EAAA,qBAAqB,CAAO,EAErB,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,GACC,EAAA,aAAc,IACd,EAAA,WAAY,GACb,IAAI,MAAO,CACT,OAAO,EAAM,IACf,EACA,SAAY,EAAM,KAAK,EACvB,UACA,UAAY,GAAyB,EAAM,UAAU,CAAQ,GAC5D,OAAO,SAAU,EAClB,IAAI,OAAQ,CACV,OAAO,EAAM,KACf,CACF,CACF"}
@@ -0,0 +1,36 @@
1
+ import type { Resource, ResourceOptions } from './types';
2
+ /**
3
+ * Creates a reactive async resource that emits a `ResourceState<T>` discriminated union.
4
+ *
5
+ * Dependencies are tracked synchronously in the factory (before the first `await`).
6
+ * The factory receives an `AbortSignal` that fires when dependencies change or
7
+ * the resource is disposed, cancelling any in-flight work.
8
+ *
9
+ * If `resource()` is created inside an active `effect()` or `scope.run()` context,
10
+ * it is automatically registered for cleanup and disposed with that context —
11
+ * matching `computed()`'s auto-disposal behavior.
12
+ *
13
+ * Call `refresh()` to force the factory to re-run immediately, even if no tracked
14
+ * dependency changed — useful for manual "retry"/"refetch" actions.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const userId = signal('u1');
19
+ * const user = resource(async (signal) => {
20
+ * const id = userId.value; // tracked dep — re-runs when userId changes
21
+ * return fetchUser(id, { signal });
22
+ * });
23
+ *
24
+ * effect(() => {
25
+ * const s = user.value;
26
+ * if (s.status === 'loading') return showSpinner();
27
+ * if (s.status === 'error') return showError(s.error);
28
+ * renderUser(s.data);
29
+ * });
30
+ *
31
+ * user.refresh(); // force a re-fetch without changing userId
32
+ * user.dispose();
33
+ * ```
34
+ */
35
+ export declare const resource: <T>(factory: (abortSignal: AbortSignal) => Promise<T>, options?: ResourceOptions<T>) => Resource<T>;
36
+ //# sourceMappingURL=async-computed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-computed.d.ts","sourceRoot":"","sources":["../src/async-computed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAiB,MAAM,SAAS,CAAC;AAOxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,SAAS,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACjD,UAAU,eAAe,CAAC,CAAC,CAAC,KAC3B,QAAQ,CAAC,CAAC,CAiFZ,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { autoRegisterDisposal as e } from "./tracking.js";
2
+ import { IS_COMPUTED as t, IS_SIGNAL as n } from "./symbols.js";
3
+ import { effect as r } from "./effect.js";
4
+ import { signal as i } from "./signal.js";
5
+ //#region src/async-computed.ts
6
+ var a = (a, o) => {
7
+ let s = o?.name, c = i({
8
+ data: o?.initialValue,
9
+ status: "loading"
10
+ }, s ? { name: s } : void 0), l = i(0), u = null, d = !1, f = r(() => {
11
+ l.value, u?.abort(), u = new AbortController();
12
+ let { signal: e } = u, t = c.peek(), n = "data" in t ? t.data : void 0;
13
+ c.value = {
14
+ data: n,
15
+ status: "loading"
16
+ }, (async () => {
17
+ try {
18
+ let t = await a(e);
19
+ !e.aborted && !d && (c.value = {
20
+ data: t,
21
+ status: "ready"
22
+ });
23
+ } catch (t) {
24
+ !e.aborted && !d && (c.value = {
25
+ data: n,
26
+ error: t,
27
+ status: "error"
28
+ });
29
+ }
30
+ })();
31
+ }, s ? { name: s } : void 0), p = () => {
32
+ d || (l.value = l.peek() + 1);
33
+ }, m = () => {
34
+ d || (d = !0, u?.abort(), f.dispose(), c.dispose(), l.dispose());
35
+ };
36
+ return e(m), {
37
+ dispose: m,
38
+ get disposed() {
39
+ return d;
40
+ },
41
+ [t]: !0,
42
+ [n]: !0,
43
+ get name() {
44
+ return c.name;
45
+ },
46
+ peek: () => c.peek(),
47
+ refresh: p,
48
+ subscribe: (e) => c.subscribe(e),
49
+ [Symbol.dispose]: m,
50
+ get value() {
51
+ return c.value;
52
+ }
53
+ };
54
+ };
55
+ //#endregion
56
+ export { a as resource };
57
+
58
+ //# sourceMappingURL=async-computed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-computed.js","names":[],"sources":["../src/async-computed.ts"],"sourcesContent":["import type { Resource, ResourceOptions, ResourceState } from './types';\n\nimport { effect } from './effect';\nimport { signal } from './signal';\nimport { IS_COMPUTED, IS_SIGNAL } from './symbols';\nimport { autoRegisterDisposal } from './tracking';\n\n/**\n * Creates a reactive async resource that emits a `ResourceState<T>` discriminated union.\n *\n * Dependencies are tracked synchronously in the factory (before the first `await`).\n * The factory receives an `AbortSignal` that fires when dependencies change or\n * the resource is disposed, cancelling any in-flight work.\n *\n * If `resource()` is created inside an active `effect()` or `scope.run()` context,\n * it is automatically registered for cleanup and disposed with that context —\n * matching `computed()`'s auto-disposal behavior.\n *\n * Call `refresh()` to force the factory to re-run immediately, even if no tracked\n * dependency changed — useful for manual \"retry\"/\"refetch\" actions.\n *\n * @example\n * ```ts\n * const userId = signal('u1');\n * const user = resource(async (signal) => {\n * const id = userId.value; // tracked dep — re-runs when userId changes\n * return fetchUser(id, { signal });\n * });\n *\n * effect(() => {\n * const s = user.value;\n * if (s.status === 'loading') return showSpinner();\n * if (s.status === 'error') return showError(s.error);\n * renderUser(s.data);\n * });\n *\n * user.refresh(); // force a re-fetch without changing userId\n * user.dispose();\n * ```\n */\nexport const resource = <T>(\n factory: (abortSignal: AbortSignal) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> => {\n const name = options?.name;\n const state = signal<ResourceState<T>>(\n { data: options?.initialValue, status: 'loading' },\n name ? { name } : undefined,\n );\n // Internal-only trigger — never exposed. Read (but never written to a meaningful\n // value) inside the effect purely so it becomes a tracked dep; refresh() bumps it\n // to force a re-run without requiring a real dependency to change.\n const epoch = signal(0);\n\n let controller: AbortController | null = null;\n let disposed = false;\n\n const stop = effect(\n () => {\n void epoch.value;\n\n controller?.abort();\n controller = new AbortController();\n\n const { signal: abortSignal } = controller;\n\n const current = state.peek();\n const prevData = 'data' in current ? current.data : undefined;\n\n state.value = { data: prevData, status: 'loading' };\n\n void (async () => {\n try {\n const result = await factory(abortSignal);\n\n if (!abortSignal.aborted && !disposed) {\n state.value = { data: result, status: 'ready' };\n }\n } catch (err) {\n if (!abortSignal.aborted && !disposed) {\n state.value = { data: prevData, error: err, status: 'error' };\n }\n }\n })();\n },\n name ? { name } : undefined,\n );\n\n const refresh = (): void => {\n if (disposed) return;\n\n epoch.value = epoch.peek() + 1;\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n controller?.abort();\n stop.dispose();\n state.dispose();\n epoch.dispose();\n };\n\n autoRegisterDisposal(dispose);\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n [IS_COMPUTED]: true as const,\n [IS_SIGNAL]: true as const,\n get name() {\n return state.name;\n },\n peek: () => state.peek(),\n refresh,\n subscribe: (listener: () => void) => state.subscribe(listener),\n [Symbol.dispose]: dispose,\n get value() {\n return state.value;\n },\n } as Resource<T>;\n};\n"],"mappings":";;;;;AAwCA,IAAa,KACX,GACA,MACgB;CAChB,IAAM,IAAO,GAAS,MAChB,IAAQ,EACZ;EAAE,MAAM,GAAS;EAAc,QAAQ;CAAU,GACjD,IAAO,EAAE,QAAK,IAAI,KAAA,CACpB,GAIM,IAAQ,EAAO,CAAC,GAElB,IAAqC,MACrC,IAAW,IAET,IAAO,QACL;EAIJ,AAHA,EAAW,OAEX,GAAY,MAAM,GAClB,IAAa,IAAI,gBAAgB;EAEjC,IAAM,EAAE,QAAQ,MAAgB,GAE1B,IAAU,EAAM,KAAK,GACrB,IAAW,UAAU,IAAU,EAAQ,OAAO,KAAA;EAIpD,AAFA,EAAM,QAAQ;GAAE,MAAM;GAAU,QAAQ;EAAU,IAE5C,YAAY;GAChB,IAAI;IACF,IAAM,IAAS,MAAM,EAAQ,CAAW;IAExC,AAAI,CAAC,EAAY,WAAW,CAAC,MAC3B,EAAM,QAAQ;KAAE,MAAM;KAAQ,QAAQ;IAAQ;GAElD,SAAS,GAAK;IACZ,AAAI,CAAC,EAAY,WAAW,CAAC,MAC3B,EAAM,QAAQ;KAAE,MAAM;KAAU,OAAO;KAAK,QAAQ;IAAQ;GAEhE;EACF,EAAA,CAAG;CACL,GACA,IAAO,EAAE,QAAK,IAAI,KAAA,CACpB,GAEM,UAAsB;EACtB,MAEJ,EAAM,QAAQ,EAAM,KAAK,IAAI;CAC/B,GAEM,UAAsB;EACtB,MAEJ,IAAW,IACX,GAAY,MAAM,GAClB,EAAK,QAAQ,GACb,EAAM,QAAQ,GACd,EAAM,QAAQ;CAChB;CAIA,OAFA,EAAqB,CAAO,GAErB;EACL;EACA,IAAI,WAAW;GACb,OAAO;EACT;GACC,IAAc;GACd,IAAY;EACb,IAAI,OAAO;GACT,OAAO,EAAM;EACf;EACA,YAAY,EAAM,KAAK;EACvB;EACA,YAAY,MAAyB,EAAM,UAAU,CAAQ;GAC5D,OAAO,UAAU;EAClB,IAAI,QAAQ;GACV,OAAO,EAAM;EACf;CACF;AACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./devtools-hook.cjs"),t=require("./_error-utils.cjs"),n=require("./errors.cjs"),r=require("./tracking.cjs"),i=require("./subscription.cjs"),a=require("./symbols.cjs"),o=require("./reactive-base.cjs");var s=class extends o.ComputedBase{value_;dirty_;computing_;disposed_;deps_;compute_;equals_;maxRevision_;constructor(e,t){let{equals:n,name:r}=t??{};super(r),this.value_=a.UNINITIALIZED,this.dirty_=!0,this.computing_=!1,this.disposed_=!1,this.deps_=[],this.maxRevision_=-1,this.compute_=e,this.equals_=n===void 0?Object.is:(e,t)=>n(e,t)}markDirty(){return this.disposed_||this.dirty_?!1:(this.dirty_=!0,!0)}refreshIfDirty(){if(!this.dirty_)return!1;if(r.getRevision()<=this.maxRevision_)return this.dirty_=!1,!1;if(this.deps_.length>0){let e=!0;for(let t of this.deps_){let n=t.source;if(`refreshIfDirty`in n&&n.refreshIfDirty(),n.version!==t.version){e=!1;break}}if(e)return this.dirty_=!1,this.maxRevision_=r.getRevision(),!1}return this.recompute()}runCompute(e){try{return r.withTracking({computed:this,depCollector:e,kind:`computed`},this.compute_)}catch(e){throw t.ensureError(e)}}recompute(){if(this.computing_)throw new n.RippleComputedCycleError(`computed cycle detected${this.name?` "${this.name}"`:``}`);this.computing_=!0;try{let t=[];e.getDevToolsHook()?.compute?.({name:this.name});let n=this.runCompute(t);return this.dirty_=!1,this.maxRevision_=r.getRevision(),this.updateDeps(t),this.value_===a.UNINITIALIZED||!this.equals_(this.value_,n)?(this.value_=n,this.version++,!0):!1}finally{this.computing_=!1}}updateDeps(e){let t=this.deps_;if(t.length===e.length&&t.every((t,n)=>t.source===e[n].source)){for(let n=0;n<e.length;n++)t[n].version=e[n].version;return}let n=new Set(t.map(e=>e.source)),r=new Set(e.map(e=>e.source));for(let e of t)r.has(e.source)||e.source.removeComputedSub(this);for(let t of e)n.has(t.source)||t.source.addComputedSub(this);this.deps_=e}get value(){return this.disposed_?this.value_===a.UNINITIALIZED?void 0:this.value_:(this.refreshIfDirty(),r.trackSource(this),this.value_)}peek(){return this.disposed_?this.value_===a.UNINITIALIZED?void 0:this.value_:(this.refreshIfDirty(),this.value_)}subscribe=e=>{if(this.disposed_){let e=new i.SubscriptionImpl(()=>{});return e.dispose(),e}return this.refreshIfDirty(),this.addEffectSub(e),new i.SubscriptionImpl(()=>{this.removeEffectSub(e)})};get disposed(){return this.disposed_}dispose(){if(!this.disposed_){this.disposed_=!0;for(let e of this.deps_)e.source.removeComputedSub(this);this.deps_=[],this.clearSubscribers(),e.getDevToolsHook()?.dispose?.({kind:`computed`,name:this.name})}}[Symbol.dispose](){this.dispose()}},c=(e,t)=>{let n=new s(e,t);return r.autoRegisterDisposal(()=>n.dispose()),n};exports.ComputedImpl=s,exports.computed=c;
2
+ //# sourceMappingURL=computed.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed.cjs","names":[],"sources":["../src/computed.ts"],"sourcesContent":["import type { Refreshable } from './reactive-base';\nimport type { DepEntry } from './tracking';\nimport type { Computed, ComputedOptions, Subscription } from './types';\n\nimport { ensureError } from './_error-utils';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleComputedCycleError } from './errors';\nimport { ComputedBase } from './reactive-base';\nimport { SubscriptionImpl } from './subscription';\nimport { UNINITIALIZED } from './symbols';\nimport { autoRegisterDisposal, getRevision, trackSource, withTracking } from './tracking';\n\nexport class ComputedImpl<T> extends ComputedBase<T> implements Computed<T> {\n private value_: T | typeof UNINITIALIZED;\n private dirty_: boolean;\n private computing_: boolean;\n private disposed_: boolean;\n private deps_: DepEntry[];\n private compute_: () => T;\n private equals_: (a: unknown, b: unknown) => boolean;\n // Global revision at last successful compute — enables O(1) \"nothing changed\" fast-path.\n private maxRevision_: number;\n\n constructor(compute: () => T, options?: ComputedOptions<T>) {\n const { equals, name } = options ?? {};\n\n super(name);\n this.value_ = UNINITIALIZED;\n this.dirty_ = true;\n this.computing_ = false;\n this.disposed_ = false;\n this.deps_ = [];\n this.maxRevision_ = -1;\n this.compute_ = compute;\n this.equals_ = equals !== undefined ? (a, b) => equals(a as T, b as T) : Object.is;\n }\n\n markDirty(): boolean {\n if (this.disposed_ || this.dirty_) return false;\n\n this.dirty_ = true;\n\n return true;\n }\n\n refreshIfDirty(): boolean {\n if (!this.dirty_) return false;\n\n // O(1) global revision fast-path:\n // If the global revision clock has not advanced past our recorded maximum,\n // no signal anywhere in the system has been written since our last compute.\n // No dep can have changed — clear dirty and skip the per-dep scan entirely.\n if (getRevision() <= this.maxRevision_) {\n this.dirty_ = false;\n\n return false;\n }\n\n // Per-dep scan: if all dep versions still match, value cannot have changed.\n // For computed deps, we must refresh them first — their version only updates\n // after recompute, so a stale-version check would incorrectly short-circuit.\n if (this.deps_.length > 0) {\n let allSame = true;\n\n for (const d of this.deps_) {\n const src = d.source;\n\n // Flush dirty computed deps before comparing versions (lazy pull chain).\n if ('refreshIfDirty' in src) {\n (src as Refreshable).refreshIfDirty();\n }\n\n if (src.version !== d.version) {\n allSame = false;\n break;\n }\n }\n\n if (allSame) {\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n return false;\n }\n }\n\n return this.recompute();\n }\n\n private runCompute(depCollector: DepEntry[]): T {\n try {\n return withTracking({ computed: this, depCollector, kind: 'computed' }, this.compute_);\n } catch (error) {\n // Sources accessed before the throw remain in their computedSubs_ until the\n // next successful recompute or dispose(). The computed stays dirty and will retry.\n throw ensureError(error);\n }\n }\n\n private recompute(): boolean {\n if (this.computing_) {\n const label = this.name ? ` \"${this.name}\"` : '';\n\n throw new RippleComputedCycleError(`computed cycle detected${label}`);\n }\n\n this.computing_ = true;\n\n try {\n const newDeps: DepEntry[] = [];\n\n getDevToolsHook()?.compute?.({ name: this.name });\n\n const next = this.runCompute(newDeps);\n\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n // Diff deps: remove subscriptions for sources that are no longer dependencies.\n // Only subscribe to genuinely NEW deps — prevents duplicate WeakRef entries.\n this.updateDeps(newDeps);\n\n const isNew = this.value_ === UNINITIALIZED;\n\n if (isNew || !this.equals_(this.value_ as T, next)) {\n this.value_ = next;\n this.version++;\n\n return true;\n }\n\n return false;\n } finally {\n this.computing_ = false;\n }\n }\n\n private updateDeps(newDeps: DepEntry[]): void {\n const oldDeps = this.deps_;\n\n // Fast path: same sources in same order — just refresh stored versions\n if (oldDeps.length === newDeps.length && oldDeps.every((d, i) => d.source === newDeps[i].source)) {\n for (let i = 0; i < newDeps.length; i++) {\n oldDeps[i]!.version = newDeps[i]!.version;\n }\n\n return;\n }\n\n // Slow path: deps changed.\n // Build a set of OLD sources so we can detect which are genuinely NEW and\n // need a fresh addComputedSub call (WeakRef registration).\n const oldSourceSet = new Set(oldDeps.map((d) => d.source));\n const newSourceSet = new Set(newDeps.map((d) => d.source));\n\n // Unsubscribe from sources that are no longer dependencies\n for (const old of oldDeps) {\n if (!newSourceSet.has(old.source)) {\n old.source.removeComputedSub(this);\n }\n }\n\n // Subscribe to sources that are genuinely NEW (not in old deps)\n for (const dep of newDeps) {\n if (!oldSourceSet.has(dep.source)) {\n dep.source.addComputedSub(this);\n }\n }\n\n this.deps_ = newDeps;\n }\n\n get value(): T {\n // After disposal: return last known value without tracking (inert node, like signal).\n // If the computed was never evaluated (still UNINITIALIZED), return undefined.\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n trackSource(this);\n\n return this.value_ as T;\n }\n\n // peek() after dispose: return last known value silently, consistent with signal.peek().\n // If the computed was never evaluated, return undefined (same as signal with undefined initial).\n peek(): T {\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n\n return this.value_ as T;\n }\n\n readonly subscribe = (listener: () => void): Subscription => {\n if (this.disposed_) {\n const noop = new SubscriptionImpl(() => {});\n\n noop.dispose();\n\n return noop;\n }\n\n this.refreshIfDirty();\n this.addEffectSub(listener);\n\n return new SubscriptionImpl(() => {\n this.removeEffectSub(listener);\n });\n };\n\n get disposed(): boolean {\n return this.disposed_;\n }\n\n dispose(): void {\n if (this.disposed_) return;\n\n this.disposed_ = true;\n\n // Unsubscribe from all upstream sources\n for (const dep of this.deps_) {\n dep.source.removeComputedSub(this);\n }\n\n this.deps_ = [];\n this.clearSubscribers();\n getDevToolsHook()?.dispose?.({ kind: 'computed', name: this.name });\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────────\n\nexport const computed = <T>(compute: () => T, options?: ComputedOptions<T>): Computed<T> => {\n const comp = new ComputedImpl(compute, options);\n\n autoRegisterDisposal(() => comp.dispose());\n\n return comp;\n};\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { Computed, ComputedOptions, Subscription } from './types';
2
+ import { ComputedBase } from './reactive-base';
3
+ export declare class ComputedImpl<T> extends ComputedBase<T> implements Computed<T> {
4
+ private value_;
5
+ private dirty_;
6
+ private computing_;
7
+ private disposed_;
8
+ private deps_;
9
+ private compute_;
10
+ private equals_;
11
+ private maxRevision_;
12
+ constructor(compute: () => T, options?: ComputedOptions<T>);
13
+ markDirty(): boolean;
14
+ refreshIfDirty(): boolean;
15
+ private runCompute;
16
+ private recompute;
17
+ private updateDeps;
18
+ get value(): T;
19
+ peek(): T;
20
+ readonly subscribe: (listener: () => void) => Subscription;
21
+ get disposed(): boolean;
22
+ dispose(): void;
23
+ [Symbol.dispose](): void;
24
+ }
25
+ export declare const computed: <T>(compute: () => T, options?: ComputedOptions<T>) => Computed<T>;
26
+ //# sourceMappingURL=computed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAKvE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK/C,qBAAa,YAAY,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAsC;IAErD,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;IAc1D,SAAS,IAAI,OAAO;IAQpB,cAAc,IAAI,OAAO;IA4CzB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,SAAS;IAsCjB,OAAO,CAAC,UAAU;IAmClB,IAAI,KAAK,IAAI,CAAC,CAWb;IAID,IAAI,IAAI,CAAC;IAUT,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,YAAY,CAevD;IAEF,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,OAAO,IAAI,IAAI;IAef,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB;AAID,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,SAAS,MAAM,CAAC,EAAE,UAAU,eAAe,CAAC,CAAC,CAAC,KAAG,QAAQ,CAAC,CAAC,CAMtF,CAAC"}
@@ -0,0 +1,113 @@
1
+ import { getDevToolsHook as e } from "./devtools-hook.js";
2
+ import { ensureError as t } from "./_error-utils.js";
3
+ import { RippleComputedCycleError as n } from "./errors.js";
4
+ import { autoRegisterDisposal as r, getRevision as i, trackSource as a, withTracking as o } from "./tracking.js";
5
+ import { SubscriptionImpl as s } from "./subscription.js";
6
+ import { UNINITIALIZED as c } from "./symbols.js";
7
+ import { ComputedBase as l } from "./reactive-base.js";
8
+ //#region src/computed.ts
9
+ var u = class extends l {
10
+ value_;
11
+ dirty_;
12
+ computing_;
13
+ disposed_;
14
+ deps_;
15
+ compute_;
16
+ equals_;
17
+ maxRevision_;
18
+ constructor(e, t) {
19
+ let { equals: n, name: r } = t ?? {};
20
+ super(r), this.value_ = c, this.dirty_ = !0, this.computing_ = !1, this.disposed_ = !1, this.deps_ = [], this.maxRevision_ = -1, this.compute_ = e, this.equals_ = n === void 0 ? Object.is : (e, t) => n(e, t);
21
+ }
22
+ markDirty() {
23
+ return this.disposed_ || this.dirty_ ? !1 : (this.dirty_ = !0, !0);
24
+ }
25
+ refreshIfDirty() {
26
+ if (!this.dirty_) return !1;
27
+ if (i() <= this.maxRevision_) return this.dirty_ = !1, !1;
28
+ if (this.deps_.length > 0) {
29
+ let e = !0;
30
+ for (let t of this.deps_) {
31
+ let n = t.source;
32
+ if ("refreshIfDirty" in n && n.refreshIfDirty(), n.version !== t.version) {
33
+ e = !1;
34
+ break;
35
+ }
36
+ }
37
+ if (e) return this.dirty_ = !1, this.maxRevision_ = i(), !1;
38
+ }
39
+ return this.recompute();
40
+ }
41
+ runCompute(e) {
42
+ try {
43
+ return o({
44
+ computed: this,
45
+ depCollector: e,
46
+ kind: "computed"
47
+ }, this.compute_);
48
+ } catch (e) {
49
+ throw t(e);
50
+ }
51
+ }
52
+ recompute() {
53
+ if (this.computing_) throw new n(`computed cycle detected${this.name ? ` "${this.name}"` : ""}`);
54
+ this.computing_ = !0;
55
+ try {
56
+ let t = [];
57
+ e()?.compute?.({ name: this.name });
58
+ let n = this.runCompute(t);
59
+ return this.dirty_ = !1, this.maxRevision_ = i(), this.updateDeps(t), this.value_ === c || !this.equals_(this.value_, n) ? (this.value_ = n, this.version++, !0) : !1;
60
+ } finally {
61
+ this.computing_ = !1;
62
+ }
63
+ }
64
+ updateDeps(e) {
65
+ let t = this.deps_;
66
+ if (t.length === e.length && t.every((t, n) => t.source === e[n].source)) {
67
+ for (let n = 0; n < e.length; n++) t[n].version = e[n].version;
68
+ return;
69
+ }
70
+ let n = new Set(t.map((e) => e.source)), r = new Set(e.map((e) => e.source));
71
+ for (let e of t) r.has(e.source) || e.source.removeComputedSub(this);
72
+ for (let t of e) n.has(t.source) || t.source.addComputedSub(this);
73
+ this.deps_ = e;
74
+ }
75
+ get value() {
76
+ return this.disposed_ ? this.value_ === c ? void 0 : this.value_ : (this.refreshIfDirty(), a(this), this.value_);
77
+ }
78
+ peek() {
79
+ return this.disposed_ ? this.value_ === c ? void 0 : this.value_ : (this.refreshIfDirty(), this.value_);
80
+ }
81
+ subscribe = (e) => {
82
+ if (this.disposed_) {
83
+ let e = new s(() => {});
84
+ return e.dispose(), e;
85
+ }
86
+ return this.refreshIfDirty(), this.addEffectSub(e), new s(() => {
87
+ this.removeEffectSub(e);
88
+ });
89
+ };
90
+ get disposed() {
91
+ return this.disposed_;
92
+ }
93
+ dispose() {
94
+ if (!this.disposed_) {
95
+ this.disposed_ = !0;
96
+ for (let e of this.deps_) e.source.removeComputedSub(this);
97
+ this.deps_ = [], this.clearSubscribers(), e()?.dispose?.({
98
+ kind: "computed",
99
+ name: this.name
100
+ });
101
+ }
102
+ }
103
+ [Symbol.dispose]() {
104
+ this.dispose();
105
+ }
106
+ }, d = (e, t) => {
107
+ let n = new u(e, t);
108
+ return r(() => n.dispose()), n;
109
+ };
110
+ //#endregion
111
+ export { u as ComputedImpl, d as computed };
112
+
113
+ //# sourceMappingURL=computed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed.js","names":[],"sources":["../src/computed.ts"],"sourcesContent":["import type { Refreshable } from './reactive-base';\nimport type { DepEntry } from './tracking';\nimport type { Computed, ComputedOptions, Subscription } from './types';\n\nimport { ensureError } from './_error-utils';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleComputedCycleError } from './errors';\nimport { ComputedBase } from './reactive-base';\nimport { SubscriptionImpl } from './subscription';\nimport { UNINITIALIZED } from './symbols';\nimport { autoRegisterDisposal, getRevision, trackSource, withTracking } from './tracking';\n\nexport class ComputedImpl<T> extends ComputedBase<T> implements Computed<T> {\n private value_: T | typeof UNINITIALIZED;\n private dirty_: boolean;\n private computing_: boolean;\n private disposed_: boolean;\n private deps_: DepEntry[];\n private compute_: () => T;\n private equals_: (a: unknown, b: unknown) => boolean;\n // Global revision at last successful compute — enables O(1) \"nothing changed\" fast-path.\n private maxRevision_: number;\n\n constructor(compute: () => T, options?: ComputedOptions<T>) {\n const { equals, name } = options ?? {};\n\n super(name);\n this.value_ = UNINITIALIZED;\n this.dirty_ = true;\n this.computing_ = false;\n this.disposed_ = false;\n this.deps_ = [];\n this.maxRevision_ = -1;\n this.compute_ = compute;\n this.equals_ = equals !== undefined ? (a, b) => equals(a as T, b as T) : Object.is;\n }\n\n markDirty(): boolean {\n if (this.disposed_ || this.dirty_) return false;\n\n this.dirty_ = true;\n\n return true;\n }\n\n refreshIfDirty(): boolean {\n if (!this.dirty_) return false;\n\n // O(1) global revision fast-path:\n // If the global revision clock has not advanced past our recorded maximum,\n // no signal anywhere in the system has been written since our last compute.\n // No dep can have changed — clear dirty and skip the per-dep scan entirely.\n if (getRevision() <= this.maxRevision_) {\n this.dirty_ = false;\n\n return false;\n }\n\n // Per-dep scan: if all dep versions still match, value cannot have changed.\n // For computed deps, we must refresh them first — their version only updates\n // after recompute, so a stale-version check would incorrectly short-circuit.\n if (this.deps_.length > 0) {\n let allSame = true;\n\n for (const d of this.deps_) {\n const src = d.source;\n\n // Flush dirty computed deps before comparing versions (lazy pull chain).\n if ('refreshIfDirty' in src) {\n (src as Refreshable).refreshIfDirty();\n }\n\n if (src.version !== d.version) {\n allSame = false;\n break;\n }\n }\n\n if (allSame) {\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n return false;\n }\n }\n\n return this.recompute();\n }\n\n private runCompute(depCollector: DepEntry[]): T {\n try {\n return withTracking({ computed: this, depCollector, kind: 'computed' }, this.compute_);\n } catch (error) {\n // Sources accessed before the throw remain in their computedSubs_ until the\n // next successful recompute or dispose(). The computed stays dirty and will retry.\n throw ensureError(error);\n }\n }\n\n private recompute(): boolean {\n if (this.computing_) {\n const label = this.name ? ` \"${this.name}\"` : '';\n\n throw new RippleComputedCycleError(`computed cycle detected${label}`);\n }\n\n this.computing_ = true;\n\n try {\n const newDeps: DepEntry[] = [];\n\n getDevToolsHook()?.compute?.({ name: this.name });\n\n const next = this.runCompute(newDeps);\n\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n // Diff deps: remove subscriptions for sources that are no longer dependencies.\n // Only subscribe to genuinely NEW deps — prevents duplicate WeakRef entries.\n this.updateDeps(newDeps);\n\n const isNew = this.value_ === UNINITIALIZED;\n\n if (isNew || !this.equals_(this.value_ as T, next)) {\n this.value_ = next;\n this.version++;\n\n return true;\n }\n\n return false;\n } finally {\n this.computing_ = false;\n }\n }\n\n private updateDeps(newDeps: DepEntry[]): void {\n const oldDeps = this.deps_;\n\n // Fast path: same sources in same order — just refresh stored versions\n if (oldDeps.length === newDeps.length && oldDeps.every((d, i) => d.source === newDeps[i].source)) {\n for (let i = 0; i < newDeps.length; i++) {\n oldDeps[i]!.version = newDeps[i]!.version;\n }\n\n return;\n }\n\n // Slow path: deps changed.\n // Build a set of OLD sources so we can detect which are genuinely NEW and\n // need a fresh addComputedSub call (WeakRef registration).\n const oldSourceSet = new Set(oldDeps.map((d) => d.source));\n const newSourceSet = new Set(newDeps.map((d) => d.source));\n\n // Unsubscribe from sources that are no longer dependencies\n for (const old of oldDeps) {\n if (!newSourceSet.has(old.source)) {\n old.source.removeComputedSub(this);\n }\n }\n\n // Subscribe to sources that are genuinely NEW (not in old deps)\n for (const dep of newDeps) {\n if (!oldSourceSet.has(dep.source)) {\n dep.source.addComputedSub(this);\n }\n }\n\n this.deps_ = newDeps;\n }\n\n get value(): T {\n // After disposal: return last known value without tracking (inert node, like signal).\n // If the computed was never evaluated (still UNINITIALIZED), return undefined.\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n trackSource(this);\n\n return this.value_ as T;\n }\n\n // peek() after dispose: return last known value silently, consistent with signal.peek().\n // If the computed was never evaluated, return undefined (same as signal with undefined initial).\n peek(): T {\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n\n return this.value_ as T;\n }\n\n readonly subscribe = (listener: () => void): Subscription => {\n if (this.disposed_) {\n const noop = new SubscriptionImpl(() => {});\n\n noop.dispose();\n\n return noop;\n }\n\n this.refreshIfDirty();\n this.addEffectSub(listener);\n\n return new SubscriptionImpl(() => {\n this.removeEffectSub(listener);\n });\n };\n\n get disposed(): boolean {\n return this.disposed_;\n }\n\n dispose(): void {\n if (this.disposed_) return;\n\n this.disposed_ = true;\n\n // Unsubscribe from all upstream sources\n for (const dep of this.deps_) {\n dep.source.removeComputedSub(this);\n }\n\n this.deps_ = [];\n this.clearSubscribers();\n getDevToolsHook()?.dispose?.({ kind: 'computed', name: this.name });\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────────\n\nexport const computed = <T>(compute: () => T, options?: ComputedOptions<T>): Computed<T> => {\n const comp = new ComputedImpl(compute, options);\n\n autoRegisterDisposal(() => comp.dispose());\n\n return comp;\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=null,t=()=>e,n=t=>{e=t};exports.getDevToolsHook=t,exports.setDevToolsHook=n;
2
+ //# sourceMappingURL=devtools-hook.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-hook.cjs","names":[],"sources":["../src/devtools-hook.ts"],"sourcesContent":["// ── DevTools hook — core stub ─────────────────────────────────────────────────\n//\n// This module is intentionally minimal: it only holds the module-level _hook\n// variable and exposes getDevToolsHook() for the hot paths in signal.ts,\n// computed.ts, effect.ts, and store.ts.\n//\n// installDevTools() lives in the @vielzeug/ripple/devtools sub-path and is\n// tree-shaken from production bundles when that sub-path is never imported.\n// setDevToolsHook() is the internal bridge called by installDevTools().\n\nimport type { RippleDevToolsHook } from './types';\n\nlet _hook: RippleDevToolsHook | null = null;\n\n/** Returns the currently installed DevTools hook, or `null` if none is installed. */\nexport const getDevToolsHook = (): RippleDevToolsHook | null => _hook;\n\n/** @internal Called only by `installDevTools` in `@vielzeug/ripple/devtools`. */\nexport const setDevToolsHook = (hook: RippleDevToolsHook | null): void => {\n _hook = hook;\n};\n"],"mappings":"AAYA,IAAI,EAAmC,KAG1B,MAAmD,EAGnD,EAAmB,GAA0C,CACxE,EAAQ,CACV"}
@@ -0,0 +1,4 @@
1
+ import type { RippleDevToolsHook } from './types';
2
+ /** Returns the currently installed DevTools hook, or `null` if none is installed. */
3
+ export declare const getDevToolsHook: () => RippleDevToolsHook | null;
4
+ //# sourceMappingURL=devtools-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-hook.d.ts","sourceRoot":"","sources":["../src/devtools-hook.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAIlD,qFAAqF;AACrF,eAAO,MAAM,eAAe,QAAO,kBAAkB,GAAG,IAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ //#region src/devtools-hook.ts
2
+ var e = null, t = () => e, n = (t) => {
3
+ e = t;
4
+ };
5
+ //#endregion
6
+ export { t as getDevToolsHook, n as setDevToolsHook };
7
+
8
+ //# sourceMappingURL=devtools-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools-hook.js","names":[],"sources":["../src/devtools-hook.ts"],"sourcesContent":["// ── DevTools hook — core stub ─────────────────────────────────────────────────\n//\n// This module is intentionally minimal: it only holds the module-level _hook\n// variable and exposes getDevToolsHook() for the hot paths in signal.ts,\n// computed.ts, effect.ts, and store.ts.\n//\n// installDevTools() lives in the @vielzeug/ripple/devtools sub-path and is\n// tree-shaken from production bundles when that sub-path is never imported.\n// setDevToolsHook() is the internal bridge called by installDevTools().\n\nimport type { RippleDevToolsHook } from './types';\n\nlet _hook: RippleDevToolsHook | null = null;\n\n/** Returns the currently installed DevTools hook, or `null` if none is installed. */\nexport const getDevToolsHook = (): RippleDevToolsHook | null => _hook;\n\n/** @internal Called only by `installDevTools` in `@vielzeug/ripple/devtools`. */\nexport const setDevToolsHook = (hook: RippleDevToolsHook | null): void => {\n _hook = hook;\n};\n"],"mappings":";AAYA,IAAI,IAAmC,MAG1B,UAAmD,GAGnD,KAAmB,MAA0C;CACxE,IAAQ;AACV"}
@@ -0,0 +1,39 @@
1
+ import type { EffectCallback, EffectHandle, EffectOptions, RippleDevToolsHook } from './types';
2
+ export type { DisposeEvent, MutateEvent, NamedEvent, RippleDevToolsHook, WriteEvent } from './types';
3
+ /**
4
+ * Installs a DevTools hook for observing ripple internals.
5
+ * Pass `null` to uninstall.
6
+ *
7
+ * Import from the dedicated sub-path to keep this out of production bundles:
8
+ * ```ts
9
+ * import { installDevTools } from '@vielzeug/ripple/devtools';
10
+ *
11
+ * installDevTools({
12
+ * write({ name, oldValue, newValue }) {
13
+ * console.log(`[ripple] ${name ?? '(unnamed)'}: ${String(oldValue)} → ${String(newValue)}`);
14
+ * },
15
+ * dispose({ kind, name }) {
16
+ * console.log(`[ripple] ${kind} "${name ?? '(unnamed)'}" disposed`);
17
+ * },
18
+ * });
19
+ * ```
20
+ */
21
+ export declare const installDevTools: (hook: RippleDevToolsHook | null) => void;
22
+ /**
23
+ * Wraps `effect()` and logs reactive dependency information on every run:
24
+ * - **Initial run**: logs all deps the effect subscribed to.
25
+ * - **Re-runs**: logs which deps changed (with old and new version numbers).
26
+ *
27
+ * Use instead of `effect()` when debugging unexpected re-renders.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { debugEffect } from '@vielzeug/ripple/devtools';
32
+ *
33
+ * const stop = debugEffect(() => {
34
+ * renderUser(userId.value, name.value);
35
+ * }, { name: 'renderUser' });
36
+ * ```
37
+ */
38
+ export declare const debugEffect: (fn: EffectCallback, options?: Omit<EffectOptions, "trace">) => EffectHandle;
39
+ //# sourceMappingURL=devtools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAa,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAQ1G,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIrG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,kBAAkB,GAAG,IAAI,KAAG,IAQjE,CAAC;AAIF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,cAAc,EAAE,UAAU,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,YA+CxF,CAAC"}