@vielzeug/ripple 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/async-computed.cjs +1 -1
- package/dist/async-computed.cjs.map +1 -1
- package/dist/async-computed.d.ts.map +1 -1
- package/dist/async-computed.js +37 -33
- package/dist/async-computed.js.map +1 -1
- package/dist/computed.cjs +1 -1
- package/dist/computed.js +13 -13
- package/dist/effect.cjs +1 -1
- package/dist/effect.cjs.map +1 -1
- package/dist/effect.d.ts.map +1 -1
- package/dist/effect.js +53 -52
- package/dist/effect.js.map +1 -1
- package/dist/execution-context.cjs +2 -0
- package/dist/execution-context.cjs.map +1 -0
- package/dist/execution-context.d.ts +49 -0
- package/dist/execution-context.d.ts.map +1 -0
- package/dist/execution-context.js +32 -0
- package/dist/execution-context.js.map +1 -0
- package/dist/{store-history.d.ts → history.d.ts} +15 -1
- package/dist/history.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/ripple.cjs +1 -1
- package/dist/ripple.cjs.map +1 -1
- package/dist/ripple.iife.js +1 -1
- package/dist/ripple.iife.js.map +1 -1
- package/dist/ripple.js +1 -1
- package/dist/ripple.js.map +1 -1
- package/dist/scheduling.cjs +1 -1
- package/dist/scheduling.cjs.map +1 -1
- package/dist/scheduling.js +8 -8
- package/dist/scheduling.js.map +1 -1
- package/dist/signal.cjs +1 -1
- package/dist/signal.js +8 -8
- package/dist/src/history.cjs +2 -0
- package/dist/src/history.cjs.map +1 -0
- package/dist/{store-history.js → src/history.js} +5 -5
- package/dist/{store-history.cjs.map → src/history.js.map} +1 -1
- package/dist/src/ssr/index.cjs +1 -1
- package/dist/src/ssr/index.cjs.map +1 -1
- package/dist/src/ssr/index.js +1 -1
- package/dist/src/ssr/index.js.map +1 -1
- package/dist/ssr/index.d.ts +1 -1
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/store.cjs +1 -1
- package/dist/store.cjs.map +1 -1
- package/dist/store.d.ts +13 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +40 -38
- package/dist/store.js.map +1 -1
- package/dist/subscription.cjs.map +1 -1
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js.map +1 -1
- package/dist/symbols.cjs +1 -1
- package/dist/symbols.cjs.map +1 -1
- package/dist/symbols.js +2 -2
- package/dist/symbols.js.map +1 -1
- package/dist/tracking.cjs +1 -1
- package/dist/tracking.cjs.map +1 -1
- package/dist/tracking.d.ts +0 -31
- package/dist/tracking.d.ts.map +1 -1
- package/dist/tracking.js +10 -58
- package/dist/tracking.js.map +1 -1
- package/dist/types.d.ts +10 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities.cjs +1 -1
- package/dist/utilities.cjs.map +1 -1
- package/dist/utilities.d.ts +2 -2
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +5 -5
- package/dist/utilities.js.map +1 -1
- package/package.json +7 -1
- package/dist/store-history.cjs +0 -2
- package/dist/store-history.d.ts.map +0 -1
- package/dist/store-history.js.map +0 -1
package/dist/effect.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getDevToolsHook as e } from "./devtools-hook.js";
|
|
2
2
|
import { collectErrors as t, rethrowWith as n, runAll as r } from "./_error-utils.js";
|
|
3
3
|
import { RippleDisposedScopeError as i, RippleInfiniteLoopError as a, RippleInvalidCleanupError as o } from "./errors.js";
|
|
4
|
-
import { getScopeCleanups as s,
|
|
4
|
+
import { getScopeCleanups as s, withScopeCleanups as c } from "./execution-context.js";
|
|
5
5
|
import "./scheduling.js";
|
|
6
|
-
import { AsyncSubscriptionImpl as
|
|
7
|
-
import { IS_COMPUTED as
|
|
6
|
+
import { AsyncSubscriptionImpl as l, SubscriptionImpl as u } from "./subscription.js";
|
|
7
|
+
import { IS_COMPUTED as d, SUPPRESS_RUN_EVENT as f } from "./symbols.js";
|
|
8
|
+
import { getTracking as p, withTracking as m } from "./tracking.js";
|
|
8
9
|
//#region src/effect.ts
|
|
9
|
-
var
|
|
10
|
+
var h = (e, t) => {
|
|
10
11
|
if (t === "sync" || t === void 0) return e;
|
|
11
12
|
let n = !1;
|
|
12
13
|
return () => {
|
|
@@ -14,75 +15,75 @@ var m = (e, t) => {
|
|
|
14
15
|
n = !1, e();
|
|
15
16
|
}));
|
|
16
17
|
};
|
|
17
|
-
},
|
|
18
|
-
let l = c?.scheduler ?? "sync",
|
|
19
|
-
if (
|
|
20
|
-
let e = [...
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
if (!
|
|
24
|
-
if (
|
|
25
|
-
|
|
18
|
+
}, g = (i, c) => {
|
|
19
|
+
let l = c?.scheduler ?? "sync", p = c?.name, g = c?.[f], _ = [], v = /* @__PURE__ */ new Set(), y = /* @__PURE__ */ new Map(), b = !1, x = !1, S = !1, C = () => {
|
|
20
|
+
if (y.clear(), _.length === 0 && v.size === 0) return;
|
|
21
|
+
let e = [..._, ...v];
|
|
22
|
+
_.length = 0, v.clear(), r(e, "effect teardown errors");
|
|
23
|
+
}, w = () => {
|
|
24
|
+
if (!S) {
|
|
25
|
+
if (b) {
|
|
26
|
+
x = !0;
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
b = !0;
|
|
29
30
|
try {
|
|
30
31
|
let r = 0;
|
|
31
32
|
do {
|
|
32
|
-
if (++r > 100) throw new a(`infinite effect loop (> 100 iterations)${
|
|
33
|
-
|
|
33
|
+
if (++r > 100) throw new a(`infinite effect loop (> 100 iterations)${p ? ` in "${p}"` : ""}`);
|
|
34
|
+
x = !1, C(), g || e()?.run?.({ name: p });
|
|
34
35
|
let s;
|
|
35
36
|
try {
|
|
36
|
-
s =
|
|
37
|
-
cleanups:
|
|
38
|
-
deps:
|
|
39
|
-
effect:
|
|
37
|
+
s = m({
|
|
38
|
+
cleanups: _,
|
|
39
|
+
deps: y,
|
|
40
|
+
effect: T,
|
|
40
41
|
kind: "effect",
|
|
41
|
-
subscriptions:
|
|
42
|
+
subscriptions: v
|
|
42
43
|
}, i);
|
|
43
44
|
} catch (e) {
|
|
44
|
-
let r = [...t(
|
|
45
|
-
|
|
45
|
+
let r = [...t(v), ...t(_)];
|
|
46
|
+
_.length = 0, v.clear(), n(e, r, "effect failure with cleanup errors");
|
|
46
47
|
}
|
|
47
48
|
if (s !== void 0 && typeof s != "function") throw new o(`effect() returned ${typeof s} — expected a cleanup function or void.`);
|
|
48
|
-
typeof s == "function" &&
|
|
49
|
-
} while (
|
|
49
|
+
typeof s == "function" && _.push(s);
|
|
50
|
+
} while (x && !S);
|
|
50
51
|
} finally {
|
|
51
|
-
|
|
52
|
+
b = !1;
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
let
|
|
57
|
-
|
|
55
|
+
}, T = h(w, l);
|
|
56
|
+
w();
|
|
57
|
+
let E = new u(() => {
|
|
58
|
+
S || (S = !0, C(), e()?.dispose?.({
|
|
58
59
|
kind: "effect",
|
|
59
|
-
name:
|
|
60
|
+
name: p
|
|
60
61
|
}));
|
|
61
62
|
});
|
|
62
|
-
return s()?.push(() =>
|
|
63
|
-
dispose: () =>
|
|
63
|
+
return s()?.push(() => E.dispose()), {
|
|
64
|
+
dispose: () => E.dispose(),
|
|
64
65
|
get disposed() {
|
|
65
|
-
return
|
|
66
|
+
return E.disposed;
|
|
66
67
|
},
|
|
67
68
|
getDependencies() {
|
|
68
|
-
return [...
|
|
69
|
-
kind:
|
|
69
|
+
return [...y.keys()].map((e) => ({
|
|
70
|
+
kind: d in e ? "computed" : "signal",
|
|
70
71
|
name: e.name
|
|
71
72
|
}));
|
|
72
73
|
},
|
|
73
74
|
[Symbol.dispose]() {
|
|
74
|
-
|
|
75
|
+
E.dispose();
|
|
75
76
|
}
|
|
76
77
|
};
|
|
77
|
-
},
|
|
78
|
+
}, _ = (e, t) => {
|
|
78
79
|
let n = null, r = null, i = null, a = t?.onError ?? ((e) => {
|
|
79
80
|
queueMicrotask(() => {
|
|
80
81
|
throw e;
|
|
81
82
|
});
|
|
82
83
|
});
|
|
83
|
-
return new
|
|
84
|
+
return new l(g(() => {
|
|
84
85
|
n?.abort(), r?.(), r = null, n = new AbortController();
|
|
85
|
-
let { signal: t } = n, o =
|
|
86
|
+
let { signal: t } = n, o = y(), s;
|
|
86
87
|
return i = s = (async () => {
|
|
87
88
|
try {
|
|
88
89
|
let n = await e(t, o);
|
|
@@ -99,8 +100,8 @@ var m = (e, t) => {
|
|
|
99
100
|
let e = i;
|
|
100
101
|
e && await e;
|
|
101
102
|
}, () => i);
|
|
102
|
-
},
|
|
103
|
-
let t =
|
|
103
|
+
}, v = (e) => {
|
|
104
|
+
let t = p();
|
|
104
105
|
if (t?.kind === "effect") {
|
|
105
106
|
t.cleanups.push(e);
|
|
106
107
|
return;
|
|
@@ -111,30 +112,30 @@ var m = (e, t) => {
|
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
throw new o("onCleanup() must be called from within an active effect or scope.");
|
|
114
|
-
},
|
|
115
|
+
}, y = (e) => {
|
|
115
116
|
let t = [], n = new AbortController(), a = !1, o = (e) => {
|
|
116
117
|
if (a) throw new i("Cannot register cleanup on a disposed scope.");
|
|
117
118
|
t.push(e);
|
|
118
119
|
}, s = (e) => {
|
|
119
120
|
if (a) throw new i("Cannot run inside a disposed scope.");
|
|
120
|
-
return
|
|
121
|
-
},
|
|
121
|
+
return c(t, e);
|
|
122
|
+
}, l = () => {
|
|
122
123
|
a || (a = !0, n.abort(), r([...t].reverse(), "scope cleanup errors"), t.length = 0);
|
|
123
124
|
}, u = {
|
|
124
125
|
add: o,
|
|
125
126
|
get disposalSignal() {
|
|
126
127
|
return n.signal;
|
|
127
128
|
},
|
|
128
|
-
dispose:
|
|
129
|
+
dispose: l,
|
|
129
130
|
get disposed() {
|
|
130
131
|
return a;
|
|
131
132
|
},
|
|
132
133
|
run: s,
|
|
133
|
-
[Symbol.dispose]:
|
|
134
|
+
[Symbol.dispose]: l
|
|
134
135
|
};
|
|
135
136
|
return e && s(e), u;
|
|
136
137
|
};
|
|
137
|
-
function
|
|
138
|
+
function b(e, t, n) {
|
|
138
139
|
let r = n?.equals ?? Object.is, i, a = (e, n) => {
|
|
139
140
|
i?.(), i = void 0;
|
|
140
141
|
let r = t(e, n);
|
|
@@ -142,7 +143,7 @@ function y(e, t, n) {
|
|
|
142
143
|
typeof r == "function" && (i = r);
|
|
143
144
|
}, c;
|
|
144
145
|
if (typeof e == "function") {
|
|
145
|
-
let t, o = !0, s =
|
|
146
|
+
let t, o = !0, s = g(() => {
|
|
146
147
|
let i = e();
|
|
147
148
|
if (o) {
|
|
148
149
|
o = !1, t = i;
|
|
@@ -153,7 +154,7 @@ function y(e, t, n) {
|
|
|
153
154
|
t = i, a(i, e), n?.once && c.dispose();
|
|
154
155
|
}
|
|
155
156
|
}, { name: n?.name });
|
|
156
|
-
c = new
|
|
157
|
+
c = new u(() => {
|
|
157
158
|
i?.(), i = void 0, s.dispose();
|
|
158
159
|
}), n?.immediate && (a(t, void 0), n.once && c.dispose());
|
|
159
160
|
} else {
|
|
@@ -161,13 +162,13 @@ function y(e, t, n) {
|
|
|
161
162
|
let i = e.peek();
|
|
162
163
|
r(t, i) || (a(i, t), t = i, n?.once && c.dispose());
|
|
163
164
|
});
|
|
164
|
-
c = new
|
|
165
|
+
c = new u(() => {
|
|
165
166
|
i?.(), i = void 0, o.dispose();
|
|
166
167
|
}), n?.immediate && (a(t, void 0), n.once && c.dispose());
|
|
167
168
|
}
|
|
168
169
|
return s()?.push(() => c.dispose()), c;
|
|
169
170
|
}
|
|
170
171
|
//#endregion
|
|
171
|
-
export {
|
|
172
|
+
export { g as effect, _ as effectAsync, v as onCleanup, y as scope, b as watch };
|
|
172
173
|
|
|
173
174
|
//# sourceMappingURL=effect.js.map
|
package/dist/effect.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.js","names":[],"sources":["../src/effect.ts"],"sourcesContent":["import type { ReactiveBase } from './reactive-base';\nimport type {\n AsyncEffectCallback,\n AsyncSubscription,\n CleanupFn,\n DepInfo,\n EffectAsyncOptions,\n EffectCallback,\n EffectHandle,\n EffectOptions,\n EqualityFn,\n Readable,\n Scope,\n Subscriber,\n Subscription,\n WatchOptions,\n} from './types';\n\nimport { collectErrors, rethrowWith, runAll } from './_error-utils';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleDisposedScopeError, RippleInfiniteLoopError, RippleInvalidCleanupError } from './errors';\nimport { DEFAULT_MAX_ITERATIONS } from './scheduling';\nimport { AsyncSubscriptionImpl, SubscriptionImpl } from './subscription';\nimport { IS_COMPUTED } from './symbols';\nimport { getScopeCleanups, getTracking, withScopeCleanups, withTracking } from './tracking';\n\n/**\n * Wraps a core `run` function with a microtask-based scheduler that coalesces rapid re-runs.\n * The `subscriber` returned here is what gets registered in signal subscriber sets —\n * so notifications call the deferred wrapper, not `run` directly.\n *\n * The scheduler identity is stable for the lifetime of the effect.\n * Never replace `subscriber` with a new function after creation — doing so would break\n * the removeEffectSub calls that rely on reference identity.\n */\nconst withScheduler = (run: Subscriber, scheduler: EffectOptions['scheduler']): Subscriber => {\n if (scheduler === 'sync' || scheduler === undefined) return run;\n\n // 'microtask'\n let scheduled = false;\n\n return (): void => {\n if (scheduled) return;\n\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n run();\n });\n };\n};\n\n/**\n * Creates a reactive side-effect.\n * Runs immediately, re-runs whenever tracked dependencies change.\n * The callback may return a cleanup function that runs before the next re-run and on dispose.\n *\n * @example\n * ```ts\n * const stop = effect(() => {\n * document.title = count.value.toString();\n * return () => { document.title = ''; }; // optional cleanup\n * });\n *\n * stop.dispose(); // or: using stop = effect(...)\n * console.log(stop.getDependencies()); // reactive graph introspection\n * ```\n */\nexport const effect = (fn: EffectCallback, options?: EffectOptions): EffectHandle => {\n const scheduler = options?.scheduler ?? 'sync';\n const effectName = options?.name;\n\n const runCleanups: CleanupFn[] = [];\n const subscriptions = new Set<CleanupFn>();\n const currentDeps = new Map<ReactiveBase<unknown>, number>();\n let isRunning = false;\n let isDirty = false;\n let isDisposed = false;\n\n const teardown = (): void => {\n currentDeps.clear();\n\n if (runCleanups.length === 0 && subscriptions.size === 0) return;\n\n const toRun = [...runCleanups, ...subscriptions];\n\n runCleanups.length = 0;\n subscriptions.clear();\n\n runAll(toRun, 'effect teardown errors');\n };\n\n // The inner do-while handles self-cascade: when the effect body writes to a signal it reads,\n // the recursive flush sets isDirty via the isRunning guard.\n const run: Subscriber = (): void => {\n if (isDisposed) return;\n\n if (isRunning) {\n isDirty = true;\n\n return;\n }\n\n isRunning = true;\n\n try {\n let iterations = 0;\n\n do {\n if (++iterations > DEFAULT_MAX_ITERATIONS) {\n const label = effectName ? ` in \"${effectName}\"` : '';\n\n throw new RippleInfiniteLoopError(`infinite effect loop (> ${DEFAULT_MAX_ITERATIONS} iterations)${label}`);\n }\n\n isDirty = false;\n teardown();\n\n getDevToolsHook()?.run?.({ name: effectName });\n\n let returnedCleanup: CleanupFn | void = undefined;\n\n try {\n returnedCleanup = withTracking(\n { cleanups: runCleanups, deps: currentDeps, effect: subscriber, kind: 'effect', subscriptions },\n fn,\n );\n } catch (error) {\n const cleanupErrors = [...collectErrors(subscriptions), ...collectErrors(runCleanups)];\n\n runCleanups.length = 0;\n subscriptions.clear();\n rethrowWith(error, cleanupErrors, 'effect failure with cleanup errors');\n }\n\n if (returnedCleanup !== undefined && typeof returnedCleanup !== 'function') {\n throw new RippleInvalidCleanupError(\n `effect() returned ${typeof returnedCleanup} — expected a cleanup function or void.`,\n );\n }\n\n if (typeof returnedCleanup === 'function') runCleanups.push(returnedCleanup);\n } while (isDirty && !isDisposed);\n } finally {\n isRunning = false;\n }\n };\n\n // subscriber is what gets registered in signal subscriber Sets.\n // For 'sync' it is the same as run; for deferred schedulers it's the debouncing wrapper.\n const subscriber = withScheduler(run, scheduler);\n\n // Always run synchronously on creation to establish initial tracking.\n run();\n\n const sub = new SubscriptionImpl(() => {\n if (isDisposed) return;\n\n isDisposed = true;\n teardown();\n getDevToolsHook()?.dispose?.({ kind: 'effect', name: effectName });\n });\n\n // Auto-register disposal into the enclosing scope (if any).\n // Uses the separate scope stack — orthogonal to the effect/computed tracking context.\n getScopeCleanups()?.push(() => sub.dispose());\n\n const handle: EffectHandle = {\n dispose: () => sub.dispose(),\n get disposed() {\n return sub.disposed;\n },\n getDependencies(): ReadonlyArray<DepInfo> {\n return [...currentDeps.keys()].map((src) => ({\n kind: IS_COMPUTED in (src as object) ? 'computed' : 'signal',\n name: src.name,\n }));\n },\n [Symbol.dispose]() {\n sub.dispose();\n },\n };\n\n return handle;\n};\n\n/**\n * Like `effect()`, but the callback is async and receives an AbortSignal that fires when the\n * effect re-runs or is disposed. Read reactive dependencies synchronously before the first\n * `await` to register them as tracked.\n *\n * Returns an {@link AsyncSubscription} — call `await stop[Symbol.asyncDispose]()` to await\n * full teardown, or `stop.run()` to await just the current in-flight run.\n *\n * @example\n * ```ts\n * const userId = signal('u1');\n * const stop = effectAsync(async (signal) => {\n * const id = userId.value; // sync dep — tracked\n * const data = await fetchUser(id, { signal }); // aborted automatically if id changes\n * renderUser(data);\n * });\n *\n * await stop[Symbol.asyncDispose]();\n * ```\n */\nexport const effectAsync = (fn: AsyncEffectCallback, options?: EffectAsyncOptions): AsyncSubscription => {\n let controller: AbortController | null = null;\n let asyncCleanup: CleanupFn | null = null;\n let currentRunPromise: Promise<void> | null = null;\n\n const onError =\n options?.onError ??\n ((err: unknown) => {\n queueMicrotask(() => {\n throw err;\n });\n });\n\n const syncStop = effect(\n () => {\n controller?.abort();\n asyncCleanup?.();\n asyncCleanup = null;\n\n controller = new AbortController();\n\n const { signal } = controller;\n const runOwner = scope(); // per-run owner scope for async body resources\n\n let theRun!: Promise<void>;\n\n currentRunPromise = theRun = (async () => {\n try {\n const returned = await fn(signal, runOwner);\n\n if (typeof returned === 'function') {\n if (signal.aborted) {\n // Aborted while async fn was running — run cleanup immediately.\n returned();\n } else {\n asyncCleanup = returned;\n }\n }\n } catch (err) {\n if (!signal.aborted) onError(err);\n } finally {\n runOwner.dispose();\n\n if (currentRunPromise === theRun) currentRunPromise = null;\n }\n })();\n\n return () => {\n controller?.abort();\n asyncCleanup?.();\n asyncCleanup = null;\n };\n },\n { name: options?.name },\n );\n\n return new AsyncSubscriptionImpl(\n syncStop,\n async () => {\n const runningPromise = currentRunPromise;\n\n if (runningPromise) await runningPromise;\n },\n () => currentRunPromise,\n );\n};\n\nexport const onCleanup = (fn: CleanupFn): void => {\n const ctx = getTracking();\n\n if (ctx?.kind === 'effect') {\n ctx.cleanups.push(fn);\n\n return;\n }\n\n const scopeCleanups = getScopeCleanups();\n\n if (scopeCleanups !== null) {\n scopeCleanups.push(fn);\n\n return;\n }\n\n throw new RippleInvalidCleanupError('onCleanup() must be called from within an active effect or scope.');\n};\n\n/**\n * Creates a lifecycle scope that collects `onCleanup()` registrations and runs\n * them in reverse order when `dispose()` is called.\n *\n * The optional `setup` callback is run immediately inside the scope so that\n * `onCleanup()` calls in setup are captured without requiring a separate\n * `scope.run(setup)` call.\n *\n * Use `scope.add(fn)` to explicitly register a cleanup into this scope from\n * anywhere — including from inside an effect body where `onCleanup()` would otherwise\n * go to the effect's own cleanup queue.\n *\n * @example\n * ```ts\n * const s = scope(() => {\n * onCleanup(() => expensiveCleanup()); // captured by scope\n * });\n *\n * const stop = effect(() => {\n * void count.value;\n * s.add(() => doWork()); // explicit: goes to scope, not effect\n * onCleanup(() => cheapCleanup()); // goes to effect\n * });\n *\n * // later:\n * s.dispose(); // or: using s = scope(...)\n * ```\n */\nexport const scope = (setup?: () => void): Scope => {\n const cleanups: CleanupFn[] = [];\n const ac = new AbortController();\n let disposed = false;\n\n const scopeOnCleanup = (fn: CleanupFn): void => {\n if (disposed) throw new RippleDisposedScopeError('Cannot register cleanup on a disposed scope.');\n\n cleanups.push(fn);\n };\n\n const run = <T>(fn: () => T): T => {\n if (disposed) throw new RippleDisposedScopeError('Cannot run inside a disposed scope.');\n\n return withScopeCleanups(cleanups, fn);\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n runAll([...cleanups].reverse(), 'scope cleanup errors');\n cleanups.length = 0;\n };\n\n const api: Scope = {\n add: scopeOnCleanup,\n get disposalSignal() {\n return ac.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n run,\n [Symbol.dispose]: dispose,\n };\n\n if (setup) run(setup);\n\n return api;\n};\n\n// ── watch() ───────────────────────────────────────────────────────────────────\n\n/**\n * Observes a reactive source and calls `callback` whenever its value changes.\n * Does NOT invoke `callback` on creation (unless `immediate: true` is passed).\n *\n * `source` may be a single `Readable<T>` (signal, computed, or store lens), or a\n * function that reads multiple reactive sources — the function form tracks every\n * dependency read inside it, like `computed()`, without needing an intermediate\n * `computed()` node.\n *\n * When called without `immediate`, `prev` is always `T` — the previous value.\n * When `immediate: true` is passed, `prev` is `T | undefined` on the first call.\n *\n * @example\n * ```ts\n * const stop = watch(count, (next, prev) => {\n * console.log(`count changed: ${prev} → ${next}`);\n * });\n *\n * stop.dispose();\n * ```\n *\n * @example\n * ```ts\n * // Function form — tracks every signal read inside, re-runs when any changes.\n * const stopSum = watch(\n * () => a.value + b.value,\n * (sum, prevSum) => console.log(`sum: ${prevSum} → ${sum}`),\n * );\n *\n * stopSum.dispose();\n * ```\n */\nexport function watch<T>(\n source: Readable<T>,\n callback: (value: T, prev: T | undefined) => CleanupFn | void,\n options?: WatchOptions<T>,\n): Subscription;\nexport function watch<T>(\n source: () => T,\n callback: (value: T, prev: T | undefined) => CleanupFn | void,\n options?: WatchOptions<T>,\n): Subscription;\nexport function watch<T>(\n source: Readable<T> | (() => T),\n callback: (value: T, prev: T | undefined) => CleanupFn | void,\n options?: WatchOptions<T>,\n): Subscription {\n const equals: EqualityFn<T> = options?.equals ?? Object.is;\n let pendingCleanup: CleanupFn | undefined;\n const invokeCallback = (next: T, p: T | undefined): void => {\n pendingCleanup?.();\n pendingCleanup = undefined;\n\n const returned = callback(next, p);\n\n if (returned !== undefined && typeof returned !== 'function') {\n throw new RippleInvalidCleanupError(\n `watch() callback returned ${typeof returned} — expected a cleanup function or void.`,\n );\n }\n\n if (typeof returned === 'function') pendingCleanup = returned;\n };\n\n // Declared before the subscriber/effect closures so both they and the immediate\n // path can call watchSub.dispose() when once:true. Subscribers/effects always\n // (re-)run after watchSub is assigned, since it's assigned synchronously right after.\n\n let watchSub!: SubscriptionImpl;\n\n if (typeof source === 'function') {\n // Function form — track every dependency read inside `source` via a real effect,\n // mirroring computed()'s dependency collection. `first` suppresses the callback\n // on the initial (tracking-establishing) run — handled explicitly below (after\n // watchSub exists) instead of inside this synchronous first run, since effect()\n // runs its body immediately and watchSub isn't assigned until after it returns.\n let prev!: T;\n let first = true;\n\n const stopEffect = effect(\n () => {\n const next = source();\n\n if (first) {\n first = false;\n prev = next;\n\n return;\n }\n\n if (!equals(prev, next)) {\n const p = prev;\n\n prev = next;\n invokeCallback(next, p);\n\n if (options?.once) watchSub.dispose();\n }\n },\n { name: options?.name },\n );\n\n watchSub = new SubscriptionImpl(() => {\n pendingCleanup?.();\n pendingCleanup = undefined;\n stopEffect.dispose();\n });\n\n if (options?.immediate) {\n invokeCallback(prev, undefined);\n\n if (options.once) watchSub.dispose();\n }\n } else {\n let prev: T = source.peek();\n\n const innerSub = source.subscribe(() => {\n const next = source.peek();\n\n if (!equals(prev, next)) {\n invokeCallback(next, prev);\n prev = next;\n\n if (options?.once) watchSub.dispose();\n }\n });\n\n watchSub = new SubscriptionImpl(() => {\n pendingCleanup?.();\n pendingCleanup = undefined;\n innerSub.dispose();\n });\n\n if (options?.immediate) {\n invokeCallback(prev, undefined);\n\n if (options.once) watchSub.dispose();\n }\n }\n\n // Auto-register disposal into the enclosing scope (if any) — matches effect() behaviour.\n getScopeCleanups()?.push(() => watchSub.dispose());\n\n return watchSub;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"effect.js","names":[],"sources":["../src/effect.ts"],"sourcesContent":["import type { ReactiveBase } from './reactive-base';\nimport type {\n AsyncEffectCallback,\n AsyncSubscription,\n CleanupFn,\n DepInfo,\n EffectAsyncOptions,\n EffectCallback,\n EffectHandle,\n EffectOptions,\n EqualityFn,\n Readable,\n Scope,\n Subscriber,\n Subscription,\n WatchOptions,\n} from './types';\n\nimport { collectErrors, rethrowWith, runAll } from './_error-utils';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleDisposedScopeError, RippleInfiniteLoopError, RippleInvalidCleanupError } from './errors';\nimport { getScopeCleanups, withScopeCleanups } from './execution-context';\nimport { DEFAULT_MAX_ITERATIONS } from './scheduling';\nimport { AsyncSubscriptionImpl, SubscriptionImpl } from './subscription';\nimport { IS_COMPUTED, SUPPRESS_RUN_EVENT } from './symbols';\nimport { getTracking, withTracking } from './tracking';\n\n/**\n * Wraps a core `run` function with a microtask-based scheduler that coalesces rapid re-runs.\n * The `subscriber` returned here is what gets registered in signal subscriber sets —\n * so notifications call the deferred wrapper, not `run` directly.\n *\n * The scheduler identity is stable for the lifetime of the effect.\n * Never replace `subscriber` with a new function after creation — doing so would break\n * the removeEffectSub calls that rely on reference identity.\n */\nconst withScheduler = (run: Subscriber, scheduler: EffectOptions['scheduler']): Subscriber => {\n if (scheduler === 'sync' || scheduler === undefined) return run;\n\n // 'microtask'\n let scheduled = false;\n\n return (): void => {\n if (scheduled) return;\n\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n run();\n });\n };\n};\n\n/**\n * Creates a reactive side-effect.\n * Runs immediately, re-runs whenever tracked dependencies change.\n * The callback may return a cleanup function that runs before the next re-run and on dispose.\n *\n * @example\n * ```ts\n * const stop = effect(() => {\n * document.title = count.value.toString();\n * return () => { document.title = ''; }; // optional cleanup\n * });\n *\n * stop.dispose(); // or: using stop = effect(...)\n * console.log(stop.getDependencies()); // reactive graph introspection\n * ```\n */\nexport const effect = (fn: EffectCallback, options?: EffectOptions): EffectHandle => {\n const scheduler = options?.scheduler ?? 'sync';\n const effectName = options?.name;\n // Internal-only escape hatch — see SUPPRESS_RUN_EVENT's JSDoc in symbols.ts.\n // No public option can ever reach this: EffectOptions doesn't declare the key, and\n // the symbol is never exported from index.ts.\n const suppressRunEvent = (options as Partial<Record<typeof SUPPRESS_RUN_EVENT, boolean>> | undefined)?.[\n SUPPRESS_RUN_EVENT\n ];\n\n const runCleanups: CleanupFn[] = [];\n const subscriptions = new Set<CleanupFn>();\n const currentDeps = new Map<ReactiveBase<unknown>, number>();\n let isRunning = false;\n let isDirty = false;\n let isDisposed = false;\n\n const teardown = (): void => {\n currentDeps.clear();\n\n if (runCleanups.length === 0 && subscriptions.size === 0) return;\n\n const toRun = [...runCleanups, ...subscriptions];\n\n runCleanups.length = 0;\n subscriptions.clear();\n\n runAll(toRun, 'effect teardown errors');\n };\n\n // The inner do-while handles self-cascade: when the effect body writes to a signal it reads,\n // the recursive flush sets isDirty via the isRunning guard.\n const run: Subscriber = (): void => {\n if (isDisposed) return;\n\n if (isRunning) {\n isDirty = true;\n\n return;\n }\n\n isRunning = true;\n\n try {\n let iterations = 0;\n\n do {\n if (++iterations > DEFAULT_MAX_ITERATIONS) {\n const label = effectName ? ` in \"${effectName}\"` : '';\n\n throw new RippleInfiniteLoopError(`infinite effect loop (> ${DEFAULT_MAX_ITERATIONS} iterations)${label}`);\n }\n\n isDirty = false;\n teardown();\n\n if (!suppressRunEvent) getDevToolsHook()?.run?.({ name: effectName });\n\n let returnedCleanup: CleanupFn | void = undefined;\n\n try {\n returnedCleanup = withTracking(\n { cleanups: runCleanups, deps: currentDeps, effect: subscriber, kind: 'effect', subscriptions },\n fn,\n );\n } catch (error) {\n const cleanupErrors = [...collectErrors(subscriptions), ...collectErrors(runCleanups)];\n\n runCleanups.length = 0;\n subscriptions.clear();\n rethrowWith(error, cleanupErrors, 'effect failure with cleanup errors');\n }\n\n if (returnedCleanup !== undefined && typeof returnedCleanup !== 'function') {\n throw new RippleInvalidCleanupError(\n `effect() returned ${typeof returnedCleanup} — expected a cleanup function or void.`,\n );\n }\n\n if (typeof returnedCleanup === 'function') runCleanups.push(returnedCleanup);\n } while (isDirty && !isDisposed);\n } finally {\n isRunning = false;\n }\n };\n\n // subscriber is what gets registered in signal subscriber Sets.\n // For 'sync' it is the same as run; for deferred schedulers it's the debouncing wrapper.\n const subscriber = withScheduler(run, scheduler);\n\n // Always run synchronously on creation to establish initial tracking.\n run();\n\n const sub = new SubscriptionImpl(() => {\n if (isDisposed) return;\n\n isDisposed = true;\n teardown();\n getDevToolsHook()?.dispose?.({ kind: 'effect', name: effectName });\n });\n\n // Auto-register disposal into the enclosing scope (if any).\n // Uses the separate scope stack — orthogonal to the effect/computed tracking context.\n getScopeCleanups()?.push(() => sub.dispose());\n\n const handle: EffectHandle = {\n dispose: () => sub.dispose(),\n get disposed() {\n return sub.disposed;\n },\n getDependencies(): ReadonlyArray<DepInfo> {\n return [...currentDeps.keys()].map((src) => ({\n kind: IS_COMPUTED in (src as object) ? 'computed' : 'signal',\n name: src.name,\n }));\n },\n [Symbol.dispose]() {\n sub.dispose();\n },\n };\n\n return handle;\n};\n\n/**\n * Like `effect()`, but the callback is async and receives an AbortSignal that fires when the\n * effect re-runs or is disposed. Read reactive dependencies synchronously before the first\n * `await` to register them as tracked.\n *\n * Returns an {@link AsyncSubscription} — call `await stop[Symbol.asyncDispose]()` to await\n * full teardown, or `stop.run()` to await just the current in-flight run.\n *\n * @example\n * ```ts\n * const userId = signal('u1');\n * const stop = effectAsync(async (signal) => {\n * const id = userId.value; // sync dep — tracked\n * const data = await fetchUser(id, { signal }); // aborted automatically if id changes\n * renderUser(data);\n * });\n *\n * await stop[Symbol.asyncDispose]();\n * ```\n */\nexport const effectAsync = (fn: AsyncEffectCallback, options?: EffectAsyncOptions): AsyncSubscription => {\n let controller: AbortController | null = null;\n let asyncCleanup: CleanupFn | null = null;\n let currentRunPromise: Promise<void> | null = null;\n\n const onError =\n options?.onError ??\n ((err: unknown) => {\n queueMicrotask(() => {\n throw err;\n });\n });\n\n const syncStop = effect(\n () => {\n controller?.abort();\n asyncCleanup?.();\n asyncCleanup = null;\n\n controller = new AbortController();\n\n const { signal } = controller;\n const runOwner = scope(); // per-run owner scope for async body resources\n\n let theRun!: Promise<void>;\n\n currentRunPromise = theRun = (async () => {\n try {\n const returned = await fn(signal, runOwner);\n\n if (typeof returned === 'function') {\n if (signal.aborted) {\n // Aborted while async fn was running — run cleanup immediately.\n returned();\n } else {\n asyncCleanup = returned;\n }\n }\n } catch (err) {\n if (!signal.aborted) onError(err);\n } finally {\n runOwner.dispose();\n\n if (currentRunPromise === theRun) currentRunPromise = null;\n }\n })();\n\n return () => {\n controller?.abort();\n asyncCleanup?.();\n asyncCleanup = null;\n };\n },\n { name: options?.name },\n );\n\n return new AsyncSubscriptionImpl(\n syncStop,\n async () => {\n const runningPromise = currentRunPromise;\n\n if (runningPromise) await runningPromise;\n },\n () => currentRunPromise,\n );\n};\n\nexport const onCleanup = (fn: CleanupFn): void => {\n const ctx = getTracking();\n\n if (ctx?.kind === 'effect') {\n ctx.cleanups.push(fn);\n\n return;\n }\n\n const scopeCleanups = getScopeCleanups();\n\n if (scopeCleanups !== null) {\n scopeCleanups.push(fn);\n\n return;\n }\n\n throw new RippleInvalidCleanupError('onCleanup() must be called from within an active effect or scope.');\n};\n\n/**\n * Creates a lifecycle scope that collects `onCleanup()` registrations and runs\n * them in reverse order when `dispose()` is called.\n *\n * The optional `setup` callback is run immediately inside the scope so that\n * `onCleanup()` calls in setup are captured without requiring a separate\n * `scope.run(setup)` call.\n *\n * Use `scope.add(fn)` to explicitly register a cleanup into this scope from\n * anywhere — including from inside an effect body where `onCleanup()` would otherwise\n * go to the effect's own cleanup queue.\n *\n * @example\n * ```ts\n * const s = scope(() => {\n * onCleanup(() => expensiveCleanup()); // captured by scope\n * });\n *\n * const stop = effect(() => {\n * void count.value;\n * s.add(() => doWork()); // explicit: goes to scope, not effect\n * onCleanup(() => cheapCleanup()); // goes to effect\n * });\n *\n * // later:\n * s.dispose(); // or: using s = scope(...)\n * ```\n */\nexport const scope = (setup?: () => void): Scope => {\n const cleanups: CleanupFn[] = [];\n const ac = new AbortController();\n let disposed = false;\n\n const scopeOnCleanup = (fn: CleanupFn): void => {\n if (disposed) throw new RippleDisposedScopeError('Cannot register cleanup on a disposed scope.');\n\n cleanups.push(fn);\n };\n\n const run = <T>(fn: () => T): T => {\n if (disposed) throw new RippleDisposedScopeError('Cannot run inside a disposed scope.');\n\n return withScopeCleanups(cleanups, fn);\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n runAll([...cleanups].reverse(), 'scope cleanup errors');\n cleanups.length = 0;\n };\n\n const api: Scope = {\n add: scopeOnCleanup,\n get disposalSignal() {\n return ac.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n run,\n [Symbol.dispose]: dispose,\n };\n\n if (setup) run(setup);\n\n return api;\n};\n\n// ── watch() ───────────────────────────────────────────────────────────────────\n\n/**\n * Observes a reactive source and calls `callback` whenever its value changes.\n * Does NOT invoke `callback` on creation (unless `immediate: true` is passed).\n *\n * `source` may be a single `Readable<T>` (signal, computed, or store lens), or a\n * function that reads multiple reactive sources — the function form tracks every\n * dependency read inside it, like `computed()`, without needing an intermediate\n * `computed()` node.\n *\n * When called without `immediate`, `prev` is always `T` — the previous value.\n * When `immediate: true` is passed, `prev` is `T | undefined` on the first call.\n *\n * @example\n * ```ts\n * const stop = watch(count, (next, prev) => {\n * console.log(`count changed: ${prev} → ${next}`);\n * });\n *\n * stop.dispose();\n * ```\n *\n * @example\n * ```ts\n * // Function form — tracks every signal read inside, re-runs when any changes.\n * const stopSum = watch(\n * () => a.value + b.value,\n * (sum, prevSum) => console.log(`sum: ${prevSum} → ${sum}`),\n * );\n *\n * stopSum.dispose();\n * ```\n */\nexport function watch<T>(\n source: Readable<T>,\n callback: (value: T, prev: T | undefined) => CleanupFn | void,\n options?: WatchOptions<T>,\n): Subscription;\nexport function watch<T>(\n source: () => T,\n callback: (value: T, prev: T | undefined) => CleanupFn | void,\n options?: WatchOptions<T>,\n): Subscription;\nexport function watch<T>(\n source: Readable<T> | (() => T),\n callback: (value: T, prev: T | undefined) => CleanupFn | void,\n options?: WatchOptions<T>,\n): Subscription {\n const equals: EqualityFn<T> = options?.equals ?? Object.is;\n let pendingCleanup: CleanupFn | undefined;\n const invokeCallback = (next: T, p: T | undefined): void => {\n pendingCleanup?.();\n pendingCleanup = undefined;\n\n const returned = callback(next, p);\n\n if (returned !== undefined && typeof returned !== 'function') {\n throw new RippleInvalidCleanupError(\n `watch() callback returned ${typeof returned} — expected a cleanup function or void.`,\n );\n }\n\n if (typeof returned === 'function') pendingCleanup = returned;\n };\n\n // Declared before the subscriber/effect closures so both they and the immediate\n // path can call watchSub.dispose() when once:true. Subscribers/effects always\n // (re-)run after watchSub is assigned, since it's assigned synchronously right after.\n\n let watchSub!: SubscriptionImpl;\n\n if (typeof source === 'function') {\n // Function form — track every dependency read inside `source` via a real effect,\n // mirroring computed()'s dependency collection. `first` suppresses the callback\n // on the initial (tracking-establishing) run — handled explicitly below (after\n // watchSub exists) instead of inside this synchronous first run, since effect()\n // runs its body immediately and watchSub isn't assigned until after it returns.\n let prev!: T;\n let first = true;\n\n const stopEffect = effect(\n () => {\n const next = source();\n\n if (first) {\n first = false;\n prev = next;\n\n return;\n }\n\n if (!equals(prev, next)) {\n const p = prev;\n\n prev = next;\n invokeCallback(next, p);\n\n if (options?.once) watchSub.dispose();\n }\n },\n { name: options?.name },\n );\n\n watchSub = new SubscriptionImpl(() => {\n pendingCleanup?.();\n pendingCleanup = undefined;\n stopEffect.dispose();\n });\n\n if (options?.immediate) {\n invokeCallback(prev, undefined);\n\n if (options.once) watchSub.dispose();\n }\n } else {\n let prev: T = source.peek();\n\n const innerSub = source.subscribe(() => {\n const next = source.peek();\n\n if (!equals(prev, next)) {\n invokeCallback(next, prev);\n prev = next;\n\n if (options?.once) watchSub.dispose();\n }\n });\n\n watchSub = new SubscriptionImpl(() => {\n pendingCleanup?.();\n pendingCleanup = undefined;\n innerSub.dispose();\n });\n\n if (options?.immediate) {\n invokeCallback(prev, undefined);\n\n if (options.once) watchSub.dispose();\n }\n }\n\n // Auto-register disposal into the enclosing scope (if any) — matches effect() behaviour.\n getScopeCleanups()?.push(() => watchSub.dispose());\n\n return watchSub;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=()=>({activeDirty:`a`,batchDepth:0,dirtyWithEffectSubsA:new Set,dirtyWithEffectSubsB:new Set,pendingSubscribers:new Set}),t={scheduling:e(),scopeCleanups:null,tracking:null},n=null,r=()=>n===null?t:n.get(),i=(e,i)=>{let a={...r(),...e};if(n!==null)return n.run(a,i);let o=t;t=a;try{return i()}finally{t=o}},a=()=>r().scheduling,o=()=>n!==null,s=()=>r().scopeCleanups,c=(e,t)=>i({scopeCleanups:e},t),l=e=>{let t=n;return n=e,t};exports._installContextHook=l,exports.createSchedulingState=e,exports.getExecutionContext=r,exports.getSchedulingState=a,exports.getScopeCleanups=s,exports.hasContextHook=o,exports.runInContext=i,exports.withScopeCleanups=c;
|
|
2
|
+
//# sourceMappingURL=execution-context.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-context.cjs","names":[],"sources":["../src/execution-context.ts"],"sourcesContent":["// ── ExecutionContext ──────────────────────────────────────────────────────────\n//\n// Owns the one piece of process-wide (or, once an SSR hook is installed,\n// per-request) mutable state every reactive primitive reads through: the active\n// scheduling bucket, the active scope's cleanup array, and the active tracking\n// context. Split out of tracking.ts so this file's only job is \"where does the\n// current execution context live and how do you swap it\" — tracking.ts owns\n// what a TrackingCtx *means* (dep collection), this file owns how one gets\n// installed for the duration of a call.\n//\n// TrackingCtx is imported type-only here (erased at compile time) so tracking.ts\n// can depend on this file's runtime exports (getExecutionContext, runInContext)\n// without creating an actual circular import — only one direction is real at\n// runtime: tracking.ts -> execution-context.ts.\n\nimport type { ComputedBase } from './reactive-base';\nimport type { TrackingCtx } from './tracking';\nimport type { CleanupFn, Subscriber } from './types';\n\n// ── Scheduling state ────────────────────────────────────────────────────────\n//\n// batchDepth, pendingSubscribers, and the dirty-computed sets live here (rather\n// than as scheduling.ts module-level variables) so the /ripple/ssr sub-path can\n// isolate the flush queue per request via the same context hook that isolates\n// tracking — closing the gap where concurrent SSR requests would otherwise share\n// one flush queue. Outside SSR (no hook installed), this is a single\n// module-level singleton with identical behavior and cost to a plain global.\n\nexport type SchedulingState = {\n activeDirty: 'a' | 'b';\n batchDepth: number;\n readonly dirtyWithEffectSubsA: Set<ComputedBase<unknown>>;\n readonly dirtyWithEffectSubsB: Set<ComputedBase<unknown>>;\n readonly pendingSubscribers: Set<Subscriber>;\n};\n\nexport const createSchedulingState = (): SchedulingState => ({\n activeDirty: 'a',\n batchDepth: 0,\n dirtyWithEffectSubsA: new Set(),\n dirtyWithEffectSubsB: new Set(),\n pendingSubscribers: new Set(),\n});\n\n// ── The context itself ────────────────────────────────────────────────────────\n\nexport type ExecutionContext = {\n readonly scheduling: SchedulingState;\n readonly scopeCleanups: CleanupFn[] | null;\n readonly tracking: TrackingCtx | null;\n};\n\nexport type ContextHook = {\n get(): ExecutionContext;\n run<T>(ctx: ExecutionContext, fn: () => T): T;\n};\n\nlet _ctx: ExecutionContext = { scheduling: createSchedulingState(), scopeCleanups: null, tracking: null };\nlet _hook: ContextHook | null = null;\n\n/** Returns the active context — the installed SSR hook's, or the module-level singleton. */\nexport const getExecutionContext = (): ExecutionContext => (_hook !== null ? _hook.get() : _ctx);\n\n/**\n * Runs `fn` with `patch` merged onto the active context, restoring the previous\n * context afterwards. Delegates to the SSR hook when one is installed (it owns its\n * own restore semantics, e.g. via AsyncLocalStorage); otherwise swaps the module-level\n * singleton directly. Every \"run with a modified context\" helper (withTracking,\n * withScopeCleanups) is a one-line call to this — there is exactly one context\n * swap/restore implementation.\n *\n * Takes a `Partial<ExecutionContext>` (merged internally), not a full replacement\n * context — a caller can only ever override the field(s) it names, so it's\n * impossible to accidentally drop `scheduling` or `scopeCleanups` by forgetting to\n * spread the current context first.\n */\nexport const runInContext = <T>(patch: Partial<ExecutionContext>, fn: () => T): T => {\n const ctx = { ...getExecutionContext(), ...patch };\n\n if (_hook !== null) return _hook.run(ctx, fn);\n\n const prev = _ctx;\n\n _ctx = ctx;\n\n try {\n return fn();\n } finally {\n _ctx = prev;\n }\n};\n\n/** Returns the scheduling-state bucket for the active execution context. */\nexport const getSchedulingState = (): SchedulingState => getExecutionContext().scheduling;\n\n/** `true` once an SSR context hook is installed — scheduling state is then request-isolated. */\nexport const hasContextHook = (): boolean => _hook !== null;\n\n// ── Scope cleanup ─────────────────────────────────────────────────────────────\n\n/** Returns the cleanup array of the innermost active scope, or `null` if not inside a scope. */\nexport const getScopeCleanups = (): CleanupFn[] | null => getExecutionContext().scopeCleanups;\n\n/**\n * Pushes `cleanups` as the active scope cleanup array for the duration of `fn`.\n * Any `onCleanup()` calls inside `fn` register into these cleanups; effects and\n * computed values created inside `fn` auto-register their disposal too.\n */\nexport const withScopeCleanups = <T>(cleanups: CleanupFn[], fn: () => T): T =>\n runInContext({ scopeCleanups: cleanups }, fn);\n\n// ── Context hook (for SSR) ────────────────────────────────────────────────────\n\n/**\n * @internal Used only by `/ripple/ssr`.\n * Installs a context hook that overrides both tracking and scope-cleanup access.\n * Returns the previous hook so callers can restore it.\n */\nexport const _installContextHook = (hook: ContextHook | null): ContextHook | null => {\n const prev = _hook;\n\n _hook = hook;\n\n return prev;\n};\n"],"mappings":"AAoCA,IAAa,OAAgD,CAC3D,YAAa,IACb,WAAY,EACZ,qBAAsB,IAAI,IAC1B,qBAAsB,IAAI,IAC1B,mBAAoB,IAAI,GAC1B,GAeI,EAAyB,CAAE,WAAY,EAAsB,EAAG,cAAe,KAAM,SAAU,IAAK,EACpG,EAA4B,KAGnB,MAA+C,IAAU,KAAqB,EAAd,EAAM,IAAI,EAe1E,GAAmB,EAAkC,IAAmB,CACnF,IAAM,EAAM,CAAE,GAAG,EAAoB,EAAG,GAAG,CAAM,EAEjD,GAAI,IAAU,KAAM,OAAO,EAAM,IAAI,EAAK,CAAE,EAE5C,IAAM,EAAO,EAEb,EAAO,EAEP,GAAI,CACF,OAAO,EAAG,CACZ,QAAU,CACR,EAAO,CACT,CACF,EAGa,MAA4C,EAAoB,CAAC,CAAC,WAGlE,MAAgC,IAAU,KAK1C,MAA6C,EAAoB,CAAC,CAAC,cAOnE,GAAwB,EAAuB,IAC1D,EAAa,CAAE,cAAe,CAAS,EAAG,CAAE,EASjC,EAAuB,GAAiD,CACnF,IAAM,EAAO,EAIb,MAFA,GAAQ,EAED,CACT"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ComputedBase } from './reactive-base';
|
|
2
|
+
import type { TrackingCtx } from './tracking';
|
|
3
|
+
import type { CleanupFn, Subscriber } from './types';
|
|
4
|
+
export type SchedulingState = {
|
|
5
|
+
activeDirty: 'a' | 'b';
|
|
6
|
+
batchDepth: number;
|
|
7
|
+
readonly dirtyWithEffectSubsA: Set<ComputedBase<unknown>>;
|
|
8
|
+
readonly dirtyWithEffectSubsB: Set<ComputedBase<unknown>>;
|
|
9
|
+
readonly pendingSubscribers: Set<Subscriber>;
|
|
10
|
+
};
|
|
11
|
+
export declare const createSchedulingState: () => SchedulingState;
|
|
12
|
+
export type ExecutionContext = {
|
|
13
|
+
readonly scheduling: SchedulingState;
|
|
14
|
+
readonly scopeCleanups: CleanupFn[] | null;
|
|
15
|
+
readonly tracking: TrackingCtx | null;
|
|
16
|
+
};
|
|
17
|
+
export type ContextHook = {
|
|
18
|
+
get(): ExecutionContext;
|
|
19
|
+
run<T>(ctx: ExecutionContext, fn: () => T): T;
|
|
20
|
+
};
|
|
21
|
+
/** Returns the active context — the installed SSR hook's, or the module-level singleton. */
|
|
22
|
+
export declare const getExecutionContext: () => ExecutionContext;
|
|
23
|
+
/**
|
|
24
|
+
* Runs `fn` with `patch` merged onto the active context, restoring the previous
|
|
25
|
+
* context afterwards. Delegates to the SSR hook when one is installed (it owns its
|
|
26
|
+
* own restore semantics, e.g. via AsyncLocalStorage); otherwise swaps the module-level
|
|
27
|
+
* singleton directly. Every "run with a modified context" helper (withTracking,
|
|
28
|
+
* withScopeCleanups) is a one-line call to this — there is exactly one context
|
|
29
|
+
* swap/restore implementation.
|
|
30
|
+
*
|
|
31
|
+
* Takes a `Partial<ExecutionContext>` (merged internally), not a full replacement
|
|
32
|
+
* context — a caller can only ever override the field(s) it names, so it's
|
|
33
|
+
* impossible to accidentally drop `scheduling` or `scopeCleanups` by forgetting to
|
|
34
|
+
* spread the current context first.
|
|
35
|
+
*/
|
|
36
|
+
export declare const runInContext: <T>(patch: Partial<ExecutionContext>, fn: () => T) => T;
|
|
37
|
+
/** Returns the scheduling-state bucket for the active execution context. */
|
|
38
|
+
export declare const getSchedulingState: () => SchedulingState;
|
|
39
|
+
/** `true` once an SSR context hook is installed — scheduling state is then request-isolated. */
|
|
40
|
+
export declare const hasContextHook: () => boolean;
|
|
41
|
+
/** Returns the cleanup array of the innermost active scope, or `null` if not inside a scope. */
|
|
42
|
+
export declare const getScopeCleanups: () => CleanupFn[] | null;
|
|
43
|
+
/**
|
|
44
|
+
* Pushes `cleanups` as the active scope cleanup array for the duration of `fn`.
|
|
45
|
+
* Any `onCleanup()` calls inside `fn` register into these cleanups; effects and
|
|
46
|
+
* computed values created inside `fn` auto-register their disposal too.
|
|
47
|
+
*/
|
|
48
|
+
export declare const withScopeCleanups: <T>(cleanups: CleanupFn[], fn: () => T) => T;
|
|
49
|
+
//# sourceMappingURL=execution-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-context.d.ts","sourceRoot":"","sources":["../src/execution-context.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAWrD,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,GAAG,GAAG,GAAG,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,eAMvC,CAAC;AAIH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,IAAI,gBAAgB,CAAC;IACxB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;CAC/C,CAAC;AAKF,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,QAAO,gBAAyD,CAAC;AAEjG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,OAAO,OAAO,CAAC,gBAAgB,CAAC,EAAE,IAAI,MAAM,CAAC,KAAG,CAc/E,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,QAAO,eAAmD,CAAC;AAE1F,gGAAgG;AAChG,eAAO,MAAM,cAAc,QAAO,OAAyB,CAAC;AAI5D,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,QAAO,SAAS,EAAE,GAAG,IAA2C,CAAC;AAE9F;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,UAAU,SAAS,EAAE,EAAE,IAAI,MAAM,CAAC,KAAG,CAC3B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/execution-context.ts
|
|
2
|
+
var e = () => ({
|
|
3
|
+
activeDirty: "a",
|
|
4
|
+
batchDepth: 0,
|
|
5
|
+
dirtyWithEffectSubsA: /* @__PURE__ */ new Set(),
|
|
6
|
+
dirtyWithEffectSubsB: /* @__PURE__ */ new Set(),
|
|
7
|
+
pendingSubscribers: /* @__PURE__ */ new Set()
|
|
8
|
+
}), t = {
|
|
9
|
+
scheduling: e(),
|
|
10
|
+
scopeCleanups: null,
|
|
11
|
+
tracking: null
|
|
12
|
+
}, n = null, r = () => n === null ? t : n.get(), i = (e, i) => {
|
|
13
|
+
let a = {
|
|
14
|
+
...r(),
|
|
15
|
+
...e
|
|
16
|
+
};
|
|
17
|
+
if (n !== null) return n.run(a, i);
|
|
18
|
+
let o = t;
|
|
19
|
+
t = a;
|
|
20
|
+
try {
|
|
21
|
+
return i();
|
|
22
|
+
} finally {
|
|
23
|
+
t = o;
|
|
24
|
+
}
|
|
25
|
+
}, a = () => r().scheduling, o = () => n !== null, s = () => r().scopeCleanups, c = (e, t) => i({ scopeCleanups: e }, t), l = (e) => {
|
|
26
|
+
let t = n;
|
|
27
|
+
return n = e, t;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { l as _installContextHook, e as createSchedulingState, r as getExecutionContext, a as getSchedulingState, s as getScopeCleanups, o as hasContextHook, i as runInContext, c as withScopeCleanups };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=execution-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-context.js","names":[],"sources":["../src/execution-context.ts"],"sourcesContent":["// ── ExecutionContext ──────────────────────────────────────────────────────────\n//\n// Owns the one piece of process-wide (or, once an SSR hook is installed,\n// per-request) mutable state every reactive primitive reads through: the active\n// scheduling bucket, the active scope's cleanup array, and the active tracking\n// context. Split out of tracking.ts so this file's only job is \"where does the\n// current execution context live and how do you swap it\" — tracking.ts owns\n// what a TrackingCtx *means* (dep collection), this file owns how one gets\n// installed for the duration of a call.\n//\n// TrackingCtx is imported type-only here (erased at compile time) so tracking.ts\n// can depend on this file's runtime exports (getExecutionContext, runInContext)\n// without creating an actual circular import — only one direction is real at\n// runtime: tracking.ts -> execution-context.ts.\n\nimport type { ComputedBase } from './reactive-base';\nimport type { TrackingCtx } from './tracking';\nimport type { CleanupFn, Subscriber } from './types';\n\n// ── Scheduling state ────────────────────────────────────────────────────────\n//\n// batchDepth, pendingSubscribers, and the dirty-computed sets live here (rather\n// than as scheduling.ts module-level variables) so the /ripple/ssr sub-path can\n// isolate the flush queue per request via the same context hook that isolates\n// tracking — closing the gap where concurrent SSR requests would otherwise share\n// one flush queue. Outside SSR (no hook installed), this is a single\n// module-level singleton with identical behavior and cost to a plain global.\n\nexport type SchedulingState = {\n activeDirty: 'a' | 'b';\n batchDepth: number;\n readonly dirtyWithEffectSubsA: Set<ComputedBase<unknown>>;\n readonly dirtyWithEffectSubsB: Set<ComputedBase<unknown>>;\n readonly pendingSubscribers: Set<Subscriber>;\n};\n\nexport const createSchedulingState = (): SchedulingState => ({\n activeDirty: 'a',\n batchDepth: 0,\n dirtyWithEffectSubsA: new Set(),\n dirtyWithEffectSubsB: new Set(),\n pendingSubscribers: new Set(),\n});\n\n// ── The context itself ────────────────────────────────────────────────────────\n\nexport type ExecutionContext = {\n readonly scheduling: SchedulingState;\n readonly scopeCleanups: CleanupFn[] | null;\n readonly tracking: TrackingCtx | null;\n};\n\nexport type ContextHook = {\n get(): ExecutionContext;\n run<T>(ctx: ExecutionContext, fn: () => T): T;\n};\n\nlet _ctx: ExecutionContext = { scheduling: createSchedulingState(), scopeCleanups: null, tracking: null };\nlet _hook: ContextHook | null = null;\n\n/** Returns the active context — the installed SSR hook's, or the module-level singleton. */\nexport const getExecutionContext = (): ExecutionContext => (_hook !== null ? _hook.get() : _ctx);\n\n/**\n * Runs `fn` with `patch` merged onto the active context, restoring the previous\n * context afterwards. Delegates to the SSR hook when one is installed (it owns its\n * own restore semantics, e.g. via AsyncLocalStorage); otherwise swaps the module-level\n * singleton directly. Every \"run with a modified context\" helper (withTracking,\n * withScopeCleanups) is a one-line call to this — there is exactly one context\n * swap/restore implementation.\n *\n * Takes a `Partial<ExecutionContext>` (merged internally), not a full replacement\n * context — a caller can only ever override the field(s) it names, so it's\n * impossible to accidentally drop `scheduling` or `scopeCleanups` by forgetting to\n * spread the current context first.\n */\nexport const runInContext = <T>(patch: Partial<ExecutionContext>, fn: () => T): T => {\n const ctx = { ...getExecutionContext(), ...patch };\n\n if (_hook !== null) return _hook.run(ctx, fn);\n\n const prev = _ctx;\n\n _ctx = ctx;\n\n try {\n return fn();\n } finally {\n _ctx = prev;\n }\n};\n\n/** Returns the scheduling-state bucket for the active execution context. */\nexport const getSchedulingState = (): SchedulingState => getExecutionContext().scheduling;\n\n/** `true` once an SSR context hook is installed — scheduling state is then request-isolated. */\nexport const hasContextHook = (): boolean => _hook !== null;\n\n// ── Scope cleanup ─────────────────────────────────────────────────────────────\n\n/** Returns the cleanup array of the innermost active scope, or `null` if not inside a scope. */\nexport const getScopeCleanups = (): CleanupFn[] | null => getExecutionContext().scopeCleanups;\n\n/**\n * Pushes `cleanups` as the active scope cleanup array for the duration of `fn`.\n * Any `onCleanup()` calls inside `fn` register into these cleanups; effects and\n * computed values created inside `fn` auto-register their disposal too.\n */\nexport const withScopeCleanups = <T>(cleanups: CleanupFn[], fn: () => T): T =>\n runInContext({ scopeCleanups: cleanups }, fn);\n\n// ── Context hook (for SSR) ────────────────────────────────────────────────────\n\n/**\n * @internal Used only by `/ripple/ssr`.\n * Installs a context hook that overrides both tracking and scope-cleanup access.\n * Returns the previous hook so callers can restore it.\n */\nexport const _installContextHook = (hook: ContextHook | null): ContextHook | null => {\n const prev = _hook;\n\n _hook = hook;\n\n return prev;\n};\n"],"mappings":";AAoCA,IAAa,WAAgD;CAC3D,aAAa;CACb,YAAY;CACZ,sCAAsB,IAAI,IAAI;CAC9B,sCAAsB,IAAI,IAAI;CAC9B,oCAAoB,IAAI,IAAI;AAC9B,IAeI,IAAyB;CAAE,YAAY,EAAsB;CAAG,eAAe;CAAM,UAAU;AAAK,GACpG,IAA4B,MAGnB,UAA+C,MAAU,OAAqB,IAAd,EAAM,IAAI,GAe1E,KAAmB,GAAkC,MAAmB;CACnF,IAAM,IAAM;EAAE,GAAG,EAAoB;EAAG,GAAG;CAAM;CAEjD,IAAI,MAAU,MAAM,OAAO,EAAM,IAAI,GAAK,CAAE;CAE5C,IAAM,IAAO;CAEb,IAAO;CAEP,IAAI;EACF,OAAO,EAAG;CACZ,UAAU;EACR,IAAO;CACT;AACF,GAGa,UAA4C,EAAoB,CAAC,CAAC,YAGlE,UAAgC,MAAU,MAK1C,UAA6C,EAAoB,CAAC,CAAC,eAOnE,KAAwB,GAAuB,MAC1D,EAAa,EAAE,eAAe,EAAS,GAAG,CAAE,GASjC,KAAuB,MAAiD;CACnF,IAAM,IAAO;CAIb,OAFA,IAAQ,GAED;AACT"}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vielzeug/ripple — snapshot-based store undo/redo.
|
|
3
|
+
*
|
|
4
|
+
* Import from the dedicated sub-path so it is tree-shaken from bundles that
|
|
5
|
+
* never use it:
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { storeWithHistory } from '@vielzeug/ripple/history';
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* This is a thin, synchronous, single-store adapter — for async commands,
|
|
11
|
+
* multi-step undo grouping, or history over anything other than a `Store<T>`,
|
|
12
|
+
* use `@vielzeug/ledger` instead.
|
|
13
|
+
*/
|
|
1
14
|
import type { Store, StoreWithHistory } from './types';
|
|
15
|
+
export type { HistoryEntry, StoreWithHistory } from './types';
|
|
2
16
|
/**
|
|
3
17
|
* Wraps a store (or creates one from an initial value) with explicit snapshot history
|
|
4
18
|
* for undo/redo / time-travel.
|
|
@@ -39,4 +53,4 @@ export declare const storeWithHistory: <T extends object>(storeOrInitial: Store<
|
|
|
39
53
|
maxHistory?: number;
|
|
40
54
|
name?: string;
|
|
41
55
|
}) => StoreWithHistory<T>;
|
|
42
|
-
//# sourceMappingURL=
|
|
56
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAgB,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQrE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAO9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,MAAM,EAC/C,gBAAgB,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAC5B,UAAU;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAC/C,gBAAgB,CAAC,CAAC,CAgGpB,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./devtools-hook.cjs"),t=require("./errors.cjs"),n=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./devtools-hook.cjs"),t=require("./errors.cjs"),n=require("./scheduling.cjs"),r=require("./tracking.cjs"),i=require("./effect.cjs"),a=require("./signal.cjs"),o=require("./computed.cjs"),s=require("./store.cjs"),c=require("./utilities.cjs"),l=require("./async-computed.cjs");exports.RippleComputedCycleError=t.RippleComputedCycleError,exports.RippleDisposedScopeError=t.RippleDisposedScopeError,exports.RippleEnvironmentError=t.RippleEnvironmentError,exports.RippleError=t.RippleError,exports.RippleInfiniteLoopError=t.RippleInfiniteLoopError,exports.RippleInvalidCleanupError=t.RippleInvalidCleanupError,exports.RippleInvalidStoreError=t.RippleInvalidStoreError,exports.batch=n.batch,exports.computed=o.computed,exports.effect=i.effect,exports.effectAsync=i.effectAsync,exports.getDevToolsHook=e.getDevToolsHook,exports.isComputed=c.isComputed,exports.isReactive=c.isReactive,exports.isSignal=c.isSignal,exports.isStore=c.isStore,exports.onCleanup=i.onCleanup,exports.readonly=c.readonly,exports.resource=l.resource,exports.scope=i.scope,exports.signal=a.signal,exports.store=s.store,exports.untrack=r.untrack,exports.watch=i.watch;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AsyncEffectCallback, AsyncSubscription, CleanupFn, Computed, ComputedOptions, DepInfo, EffectAsyncOptions, EffectCallback, EffectHandle, EffectOptions, EffectScheduler, EqualityFn,
|
|
1
|
+
export type { AsyncEffectCallback, AsyncSubscription, CleanupFn, Computed, ComputedOptions, DepInfo, EffectAsyncOptions, EffectCallback, EffectHandle, EffectOptions, EffectScheduler, EqualityFn, PathValue, Readable, Resource, ResourceOptions, ResourceState, Scope, Signal, SignalOptions, Store, Subscription, WatchOptions, } from './types';
|
|
2
2
|
export { RippleComputedCycleError, RippleDisposedScopeError, RippleEnvironmentError, RippleError, RippleInfiniteLoopError, RippleInvalidCleanupError, RippleInvalidStoreError, } from './errors';
|
|
3
3
|
export { computed } from './computed';
|
|
4
4
|
export { signal } from './signal';
|
|
@@ -7,6 +7,5 @@ export { batch } from './scheduling';
|
|
|
7
7
|
export { effect, effectAsync, onCleanup, scope, watch } from './effect';
|
|
8
8
|
export { isComputed, isReactive, isSignal, isStore, readonly, untrack } from './utilities';
|
|
9
9
|
export { resource } from './async-computed';
|
|
10
|
-
export { storeWithHistory } from './store-history';
|
|
11
10
|
export { getDevToolsHook } from './devtools-hook';
|
|
12
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,QAAQ,EACR,eAAe,EACf,OAAO,EACP,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,aAAa,EACb,KAAK,EACL,MAAM,EACN,aAAa,EACb,KAAK,EACL,YAAY,EACZ,YAAY,GACb,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,WAAW,EACX,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGxE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3F,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { getDevToolsHook as e } from "./devtools-hook.js";
|
|
2
2
|
import { RippleComputedCycleError as t, RippleDisposedScopeError as n, RippleEnvironmentError as r, RippleError as i, RippleInfiniteLoopError as a, RippleInvalidCleanupError as o, RippleInvalidStoreError as s } from "./errors.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { batch as c } from "./scheduling.js";
|
|
4
|
+
import { untrack as l } from "./tracking.js";
|
|
5
5
|
import { effect as u, effectAsync as d, onCleanup as f, scope as p, watch as m } from "./effect.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { signal as h } from "./signal.js";
|
|
7
|
+
import { computed as g } from "./computed.js";
|
|
8
8
|
import { store as _ } from "./store.js";
|
|
9
9
|
import { isComputed as v, isReactive as y, isSignal as b, isStore as x, readonly as S } from "./utilities.js";
|
|
10
10
|
import { resource as C } from "./async-computed.js";
|
|
11
|
-
|
|
12
|
-
export { t as RippleComputedCycleError, n as RippleDisposedScopeError, r as RippleEnvironmentError, i as RippleError, a as RippleInfiniteLoopError, o as RippleInvalidCleanupError, s as RippleInvalidStoreError, l as batch, h as computed, u as effect, d as effectAsync, e as getDevToolsHook, v as isComputed, y as isReactive, b as isSignal, x as isStore, f as onCleanup, S as readonly, C as resource, p as scope, g as signal, _ as store, w as storeWithHistory, c as untrack, m as watch };
|
|
11
|
+
export { t as RippleComputedCycleError, n as RippleDisposedScopeError, r as RippleEnvironmentError, i as RippleError, a as RippleInfiniteLoopError, o as RippleInvalidCleanupError, s as RippleInvalidStoreError, c as batch, g as computed, u as effect, d as effectAsync, e as getDevToolsHook, v as isComputed, y as isReactive, b as isSignal, x as isStore, f as onCleanup, S as readonly, C as resource, p as scope, h as signal, _ as store, l as untrack, m as watch };
|
package/dist/ripple.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{},n=class extends e{},r=class extends e{},i=class extends e{},a=class extends e{},o=class extends e{},s=e=>e instanceof Error?e:Error(`Non-Error thrown: ${String(e)}`),c=e=>{let t=[];for(let n of e)try{n()}catch(e){t.push(s(e))}return t},l=(e,t)=>{let n=c(e);if(n.length===1)throw n[0];if(n.length>0)throw AggregateError(n,t)},u=(e,t,n)=>{throw t.length===0?e:AggregateError([s(e),...t],n,{cause:s(e)})},d=null,f=()=>d,p=Symbol(`ripple.is-signal`),m=Symbol(`ripple.is-computed`),h=Symbol(`ripple.is-store`),g=Symbol(`ripple.uninitialized`),_=new FinalizationRegistry(({key:e,map:t})=>{t.delete(e)}),v=class{version=0;name;[p]=!0;computedSubs_=new Map;effectSubs_=new Set;constructor(e){this.name=e}addComputedSub(e){let t=new WeakRef(e);this.computedSubs_.set(e,t),_.register(e,{key:e,map:this.computedSubs_},t)}removeComputedSub(e){let t=this.computedSubs_.get(e);t!==void 0&&(this.computedSubs_.delete(e),_.unregister(t))}addEffectSub(e){this.effectSubs_.add(e)}removeEffectSub(e){this.effectSubs_.delete(e)}clearSubscribers(){for(let e of this.computedSubs_.values())_.unregister(e);this.computedSubs_.clear(),this.effectSubs_.clear()}hasSubscribers(){if(this.effectSubs_.size>0)return!0;for(let e of this.computedSubs_.values())if(e.deref()!==void 0)return!0;return!1}*computedSubs(){for(let[e,t]of this.computedSubs_){let n=t.deref();n===void 0?(this.computedSubs_.delete(e),_.unregister(t)):yield n}}effectSubs(){return this.effectSubs_}},y=class extends v{[m]=!0;lastPropEpoch_=0},b=class{fn_;disposed_=!1;constructor(e){this.fn_=e}get disposed(){return this.disposed_}dispose(){if(this.disposed_)return;this.disposed_=!0;let e=this.fn_;this.fn_=null,e()}[Symbol.dispose](){this.dispose()}},x=class{awaitDone_;getCurrentRun_;syncStop_;asyncDisposePromise_=null;constructor(e,t,n){this.awaitDone_=t,this.getCurrentRun_=n,this.syncStop_=e}get disposed(){return this.syncStop_.disposed}dispose(){this.syncStop_.dispose()}run(){return this.getCurrentRun_()??Promise.resolve()}[Symbol.dispose](){this.dispose()}[Symbol.asyncDispose](){return this.asyncDisposePromise_===null?(this.dispose(),this.asyncDisposePromise_=this.awaitDone_(),this.asyncDisposePromise_):this.asyncDisposePromise_}},S=0,C=()=>++S,w=()=>S,T={scheduling:{activeDirty:`a`,batchDepth:0,dirtyWithEffectSubsA:new Set,dirtyWithEffectSubsB:new Set,pendingSubscribers:new Set},scopeCleanups:null,tracking:null},E=null,D=()=>E===null?T:E.get(),O=()=>D().scheduling,ee=()=>E!==null,k=()=>D().tracking,A=(e,t)=>{if(E!==null){let n=E.get();return E.run({...n,tracking:e},t)}let n=T;T={...T,tracking:e};try{return t()}finally{T=n}},j=(e,t)=>{if(E!==null){let n=E.get();return E.run({...n,scopeCleanups:e},t)}let n=T;T={...T,scopeCleanups:e};try{return t()}finally{T=n}},M=()=>D().scopeCleanups,N=e=>{let t=k();t?.kind===`effect`?t.cleanups.push(e):M()?.push(e)},P=e=>A(null,e),F=e=>{let t=k();if(t!==null){if(t.sourceObserver?.(e),t.kind===`computed`)t.depCollector.push({source:e,version:e.version});else if(t.kind===`effect`){let n=t.effect;e.addEffectSub(n),t.subscriptions.add(()=>e.removeEffectSub(n)),t.deps.set(e,e.version)}}},te=class extends y{value_;dirty_;computing_;disposed_;deps_;compute_;equals_;maxRevision_;constructor(e,t){let{equals:n,name:r}=t??{};super(r),this.value_=g,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(w()<=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_=w(),!1}return this.recompute()}runCompute(e){try{return A({computed:this,depCollector:e,kind:`computed`},this.compute_)}catch(e){throw s(e)}}recompute(){if(this.computing_)throw new t(`computed cycle detected${this.name?` "${this.name}"`:``}`);this.computing_=!0;try{let e=[];f()?.compute?.({name:this.name});let t=this.runCompute(e);return this.dirty_=!1,this.maxRevision_=w(),this.updateDeps(e),this.value_===g||!this.equals_(this.value_,t)?(this.value_=t,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_===g?void 0:this.value_:(this.refreshIfDirty(),F(this),this.value_)}peek(){return this.disposed_?this.value_===g?void 0:this.value_:(this.refreshIfDirty(),this.value_)}subscribe=e=>{if(this.disposed_){let e=new b(()=>{});return e.dispose(),e}return this.refreshIfDirty(),this.addEffectSub(e),new b(()=>{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(),f()?.dispose?.({kind:`computed`,name:this.name})}}[Symbol.dispose](){this.dispose()}},I=(e,t)=>{let n=new te(e,t);return N(()=>n.dispose()),n},ne=!globalThis.__RIPPLE_PROD__;function re(e){ne&&console.warn(`[@vielzeug/ripple] ${e}`)}var L=!1,R=0,z=[],B=e=>e.activeDirty===`a`?e.dirtyWithEffectSubsA:e.dirtyWithEffectSubsB,ie=(e,t)=>{let n=++R;for(let n of t.effectSubs())e.pendingSubscribers.add(n);for(let e of t.computedSubs())z.push(e);try{for(;z.length>0;){let t=z.pop();if(t.lastPropEpoch_!==n&&(t.lastPropEpoch_=n,t.markDirty())){t.effectSubs().size>0&&B(e).add(t);for(let e of t.computedSubs())z.push(e)}}}finally{z.length=0}},ae=e=>{for(;B(e).size>0;){let t=B(e);e.activeDirty=e.activeDirty===`a`?`b`:`a`,B(e).clear();for(let n of t)if(n.hasSubscribers()&&n.refreshIfDirty()){for(let t of n.effectSubs())e.pendingSubscribers.add(t);for(let t of n.computedSubs())t.markDirty()&&t.effectSubs().size>0&&B(e).add(t)}t.clear()}},V=e=>{let t=0;for(;e.pendingSubscribers.size>0||B(e).size>0;){if(++t>100)throw new i(`infinite flush loop (> 100 iterations)`);if(B(e).size>0&&ae(e),e.pendingSubscribers.size===0)continue;let n=[...e.pendingSubscribers];e.pendingSubscribers.clear(),l(n,`subscriber errors`)}},oe=globalThis.process?.versions!=null,se=e=>{if(!e.hasSubscribers())return;!L&&oe&&!ee()&&(L=!0,re(`Signal updated in a Node.js-like environment. The module-level flush queue is shared across concurrent requests — use per-request worker isolation or the @vielzeug/ripple/ssr sub-path for request-isolated scheduling.`));let t=O();ie(t,e),t.batchDepth===0&&V(t)},H=e=>{let t=O();t.batchDepth++;let n;try{n=e()}catch(e){throw t.batchDepth--,t.batchDepth===0&&(t.pendingSubscribers.clear(),t.dirtyWithEffectSubsA.clear(),t.dirtyWithEffectSubsB.clear()),e}return t.batchDepth--,t.batchDepth===0&&V(t),n},U=class extends v{value_;equals_;disposed_;constructor(e,t,n){super(n),this.value_=e,this.equals_=t??Object.is,this.disposed_=!1}get value(){return this.disposed_||F(this),this.value_}set value(e){if(this.disposed_||this.equals_(this.value_,e))return;let t=this.value_;this.value_=e,this.version=C(),f()?.write?.({name:this.name,newValue:e,oldValue:t}),se(this)}peek(){return this.value_}subscribe=e=>{if(this.disposed_){let e=new b(()=>{});return e.dispose(),e}return this.addEffectSub(e),new b(()=>{this.removeEffectSub(e)})};get disposed(){return this.disposed_}dispose(){this.disposed_||(this.disposed_=!0,this.clearSubscribers(),f()?.dispose?.({kind:`signal`,name:this.name}))}[Symbol.dispose](){this.dispose()}},W=(e,t)=>new U(e,t?.equals,t?.name),G=e=>{if(e===`__proto__`||e===`constructor`||e===`prototype`)throw new o(`Unsafe key "${e}" rejected to prevent prototype pollution.`)},ce=(e,t)=>{let n=e;for(let e of t){if(typeof n!=`object`||!n)return;n=n[e]}return n},K=(e,t,n)=>{if(t.length===0)return n;if(typeof e!=`object`||!e)throw new o(`Cannot write to a nested path through a null or non-object intermediate value.`);let[r,...i]=t,a=e;return{...a,[r]:K(a[r],i,n)}},q=class{[p]=!0;source_;write_;evict_;disposeSource_;name_;disposed_=!1;get name(){return this.name_??this.source_.name}constructor(e){this.source_=e.source,this.write_=e.write,this.evict_=e.evict,this.disposeSource_=e.disposeSource,this.name_=e.name}get value(){return this.source_.value}set value(e){this.write_(e)}peek(){return this.source_.peek()}subscribe(e){return this.source_.subscribe(e)}get disposed(){return this.disposed_||this.source_.disposed}dispose(){this.disposed_=!0,this.evict_(),this.disposeSource_?.()}[Symbol.dispose](){this.dispose()}},le=class{[p]=!0;[h]=!0;name;current_;disposed_;initial_;lensCache_;propSignals_;readonlyProxy_;version_;constructor(e,t){this.name=t,this.current_=structuredClone(e),this.disposed_=!1,this.initial_=structuredClone(e),this.lensCache_=new Map,this.propSignals_=new Map,this.version_=new U(0,void 0,t?`${t}.version`:void 0),this.readonlyProxy_=new Proxy(this.current_,{deleteProperty(e,t){throw new o(`Direct deletion from store.value is not allowed. Use store.patch() or store.lens(). (key: "${String(t)}")`)},set(e,t){throw new o(`Direct mutation of store.value is not allowed. Use store.patch(), store.lens(), or store.replace(). (key: "${String(t)}")`)}})}propSignalFor_(e){let t=this.propSignals_.get(e);return t===void 0&&(t=new U(this.current_[e],void 0,this.name===void 0?e:`${this.name}.${e}`),this.propSignals_.set(e,t)),t}setPath_(e,t){let[n,...r]=e,i=this.current_[n],a=K(i,r,t);this.applyTopLevelChange_(n,a)}applyTopLevelChange_(e,t){G(e);let n=this.current_[e];Object.is(n,t)||(this.current_[e]=t,this.propSignalFor_(e).value=t,this.version_.value=this.version_.peek()+1)}deleteTopLevelKey_(e){Object.hasOwn(this.current_,e)&&(delete this.current_[e],this.propSignalFor_(e).value=void 0,this.version_.value=this.version_.peek()+1)}applyFullState_(e){let t=this.current_,n=new Set([...Object.keys(t),...Object.keys(e)]);for(let e of n)G(e);H(()=>{for(let t of n)Object.hasOwn(e,t)?this.applyTopLevelChange_(t,e[t]):this.deleteTopLevelKey_(t)})}get value(){return this.version_.value,this.readonlyProxy_}peek(){return Object.freeze(structuredClone(this.current_))}subscribe=e=>this.version_.subscribe(e);patch(e){if(typeof e!=`object`||!e||Array.isArray(e))throw new o(`store.patch() requires a plain object partial.`);let t=Object.keys(e);if(t.length!==0){for(let e of t)G(e);H(()=>{for(let n of t)this.applyTopLevelChange_(n,e[n])}),f()?.mutate?.({kind:`patch`,name:this.name})}}replace(e){let t=structuredClone(this.current_),n=e(t);n!==t&&(this.applyFullState_(n),f()?.mutate?.({kind:`replace`,name:this.name}))}reset(){this.applyFullState_(this.initial_),f()?.mutate?.({kind:`reset`,name:this.name})}lens(e){let t=this.lensCache_.get(e);if(t!==void 0)return t;let n=()=>{this.lensCache_.delete(e)},r=e.split(`.`);if(r.length>32)throw new o(`Lens path exceeds maximum depth of 32 segments: "${e}".`);for(let t of r){if(t===``)throw new o(`Empty path segment in lens path "${e}". Check for consecutive dots.`);if(t===`__proto__`||t===`constructor`||t===`prototype`)throw new o(`Unsafe path segment "${t}" in lens path "${e}".`)}let i;if(r.length===1)i=new q({evict:n,source:this.propSignalFor_(r[0]),write:t=>{H(()=>this.applyTopLevelChange_(r[0],t)),f()?.mutate?.({kind:`lens`,name:this.name,path:e})}});else{let t=I(()=>ce(this.propSignalFor_(r[0]).value,r.slice(1)));i=new q({disposeSource:()=>t.dispose(),evict:n,name:this.name?`${this.name}.${e}`:e,source:t,write:n=>{let i=t.peek();Object.is(i,n)||(H(()=>this.setPath_(r,n)),f()?.mutate?.({kind:`lens`,name:this.name,path:e}))}})}return this.lensCache_.set(e,i),i}get disposed(){return this.disposed_}dispose(){if(!this.disposed_){this.disposed_=!0;for(let e of[...this.lensCache_.values()])e.dispose();this.lensCache_.clear();for(let e of this.propSignals_.values())e.dispose();this.propSignals_.clear(),this.version_.dispose(),f()?.dispose?.({kind:`store`,name:this.name})}}[Symbol.dispose](){this.dispose()}},J=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))throw new o(`store() requires a plain object initial state.`);for(let t of Object.keys(e))G(t);return new le(e,t?.name)},ue=(e,t)=>{if(t===`sync`||t===void 0)return e;let n=!1;return()=>{n||(n=!0,queueMicrotask(()=>{n=!1,e()}))}},Y=(e,t)=>{let n=t?.scheduler??`sync`,r=t?.name,o=[],s=new Set,d=new Map,p=!1,h=!1,g=!1,_=()=>{if(d.clear(),o.length===0&&s.size===0)return;let e=[...o,...s];o.length=0,s.clear(),l(e,`effect teardown errors`)},v=()=>{if(!g){if(p){h=!0;return}p=!0;try{let t=0;do{if(++t>100)throw new i(`infinite effect loop (> 100 iterations)${r?` in "${r}"`:``}`);h=!1,_(),f()?.run?.({name:r});let n;try{n=A({cleanups:o,deps:d,effect:y,kind:`effect`,subscriptions:s},e)}catch(e){let t=[...c(s),...c(o)];o.length=0,s.clear(),u(e,t,`effect failure with cleanup errors`)}if(n!==void 0&&typeof n!=`function`)throw new a(`effect() returned ${typeof n} — expected a cleanup function or void.`);typeof n==`function`&&o.push(n)}while(h&&!g)}finally{p=!1}}},y=ue(v,n);v();let x=new b(()=>{g||(g=!0,_(),f()?.dispose?.({kind:`effect`,name:r}))});return M()?.push(()=>x.dispose()),{dispose:()=>x.dispose(),get disposed(){return x.disposed},getDependencies(){return[...d.keys()].map(e=>({kind:m in e?`computed`:`signal`,name:e.name}))},[Symbol.dispose](){x.dispose()}}},de=(e,t)=>{let n=null,r=null,i=null,a=t?.onError??(e=>{queueMicrotask(()=>{throw e})});return new x(Y(()=>{n?.abort(),r?.(),r=null,n=new AbortController;let{signal:t}=n,o=X(),s;return i=s=(async()=>{try{let n=await e(t,o);typeof n==`function`&&(t.aborted?n():r=n)}catch(e){t.aborted||a(e)}finally{o.dispose(),i===s&&(i=null)}})(),()=>{n?.abort(),r?.(),r=null}},{name:t?.name}),async()=>{let e=i;e&&await e},()=>i)},fe=e=>{let t=k();if(t?.kind===`effect`){t.cleanups.push(e);return}let n=M();if(n!==null){n.push(e);return}throw new a(`onCleanup() must be called from within an active effect or scope.`)},X=e=>{let t=[],r=new AbortController,i=!1,a=e=>{if(i)throw new n(`Cannot register cleanup on a disposed scope.`);t.push(e)},o=e=>{if(i)throw new n(`Cannot run inside a disposed scope.`);return j(t,e)},s=()=>{i||(i=!0,r.abort(),l([...t].reverse(),`scope cleanup errors`),t.length=0)},c={add:a,get disposalSignal(){return r.signal},dispose:s,get disposed(){return i},run:o,[Symbol.dispose]:s};return e&&o(e),c};function pe(e,t,n){let r=n?.equals??Object.is,i,o=(e,n)=>{i?.(),i=void 0;let r=t(e,n);if(r!==void 0&&typeof r!=`function`)throw new a(`watch() callback returned ${typeof r} — expected a cleanup function or void.`);typeof r==`function`&&(i=r)},s;if(typeof e==`function`){let t,a=!0,c=Y(()=>{let i=e();if(a){a=!1,t=i;return}if(!r(t,i)){let e=t;t=i,o(i,e),n?.once&&s.dispose()}},{name:n?.name});s=new b(()=>{i?.(),i=void 0,c.dispose()}),n?.immediate&&(o(t,void 0),n.once&&s.dispose())}else{let t=e.peek(),a=e.subscribe(()=>{let i=e.peek();r(t,i)||(o(i,t),t=i,n?.once&&s.dispose())});s=new b(()=>{i?.(),i=void 0,a.dispose()}),n?.immediate&&(o(t,void 0),n.once&&s.dispose())}return M()?.push(()=>s.dispose()),s}var me=e=>({get disposed(){return e.disposed},[p]:!0,get name(){return e.name},peek(){return e.peek()},subscribe(t){return e.subscribe(t)},get value(){return e.value}}),Z=e=>typeof e==`object`&&!!e&&!!e[p],he=e=>Z(e)&&!e[m]&&!e[h],ge=e=>typeof e==`object`&&!!e&&!!e[m],Q=e=>typeof e==`object`&&!!e&&!!e[h],_e=(e,t)=>{let n=t?.name,r=W({data:t?.initialValue,status:`loading`},n?{name:n}:void 0),i=W(0),a=null,o=!1,s=Y(()=>{i.value,a?.abort(),a=new AbortController;let{signal:t}=a,n=r.peek(),s=`data`in n?n.data:void 0;r.value={data:s,status:`loading`},(async()=>{try{let n=await e(t);!t.aborted&&!o&&(r.value={data:n,status:`ready`})}catch(e){!t.aborted&&!o&&(r.value={data:s,error:e,status:`error`})}})()},n?{name:n}:void 0),c=()=>{o||(i.value=i.peek()+1)},l=()=>{o||(o=!0,a?.abort(),s.dispose(),r.dispose(),i.dispose())};return N(l),{dispose:l,get disposed(){return o},[m]:!0,[p]:!0,get name(){return r.name},peek:()=>r.peek(),refresh:c,subscribe:e=>r.subscribe(e),[Symbol.dispose]:l,get value(){return r.value}}},$=(e,t)=>({label:t,state:Object.freeze(structuredClone({...e}))}),ve=(e,t)=>{let n=t?.maxHistory??50,r=!Q(e),i=r?J(e,{name:t?.name}):e,a=[$(i.peek())],o=W(0,{name:t?.name?`${t.name}.cursor`:void 0}),s=!1,c=e=>{s||(a.splice(o.peek()+1),a.push($(i.peek(),e)),a.length>n&&a.splice(0,a.length-n),o.value=a.length-1)},l=()=>{s||o.peek()<=0||(o.value=o.peek()-1,i.replace(()=>a[o.peek()].state))},u=()=>{s||o.peek()>=a.length-1||(o.value=o.peek()+1,i.replace(()=>a[o.peek()].state))},d=e=>{if(!(e<0||e>=a.length))return a[e]},f=()=>{s||(s=!0,o.dispose(),r&&i.dispose())};return{get canRedo(){return o.value<a.length-1},get canUndo(){return o.value>0},dispose:f,get disposed(){return s},historyAt:d,get historyLength(){return a.length},lens:e=>i.lens(e),get name(){return i.name},patch:e=>i.patch(e),peek:()=>i.peek(),push:()=>c(),pushNamed:e=>c(e),redo:u,replace:e=>i.replace(e),reset:()=>i.reset(),get store(){return i},subscribe:e=>i.subscribe(e),[Symbol.dispose]:f,undo:l,get value(){return i.value}}};exports.RippleComputedCycleError=t,exports.RippleDisposedScopeError=n,exports.RippleEnvironmentError=r,exports.RippleError=e,exports.RippleInfiniteLoopError=i,exports.RippleInvalidCleanupError=a,exports.RippleInvalidStoreError=o,exports.batch=H,exports.computed=I,exports.effect=Y,exports.effectAsync=de,exports.getDevToolsHook=f,exports.isComputed=ge,exports.isReactive=Z,exports.isSignal=he,exports.isStore=Q,exports.onCleanup=fe,exports.readonly=me,exports.resource=_e,exports.scope=X,exports.signal=W,exports.store=J,exports.storeWithHistory=ve,exports.untrack=P,exports.watch=pe;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{},n=class extends e{},r=class extends e{},i=class extends e{},a=class extends e{},o=class extends e{},s=e=>e instanceof Error?e:Error(`Non-Error thrown: ${String(e)}`),c=e=>{let t=[];for(let n of e)try{n()}catch(e){t.push(s(e))}return t},l=(e,t)=>{let n=c(e);if(n.length===1)throw n[0];if(n.length>0)throw AggregateError(n,t)},ee=(e,t,n)=>{throw t.length===0?e:AggregateError([s(e),...t],n,{cause:s(e)})},u=null,d=()=>u,f=Symbol(`ripple.is-signal`),p=Symbol(`ripple.is-computed`),m=Symbol(`ripple.is-store`),h=Symbol(`ripple.uninitialized`),g=Symbol(`ripple.suppress-run-event`),_=new FinalizationRegistry(({key:e,map:t})=>{t.delete(e)}),v=class{version=0;name;[f]=!0;computedSubs_=new Map;effectSubs_=new Set;constructor(e){this.name=e}addComputedSub(e){let t=new WeakRef(e);this.computedSubs_.set(e,t),_.register(e,{key:e,map:this.computedSubs_},t)}removeComputedSub(e){let t=this.computedSubs_.get(e);t!==void 0&&(this.computedSubs_.delete(e),_.unregister(t))}addEffectSub(e){this.effectSubs_.add(e)}removeEffectSub(e){this.effectSubs_.delete(e)}clearSubscribers(){for(let e of this.computedSubs_.values())_.unregister(e);this.computedSubs_.clear(),this.effectSubs_.clear()}hasSubscribers(){if(this.effectSubs_.size>0)return!0;for(let e of this.computedSubs_.values())if(e.deref()!==void 0)return!0;return!1}*computedSubs(){for(let[e,t]of this.computedSubs_){let n=t.deref();n===void 0?(this.computedSubs_.delete(e),_.unregister(t)):yield n}}effectSubs(){return this.effectSubs_}},y=class extends v{[p]=!0;lastPropEpoch_=0},b=class{fn_;disposed_=!1;constructor(e){this.fn_=e}get disposed(){return this.disposed_}dispose(){if(this.disposed_)return;this.disposed_=!0;let e=this.fn_;this.fn_=null,e()}[Symbol.dispose](){this.dispose()}},x=class{awaitDone_;getCurrentRun_;syncStop_;asyncDisposePromise_=null;constructor(e,t,n){this.awaitDone_=t,this.getCurrentRun_=n,this.syncStop_=e}get disposed(){return this.syncStop_.disposed}dispose(){this.syncStop_.dispose()}run(){return this.getCurrentRun_()??Promise.resolve()}[Symbol.dispose](){this.dispose()}[Symbol.asyncDispose](){return this.asyncDisposePromise_===null?(this.dispose(),this.asyncDisposePromise_=this.awaitDone_(),this.asyncDisposePromise_):this.asyncDisposePromise_}},S={scheduling:{activeDirty:`a`,batchDepth:0,dirtyWithEffectSubsA:new Set,dirtyWithEffectSubsB:new Set,pendingSubscribers:new Set},scopeCleanups:null,tracking:null},C=null,w=()=>C===null?S:C.get(),T=(e,t)=>{let n={...w(),...e};if(C!==null)return C.run(n,t);let r=S;S=n;try{return t()}finally{S=r}},E=()=>w().scheduling,te=()=>C!==null,D=()=>w().scopeCleanups,O=(e,t)=>T({scopeCleanups:e},t),k=0,ne=()=>++k,A=()=>k,j=()=>w().tracking,M=(e,t)=>T({tracking:e},t),N=e=>{let t=j();t?.kind===`effect`?t.cleanups.push(e):D()?.push(e)},P=e=>M(null,e),F=e=>{let t=j();if(t!==null){if(t.sourceObserver?.(e),t.kind===`computed`)t.depCollector.push({source:e,version:e.version});else if(t.kind===`effect`){let n=t.effect;e.addEffectSub(n),t.subscriptions.add(()=>e.removeEffectSub(n)),t.deps.set(e,e.version)}}},re=class extends y{value_;dirty_;computing_;disposed_;deps_;compute_;equals_;maxRevision_;constructor(e,t){let{equals:n,name:r}=t??{};super(r),this.value_=h,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(A()<=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_=A(),!1}return this.recompute()}runCompute(e){try{return M({computed:this,depCollector:e,kind:`computed`},this.compute_)}catch(e){throw s(e)}}recompute(){if(this.computing_)throw new t(`computed cycle detected${this.name?` "${this.name}"`:``}`);this.computing_=!0;try{let e=[];d()?.compute?.({name:this.name});let t=this.runCompute(e);return this.dirty_=!1,this.maxRevision_=A(),this.updateDeps(e),this.value_===h||!this.equals_(this.value_,t)?(this.value_=t,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_===h?void 0:this.value_:(this.refreshIfDirty(),F(this),this.value_)}peek(){return this.disposed_?this.value_===h?void 0:this.value_:(this.refreshIfDirty(),this.value_)}subscribe=e=>{if(this.disposed_){let e=new b(()=>{});return e.dispose(),e}return this.refreshIfDirty(),this.addEffectSub(e),new b(()=>{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(),d()?.dispose?.({kind:`computed`,name:this.name})}}[Symbol.dispose](){this.dispose()}},I=(e,t)=>{let n=new re(e,t);return N(()=>n.dispose()),n},L=!globalThis.__RIPPLE_PROD__;function ie(e){L&&console.warn(`[@vielzeug/ripple] ${e}`)}var R=!1,ae=0,z=[],B=e=>e.activeDirty===`a`?e.dirtyWithEffectSubsA:e.dirtyWithEffectSubsB,oe=(e,t)=>{let n=++ae;for(let n of t.effectSubs())e.pendingSubscribers.add(n);for(let e of t.computedSubs())z.push(e);try{for(;z.length>0;){let t=z.pop();if(t.lastPropEpoch_!==n&&(t.lastPropEpoch_=n,t.markDirty())){t.effectSubs().size>0&&B(e).add(t);for(let e of t.computedSubs())z.push(e)}}}finally{z.length=0}},se=e=>{for(;B(e).size>0;){let t=B(e);e.activeDirty=e.activeDirty===`a`?`b`:`a`,B(e).clear();for(let n of t)if(n.hasSubscribers()&&n.refreshIfDirty()){for(let t of n.effectSubs())e.pendingSubscribers.add(t);for(let t of n.computedSubs())t.markDirty()&&t.effectSubs().size>0&&B(e).add(t)}t.clear()}},V=e=>{let t=0;for(;e.pendingSubscribers.size>0||B(e).size>0;){if(++t>100)throw new i(`infinite flush loop (> 100 iterations)`);if(B(e).size>0&&se(e),e.pendingSubscribers.size===0)continue;let n=[...e.pendingSubscribers];e.pendingSubscribers.clear(),l(n,`subscriber errors`)}},H=globalThis.process?.versions!=null,U=e=>{if(!e.hasSubscribers())return;!R&&H&&!te()&&(R=!0,ie(`Signal updated in a Node.js-like environment. The module-level flush queue is shared across concurrent requests — use per-request worker isolation or the @vielzeug/ripple/ssr sub-path for request-isolated scheduling.`));let t=E();oe(t,e),t.batchDepth===0&&V(t)},W=e=>{let t=E();t.batchDepth++;let n;try{n=e()}catch(e){throw t.batchDepth--,t.batchDepth===0&&(t.pendingSubscribers.clear(),t.dirtyWithEffectSubsA.clear(),t.dirtyWithEffectSubsB.clear()),e}return t.batchDepth--,t.batchDepth===0&&V(t),n},G=class extends v{value_;equals_;disposed_;constructor(e,t,n){super(n),this.value_=e,this.equals_=t??Object.is,this.disposed_=!1}get value(){return this.disposed_||F(this),this.value_}set value(e){if(this.disposed_||this.equals_(this.value_,e))return;let t=this.value_;this.value_=e,this.version=ne(),d()?.write?.({name:this.name,newValue:e,oldValue:t}),U(this)}peek(){return this.value_}subscribe=e=>{if(this.disposed_){let e=new b(()=>{});return e.dispose(),e}return this.addEffectSub(e),new b(()=>{this.removeEffectSub(e)})};get disposed(){return this.disposed_}dispose(){this.disposed_||(this.disposed_=!0,this.clearSubscribers(),d()?.dispose?.({kind:`signal`,name:this.name}))}[Symbol.dispose](){this.dispose()}},K=(e,t)=>new G(e,t?.equals,t?.name),q=e=>{if(e===`__proto__`||e===`constructor`||e===`prototype`)throw new o(`Unsafe key "${e}" rejected to prevent prototype pollution.`)},ce=(e,t)=>{let n=e;for(let e of t){if(typeof n!=`object`||!n)return;n=n[e]}return n},J=(e,t,n)=>{if(t.length===0)return n;if(typeof e!=`object`||!e)throw new o(`Cannot write to a nested path through a null or non-object intermediate value.`);let[r,...i]=t,a=e;return{...a,[r]:J(a[r],i,n)}},Y=class{[f]=!0;source_;write_;evict_;disposeSource_;name_;disposed_=!1;get name(){return this.name_??this.source_.name}constructor(e){this.source_=e.source,this.write_=e.write,this.evict_=e.evict,this.disposeSource_=e.disposeSource,this.name_=e.name}get value(){return this.source_.value}set value(e){this.write_(e)}peek(){return this.source_.peek()}subscribe(e){return this.source_.subscribe(e)}get disposed(){return this.disposed_||this.source_.disposed}dispose(){this.disposed_=!0,this.evict_(),this.disposeSource_?.()}[Symbol.dispose](){this.dispose()}},le=class{[f]=!0;[m]=!0;name;current_;disposed_;initial_;lensCache_;propSignals_;readonlyProxy_;version_;constructor(e,t){this.name=t,this.current_=structuredClone(e),this.disposed_=!1,this.initial_=structuredClone(e),this.lensCache_=new Map,this.propSignals_=new Map,this.version_=new G(0,void 0,t?`${t}.version`:void 0),this.readonlyProxy_=new Proxy(this.current_,{deleteProperty(e,t){throw new o(`Direct deletion from store.value is not allowed. Use store.patch() or store.lens(). (key: "${String(t)}")`)},set(e,t){throw new o(`Direct mutation of store.value is not allowed. Use store.patch(), store.lens(), or store.replace(). (key: "${String(t)}")`)}})}propSignalFor_(e){let t=this.propSignals_.get(e);return t===void 0&&(t=new G(this.current_[e],void 0,this.name===void 0?e:`${this.name}.${e}`),this.propSignals_.set(e,t)),t}setPath_(e,t){let[n,...r]=e,i=this.current_[n],a=J(i,r,t);this.applyTopLevelChange_(n,a)}applyTopLevelChange_(e,t){q(e);let n=this.current_[e];Object.is(n,t)||(this.current_[e]=t,this.propSignalFor_(e).value=t,this.version_.value=this.version_.peek()+1)}deleteTopLevelKey_(e){Object.hasOwn(this.current_,e)&&(delete this.current_[e],this.propSignalFor_(e).value=void 0,this.version_.value=this.version_.peek()+1)}applyFullState_(e){let t=this.current_,n=new Set([...Object.keys(t),...Object.keys(e)]);for(let e of n)q(e);W(()=>{for(let t of n)Object.hasOwn(e,t)?this.applyTopLevelChange_(t,e[t]):this.deleteTopLevelKey_(t)})}get value(){return this.version_.value,this.readonlyProxy_}peek(){return Object.freeze(structuredClone(this.current_))}subscribe=e=>this.version_.subscribe(e);patch(e){if(typeof e!=`object`||!e||Array.isArray(e))throw new o(`store.patch() requires a plain object partial.`);let t=Object.keys(e);if(t.length!==0){for(let e of t)q(e);W(()=>{for(let n of t)this.applyTopLevelChange_(n,e[n])}),d()?.mutate?.({kind:`patch`,name:this.name})}}replace(e){let t=structuredClone(this.current_),n=e(t);n!==t&&(this.applyFullState_(n),d()?.mutate?.({kind:`replace`,name:this.name}))}reset(){this.applyFullState_(this.initial_),d()?.mutate?.({kind:`reset`,name:this.name})}topLevelLens_(e,t){return new Y({evict:()=>this.lensCache_.delete(t),source:this.propSignalFor_(e),write:n=>{W(()=>this.applyTopLevelChange_(e,n)),d()?.mutate?.({kind:`lens`,name:this.name,path:t})}})}nestedLens_(e,t){let n=I(()=>ce(this.propSignalFor_(e[0]).value,e.slice(1)));return new Y({disposeSource:()=>n.dispose(),evict:()=>this.lensCache_.delete(t),name:this.name?`${this.name}.${t}`:t,source:n,write:r=>{Object.is(n.peek(),r)||(W(()=>this.setPath_(e,r)),d()?.mutate?.({kind:`lens`,name:this.name,path:t}))}})}validatedLensParts_(e){let t=e.split(`.`);if(t.length>32)throw new o(`Lens path exceeds maximum depth of 32 segments: "${e}".`);for(let n of t){if(n===``)throw new o(`Empty path segment in lens path "${e}". Check for consecutive dots.`);if(n===`__proto__`||n===`constructor`||n===`prototype`)throw new o(`Unsafe path segment "${n}" in lens path "${e}".`)}return t}lens(e){let t=this.lensCache_.get(e);if(t!==void 0)return t;let n=this.validatedLensParts_(e),r=n.length===1?this.topLevelLens_(n[0],e):this.nestedLens_(n,e);return this.lensCache_.set(e,r),r}get disposed(){return this.disposed_}dispose(){if(!this.disposed_){this.disposed_=!0;for(let e of[...this.lensCache_.values()])e.dispose();this.lensCache_.clear();for(let e of this.propSignals_.values())e.dispose();this.propSignals_.clear(),this.version_.dispose(),d()?.dispose?.({kind:`store`,name:this.name})}}[Symbol.dispose](){this.dispose()}},ue=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))throw new o(`store() requires a plain object initial state.`);for(let t of Object.keys(e))q(t);return new le(e,t?.name)},de=(e,t)=>{if(t===`sync`||t===void 0)return e;let n=!1;return()=>{n||(n=!0,queueMicrotask(()=>{n=!1,e()}))}},X=(e,t)=>{let n=t?.scheduler??`sync`,r=t?.name,o=t?.[g],s=[],u=new Set,f=new Map,m=!1,h=!1,_=!1,v=()=>{if(f.clear(),s.length===0&&u.size===0)return;let e=[...s,...u];s.length=0,u.clear(),l(e,`effect teardown errors`)},y=()=>{if(!_){if(m){h=!0;return}m=!0;try{let t=0;do{if(++t>100)throw new i(`infinite effect loop (> 100 iterations)${r?` in "${r}"`:``}`);h=!1,v(),o||d()?.run?.({name:r});let n;try{n=M({cleanups:s,deps:f,effect:x,kind:`effect`,subscriptions:u},e)}catch(e){let t=[...c(u),...c(s)];s.length=0,u.clear(),ee(e,t,`effect failure with cleanup errors`)}if(n!==void 0&&typeof n!=`function`)throw new a(`effect() returned ${typeof n} — expected a cleanup function or void.`);typeof n==`function`&&s.push(n)}while(h&&!_)}finally{m=!1}}},x=de(y,n);y();let S=new b(()=>{_||(_=!0,v(),d()?.dispose?.({kind:`effect`,name:r}))});return D()?.push(()=>S.dispose()),{dispose:()=>S.dispose(),get disposed(){return S.disposed},getDependencies(){return[...f.keys()].map(e=>({kind:p in e?`computed`:`signal`,name:e.name}))},[Symbol.dispose](){S.dispose()}}},Z=(e,t)=>{let n=null,r=null,i=null,a=t?.onError??(e=>{queueMicrotask(()=>{throw e})});return new x(X(()=>{n?.abort(),r?.(),r=null,n=new AbortController;let{signal:t}=n,o=Q(),s;return i=s=(async()=>{try{let n=await e(t,o);typeof n==`function`&&(t.aborted?n():r=n)}catch(e){t.aborted||a(e)}finally{o.dispose(),i===s&&(i=null)}})(),()=>{n?.abort(),r?.(),r=null}},{name:t?.name}),async()=>{let e=i;e&&await e},()=>i)},fe=e=>{let t=j();if(t?.kind===`effect`){t.cleanups.push(e);return}let n=D();if(n!==null){n.push(e);return}throw new a(`onCleanup() must be called from within an active effect or scope.`)},Q=e=>{let t=[],r=new AbortController,i=!1,a=e=>{if(i)throw new n(`Cannot register cleanup on a disposed scope.`);t.push(e)},o=e=>{if(i)throw new n(`Cannot run inside a disposed scope.`);return O(t,e)},s=()=>{i||(i=!0,r.abort(),l([...t].reverse(),`scope cleanup errors`),t.length=0)},c={add:a,get disposalSignal(){return r.signal},dispose:s,get disposed(){return i},run:o,[Symbol.dispose]:s};return e&&o(e),c};function pe(e,t,n){let r=n?.equals??Object.is,i,o=(e,n)=>{i?.(),i=void 0;let r=t(e,n);if(r!==void 0&&typeof r!=`function`)throw new a(`watch() callback returned ${typeof r} — expected a cleanup function or void.`);typeof r==`function`&&(i=r)},s;if(typeof e==`function`){let t,a=!0,c=X(()=>{let i=e();if(a){a=!1,t=i;return}if(!r(t,i)){let e=t;t=i,o(i,e),n?.once&&s.dispose()}},{name:n?.name});s=new b(()=>{i?.(),i=void 0,c.dispose()}),n?.immediate&&(o(t,void 0),n.once&&s.dispose())}else{let t=e.peek(),a=e.subscribe(()=>{let i=e.peek();r(t,i)||(o(i,t),t=i,n?.once&&s.dispose())});s=new b(()=>{i?.(),i=void 0,a.dispose()}),n?.immediate&&(o(t,void 0),n.once&&s.dispose())}return D()?.push(()=>s.dispose()),s}var me=e=>({get disposed(){return e.disposed},[f]:!0,get name(){return e.name},peek(){return e.peek()},subscribe(t){return e.subscribe(t)},get value(){return e.value}}),$=e=>typeof e==`object`&&!!e&&!!e[f],he=e=>$(e)&&!e[p]&&!e[m],ge=e=>typeof e==`object`&&!!e&&!!e[p],_e=e=>typeof e==`object`&&!!e&&!!e[m],ve=(e,t)=>{let n=t?.name,r=K({data:t?.initialValue,status:`loading`},n?{name:n}:void 0),i=K(0),a=null,o=!1,s=X(()=>{i.value,d()?.compute?.({name:n}),a?.abort(),a=new AbortController;let{signal:t}=a,s=r.peek(),c=`data`in s?s.data:void 0;r.value={data:c,status:`loading`},(async()=>{try{let n=await e(t);!t.aborted&&!o&&(r.value={data:n,status:`ready`})}catch(e){!t.aborted&&!o&&(r.value={data:c,error:e,status:`error`})}})()},{name:n,[g]:!0}),c=()=>{o||(i.value=i.peek()+1)},l=()=>{o||(o=!0,a?.abort(),s.dispose(),r.dispose(),i.dispose())};return N(l),{dispose:l,get disposed(){return o},[p]:!0,[f]:!0,get name(){return r.name},peek:()=>r.peek(),refresh:c,subscribe:e=>r.subscribe(e),[Symbol.dispose]:l,get value(){return r.value}}};exports.RippleComputedCycleError=t,exports.RippleDisposedScopeError=n,exports.RippleEnvironmentError=r,exports.RippleError=e,exports.RippleInfiniteLoopError=i,exports.RippleInvalidCleanupError=a,exports.RippleInvalidStoreError=o,exports.batch=W,exports.computed=I,exports.effect=X,exports.effectAsync=Z,exports.getDevToolsHook=d,exports.isComputed=ge,exports.isReactive=$,exports.isSignal=he,exports.isStore=_e,exports.onCleanup=fe,exports.readonly=me,exports.resource=ve,exports.scope=Q,exports.signal=K,exports.store=ue,exports.untrack=P,exports.watch=pe;
|
|
2
2
|
//# sourceMappingURL=ripple.cjs.map
|