@vielzeug/ripple 1.2.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -71
- package/dist/_async.cjs +2 -0
- package/dist/_async.cjs.map +1 -0
- package/dist/_async.d.ts +40 -0
- package/dist/_async.d.ts.map +1 -0
- package/dist/_async.js +75 -0
- package/dist/_async.js.map +1 -0
- package/dist/_store.cjs +2 -0
- package/dist/_store.cjs.map +1 -0
- package/dist/_store.d.ts +20 -0
- package/dist/_store.d.ts.map +1 -0
- package/dist/_store.js +24 -0
- package/dist/_store.js.map +1 -0
- package/dist/_watch.cjs +2 -0
- package/dist/_watch.cjs.map +1 -0
- package/dist/_watch.d.ts +15 -0
- package/dist/_watch.d.ts.map +1 -0
- package/dist/_watch.js +18 -0
- package/dist/_watch.js.map +1 -0
- package/dist/async.cjs +1 -0
- package/dist/async.d.ts +3 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.js +2 -0
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +2 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +34 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -12
- package/dist/index.js.map +1 -0
- 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/runtime.cjs +2 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.ts +98 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +340 -0
- package/dist/runtime.js.map +1 -0
- package/dist/store.cjs +1 -2
- package/dist/store.d.ts +2 -44
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +2 -205
- package/dist/types.d.ts +37 -292
- package/dist/types.d.ts.map +1 -1
- package/dist/watch.cjs +1 -0
- package/dist/watch.d.ts +3 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +2 -0
- package/package.json +23 -16
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.d.ts +0 -2
- package/dist/_dev.d.ts.map +0 -1
- package/dist/_dev.js +0 -9
- package/dist/_dev.js.map +0 -1
- package/dist/_error-utils.cjs +0 -2
- package/dist/_error-utils.cjs.map +0 -1
- package/dist/_error-utils.d.ts +0 -24
- package/dist/_error-utils.d.ts.map +0 -1
- package/dist/_error-utils.js +0 -20
- package/dist/_error-utils.js.map +0 -1
- package/dist/_virtual/_rolldown/runtime.js +0 -7
- package/dist/async-computed.cjs +0 -2
- package/dist/async-computed.cjs.map +0 -1
- package/dist/async-computed.d.ts +0 -36
- package/dist/async-computed.d.ts.map +0 -1
- package/dist/async-computed.js +0 -58
- package/dist/async-computed.js.map +0 -1
- package/dist/computed.cjs +0 -2
- package/dist/computed.cjs.map +0 -1
- package/dist/computed.d.ts +0 -26
- package/dist/computed.d.ts.map +0 -1
- package/dist/computed.js +0 -113
- package/dist/computed.js.map +0 -1
- package/dist/devtools-hook.cjs +0 -2
- package/dist/devtools-hook.cjs.map +0 -1
- package/dist/devtools-hook.d.ts +0 -4
- package/dist/devtools-hook.d.ts.map +0 -1
- package/dist/devtools-hook.js +0 -8
- package/dist/devtools-hook.js.map +0 -1
- package/dist/devtools.d.ts +0 -50
- package/dist/devtools.d.ts.map +0 -1
- package/dist/effect.cjs +0 -2
- package/dist/effect.cjs.map +0 -1
- package/dist/effect.d.ts +0 -104
- package/dist/effect.d.ts.map +0 -1
- package/dist/effect.js +0 -173
- package/dist/effect.js.map +0 -1
- package/dist/reactive-base.cjs +0 -2
- package/dist/reactive-base.cjs.map +0 -1
- package/dist/reactive-base.d.ts +0 -60
- package/dist/reactive-base.d.ts.map +0 -1
- package/dist/reactive-base.js +0 -56
- package/dist/reactive-base.js.map +0 -1
- package/dist/scheduling.cjs +0 -2
- package/dist/scheduling.cjs.map +0 -1
- package/dist/scheduling.d.ts +0 -5
- package/dist/scheduling.d.ts.map +0 -1
- package/dist/scheduling.js +0 -57
- package/dist/scheduling.js.map +0 -1
- package/dist/signal.cjs +0 -2
- package/dist/signal.cjs.map +0 -1
- package/dist/signal.d.ts +0 -24
- package/dist/signal.d.ts.map +0 -1
- package/dist/signal.js +0 -54
- package/dist/signal.js.map +0 -1
- package/dist/src/devtools.cjs +0 -2
- package/dist/src/devtools.cjs.map +0 -1
- package/dist/src/devtools.js +0 -36
- package/dist/src/devtools.js.map +0 -1
- package/dist/src/ssr/index.cjs +0 -2
- package/dist/src/ssr/index.cjs.map +0 -1
- package/dist/src/ssr/index.js +0 -38
- package/dist/src/ssr/index.js.map +0 -1
- package/dist/ssr/index.d.ts +0 -56
- package/dist/ssr/index.d.ts.map +0 -1
- package/dist/store-history.cjs +0 -2
- package/dist/store-history.cjs.map +0 -1
- package/dist/store-history.d.ts +0 -42
- package/dist/store-history.d.ts.map +0 -1
- package/dist/store-history.js +0 -60
- package/dist/store-history.js.map +0 -1
- package/dist/store.cjs.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/subscription.cjs +0 -2
- package/dist/subscription.cjs.map +0 -1
- package/dist/subscription.d.ts +0 -22
- package/dist/subscription.d.ts.map +0 -1
- package/dist/subscription.js +0 -47
- package/dist/subscription.js.map +0 -1
- package/dist/symbols.cjs +0 -2
- package/dist/symbols.cjs.map +0 -1
- package/dist/symbols.d.ts +0 -6
- package/dist/symbols.d.ts.map +0 -1
- package/dist/symbols.js +0 -6
- package/dist/symbols.js.map +0 -1
- package/dist/tracking.cjs +0 -2
- package/dist/tracking.cjs.map +0 -1
- package/dist/tracking.d.ts +0 -82
- package/dist/tracking.d.ts.map +0 -1
- package/dist/tracking.js +0 -76
- package/dist/tracking.js.map +0 -1
- package/dist/utilities.cjs +0 -2
- package/dist/utilities.cjs.map +0 -1
- package/dist/utilities.d.ts +0 -56
- package/dist/utilities.d.ts.map +0 -1
- package/dist/utilities.js +0 -25
- package/dist/utilities.js.map +0 -1
package/dist/async-computed.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { autoRegisterDisposal as e } from "./tracking.js";
|
|
2
|
-
import { IS_COMPUTED as t, IS_SIGNAL as n } from "./symbols.js";
|
|
3
|
-
import { effect as r } from "./effect.js";
|
|
4
|
-
import { signal as i } from "./signal.js";
|
|
5
|
-
//#region src/async-computed.ts
|
|
6
|
-
var a = (a, o) => {
|
|
7
|
-
let s = o?.name, c = i({
|
|
8
|
-
data: o?.initialValue,
|
|
9
|
-
status: "loading"
|
|
10
|
-
}, s ? { name: s } : void 0), l = i(0), u = null, d = !1, f = r(() => {
|
|
11
|
-
l.value, u?.abort(), u = new AbortController();
|
|
12
|
-
let { signal: e } = u, t = c.peek(), n = "data" in t ? t.data : void 0;
|
|
13
|
-
c.value = {
|
|
14
|
-
data: n,
|
|
15
|
-
status: "loading"
|
|
16
|
-
}, (async () => {
|
|
17
|
-
try {
|
|
18
|
-
let t = await a(e);
|
|
19
|
-
!e.aborted && !d && (c.value = {
|
|
20
|
-
data: t,
|
|
21
|
-
status: "ready"
|
|
22
|
-
});
|
|
23
|
-
} catch (t) {
|
|
24
|
-
!e.aborted && !d && (c.value = {
|
|
25
|
-
data: n,
|
|
26
|
-
error: t,
|
|
27
|
-
status: "error"
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
})();
|
|
31
|
-
}, s ? { name: s } : void 0), p = () => {
|
|
32
|
-
d || (l.value = l.peek() + 1);
|
|
33
|
-
}, m = () => {
|
|
34
|
-
d || (d = !0, u?.abort(), f.dispose(), c.dispose(), l.dispose());
|
|
35
|
-
};
|
|
36
|
-
return e(m), {
|
|
37
|
-
dispose: m,
|
|
38
|
-
get disposed() {
|
|
39
|
-
return d;
|
|
40
|
-
},
|
|
41
|
-
[t]: !0,
|
|
42
|
-
[n]: !0,
|
|
43
|
-
get name() {
|
|
44
|
-
return c.name;
|
|
45
|
-
},
|
|
46
|
-
peek: () => c.peek(),
|
|
47
|
-
refresh: p,
|
|
48
|
-
subscribe: (e) => c.subscribe(e),
|
|
49
|
-
[Symbol.dispose]: m,
|
|
50
|
-
get value() {
|
|
51
|
-
return c.value;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
//#endregion
|
|
56
|
-
export { a as resource };
|
|
57
|
-
|
|
58
|
-
//# sourceMappingURL=async-computed.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"async-computed.js","names":[],"sources":["../src/async-computed.ts"],"sourcesContent":["import type { Resource, ResourceOptions, ResourceState } from './types';\n\nimport { effect } from './effect';\nimport { signal } from './signal';\nimport { IS_COMPUTED, IS_SIGNAL } from './symbols';\nimport { autoRegisterDisposal } from './tracking';\n\n/**\n * Creates a reactive async resource that emits a `ResourceState<T>` discriminated union.\n *\n * Dependencies are tracked synchronously in the factory (before the first `await`).\n * The factory receives an `AbortSignal` that fires when dependencies change or\n * the resource is disposed, cancelling any in-flight work.\n *\n * If `resource()` is created inside an active `effect()` or `scope.run()` context,\n * it is automatically registered for cleanup and disposed with that context —\n * matching `computed()`'s auto-disposal behavior.\n *\n * Call `refresh()` to force the factory to re-run immediately, even if no tracked\n * dependency changed — useful for manual \"retry\"/\"refetch\" actions.\n *\n * @example\n * ```ts\n * const userId = signal('u1');\n * const user = resource(async (signal) => {\n * const id = userId.value; // tracked dep — re-runs when userId changes\n * return fetchUser(id, { signal });\n * });\n *\n * effect(() => {\n * const s = user.value;\n * if (s.status === 'loading') return showSpinner();\n * if (s.status === 'error') return showError(s.error);\n * renderUser(s.data);\n * });\n *\n * user.refresh(); // force a re-fetch without changing userId\n * user.dispose();\n * ```\n */\nexport const resource = <T>(\n factory: (abortSignal: AbortSignal) => Promise<T>,\n options?: ResourceOptions<T>,\n): Resource<T> => {\n const name = options?.name;\n const state = signal<ResourceState<T>>(\n { data: options?.initialValue, status: 'loading' },\n name ? { name } : undefined,\n );\n // Internal-only trigger — never exposed. Read (but never written to a meaningful\n // value) inside the effect purely so it becomes a tracked dep; refresh() bumps it\n // to force a re-run without requiring a real dependency to change.\n const epoch = signal(0);\n\n let controller: AbortController | null = null;\n let disposed = false;\n\n const stop = effect(\n () => {\n void epoch.value;\n\n controller?.abort();\n controller = new AbortController();\n\n const { signal: abortSignal } = controller;\n\n const current = state.peek();\n const prevData = 'data' in current ? current.data : undefined;\n\n state.value = { data: prevData, status: 'loading' };\n\n void (async () => {\n try {\n const result = await factory(abortSignal);\n\n if (!abortSignal.aborted && !disposed) {\n state.value = { data: result, status: 'ready' };\n }\n } catch (err) {\n if (!abortSignal.aborted && !disposed) {\n state.value = { data: prevData, error: err, status: 'error' };\n }\n }\n })();\n },\n name ? { name } : undefined,\n );\n\n const refresh = (): void => {\n if (disposed) return;\n\n epoch.value = epoch.peek() + 1;\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n controller?.abort();\n stop.dispose();\n state.dispose();\n epoch.dispose();\n };\n\n autoRegisterDisposal(dispose);\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n [IS_COMPUTED]: true as const,\n [IS_SIGNAL]: true as const,\n get name() {\n return state.name;\n },\n peek: () => state.peek(),\n refresh,\n subscribe: (listener: () => void) => state.subscribe(listener),\n [Symbol.dispose]: dispose,\n get value() {\n return state.value;\n },\n } as Resource<T>;\n};\n"],"mappings":";;;;;AAwCA,IAAa,KACX,GACA,MACgB;CAChB,IAAM,IAAO,GAAS,MAChB,IAAQ,EACZ;EAAE,MAAM,GAAS;EAAc,QAAQ;CAAU,GACjD,IAAO,EAAE,QAAK,IAAI,KAAA,CACpB,GAIM,IAAQ,EAAO,CAAC,GAElB,IAAqC,MACrC,IAAW,IAET,IAAO,QACL;EAIJ,AAHA,EAAW,OAEX,GAAY,MAAM,GAClB,IAAa,IAAI,gBAAgB;EAEjC,IAAM,EAAE,QAAQ,MAAgB,GAE1B,IAAU,EAAM,KAAK,GACrB,IAAW,UAAU,IAAU,EAAQ,OAAO,KAAA;EAIpD,AAFA,EAAM,QAAQ;GAAE,MAAM;GAAU,QAAQ;EAAU,IAE5C,YAAY;GAChB,IAAI;IACF,IAAM,IAAS,MAAM,EAAQ,CAAW;IAExC,AAAI,CAAC,EAAY,WAAW,CAAC,MAC3B,EAAM,QAAQ;KAAE,MAAM;KAAQ,QAAQ;IAAQ;GAElD,SAAS,GAAK;IACZ,AAAI,CAAC,EAAY,WAAW,CAAC,MAC3B,EAAM,QAAQ;KAAE,MAAM;KAAU,OAAO;KAAK,QAAQ;IAAQ;GAEhE;EACF,EAAA,CAAG;CACL,GACA,IAAO,EAAE,QAAK,IAAI,KAAA,CACpB,GAEM,UAAsB;EACtB,MAEJ,EAAM,QAAQ,EAAM,KAAK,IAAI;CAC/B,GAEM,UAAsB;EACtB,MAEJ,IAAW,IACX,GAAY,MAAM,GAClB,EAAK,QAAQ,GACb,EAAM,QAAQ,GACd,EAAM,QAAQ;CAChB;CAIA,OAFA,EAAqB,CAAO,GAErB;EACL;EACA,IAAI,WAAW;GACb,OAAO;EACT;GACC,IAAc;GACd,IAAY;EACb,IAAI,OAAO;GACT,OAAO,EAAM;EACf;EACA,YAAY,EAAM,KAAK;EACvB;EACA,YAAY,MAAyB,EAAM,UAAU,CAAQ;GAC5D,OAAO,UAAU;EAClB,IAAI,QAAQ;GACV,OAAO,EAAM;EACf;CACF;AACF"}
|
package/dist/computed.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./devtools-hook.cjs"),t=require("./_error-utils.cjs"),n=require("./errors.cjs"),r=require("./tracking.cjs"),i=require("./subscription.cjs"),a=require("./symbols.cjs"),o=require("./reactive-base.cjs");var s=class extends o.ComputedBase{value_;dirty_;computing_;disposed_;deps_;compute_;equals_;maxRevision_;constructor(e,t){let{equals:n,name:r}=t??{};super(r),this.value_=a.UNINITIALIZED,this.dirty_=!0,this.computing_=!1,this.disposed_=!1,this.deps_=[],this.maxRevision_=-1,this.compute_=e,this.equals_=n===void 0?Object.is:(e,t)=>n(e,t)}markDirty(){return this.disposed_||this.dirty_?!1:(this.dirty_=!0,!0)}refreshIfDirty(){if(!this.dirty_)return!1;if(r.getRevision()<=this.maxRevision_)return this.dirty_=!1,!1;if(this.deps_.length>0){let e=!0;for(let t of this.deps_){let n=t.source;if(`refreshIfDirty`in n&&n.refreshIfDirty(),n.version!==t.version){e=!1;break}}if(e)return this.dirty_=!1,this.maxRevision_=r.getRevision(),!1}return this.recompute()}runCompute(e){try{return r.withTracking({computed:this,depCollector:e,kind:`computed`},this.compute_)}catch(e){throw t.ensureError(e)}}recompute(){if(this.computing_)throw new n.RippleComputedCycleError(`computed cycle detected${this.name?` "${this.name}"`:``}`);this.computing_=!0;try{let t=[];e.getDevToolsHook()?.compute?.({name:this.name});let n=this.runCompute(t);return this.dirty_=!1,this.maxRevision_=r.getRevision(),this.updateDeps(t),this.value_===a.UNINITIALIZED||!this.equals_(this.value_,n)?(this.value_=n,this.version++,!0):!1}finally{this.computing_=!1}}updateDeps(e){let t=this.deps_;if(t.length===e.length&&t.every((t,n)=>t.source===e[n].source)){for(let n=0;n<e.length;n++)t[n].version=e[n].version;return}let n=new Set(t.map(e=>e.source)),r=new Set(e.map(e=>e.source));for(let e of t)r.has(e.source)||e.source.removeComputedSub(this);for(let t of e)n.has(t.source)||t.source.addComputedSub(this);this.deps_=e}get value(){return this.disposed_?this.value_===a.UNINITIALIZED?void 0:this.value_:(this.refreshIfDirty(),r.trackSource(this),this.value_)}peek(){return this.disposed_?this.value_===a.UNINITIALIZED?void 0:this.value_:(this.refreshIfDirty(),this.value_)}subscribe=e=>{if(this.disposed_){let e=new i.SubscriptionImpl(()=>{});return e.dispose(),e}return this.refreshIfDirty(),this.addEffectSub(e),new i.SubscriptionImpl(()=>{this.removeEffectSub(e)})};get disposed(){return this.disposed_}dispose(){if(!this.disposed_){this.disposed_=!0;for(let e of this.deps_)e.source.removeComputedSub(this);this.deps_=[],this.clearSubscribers(),e.getDevToolsHook()?.dispose?.({kind:`computed`,name:this.name})}}[Symbol.dispose](){this.dispose()}},c=(e,t)=>{let n=new s(e,t);return r.autoRegisterDisposal(()=>n.dispose()),n};exports.ComputedImpl=s,exports.computed=c;
|
|
2
|
-
//# sourceMappingURL=computed.cjs.map
|
package/dist/computed.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"computed.cjs","names":[],"sources":["../src/computed.ts"],"sourcesContent":["import type { Refreshable } from './reactive-base';\nimport type { DepEntry } from './tracking';\nimport type { Computed, ComputedOptions, Subscription } from './types';\n\nimport { ensureError } from './_error-utils';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleComputedCycleError } from './errors';\nimport { ComputedBase } from './reactive-base';\nimport { SubscriptionImpl } from './subscription';\nimport { UNINITIALIZED } from './symbols';\nimport { autoRegisterDisposal, getRevision, trackSource, withTracking } from './tracking';\n\nexport class ComputedImpl<T> extends ComputedBase<T> implements Computed<T> {\n private value_: T | typeof UNINITIALIZED;\n private dirty_: boolean;\n private computing_: boolean;\n private disposed_: boolean;\n private deps_: DepEntry[];\n private compute_: () => T;\n private equals_: (a: unknown, b: unknown) => boolean;\n // Global revision at last successful compute — enables O(1) \"nothing changed\" fast-path.\n private maxRevision_: number;\n\n constructor(compute: () => T, options?: ComputedOptions<T>) {\n const { equals, name } = options ?? {};\n\n super(name);\n this.value_ = UNINITIALIZED;\n this.dirty_ = true;\n this.computing_ = false;\n this.disposed_ = false;\n this.deps_ = [];\n this.maxRevision_ = -1;\n this.compute_ = compute;\n this.equals_ = equals !== undefined ? (a, b) => equals(a as T, b as T) : Object.is;\n }\n\n markDirty(): boolean {\n if (this.disposed_ || this.dirty_) return false;\n\n this.dirty_ = true;\n\n return true;\n }\n\n refreshIfDirty(): boolean {\n if (!this.dirty_) return false;\n\n // O(1) global revision fast-path:\n // If the global revision clock has not advanced past our recorded maximum,\n // no signal anywhere in the system has been written since our last compute.\n // No dep can have changed — clear dirty and skip the per-dep scan entirely.\n if (getRevision() <= this.maxRevision_) {\n this.dirty_ = false;\n\n return false;\n }\n\n // Per-dep scan: if all dep versions still match, value cannot have changed.\n // For computed deps, we must refresh them first — their version only updates\n // after recompute, so a stale-version check would incorrectly short-circuit.\n if (this.deps_.length > 0) {\n let allSame = true;\n\n for (const d of this.deps_) {\n const src = d.source;\n\n // Flush dirty computed deps before comparing versions (lazy pull chain).\n if ('refreshIfDirty' in src) {\n (src as Refreshable).refreshIfDirty();\n }\n\n if (src.version !== d.version) {\n allSame = false;\n break;\n }\n }\n\n if (allSame) {\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n return false;\n }\n }\n\n return this.recompute();\n }\n\n private runCompute(depCollector: DepEntry[]): T {\n try {\n return withTracking({ computed: this, depCollector, kind: 'computed' }, this.compute_);\n } catch (error) {\n // Sources accessed before the throw remain in their computedSubs_ until the\n // next successful recompute or dispose(). The computed stays dirty and will retry.\n throw ensureError(error);\n }\n }\n\n private recompute(): boolean {\n if (this.computing_) {\n const label = this.name ? ` \"${this.name}\"` : '';\n\n throw new RippleComputedCycleError(`computed cycle detected${label}`);\n }\n\n this.computing_ = true;\n\n try {\n const newDeps: DepEntry[] = [];\n\n getDevToolsHook()?.compute?.({ name: this.name });\n\n const next = this.runCompute(newDeps);\n\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n // Diff deps: remove subscriptions for sources that are no longer dependencies.\n // Only subscribe to genuinely NEW deps — prevents duplicate WeakRef entries.\n this.updateDeps(newDeps);\n\n const isNew = this.value_ === UNINITIALIZED;\n\n if (isNew || !this.equals_(this.value_ as T, next)) {\n this.value_ = next;\n this.version++;\n\n return true;\n }\n\n return false;\n } finally {\n this.computing_ = false;\n }\n }\n\n private updateDeps(newDeps: DepEntry[]): void {\n const oldDeps = this.deps_;\n\n // Fast path: same sources in same order — just refresh stored versions\n if (oldDeps.length === newDeps.length && oldDeps.every((d, i) => d.source === newDeps[i].source)) {\n for (let i = 0; i < newDeps.length; i++) {\n oldDeps[i]!.version = newDeps[i]!.version;\n }\n\n return;\n }\n\n // Slow path: deps changed.\n // Build a set of OLD sources so we can detect which are genuinely NEW and\n // need a fresh addComputedSub call (WeakRef registration).\n const oldSourceSet = new Set(oldDeps.map((d) => d.source));\n const newSourceSet = new Set(newDeps.map((d) => d.source));\n\n // Unsubscribe from sources that are no longer dependencies\n for (const old of oldDeps) {\n if (!newSourceSet.has(old.source)) {\n old.source.removeComputedSub(this);\n }\n }\n\n // Subscribe to sources that are genuinely NEW (not in old deps)\n for (const dep of newDeps) {\n if (!oldSourceSet.has(dep.source)) {\n dep.source.addComputedSub(this);\n }\n }\n\n this.deps_ = newDeps;\n }\n\n get value(): T {\n // After disposal: return last known value without tracking (inert node, like signal).\n // If the computed was never evaluated (still UNINITIALIZED), return undefined.\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n trackSource(this);\n\n return this.value_ as T;\n }\n\n // peek() after dispose: return last known value silently, consistent with signal.peek().\n // If the computed was never evaluated, return undefined (same as signal with undefined initial).\n peek(): T {\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n\n return this.value_ as T;\n }\n\n readonly subscribe = (listener: () => void): Subscription => {\n if (this.disposed_) {\n const noop = new SubscriptionImpl(() => {});\n\n noop.dispose();\n\n return noop;\n }\n\n this.refreshIfDirty();\n this.addEffectSub(listener);\n\n return new SubscriptionImpl(() => {\n this.removeEffectSub(listener);\n });\n };\n\n get disposed(): boolean {\n return this.disposed_;\n }\n\n dispose(): void {\n if (this.disposed_) return;\n\n this.disposed_ = true;\n\n // Unsubscribe from all upstream sources\n for (const dep of this.deps_) {\n dep.source.removeComputedSub(this);\n }\n\n this.deps_ = [];\n this.clearSubscribers();\n getDevToolsHook()?.dispose?.({ kind: 'computed', name: this.name });\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────────\n\nexport const computed = <T>(compute: () => T, options?: ComputedOptions<T>): Computed<T> => {\n const comp = new ComputedImpl(compute, options);\n\n autoRegisterDisposal(() => comp.dispose());\n\n return comp;\n};\n"],"mappings":"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"}
|
package/dist/computed.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { Computed, ComputedOptions, Subscription } from './types';
|
|
2
|
-
import { ComputedBase } from './reactive-base';
|
|
3
|
-
export declare class ComputedImpl<T> extends ComputedBase<T> implements Computed<T> {
|
|
4
|
-
private value_;
|
|
5
|
-
private dirty_;
|
|
6
|
-
private computing_;
|
|
7
|
-
private disposed_;
|
|
8
|
-
private deps_;
|
|
9
|
-
private compute_;
|
|
10
|
-
private equals_;
|
|
11
|
-
private maxRevision_;
|
|
12
|
-
constructor(compute: () => T, options?: ComputedOptions<T>);
|
|
13
|
-
markDirty(): boolean;
|
|
14
|
-
refreshIfDirty(): boolean;
|
|
15
|
-
private runCompute;
|
|
16
|
-
private recompute;
|
|
17
|
-
private updateDeps;
|
|
18
|
-
get value(): T;
|
|
19
|
-
peek(): T;
|
|
20
|
-
readonly subscribe: (listener: () => void) => Subscription;
|
|
21
|
-
get disposed(): boolean;
|
|
22
|
-
dispose(): void;
|
|
23
|
-
[Symbol.dispose](): void;
|
|
24
|
-
}
|
|
25
|
-
export declare const computed: <T>(compute: () => T, options?: ComputedOptions<T>) => Computed<T>;
|
|
26
|
-
//# sourceMappingURL=computed.d.ts.map
|
package/dist/computed.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAKvE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK/C,qBAAa,YAAY,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAsC;IAErD,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;IAc1D,SAAS,IAAI,OAAO;IAQpB,cAAc,IAAI,OAAO;IA4CzB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,SAAS;IAsCjB,OAAO,CAAC,UAAU;IAmClB,IAAI,KAAK,IAAI,CAAC,CAWb;IAID,IAAI,IAAI,CAAC;IAUT,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,YAAY,CAevD;IAEF,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,OAAO,IAAI,IAAI;IAef,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB;AAID,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,SAAS,MAAM,CAAC,EAAE,UAAU,eAAe,CAAC,CAAC,CAAC,KAAG,QAAQ,CAAC,CAAC,CAMtF,CAAC"}
|
package/dist/computed.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { getDevToolsHook as e } from "./devtools-hook.js";
|
|
2
|
-
import { ensureError as t } from "./_error-utils.js";
|
|
3
|
-
import { RippleComputedCycleError as n } from "./errors.js";
|
|
4
|
-
import { autoRegisterDisposal as r, getRevision as i, trackSource as a, withTracking as o } from "./tracking.js";
|
|
5
|
-
import { SubscriptionImpl as s } from "./subscription.js";
|
|
6
|
-
import { UNINITIALIZED as c } from "./symbols.js";
|
|
7
|
-
import { ComputedBase as l } from "./reactive-base.js";
|
|
8
|
-
//#region src/computed.ts
|
|
9
|
-
var u = class extends l {
|
|
10
|
-
value_;
|
|
11
|
-
dirty_;
|
|
12
|
-
computing_;
|
|
13
|
-
disposed_;
|
|
14
|
-
deps_;
|
|
15
|
-
compute_;
|
|
16
|
-
equals_;
|
|
17
|
-
maxRevision_;
|
|
18
|
-
constructor(e, t) {
|
|
19
|
-
let { equals: n, name: r } = t ?? {};
|
|
20
|
-
super(r), this.value_ = c, this.dirty_ = !0, this.computing_ = !1, this.disposed_ = !1, this.deps_ = [], this.maxRevision_ = -1, this.compute_ = e, this.equals_ = n === void 0 ? Object.is : (e, t) => n(e, t);
|
|
21
|
-
}
|
|
22
|
-
markDirty() {
|
|
23
|
-
return this.disposed_ || this.dirty_ ? !1 : (this.dirty_ = !0, !0);
|
|
24
|
-
}
|
|
25
|
-
refreshIfDirty() {
|
|
26
|
-
if (!this.dirty_) return !1;
|
|
27
|
-
if (i() <= this.maxRevision_) return this.dirty_ = !1, !1;
|
|
28
|
-
if (this.deps_.length > 0) {
|
|
29
|
-
let e = !0;
|
|
30
|
-
for (let t of this.deps_) {
|
|
31
|
-
let n = t.source;
|
|
32
|
-
if ("refreshIfDirty" in n && n.refreshIfDirty(), n.version !== t.version) {
|
|
33
|
-
e = !1;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (e) return this.dirty_ = !1, this.maxRevision_ = i(), !1;
|
|
38
|
-
}
|
|
39
|
-
return this.recompute();
|
|
40
|
-
}
|
|
41
|
-
runCompute(e) {
|
|
42
|
-
try {
|
|
43
|
-
return o({
|
|
44
|
-
computed: this,
|
|
45
|
-
depCollector: e,
|
|
46
|
-
kind: "computed"
|
|
47
|
-
}, this.compute_);
|
|
48
|
-
} catch (e) {
|
|
49
|
-
throw t(e);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
recompute() {
|
|
53
|
-
if (this.computing_) throw new n(`computed cycle detected${this.name ? ` "${this.name}"` : ""}`);
|
|
54
|
-
this.computing_ = !0;
|
|
55
|
-
try {
|
|
56
|
-
let t = [];
|
|
57
|
-
e()?.compute?.({ name: this.name });
|
|
58
|
-
let n = this.runCompute(t);
|
|
59
|
-
return this.dirty_ = !1, this.maxRevision_ = i(), this.updateDeps(t), this.value_ === c || !this.equals_(this.value_, n) ? (this.value_ = n, this.version++, !0) : !1;
|
|
60
|
-
} finally {
|
|
61
|
-
this.computing_ = !1;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
updateDeps(e) {
|
|
65
|
-
let t = this.deps_;
|
|
66
|
-
if (t.length === e.length && t.every((t, n) => t.source === e[n].source)) {
|
|
67
|
-
for (let n = 0; n < e.length; n++) t[n].version = e[n].version;
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
let n = new Set(t.map((e) => e.source)), r = new Set(e.map((e) => e.source));
|
|
71
|
-
for (let e of t) r.has(e.source) || e.source.removeComputedSub(this);
|
|
72
|
-
for (let t of e) n.has(t.source) || t.source.addComputedSub(this);
|
|
73
|
-
this.deps_ = e;
|
|
74
|
-
}
|
|
75
|
-
get value() {
|
|
76
|
-
return this.disposed_ ? this.value_ === c ? void 0 : this.value_ : (this.refreshIfDirty(), a(this), this.value_);
|
|
77
|
-
}
|
|
78
|
-
peek() {
|
|
79
|
-
return this.disposed_ ? this.value_ === c ? void 0 : this.value_ : (this.refreshIfDirty(), this.value_);
|
|
80
|
-
}
|
|
81
|
-
subscribe = (e) => {
|
|
82
|
-
if (this.disposed_) {
|
|
83
|
-
let e = new s(() => {});
|
|
84
|
-
return e.dispose(), e;
|
|
85
|
-
}
|
|
86
|
-
return this.refreshIfDirty(), this.addEffectSub(e), new s(() => {
|
|
87
|
-
this.removeEffectSub(e);
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
get disposed() {
|
|
91
|
-
return this.disposed_;
|
|
92
|
-
}
|
|
93
|
-
dispose() {
|
|
94
|
-
if (!this.disposed_) {
|
|
95
|
-
this.disposed_ = !0;
|
|
96
|
-
for (let e of this.deps_) e.source.removeComputedSub(this);
|
|
97
|
-
this.deps_ = [], this.clearSubscribers(), e()?.dispose?.({
|
|
98
|
-
kind: "computed",
|
|
99
|
-
name: this.name
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
[Symbol.dispose]() {
|
|
104
|
-
this.dispose();
|
|
105
|
-
}
|
|
106
|
-
}, d = (e, t) => {
|
|
107
|
-
let n = new u(e, t);
|
|
108
|
-
return r(() => n.dispose()), n;
|
|
109
|
-
};
|
|
110
|
-
//#endregion
|
|
111
|
-
export { u as ComputedImpl, d as computed };
|
|
112
|
-
|
|
113
|
-
//# sourceMappingURL=computed.js.map
|
package/dist/computed.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"computed.js","names":[],"sources":["../src/computed.ts"],"sourcesContent":["import type { Refreshable } from './reactive-base';\nimport type { DepEntry } from './tracking';\nimport type { Computed, ComputedOptions, Subscription } from './types';\n\nimport { ensureError } from './_error-utils';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleComputedCycleError } from './errors';\nimport { ComputedBase } from './reactive-base';\nimport { SubscriptionImpl } from './subscription';\nimport { UNINITIALIZED } from './symbols';\nimport { autoRegisterDisposal, getRevision, trackSource, withTracking } from './tracking';\n\nexport class ComputedImpl<T> extends ComputedBase<T> implements Computed<T> {\n private value_: T | typeof UNINITIALIZED;\n private dirty_: boolean;\n private computing_: boolean;\n private disposed_: boolean;\n private deps_: DepEntry[];\n private compute_: () => T;\n private equals_: (a: unknown, b: unknown) => boolean;\n // Global revision at last successful compute — enables O(1) \"nothing changed\" fast-path.\n private maxRevision_: number;\n\n constructor(compute: () => T, options?: ComputedOptions<T>) {\n const { equals, name } = options ?? {};\n\n super(name);\n this.value_ = UNINITIALIZED;\n this.dirty_ = true;\n this.computing_ = false;\n this.disposed_ = false;\n this.deps_ = [];\n this.maxRevision_ = -1;\n this.compute_ = compute;\n this.equals_ = equals !== undefined ? (a, b) => equals(a as T, b as T) : Object.is;\n }\n\n markDirty(): boolean {\n if (this.disposed_ || this.dirty_) return false;\n\n this.dirty_ = true;\n\n return true;\n }\n\n refreshIfDirty(): boolean {\n if (!this.dirty_) return false;\n\n // O(1) global revision fast-path:\n // If the global revision clock has not advanced past our recorded maximum,\n // no signal anywhere in the system has been written since our last compute.\n // No dep can have changed — clear dirty and skip the per-dep scan entirely.\n if (getRevision() <= this.maxRevision_) {\n this.dirty_ = false;\n\n return false;\n }\n\n // Per-dep scan: if all dep versions still match, value cannot have changed.\n // For computed deps, we must refresh them first — their version only updates\n // after recompute, so a stale-version check would incorrectly short-circuit.\n if (this.deps_.length > 0) {\n let allSame = true;\n\n for (const d of this.deps_) {\n const src = d.source;\n\n // Flush dirty computed deps before comparing versions (lazy pull chain).\n if ('refreshIfDirty' in src) {\n (src as Refreshable).refreshIfDirty();\n }\n\n if (src.version !== d.version) {\n allSame = false;\n break;\n }\n }\n\n if (allSame) {\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n return false;\n }\n }\n\n return this.recompute();\n }\n\n private runCompute(depCollector: DepEntry[]): T {\n try {\n return withTracking({ computed: this, depCollector, kind: 'computed' }, this.compute_);\n } catch (error) {\n // Sources accessed before the throw remain in their computedSubs_ until the\n // next successful recompute or dispose(). The computed stays dirty and will retry.\n throw ensureError(error);\n }\n }\n\n private recompute(): boolean {\n if (this.computing_) {\n const label = this.name ? ` \"${this.name}\"` : '';\n\n throw new RippleComputedCycleError(`computed cycle detected${label}`);\n }\n\n this.computing_ = true;\n\n try {\n const newDeps: DepEntry[] = [];\n\n getDevToolsHook()?.compute?.({ name: this.name });\n\n const next = this.runCompute(newDeps);\n\n this.dirty_ = false;\n this.maxRevision_ = getRevision();\n\n // Diff deps: remove subscriptions for sources that are no longer dependencies.\n // Only subscribe to genuinely NEW deps — prevents duplicate WeakRef entries.\n this.updateDeps(newDeps);\n\n const isNew = this.value_ === UNINITIALIZED;\n\n if (isNew || !this.equals_(this.value_ as T, next)) {\n this.value_ = next;\n this.version++;\n\n return true;\n }\n\n return false;\n } finally {\n this.computing_ = false;\n }\n }\n\n private updateDeps(newDeps: DepEntry[]): void {\n const oldDeps = this.deps_;\n\n // Fast path: same sources in same order — just refresh stored versions\n if (oldDeps.length === newDeps.length && oldDeps.every((d, i) => d.source === newDeps[i].source)) {\n for (let i = 0; i < newDeps.length; i++) {\n oldDeps[i]!.version = newDeps[i]!.version;\n }\n\n return;\n }\n\n // Slow path: deps changed.\n // Build a set of OLD sources so we can detect which are genuinely NEW and\n // need a fresh addComputedSub call (WeakRef registration).\n const oldSourceSet = new Set(oldDeps.map((d) => d.source));\n const newSourceSet = new Set(newDeps.map((d) => d.source));\n\n // Unsubscribe from sources that are no longer dependencies\n for (const old of oldDeps) {\n if (!newSourceSet.has(old.source)) {\n old.source.removeComputedSub(this);\n }\n }\n\n // Subscribe to sources that are genuinely NEW (not in old deps)\n for (const dep of newDeps) {\n if (!oldSourceSet.has(dep.source)) {\n dep.source.addComputedSub(this);\n }\n }\n\n this.deps_ = newDeps;\n }\n\n get value(): T {\n // After disposal: return last known value without tracking (inert node, like signal).\n // If the computed was never evaluated (still UNINITIALIZED), return undefined.\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n trackSource(this);\n\n return this.value_ as T;\n }\n\n // peek() after dispose: return last known value silently, consistent with signal.peek().\n // If the computed was never evaluated, return undefined (same as signal with undefined initial).\n peek(): T {\n if (this.disposed_) {\n return this.value_ !== UNINITIALIZED ? (this.value_ as T) : (undefined as unknown as T);\n }\n\n this.refreshIfDirty();\n\n return this.value_ as T;\n }\n\n readonly subscribe = (listener: () => void): Subscription => {\n if (this.disposed_) {\n const noop = new SubscriptionImpl(() => {});\n\n noop.dispose();\n\n return noop;\n }\n\n this.refreshIfDirty();\n this.addEffectSub(listener);\n\n return new SubscriptionImpl(() => {\n this.removeEffectSub(listener);\n });\n };\n\n get disposed(): boolean {\n return this.disposed_;\n }\n\n dispose(): void {\n if (this.disposed_) return;\n\n this.disposed_ = true;\n\n // Unsubscribe from all upstream sources\n for (const dep of this.deps_) {\n dep.source.removeComputedSub(this);\n }\n\n this.deps_ = [];\n this.clearSubscribers();\n getDevToolsHook()?.dispose?.({ kind: 'computed', name: this.name });\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────────\n\nexport const computed = <T>(compute: () => T, options?: ComputedOptions<T>): Computed<T> => {\n const comp = new ComputedImpl(compute, options);\n\n autoRegisterDisposal(() => comp.dispose());\n\n return comp;\n};\n"],"mappings":"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"}
|
package/dist/devtools-hook.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-hook.cjs","names":[],"sources":["../src/devtools-hook.ts"],"sourcesContent":["// ── DevTools hook — core stub ─────────────────────────────────────────────────\n//\n// This module is intentionally minimal: it only holds the module-level _hook\n// variable and exposes getDevToolsHook() for the hot paths in signal.ts,\n// computed.ts, effect.ts, and store.ts.\n//\n// installDevTools() lives in the @vielzeug/ripple/devtools sub-path and is\n// tree-shaken from production bundles when that sub-path is never imported.\n// setDevToolsHook() is the internal bridge called by installDevTools().\n\nimport type { RippleDevToolsHook } from './types';\n\nlet _hook: RippleDevToolsHook | null = null;\n\n/** Returns the currently installed DevTools hook, or `null` if none is installed. */\nexport const getDevToolsHook = (): RippleDevToolsHook | null => _hook;\n\n/** @internal Called only by `installDevTools` in `@vielzeug/ripple/devtools`. */\nexport const setDevToolsHook = (hook: RippleDevToolsHook | null): void => {\n _hook = hook;\n};\n"],"mappings":"AAYA,IAAI,EAAmC,KAG1B,MAAmD,EAGnD,EAAmB,GAA0C,CACxE,EAAQ,CACV"}
|
package/dist/devtools-hook.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-hook.d.ts","sourceRoot":"","sources":["../src/devtools-hook.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAIlD,qFAAqF;AACrF,eAAO,MAAM,eAAe,QAAO,kBAAkB,GAAG,IAAa,CAAC"}
|
package/dist/devtools-hook.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-hook.js","names":[],"sources":["../src/devtools-hook.ts"],"sourcesContent":["// ── DevTools hook — core stub ─────────────────────────────────────────────────\n//\n// This module is intentionally minimal: it only holds the module-level _hook\n// variable and exposes getDevToolsHook() for the hot paths in signal.ts,\n// computed.ts, effect.ts, and store.ts.\n//\n// installDevTools() lives in the @vielzeug/ripple/devtools sub-path and is\n// tree-shaken from production bundles when that sub-path is never imported.\n// setDevToolsHook() is the internal bridge called by installDevTools().\n\nimport type { RippleDevToolsHook } from './types';\n\nlet _hook: RippleDevToolsHook | null = null;\n\n/** Returns the currently installed DevTools hook, or `null` if none is installed. */\nexport const getDevToolsHook = (): RippleDevToolsHook | null => _hook;\n\n/** @internal Called only by `installDevTools` in `@vielzeug/ripple/devtools`. */\nexport const setDevToolsHook = (hook: RippleDevToolsHook | null): void => {\n _hook = hook;\n};\n"],"mappings":";AAYA,IAAI,IAAmC,MAG1B,UAAmD,GAGnD,KAAmB,MAA0C;CACxE,IAAQ;AACV"}
|
package/dist/devtools.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vielzeug/ripple — debug utilities for reactivity visualisation.
|
|
3
|
-
*
|
|
4
|
-
* Import from the dedicated sub-path so it is tree-shaken from production bundles:
|
|
5
|
-
* ```ts
|
|
6
|
-
* import { installDevTools, debugEffect } from '@vielzeug/ripple/devtools';
|
|
7
|
-
* ```
|
|
8
|
-
*
|
|
9
|
-
* The hot-path stub (`getDevToolsHook`) lives in `devtools-hook.ts` and is the only
|
|
10
|
-
* part that ships in the core bundle.
|
|
11
|
-
*/
|
|
12
|
-
import type { EffectCallback, EffectHandle, EffectOptions, RippleDevToolsHook } from './types';
|
|
13
|
-
export type { DisposeEvent, MutateEvent, NamedEvent, RippleDevToolsHook, WriteEvent } from './types';
|
|
14
|
-
/**
|
|
15
|
-
* Installs a DevTools hook for observing ripple internals.
|
|
16
|
-
* Pass `null` to uninstall.
|
|
17
|
-
*
|
|
18
|
-
* Import from the dedicated sub-path to keep this out of production bundles:
|
|
19
|
-
* ```ts
|
|
20
|
-
* import { installDevTools } from '@vielzeug/ripple/devtools';
|
|
21
|
-
*
|
|
22
|
-
* installDevTools({
|
|
23
|
-
* write({ name, oldValue, newValue }) {
|
|
24
|
-
* console.log(`[ripple] ${name ?? '(unnamed)'}: ${String(oldValue)} → ${String(newValue)}`);
|
|
25
|
-
* },
|
|
26
|
-
* dispose({ kind, name }) {
|
|
27
|
-
* console.log(`[ripple] ${kind} "${name ?? '(unnamed)'}" disposed`);
|
|
28
|
-
* },
|
|
29
|
-
* });
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export declare const installDevTools: (hook: RippleDevToolsHook | null) => void;
|
|
33
|
-
/**
|
|
34
|
-
* Wraps `effect()` and logs reactive dependency information on every run:
|
|
35
|
-
* - **Initial run**: logs all deps the effect subscribed to.
|
|
36
|
-
* - **Re-runs**: logs which deps changed (with old and new version numbers).
|
|
37
|
-
*
|
|
38
|
-
* Use instead of `effect()` when debugging unexpected re-renders.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { debugEffect } from '@vielzeug/ripple/devtools';
|
|
43
|
-
*
|
|
44
|
-
* const stop = debugEffect(() => {
|
|
45
|
-
* renderUser(userId.value, name.value);
|
|
46
|
-
* }, { name: 'renderUser' });
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
export declare const debugEffect: (fn: EffectCallback, options?: EffectOptions) => EffectHandle;
|
|
50
|
-
//# sourceMappingURL=devtools.d.ts.map
|
package/dist/devtools.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAa,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAQ1G,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIrG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,kBAAkB,GAAG,IAAI,KAAG,IAQjE,CAAC;AAIF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,cAAc,EAAE,UAAU,aAAa,KAAG,YA+CzE,CAAC"}
|
package/dist/effect.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./devtools-hook.cjs"),t=require("./_error-utils.cjs"),n=require("./errors.cjs"),r=require("./tracking.cjs");require("./scheduling.cjs");const i=require("./subscription.cjs"),a=require("./symbols.cjs");var o=(e,t)=>{if(t===`sync`||t===void 0)return e;let n=!1;return()=>{n||(n=!0,queueMicrotask(()=>{n=!1,e()}))}},s=(s,c)=>{let l=c?.scheduler??`sync`,u=c?.name,d=[],f=new Set,p=new Map,m=!1,h=!1,g=!1,_=()=>{if(p.clear(),d.length===0&&f.size===0)return;let e=[...d,...f];d.length=0,f.clear(),t.runAll(e,`effect teardown errors`)},v=()=>{if(!g){if(m){h=!0;return}m=!0;try{let i=0;do{if(++i>100)throw new n.RippleInfiniteLoopError(`infinite effect loop (> 100 iterations)${u?` in "${u}"`:``}`);h=!1,_(),e.getDevToolsHook()?.run?.({name:u});let a;try{a=r.withTracking({cleanups:d,deps:p,effect:y,kind:`effect`,subscriptions:f},s)}catch(e){let n=[...t.collectErrors(f),...t.collectErrors(d)];d.length=0,f.clear(),t.rethrowWith(e,n,`effect failure with cleanup errors`)}if(a!==void 0&&typeof a!=`function`)throw new n.RippleInvalidCleanupError(`effect() returned ${typeof a} — expected a cleanup function or void.`);typeof a==`function`&&d.push(a)}while(h&&!g)}finally{m=!1}}},y=o(v,l);v();let b=new i.SubscriptionImpl(()=>{g||(g=!0,_(),e.getDevToolsHook()?.dispose?.({kind:`effect`,name:u}))});return r.getScopeCleanups()?.push(()=>b.dispose()),{dispose:()=>b.dispose(),get disposed(){return b.disposed},getDependencies(){return[...p.keys()].map(e=>({kind:a.IS_COMPUTED in e?`computed`:`signal`,name:e.name}))},[Symbol.dispose](){b.dispose()}}},c=(e,t)=>{let n=null,r=null,a=null,o=t?.onError??(e=>{queueMicrotask(()=>{throw e})});return new i.AsyncSubscriptionImpl(s(()=>{n?.abort(),r?.(),r=null,n=new AbortController;let{signal:t}=n,i=u(),s;return a=s=(async()=>{try{let n=await e(t,i);typeof n==`function`&&(t.aborted?n():r=n)}catch(e){t.aborted||o(e)}finally{i.dispose(),a===s&&(a=null)}})(),()=>{n?.abort(),r?.(),r=null}},{name:t?.name}),async()=>{let e=a;e&&await e},()=>a)},l=e=>{let t=r.getTracking();if(t?.kind===`effect`){t.cleanups.push(e);return}let i=r.getScopeCleanups();if(i!==null){i.push(e);return}throw new n.RippleInvalidCleanupError(`onCleanup() must be called from within an active effect or scope.`)},u=e=>{let i=[],a=new AbortController,o=!1,s=e=>{if(o)throw new n.RippleDisposedScopeError(`Cannot register cleanup on a disposed scope.`);i.push(e)},c=e=>{if(o)throw new n.RippleDisposedScopeError(`Cannot run inside a disposed scope.`);return r.withScopeCleanups(i,e)},l=()=>{o||(o=!0,a.abort(),t.runAll([...i].reverse(),`scope cleanup errors`),i.length=0)},u={add:s,get disposalSignal(){return a.signal},dispose:l,get disposed(){return o},run:c,[Symbol.dispose]:l};return e&&c(e),u};function d(e,t,a){let o=a?.equals??Object.is,c,l=(e,r)=>{c?.(),c=void 0;let i=t(e,r);if(i!==void 0&&typeof i!=`function`)throw new n.RippleInvalidCleanupError(`watch() callback returned ${typeof i} — expected a cleanup function or void.`);typeof i==`function`&&(c=i)},u;if(typeof e==`function`){let t,n=!0,r=s(()=>{let r=e();if(n){n=!1,t=r;return}if(!o(t,r)){let e=t;t=r,l(r,e),a?.once&&u.dispose()}},{name:a?.name});u=new i.SubscriptionImpl(()=>{c?.(),c=void 0,r.dispose()}),a?.immediate&&(l(t,void 0),a.once&&u.dispose())}else{let t=e.peek(),n=e.subscribe(()=>{let n=e.peek();o(t,n)||(l(n,t),t=n,a?.once&&u.dispose())});u=new i.SubscriptionImpl(()=>{c?.(),c=void 0,n.dispose()}),a?.immediate&&(l(t,void 0),a.once&&u.dispose())}return r.getScopeCleanups()?.push(()=>u.dispose()),u}exports.effect=s,exports.effectAsync=c,exports.onCleanup=l,exports.scope=u,exports.watch=d;
|
|
2
|
-
//# sourceMappingURL=effect.cjs.map
|
package/dist/effect.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"effect.cjs","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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"}
|
package/dist/effect.d.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { AsyncEffectCallback, AsyncSubscription, CleanupFn, EffectAsyncOptions, EffectCallback, EffectHandle, EffectOptions, Readable, Scope, Subscription, WatchOptions } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a reactive side-effect.
|
|
4
|
-
* Runs immediately, re-runs whenever tracked dependencies change.
|
|
5
|
-
* The callback may return a cleanup function that runs before the next re-run and on dispose.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* const stop = effect(() => {
|
|
10
|
-
* document.title = count.value.toString();
|
|
11
|
-
* return () => { document.title = ''; }; // optional cleanup
|
|
12
|
-
* });
|
|
13
|
-
*
|
|
14
|
-
* stop.dispose(); // or: using stop = effect(...)
|
|
15
|
-
* console.log(stop.getDependencies()); // reactive graph introspection
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
export declare const effect: (fn: EffectCallback, options?: EffectOptions) => EffectHandle;
|
|
19
|
-
/**
|
|
20
|
-
* Like `effect()`, but the callback is async and receives an AbortSignal that fires when the
|
|
21
|
-
* effect re-runs or is disposed. Read reactive dependencies synchronously before the first
|
|
22
|
-
* `await` to register them as tracked.
|
|
23
|
-
*
|
|
24
|
-
* Returns an {@link AsyncSubscription} — call `await stop[Symbol.asyncDispose]()` to await
|
|
25
|
-
* full teardown, or `stop.run()` to await just the current in-flight run.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const userId = signal('u1');
|
|
30
|
-
* const stop = effectAsync(async (signal) => {
|
|
31
|
-
* const id = userId.value; // sync dep — tracked
|
|
32
|
-
* const data = await fetchUser(id, { signal }); // aborted automatically if id changes
|
|
33
|
-
* renderUser(data);
|
|
34
|
-
* });
|
|
35
|
-
*
|
|
36
|
-
* await stop[Symbol.asyncDispose]();
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare const effectAsync: (fn: AsyncEffectCallback, options?: EffectAsyncOptions) => AsyncSubscription;
|
|
40
|
-
export declare const onCleanup: (fn: CleanupFn) => void;
|
|
41
|
-
/**
|
|
42
|
-
* Creates a lifecycle scope that collects `onCleanup()` registrations and runs
|
|
43
|
-
* them in reverse order when `dispose()` is called.
|
|
44
|
-
*
|
|
45
|
-
* The optional `setup` callback is run immediately inside the scope so that
|
|
46
|
-
* `onCleanup()` calls in setup are captured without requiring a separate
|
|
47
|
-
* `scope.run(setup)` call.
|
|
48
|
-
*
|
|
49
|
-
* Use `scope.add(fn)` to explicitly register a cleanup into this scope from
|
|
50
|
-
* anywhere — including from inside an effect body where `onCleanup()` would otherwise
|
|
51
|
-
* go to the effect's own cleanup queue.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```ts
|
|
55
|
-
* const s = scope(() => {
|
|
56
|
-
* onCleanup(() => expensiveCleanup()); // captured by scope
|
|
57
|
-
* });
|
|
58
|
-
*
|
|
59
|
-
* const stop = effect(() => {
|
|
60
|
-
* void count.value;
|
|
61
|
-
* s.add(() => doWork()); // explicit: goes to scope, not effect
|
|
62
|
-
* onCleanup(() => cheapCleanup()); // goes to effect
|
|
63
|
-
* });
|
|
64
|
-
*
|
|
65
|
-
* // later:
|
|
66
|
-
* s.dispose(); // or: using s = scope(...)
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
export declare const scope: (setup?: () => void) => Scope;
|
|
70
|
-
/**
|
|
71
|
-
* Observes a reactive source and calls `callback` whenever its value changes.
|
|
72
|
-
* Does NOT invoke `callback` on creation (unless `immediate: true` is passed).
|
|
73
|
-
*
|
|
74
|
-
* `source` may be a single `Readable<T>` (signal, computed, or store lens), or a
|
|
75
|
-
* function that reads multiple reactive sources — the function form tracks every
|
|
76
|
-
* dependency read inside it, like `computed()`, without needing an intermediate
|
|
77
|
-
* `computed()` node.
|
|
78
|
-
*
|
|
79
|
-
* When called without `immediate`, `prev` is always `T` — the previous value.
|
|
80
|
-
* When `immediate: true` is passed, `prev` is `T | undefined` on the first call.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```ts
|
|
84
|
-
* const stop = watch(count, (next, prev) => {
|
|
85
|
-
* console.log(`count changed: ${prev} → ${next}`);
|
|
86
|
-
* });
|
|
87
|
-
*
|
|
88
|
-
* stop.dispose();
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```ts
|
|
93
|
-
* // Function form — tracks every signal read inside, re-runs when any changes.
|
|
94
|
-
* const stopSum = watch(
|
|
95
|
-
* () => a.value + b.value,
|
|
96
|
-
* (sum, prevSum) => console.log(`sum: ${prevSum} → ${sum}`),
|
|
97
|
-
* );
|
|
98
|
-
*
|
|
99
|
-
* stopSum.dispose();
|
|
100
|
-
* ```
|
|
101
|
-
*/
|
|
102
|
-
export declare function watch<T>(source: Readable<T>, callback: (value: T, prev: T | undefined) => CleanupFn | void, options?: WatchOptions<T>): Subscription;
|
|
103
|
-
export declare function watch<T>(source: () => T, callback: (value: T, prev: T | undefined) => CleanupFn | void, options?: WatchOptions<T>): Subscription;
|
|
104
|
-
//# sourceMappingURL=effect.d.ts.map
|
package/dist/effect.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EAET,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,aAAa,EAEb,QAAQ,EACR,KAAK,EAEL,YAAY,EACZ,YAAY,EACb,MAAM,SAAS,CAAC;AAoCjB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM,GAAI,IAAI,cAAc,EAAE,UAAU,aAAa,KAAG,YAoHpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,mBAAmB,EAAE,UAAU,kBAAkB,KAAG,iBAiEnF,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,IAAI,SAAS,KAAG,IAkBzC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,IAAI,KAAG,KA0C1C,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,KAAK,CAAC,CAAC,EACrB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,SAAS,GAAG,IAAI,EAC7D,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,GACxB,YAAY,CAAC;AAChB,wBAAgB,KAAK,CAAC,CAAC,EACrB,MAAM,EAAE,MAAM,CAAC,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,SAAS,GAAG,IAAI,EAC7D,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,GACxB,YAAY,CAAC"}
|