@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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.cjs","names":[],"sources":["../../src/devtools.ts"],"sourcesContent":["/**\n * @vielzeug/ripple — debug utilities for reactivity visualisation.\n *\n * Import from the dedicated sub-path so it is tree-shaken from production bundles:\n * ```ts\n * import { installDevTools, debugEffect } from '@vielzeug/ripple/devtools';\n * ```\n *\n * The hot-path stub (`getDevToolsHook`) lives in `devtools-hook.ts` and is the only\n * part that ships in the core bundle.\n */\n\nimport type { DepEntry } from './tracking';\nimport type { CleanupFn, EffectCallback, EffectHandle, EffectOptions, RippleDevToolsHook } from './types';\n\nimport { setDevToolsHook } from './devtools-hook';\nimport { effect } from './effect';\nimport { withSourceObserver } from './tracking';\n\n// Re-export all hook types from the core types module so consumers of the\n// sub-path don't need to import from two places.\nexport type { DisposeEvent, MutateEvent, NamedEvent, RippleDevToolsHook, WriteEvent } from './types';\n\n// ── installDevTools ───────────────────────────────────────────────────────────\n\n/**\n * Installs a DevTools hook for observing ripple internals.\n * Pass `null` to uninstall.\n *\n * Import from the dedicated sub-path to keep this out of production bundles:\n * ```ts\n * import { installDevTools } from '@vielzeug/ripple/devtools';\n *\n * installDevTools({\n * write({ name, oldValue, newValue }) {\n * console.log(`[ripple] ${name ?? '(unnamed)'}: ${String(oldValue)} → ${String(newValue)}`);\n * },\n * dispose({ kind, name }) {\n * console.log(`[ripple] ${kind} \"${name ?? '(unnamed)'}\" disposed`);\n * },\n * });\n * ```\n */\nexport const installDevTools = (hook: RippleDevToolsHook | null): void => {\n setDevToolsHook(hook);\n\n // Keep the globalThis mirror in sync for browser-extension DevTools.\n // Use assignment (not delete) — configurable property semantics vary by environment.\n if (typeof globalThis !== 'undefined') {\n (globalThis as Record<string, unknown>)['__RIPPLE_DEVTOOLS__'] = hook ?? undefined;\n }\n};\n\n// ── debugEffect ───────────────────────────────────────────────────────────────\n\n/**\n * Wraps `effect()` and logs reactive dependency information on every run:\n * - **Initial run**: logs all deps the effect subscribed to.\n * - **Re-runs**: logs which deps changed (with old and new version numbers).\n *\n * Use instead of `effect()` when debugging unexpected re-renders.\n *\n * @example\n * ```ts\n * import { debugEffect } from '@vielzeug/ripple/devtools';\n *\n * const stop = debugEffect(() => {\n * renderUser(userId.value, name.value);\n * }, { name: 'renderUser' });\n * ```\n */\nexport const debugEffect = (fn: EffectCallback, options?: EffectOptions): EffectHandle => {\n const label = options?.name ?? 'anonymous';\n\n // Track versions seen on the last run so we can diff on the next run.\n let prevDeps: DepEntry[] = [];\n\n const wrappedFn = (): CleanupFn | void => {\n const currentDeps: DepEntry[] = [];\n\n // SourceObserver is scoped to the current TrackingCtx — nested computed\n // recomputes run in their own context (no observer), so no identity check needed.\n const result = withSourceObserver((source) => {\n currentDeps.push({ source, version: source.version });\n }, fn);\n\n if (prevDeps.length === 0) {\n // First run — log initial subscriptions.\n if (currentDeps.length > 0) {\n console.group(`[ripple:debug] \"${label}\" initial deps:`);\n\n for (const dep of currentDeps) {\n console.debug(` ${dep.source.name ?? '(unnamed)'} (v${dep.source.version})`);\n }\n\n console.groupEnd();\n }\n } else {\n // Re-run — log which sources changed.\n const changed = prevDeps.filter((d) => d.source.version !== d.version);\n\n if (changed.length > 0) {\n console.group(`[ripple:debug] \"${label}\" re-running — changed sources:`);\n\n for (const dep of changed) {\n console.debug(` ${dep.source.name ?? '(unnamed)'} (v${dep.version} -> v${dep.source.version})`);\n }\n\n console.groupEnd();\n }\n }\n\n prevDeps = currentDeps;\n\n return result;\n };\n\n return effect(wrappedFn, options);\n};\n"],"mappings":"mKA2CA,IAAa,EAAmB,GAA0C,CACxE,EAAA,gBAAgB,CAAI,EAIhB,OAAO,WAAe,MACxB,WAAwC,oBAAyB,GAAQ,IAAA,GAE7E,EAoBa,GAAe,EAAoB,IAA0C,CACxF,IAAM,EAAQ,GAAS,MAAQ,YAG3B,EAAuB,CAAC,EA0C5B,OAAO,EAAA,WAxCmC,CACxC,IAAM,EAA0B,CAAC,EAI3B,EAAS,EAAA,mBAAoB,GAAW,CAC5C,EAAY,KAAK,CAAE,SAAQ,QAAS,EAAO,OAAQ,CAAC,CACtD,EAAG,CAAE,EAEL,GAAI,EAAS,SAAW,MAElB,EAAY,OAAS,EAAG,CAC1B,QAAQ,MAAM,mBAAmB,EAAM,gBAAgB,EAEvD,IAAK,IAAM,KAAO,EAChB,QAAQ,MAAM,KAAK,EAAI,OAAO,MAAQ,YAAY,KAAK,EAAI,OAAO,QAAQ,EAAE,EAG9E,QAAQ,SAAS,CACnB,MACK,CAEL,IAAM,EAAU,EAAS,OAAQ,GAAM,EAAE,OAAO,UAAY,EAAE,OAAO,EAErE,GAAI,EAAQ,OAAS,EAAG,CACtB,QAAQ,MAAM,mBAAmB,EAAM,gCAAgC,EAEvE,IAAK,IAAM,KAAO,EAChB,QAAQ,MAAM,KAAK,EAAI,OAAO,MAAQ,YAAY,KAAK,EAAI,QAAQ,OAAO,EAAI,OAAO,QAAQ,EAAE,EAGjG,QAAQ,SAAS,CACnB,CACF,CAIA,MAFA,GAAW,EAEJ,CACT,EAEyB,CAAO,CAClC"}
|
package/dist/src/devtools.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { setDevToolsHook as e } from "../devtools-hook.js";
|
|
2
|
-
import { withSourceObserver as t } from "../tracking.js";
|
|
3
|
-
import { effect as n } from "../effect.js";
|
|
4
|
-
//#region src/devtools.ts
|
|
5
|
-
var r = (t) => {
|
|
6
|
-
e(t), typeof globalThis < "u" && (globalThis.__RIPPLE_DEVTOOLS__ = t ?? void 0);
|
|
7
|
-
}, i = (e, r) => {
|
|
8
|
-
let i = r?.name ?? "anonymous", a = [];
|
|
9
|
-
return n(() => {
|
|
10
|
-
let n = [], r = t((e) => {
|
|
11
|
-
n.push({
|
|
12
|
-
source: e,
|
|
13
|
-
version: e.version
|
|
14
|
-
});
|
|
15
|
-
}, e);
|
|
16
|
-
if (a.length === 0) {
|
|
17
|
-
if (n.length > 0) {
|
|
18
|
-
console.group(`[ripple:debug] "${i}" initial deps:`);
|
|
19
|
-
for (let e of n) console.debug(` ${e.source.name ?? "(unnamed)"} (v${e.source.version})`);
|
|
20
|
-
console.groupEnd();
|
|
21
|
-
}
|
|
22
|
-
} else {
|
|
23
|
-
let e = a.filter((e) => e.source.version !== e.version);
|
|
24
|
-
if (e.length > 0) {
|
|
25
|
-
console.group(`[ripple:debug] "${i}" re-running — changed sources:`);
|
|
26
|
-
for (let t of e) console.debug(` ${t.source.name ?? "(unnamed)"} (v${t.version} -> v${t.source.version})`);
|
|
27
|
-
console.groupEnd();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return a = n, r;
|
|
31
|
-
}, r);
|
|
32
|
-
};
|
|
33
|
-
//#endregion
|
|
34
|
-
export { i as debugEffect, r as installDevTools };
|
|
35
|
-
|
|
36
|
-
//# sourceMappingURL=devtools.js.map
|
package/dist/src/devtools.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.js","names":[],"sources":["../../src/devtools.ts"],"sourcesContent":["/**\n * @vielzeug/ripple — debug utilities for reactivity visualisation.\n *\n * Import from the dedicated sub-path so it is tree-shaken from production bundles:\n * ```ts\n * import { installDevTools, debugEffect } from '@vielzeug/ripple/devtools';\n * ```\n *\n * The hot-path stub (`getDevToolsHook`) lives in `devtools-hook.ts` and is the only\n * part that ships in the core bundle.\n */\n\nimport type { DepEntry } from './tracking';\nimport type { CleanupFn, EffectCallback, EffectHandle, EffectOptions, RippleDevToolsHook } from './types';\n\nimport { setDevToolsHook } from './devtools-hook';\nimport { effect } from './effect';\nimport { withSourceObserver } from './tracking';\n\n// Re-export all hook types from the core types module so consumers of the\n// sub-path don't need to import from two places.\nexport type { DisposeEvent, MutateEvent, NamedEvent, RippleDevToolsHook, WriteEvent } from './types';\n\n// ── installDevTools ───────────────────────────────────────────────────────────\n\n/**\n * Installs a DevTools hook for observing ripple internals.\n * Pass `null` to uninstall.\n *\n * Import from the dedicated sub-path to keep this out of production bundles:\n * ```ts\n * import { installDevTools } from '@vielzeug/ripple/devtools';\n *\n * installDevTools({\n * write({ name, oldValue, newValue }) {\n * console.log(`[ripple] ${name ?? '(unnamed)'}: ${String(oldValue)} → ${String(newValue)}`);\n * },\n * dispose({ kind, name }) {\n * console.log(`[ripple] ${kind} \"${name ?? '(unnamed)'}\" disposed`);\n * },\n * });\n * ```\n */\nexport const installDevTools = (hook: RippleDevToolsHook | null): void => {\n setDevToolsHook(hook);\n\n // Keep the globalThis mirror in sync for browser-extension DevTools.\n // Use assignment (not delete) — configurable property semantics vary by environment.\n if (typeof globalThis !== 'undefined') {\n (globalThis as Record<string, unknown>)['__RIPPLE_DEVTOOLS__'] = hook ?? undefined;\n }\n};\n\n// ── debugEffect ───────────────────────────────────────────────────────────────\n\n/**\n * Wraps `effect()` and logs reactive dependency information on every run:\n * - **Initial run**: logs all deps the effect subscribed to.\n * - **Re-runs**: logs which deps changed (with old and new version numbers).\n *\n * Use instead of `effect()` when debugging unexpected re-renders.\n *\n * @example\n * ```ts\n * import { debugEffect } from '@vielzeug/ripple/devtools';\n *\n * const stop = debugEffect(() => {\n * renderUser(userId.value, name.value);\n * }, { name: 'renderUser' });\n * ```\n */\nexport const debugEffect = (fn: EffectCallback, options?: EffectOptions): EffectHandle => {\n const label = options?.name ?? 'anonymous';\n\n // Track versions seen on the last run so we can diff on the next run.\n let prevDeps: DepEntry[] = [];\n\n const wrappedFn = (): CleanupFn | void => {\n const currentDeps: DepEntry[] = [];\n\n // SourceObserver is scoped to the current TrackingCtx — nested computed\n // recomputes run in their own context (no observer), so no identity check needed.\n const result = withSourceObserver((source) => {\n currentDeps.push({ source, version: source.version });\n }, fn);\n\n if (prevDeps.length === 0) {\n // First run — log initial subscriptions.\n if (currentDeps.length > 0) {\n console.group(`[ripple:debug] \"${label}\" initial deps:`);\n\n for (const dep of currentDeps) {\n console.debug(` ${dep.source.name ?? '(unnamed)'} (v${dep.source.version})`);\n }\n\n console.groupEnd();\n }\n } else {\n // Re-run — log which sources changed.\n const changed = prevDeps.filter((d) => d.source.version !== d.version);\n\n if (changed.length > 0) {\n console.group(`[ripple:debug] \"${label}\" re-running — changed sources:`);\n\n for (const dep of changed) {\n console.debug(` ${dep.source.name ?? '(unnamed)'} (v${dep.version} -> v${dep.source.version})`);\n }\n\n console.groupEnd();\n }\n }\n\n prevDeps = currentDeps;\n\n return result;\n };\n\n return effect(wrappedFn, options);\n};\n"],"mappings":";;;;AA2CA,IAAa,KAAmB,MAA0C;CAKxE,AAJA,EAAgB,CAAI,GAIhB,OAAO,aAAe,QACxB,WAAwC,sBAAyB,KAAQ,KAAA;AAE7E,GAoBa,KAAe,GAAoB,MAA0C;CACxF,IAAM,IAAQ,GAAS,QAAQ,aAG3B,IAAuB,CAAC;CA0C5B,OAAO,QAxCmC;EACxC,IAAM,IAA0B,CAAC,GAI3B,IAAS,GAAoB,MAAW;GAC5C,EAAY,KAAK;IAAE;IAAQ,SAAS,EAAO;GAAQ,CAAC;EACtD,GAAG,CAAE;EAEL,IAAI,EAAS,WAAW;OAElB,EAAY,SAAS,GAAG;IAC1B,QAAQ,MAAM,mBAAmB,EAAM,gBAAgB;IAEvD,KAAK,IAAM,KAAO,GAChB,QAAQ,MAAM,KAAK,EAAI,OAAO,QAAQ,YAAY,KAAK,EAAI,OAAO,QAAQ,EAAE;IAG9E,QAAQ,SAAS;GACnB;SACK;GAEL,IAAM,IAAU,EAAS,QAAQ,MAAM,EAAE,OAAO,YAAY,EAAE,OAAO;GAErE,IAAI,EAAQ,SAAS,GAAG;IACtB,QAAQ,MAAM,mBAAmB,EAAM,gCAAgC;IAEvE,KAAK,IAAM,KAAO,GAChB,QAAQ,MAAM,KAAK,EAAI,OAAO,QAAQ,YAAY,KAAK,EAAI,QAAQ,OAAO,EAAI,OAAO,QAAQ,EAAE;IAGjG,QAAQ,SAAS;GACnB;EACF;EAIA,OAFA,IAAW,GAEJ;CACT,GAEyB,CAAO;AAClC"}
|
package/dist/src/ssr/index.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../errors.cjs"),t=require("../../tracking.cjs");var n={scheduling:t.createSchedulingState(),scopeCleanups:null,tracking:null},r=()=>({scheduling:t.createSchedulingState(),scopeCleanups:null,tracking:null}),i=null,a={get:()=>i?.get()??n,run:(e,t)=>i?i.run(e,t):t()},o=e=>{i=e,t._installContextHook(e===null?null:a)},s=()=>{if(typeof require>`u`)throw new e.RippleEnvironmentError(`createAsyncProvider() requires Node.js (async_hooks) and cannot be used in browser environments.`);let{AsyncLocalStorage:t}=require("async_hooks"),r=new t;return{get:()=>r.getStore()??n,run:(e,t)=>r.run(e,t)}},c=(e,t)=>e.run(r(),t),l=(e,n)=>{let o=i;i=e;let s=t._installContextHook(a);try{return e.run(r(),n)}finally{i=o,t._installContextHook(s)}};exports.createAsyncProvider=s,exports.runWithProvider=c,exports.setTrackingProvider=o,exports.withProvider=l;
|
|
2
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/ssr/index.ts"],"sourcesContent":["/**\n * /ripple/ssr\n *\n * SSR helpers for installing a custom tracking context provider.\n * Import this sub-entry **only** in server-side code — it is not needed for browser builds.\n *\n * @example\n * ```ts\n * // frameworks/hono.ts\n * import { createAsyncProvider, setTrackingProvider } from '@vielzeug/ripple/ssr';\n *\n * const provider = createAsyncProvider();\n * setTrackingProvider(provider);\n *\n * // Inside a request handler:\n * await runWithProvider(provider, () => renderToString(App));\n * ```\n */\n\nimport { RippleEnvironmentError } from '../errors';\nimport { _installContextHook, createSchedulingState, type ContextHook, type ExecutionContext } from '../tracking';\n\n// eslint-disable-next-line no-var\ndeclare var require: (id: string) => unknown;\n\ninterface AsyncLocalStorageType<T> {\n getStore(): T | undefined;\n run<R>(store: T, callback: () => R): R;\n}\n\n// ── Public interfaces ─────────────────────────────────────────────────────────\n\n/**\n * The clean starting context used as a fallback when no request is active\n * (e.g. a signal write outside any `runWithProvider`/`withProvider` call) —\n * a single shared instance is fine here since there's no concurrent request to isolate from.\n */\nconst EMPTY_CTX: ExecutionContext = { scheduling: createSchedulingState(), scopeCleanups: null, tracking: null };\n\n/**\n * Builds a fresh, isolated starting context for one request — no tracking, no scope,\n * and its own `SchedulingState` so this request's `batch()`/flush queue never shares\n * state with a concurrently-running request. Must be called anew per `run()` — reusing\n * a single context object across requests would defeat scheduling isolation.\n */\nconst freshRequestContext = (): ExecutionContext => ({\n scheduling: createSchedulingState(),\n scopeCleanups: null,\n tracking: null,\n});\n\nexport interface TrackingProvider {\n get(): ExecutionContext;\n run<T>(ctx: ExecutionContext, fn: () => T): T;\n}\n\n// ── Sync provider (simple stack-based) ───────────────────────────────────────\n\nlet _currentProvider: TrackingProvider | null = null;\n\nconst syncHook: ContextHook = {\n get: () => _currentProvider?.get() ?? EMPTY_CTX,\n run: <T>(ctx: ExecutionContext, fn: () => T): T => {\n if (_currentProvider) return _currentProvider.run(ctx, fn);\n\n return fn();\n },\n};\n\nexport const setTrackingProvider = (provider: TrackingProvider | null): void => {\n _currentProvider = provider;\n _installContextHook(provider !== null ? syncHook : null);\n};\n\n// ── Async provider (AsyncLocalStorage-based) ─────────────────────────────────\n\n/**\n * Creates a provider that uses `AsyncLocalStorage` to carry the full execution\n * context (tracking + scope cleanups) across async boundaries.\n * Requires Node.js 16+ or a compatible runtime.\n *\n * @example\n * ```ts\n * const provider = createAsyncProvider();\n * setTrackingProvider(provider);\n *\n * // In a request handler:\n * await runWithProvider(provider, requestHandler);\n * ```\n */\nexport const createAsyncProvider = (): TrackingProvider => {\n // Dynamic require so this module can be bundled for environments without AsyncLocalStorage.\n // In browser builds, this code path should never be reached.\n if (typeof require === 'undefined') {\n throw new RippleEnvironmentError(\n 'createAsyncProvider() requires Node.js (async_hooks) and cannot be used in browser environments.',\n );\n }\n\n const { AsyncLocalStorage } = require('async_hooks') as { AsyncLocalStorage: new <T>() => AsyncLocalStorageType<T> };\n const storage = new AsyncLocalStorage<ExecutionContext>();\n\n return {\n get: () => storage.getStore() ?? EMPTY_CTX,\n run: <T>(ctx: ExecutionContext, fn: () => T): T => storage.run(ctx, fn),\n };\n};\n\n/**\n * Runs `fn` inside the given provider with a clean (no tracking, no scope) context.\n * Signals read inside `fn` will not register any tracking deps — equivalent to\n * starting a clean reactive root for each request.\n *\n * @example\n * ```ts\n * const html = await runWithProvider(provider, () => renderToString(App));\n * ```\n */\nexport const runWithProvider = <T>(provider: TrackingProvider, fn: () => T): T =>\n provider.run(freshRequestContext(), fn);\n\n// ── Convenience: run once without installing a persistent provider ─────────────\n\n/**\n * Temporarily installs a provider, runs `fn`, then restores the previous provider.\n * Useful for one-off server renders without modifying global state.\n */\nexport const withProvider = <T>(provider: TrackingProvider, fn: () => T): T => {\n const prevProvider = _currentProvider;\n\n _currentProvider = provider;\n\n const prevHook = _installContextHook(syncHook);\n\n try {\n return provider.run(freshRequestContext(), fn);\n } finally {\n _currentProvider = prevProvider;\n _installContextHook(prevHook);\n }\n};\n"],"mappings":"uIAqCA,IAAM,EAA8B,CAAE,WAAY,EAAA,sBAAsB,EAAG,cAAe,KAAM,SAAU,IAAK,EAQzG,OAA+C,CACnD,WAAY,EAAA,sBAAsB,EAClC,cAAe,KACf,SAAU,IACZ,GASI,EAA4C,KAE1C,EAAwB,CAC5B,QAAW,GAAkB,IAAI,GAAK,EACtC,KAAS,EAAuB,IAC1B,EAAyB,EAAiB,IAAI,EAAK,CAAE,EAElD,EAAG,CAEd,EAEa,EAAuB,GAA4C,CAC9E,EAAmB,EACnB,EAAA,oBAAoB,IAAa,KAAkB,KAAX,CAAe,CACzD,EAkBa,MAA8C,CAGzD,GAAI,OAAO,QAAY,IACrB,MAAM,IAAI,EAAA,uBACR,kGACF,EAGF,GAAM,CAAE,qBAAsB,QAAQ,aAAa,EAC7C,EAAU,IAAI,EAEpB,MAAO,CACL,QAAW,EAAQ,SAAS,GAAK,EACjC,KAAS,EAAuB,IAAmB,EAAQ,IAAI,EAAK,CAAE,CACxE,CACF,EAYa,GAAsB,EAA4B,IAC7D,EAAS,IAAI,EAAoB,EAAG,CAAE,EAQ3B,GAAmB,EAA4B,IAAmB,CAC7E,IAAM,EAAe,EAErB,EAAmB,EAEnB,IAAM,EAAW,EAAA,oBAAoB,CAAQ,EAE7C,GAAI,CACF,OAAO,EAAS,IAAI,EAAoB,EAAG,CAAE,CAC/C,QAAU,CACR,EAAmB,EACnB,EAAA,oBAAoB,CAAQ,CAC9B,CACF"}
|
package/dist/src/ssr/index.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { __require as e } from "../../_virtual/_rolldown/runtime.js";
|
|
2
|
-
import { RippleEnvironmentError as t } from "../../errors.js";
|
|
3
|
-
import { _installContextHook as n, createSchedulingState as r } from "../../tracking.js";
|
|
4
|
-
//#region src/ssr/index.ts
|
|
5
|
-
var i = {
|
|
6
|
-
scheduling: r(),
|
|
7
|
-
scopeCleanups: null,
|
|
8
|
-
tracking: null
|
|
9
|
-
}, a = () => ({
|
|
10
|
-
scheduling: r(),
|
|
11
|
-
scopeCleanups: null,
|
|
12
|
-
tracking: null
|
|
13
|
-
}), o = null, s = {
|
|
14
|
-
get: () => o?.get() ?? i,
|
|
15
|
-
run: (e, t) => o ? o.run(e, t) : t()
|
|
16
|
-
}, c = (e) => {
|
|
17
|
-
o = e, n(e === null ? null : s);
|
|
18
|
-
}, l = () => {
|
|
19
|
-
if (e === void 0) throw new t("createAsyncProvider() requires Node.js (async_hooks) and cannot be used in browser environments.");
|
|
20
|
-
let { AsyncLocalStorage: n } = e("async_hooks"), r = new n();
|
|
21
|
-
return {
|
|
22
|
-
get: () => r.getStore() ?? i,
|
|
23
|
-
run: (e, t) => r.run(e, t)
|
|
24
|
-
};
|
|
25
|
-
}, u = (e, t) => e.run(a(), t), d = (e, t) => {
|
|
26
|
-
let r = o;
|
|
27
|
-
o = e;
|
|
28
|
-
let i = n(s);
|
|
29
|
-
try {
|
|
30
|
-
return e.run(a(), t);
|
|
31
|
-
} finally {
|
|
32
|
-
o = r, n(i);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
//#endregion
|
|
36
|
-
export { l as createAsyncProvider, u as runWithProvider, c as setTrackingProvider, d as withProvider };
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/ssr/index.ts"],"sourcesContent":["/**\n * /ripple/ssr\n *\n * SSR helpers for installing a custom tracking context provider.\n * Import this sub-entry **only** in server-side code — it is not needed for browser builds.\n *\n * @example\n * ```ts\n * // frameworks/hono.ts\n * import { createAsyncProvider, setTrackingProvider } from '@vielzeug/ripple/ssr';\n *\n * const provider = createAsyncProvider();\n * setTrackingProvider(provider);\n *\n * // Inside a request handler:\n * await runWithProvider(provider, () => renderToString(App));\n * ```\n */\n\nimport { RippleEnvironmentError } from '../errors';\nimport { _installContextHook, createSchedulingState, type ContextHook, type ExecutionContext } from '../tracking';\n\n// eslint-disable-next-line no-var\ndeclare var require: (id: string) => unknown;\n\ninterface AsyncLocalStorageType<T> {\n getStore(): T | undefined;\n run<R>(store: T, callback: () => R): R;\n}\n\n// ── Public interfaces ─────────────────────────────────────────────────────────\n\n/**\n * The clean starting context used as a fallback when no request is active\n * (e.g. a signal write outside any `runWithProvider`/`withProvider` call) —\n * a single shared instance is fine here since there's no concurrent request to isolate from.\n */\nconst EMPTY_CTX: ExecutionContext = { scheduling: createSchedulingState(), scopeCleanups: null, tracking: null };\n\n/**\n * Builds a fresh, isolated starting context for one request — no tracking, no scope,\n * and its own `SchedulingState` so this request's `batch()`/flush queue never shares\n * state with a concurrently-running request. Must be called anew per `run()` — reusing\n * a single context object across requests would defeat scheduling isolation.\n */\nconst freshRequestContext = (): ExecutionContext => ({\n scheduling: createSchedulingState(),\n scopeCleanups: null,\n tracking: null,\n});\n\nexport interface TrackingProvider {\n get(): ExecutionContext;\n run<T>(ctx: ExecutionContext, fn: () => T): T;\n}\n\n// ── Sync provider (simple stack-based) ───────────────────────────────────────\n\nlet _currentProvider: TrackingProvider | null = null;\n\nconst syncHook: ContextHook = {\n get: () => _currentProvider?.get() ?? EMPTY_CTX,\n run: <T>(ctx: ExecutionContext, fn: () => T): T => {\n if (_currentProvider) return _currentProvider.run(ctx, fn);\n\n return fn();\n },\n};\n\nexport const setTrackingProvider = (provider: TrackingProvider | null): void => {\n _currentProvider = provider;\n _installContextHook(provider !== null ? syncHook : null);\n};\n\n// ── Async provider (AsyncLocalStorage-based) ─────────────────────────────────\n\n/**\n * Creates a provider that uses `AsyncLocalStorage` to carry the full execution\n * context (tracking + scope cleanups) across async boundaries.\n * Requires Node.js 16+ or a compatible runtime.\n *\n * @example\n * ```ts\n * const provider = createAsyncProvider();\n * setTrackingProvider(provider);\n *\n * // In a request handler:\n * await runWithProvider(provider, requestHandler);\n * ```\n */\nexport const createAsyncProvider = (): TrackingProvider => {\n // Dynamic require so this module can be bundled for environments without AsyncLocalStorage.\n // In browser builds, this code path should never be reached.\n if (typeof require === 'undefined') {\n throw new RippleEnvironmentError(\n 'createAsyncProvider() requires Node.js (async_hooks) and cannot be used in browser environments.',\n );\n }\n\n const { AsyncLocalStorage } = require('async_hooks') as { AsyncLocalStorage: new <T>() => AsyncLocalStorageType<T> };\n const storage = new AsyncLocalStorage<ExecutionContext>();\n\n return {\n get: () => storage.getStore() ?? EMPTY_CTX,\n run: <T>(ctx: ExecutionContext, fn: () => T): T => storage.run(ctx, fn),\n };\n};\n\n/**\n * Runs `fn` inside the given provider with a clean (no tracking, no scope) context.\n * Signals read inside `fn` will not register any tracking deps — equivalent to\n * starting a clean reactive root for each request.\n *\n * @example\n * ```ts\n * const html = await runWithProvider(provider, () => renderToString(App));\n * ```\n */\nexport const runWithProvider = <T>(provider: TrackingProvider, fn: () => T): T =>\n provider.run(freshRequestContext(), fn);\n\n// ── Convenience: run once without installing a persistent provider ─────────────\n\n/**\n * Temporarily installs a provider, runs `fn`, then restores the previous provider.\n * Useful for one-off server renders without modifying global state.\n */\nexport const withProvider = <T>(provider: TrackingProvider, fn: () => T): T => {\n const prevProvider = _currentProvider;\n\n _currentProvider = provider;\n\n const prevHook = _installContextHook(syncHook);\n\n try {\n return provider.run(freshRequestContext(), fn);\n } finally {\n _currentProvider = prevProvider;\n _installContextHook(prevHook);\n }\n};\n"],"mappings":";;;;AAqCA,IAAM,IAA8B;CAAE,YAAY,EAAsB;CAAG,eAAe;CAAM,UAAU;AAAK,GAQzG,WAA+C;CACnD,YAAY,EAAsB;CAClC,eAAe;CACf,UAAU;AACZ,IASI,IAA4C,MAE1C,IAAwB;CAC5B,WAAW,GAAkB,IAAI,KAAK;CACtC,MAAS,GAAuB,MAC1B,IAAyB,EAAiB,IAAI,GAAK,CAAE,IAElD,EAAG;AAEd,GAEa,KAAuB,MAA4C;CAE9E,AADA,IAAmB,GACnB,EAAoB,MAAa,OAAkB,OAAX,CAAe;AACzD,GAkBa,UAA8C;CAGzD,IAAI,MAAmB,QACrB,MAAM,IAAI,EACR,kGACF;CAGF,IAAM,EAAE,yBAAA,EAA8B,aAAa,GAC7C,IAAU,IAAI,EAAoC;CAExD,OAAO;EACL,WAAW,EAAQ,SAAS,KAAK;EACjC,MAAS,GAAuB,MAAmB,EAAQ,IAAI,GAAK,CAAE;CACxE;AACF,GAYa,KAAsB,GAA4B,MAC7D,EAAS,IAAI,EAAoB,GAAG,CAAE,GAQ3B,KAAmB,GAA4B,MAAmB;CAC7E,IAAM,IAAe;CAErB,IAAmB;CAEnB,IAAM,IAAW,EAAoB,CAAQ;CAE7C,IAAI;EACF,OAAO,EAAS,IAAI,EAAoB,GAAG,CAAE;CAC/C,UAAU;EAER,AADA,IAAmB,GACnB,EAAoB,CAAQ;CAC9B;AACF"}
|
package/dist/ssr/index.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /ripple/ssr
|
|
3
|
-
*
|
|
4
|
-
* SSR helpers for installing a custom tracking context provider.
|
|
5
|
-
* Import this sub-entry **only** in server-side code — it is not needed for browser builds.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* // frameworks/hono.ts
|
|
10
|
-
* import { createAsyncProvider, setTrackingProvider } from '@vielzeug/ripple/ssr';
|
|
11
|
-
*
|
|
12
|
-
* const provider = createAsyncProvider();
|
|
13
|
-
* setTrackingProvider(provider);
|
|
14
|
-
*
|
|
15
|
-
* // Inside a request handler:
|
|
16
|
-
* await runWithProvider(provider, () => renderToString(App));
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
import { type ExecutionContext } from '../tracking';
|
|
20
|
-
export interface TrackingProvider {
|
|
21
|
-
get(): ExecutionContext;
|
|
22
|
-
run<T>(ctx: ExecutionContext, fn: () => T): T;
|
|
23
|
-
}
|
|
24
|
-
export declare const setTrackingProvider: (provider: TrackingProvider | null) => void;
|
|
25
|
-
/**
|
|
26
|
-
* Creates a provider that uses `AsyncLocalStorage` to carry the full execution
|
|
27
|
-
* context (tracking + scope cleanups) across async boundaries.
|
|
28
|
-
* Requires Node.js 16+ or a compatible runtime.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```ts
|
|
32
|
-
* const provider = createAsyncProvider();
|
|
33
|
-
* setTrackingProvider(provider);
|
|
34
|
-
*
|
|
35
|
-
* // In a request handler:
|
|
36
|
-
* await runWithProvider(provider, requestHandler);
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare const createAsyncProvider: () => TrackingProvider;
|
|
40
|
-
/**
|
|
41
|
-
* Runs `fn` inside the given provider with a clean (no tracking, no scope) context.
|
|
42
|
-
* Signals read inside `fn` will not register any tracking deps — equivalent to
|
|
43
|
-
* starting a clean reactive root for each request.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* const html = await runWithProvider(provider, () => renderToString(App));
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
export declare const runWithProvider: <T>(provider: TrackingProvider, fn: () => T) => T;
|
|
51
|
-
/**
|
|
52
|
-
* Temporarily installs a provider, runs `fn`, then restores the previous provider.
|
|
53
|
-
* Useful for one-off server renders without modifying global state.
|
|
54
|
-
*/
|
|
55
|
-
export declare const withProvider: <T>(provider: TrackingProvider, fn: () => T) => T;
|
|
56
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/ssr/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ssr/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAAgE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA+BlH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,IAAI,gBAAgB,CAAC;IACxB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;CAC/C;AAeD,eAAO,MAAM,mBAAmB,GAAI,UAAU,gBAAgB,GAAG,IAAI,KAAG,IAGvE,CAAC;AAIF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QAAO,gBAgBtC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,UAAU,gBAAgB,EAAE,IAAI,MAAM,CAAC,KAAG,CACpC,CAAC;AAI1C;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,UAAU,gBAAgB,EAAE,IAAI,MAAM,CAAC,KAAG,CAazE,CAAC"}
|
package/dist/store-history.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./signal.cjs"),t=require("./store.cjs"),n=require("./utilities.cjs");var r=(e,t)=>({label:t,state:Object.freeze(structuredClone({...e}))}),i=(i,a)=>{let o=a?.maxHistory??50,s=!n.isStore(i),c=s?t.store(i,{name:a?.name}):i,l=[r(c.peek())],u=e.signal(0,{name:a?.name?`${a.name}.cursor`:void 0}),d=!1,f=e=>{d||(l.splice(u.peek()+1),l.push(r(c.peek(),e)),l.length>o&&l.splice(0,l.length-o),u.value=l.length-1)},p=()=>{d||u.peek()<=0||(u.value=u.peek()-1,c.replace(()=>l[u.peek()].state))},m=()=>{d||u.peek()>=l.length-1||(u.value=u.peek()+1,c.replace(()=>l[u.peek()].state))},h=e=>{if(!(e<0||e>=l.length))return l[e]},g=()=>{d||(d=!0,u.dispose(),s&&c.dispose())};return{get canRedo(){return u.value<l.length-1},get canUndo(){return u.value>0},dispose:g,get disposed(){return d},historyAt:h,get historyLength(){return l.length},lens:e=>c.lens(e),get name(){return c.name},patch:e=>c.patch(e),peek:()=>c.peek(),push:()=>f(),pushNamed:e=>f(e),redo:m,replace:e=>c.replace(e),reset:()=>c.reset(),get store(){return c},subscribe:e=>c.subscribe(e),[Symbol.dispose]:g,undo:p,get value(){return c.value}}};exports.storeWithHistory=i;
|
|
2
|
-
//# sourceMappingURL=store-history.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store-history.cjs","names":[],"sources":["../src/store-history.ts"],"sourcesContent":["import type { HistoryEntry, Store, StoreWithHistory } from './types';\n\nimport { signal } from './signal';\nimport { store } from './store';\nimport { isStore } from './utilities';\n\nconst snapshot = <T extends object>(state: Readonly<T>, label?: string): HistoryEntry<T> => ({\n label,\n state: Object.freeze(structuredClone({ ...state })) as Readonly<T>,\n});\n\n/**\n * Wraps a store (or creates one from an initial value) with explicit snapshot history\n * for undo/redo / time-travel.\n *\n * **Ownership:** if called with an initial value, the adapter creates and owns\n * the underlying store — `dispose()` will also dispose it. If called with an\n * existing `Store<T>`, the adapter does NOT own it — `dispose()` leaves the\n * store alive.\n *\n * History is **explicit** — call `push()` or `pushNamed()` after mutations you want\n * to be undoable. This gives you full control over snapshot granularity: you can\n * batch multiple mutations before pushing, or avoid pushing for programmatic\n * operations like server sync or reset-and-reload.\n *\n * This is a pure external adapter — it does not access any store internals.\n *\n * @example\n * ```ts\n * const h = storeWithHistory({ count: 0 }, { maxHistory: 10 });\n *\n * h.patch({ count: 1 });\n * h.push(); // save checkpoint\n * h.patch({ count: 2 });\n * h.pushNamed('increment');\n *\n * h.undo(); // back to { count: 1 }\n * h.redo(); // forward to { count: 2 }\n * h.historyAt(0); // { label: undefined, state: { count: 0 } }\n * h.canUndo; // true\n *\n * // Wrap an existing store:\n * const existingStore = store({ x: 0 });\n * const h2 = storeWithHistory(existingStore);\n * h2.dispose(); // existingStore is NOT disposed\n * ```\n */\nexport const storeWithHistory = <T extends object>(\n storeOrInitial: Store<T> | T,\n options?: { maxHistory?: number; name?: string },\n): StoreWithHistory<T> => {\n const maxHistory = options?.maxHistory ?? 50;\n const ownsStore = !isStore(storeOrInitial);\n const base: Store<T> = ownsStore ? store(storeOrInitial as T, { name: options?.name }) : (storeOrInitial as Store<T>);\n\n const snapshots: HistoryEntry<T>[] = [snapshot(base.peek())];\n\n // cursor is a reactive signal so canUndo/canRedo participate in the reactive graph.\n const cursor = signal(0, { name: options?.name ? `${options.name}.cursor` : undefined });\n\n let disposed = false;\n\n const pushSnapshot = (label?: string): void => {\n if (disposed) return;\n\n snapshots.splice(cursor.peek() + 1);\n snapshots.push(snapshot(base.peek(), label));\n\n if (snapshots.length > maxHistory) {\n snapshots.splice(0, snapshots.length - maxHistory);\n }\n\n cursor.value = snapshots.length - 1;\n };\n\n const undo = (): void => {\n if (disposed || cursor.peek() <= 0) return;\n\n cursor.value = cursor.peek() - 1;\n base.replace(() => snapshots[cursor.peek()]!.state as T);\n };\n\n const redo = (): void => {\n if (disposed || cursor.peek() >= snapshots.length - 1) return;\n\n cursor.value = cursor.peek() + 1;\n base.replace(() => snapshots[cursor.peek()]!.state as T);\n };\n\n const historyAt = (index: number): HistoryEntry<T> | undefined => {\n if (index < 0 || index >= snapshots.length) return undefined;\n\n return snapshots[index];\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n cursor.dispose();\n\n if (ownsStore) base.dispose();\n };\n\n // Explicit delegation: all Store<T> methods are forwarded directly to base.\n // History-specific properties are defined inline.\n const adapter: StoreWithHistory<T> = {\n get canRedo() {\n return cursor.value < snapshots.length - 1;\n },\n // History-specific\n get canUndo() {\n return cursor.value > 0;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n historyAt,\n get historyLength() {\n return snapshots.length;\n },\n lens: <P extends string>(path: P) => base.lens(path),\n // Store<T> delegation\n get name() {\n return base.name;\n },\n patch: (partial) => base.patch(partial),\n peek: () => base.peek(),\n push: () => pushSnapshot(),\n pushNamed: (label: string) => pushSnapshot(label),\n redo,\n replace: (fn) => base.replace(fn),\n reset: () => base.reset(),\n get store() {\n return base;\n },\n subscribe: (listener) => base.subscribe(listener),\n [Symbol.dispose]: dispose,\n undo,\n get value() {\n return base.value;\n },\n };\n\n return adapter;\n};\n"],"mappings":"sFAMA,IAAM,GAA8B,EAAoB,KAAqC,CAC3F,QACA,MAAO,OAAO,OAAO,gBAAgB,CAAE,GAAG,CAAM,CAAC,CAAC,CACpD,GAsCa,GACX,EACA,IACwB,CACxB,IAAM,EAAa,GAAS,YAAc,GACpC,EAAY,CAAC,EAAA,QAAQ,CAAc,EACnC,EAAiB,EAAY,EAAA,MAAM,EAAqB,CAAE,KAAM,GAAS,IAAK,CAAC,EAAK,EAEpF,EAA+B,CAAC,EAAS,EAAK,KAAK,CAAC,CAAC,EAGrD,EAAS,EAAA,OAAO,EAAG,CAAE,KAAM,GAAS,KAAO,GAAG,EAAQ,KAAK,SAAW,IAAA,EAAU,CAAC,EAEnF,EAAW,GAET,EAAgB,GAAyB,CACzC,IAEJ,EAAU,OAAO,EAAO,KAAK,EAAI,CAAC,EAClC,EAAU,KAAK,EAAS,EAAK,KAAK,EAAG,CAAK,CAAC,EAEvC,EAAU,OAAS,GACrB,EAAU,OAAO,EAAG,EAAU,OAAS,CAAU,EAGnD,EAAO,MAAQ,EAAU,OAAS,EACpC,EAEM,MAAmB,CACnB,GAAY,EAAO,KAAK,GAAK,IAEjC,EAAO,MAAQ,EAAO,KAAK,EAAI,EAC/B,EAAK,YAAc,EAAU,EAAO,KAAK,EAAE,CAAE,KAAU,EACzD,EAEM,MAAmB,CACnB,GAAY,EAAO,KAAK,GAAK,EAAU,OAAS,IAEpD,EAAO,MAAQ,EAAO,KAAK,EAAI,EAC/B,EAAK,YAAc,EAAU,EAAO,KAAK,EAAE,CAAE,KAAU,EACzD,EAEM,EAAa,GAA+C,CAC5D,OAAQ,GAAK,GAAS,EAAU,QAEpC,OAAO,EAAU,EACnB,EAEM,MAAsB,CACtB,IAEJ,EAAW,GACX,EAAO,QAAQ,EAEX,GAAW,EAAK,QAAQ,EAC9B,EA2CA,MAAO,CAtCL,IAAI,SAAU,CACZ,OAAO,EAAO,MAAQ,EAAU,OAAS,CAC3C,EAEA,IAAI,SAAU,CACZ,OAAO,EAAO,MAAQ,CACxB,EACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,YACA,IAAI,eAAgB,CAClB,OAAO,EAAU,MACnB,EACA,KAAyB,GAAY,EAAK,KAAK,CAAI,EAEnD,IAAI,MAAO,CACT,OAAO,EAAK,IACd,EACA,MAAQ,GAAY,EAAK,MAAM,CAAO,EACtC,SAAY,EAAK,KAAK,EACtB,SAAY,EAAa,EACzB,UAAY,GAAkB,EAAa,CAAK,EAChD,OACA,QAAU,GAAO,EAAK,QAAQ,CAAE,EAChC,UAAa,EAAK,MAAM,EACxB,IAAI,OAAQ,CACV,OAAO,CACT,EACA,UAAY,GAAa,EAAK,UAAU,CAAQ,GAC/C,OAAO,SAAU,EAClB,OACA,IAAI,OAAQ,CACV,OAAO,EAAK,KACd,CAGK,CACT"}
|
package/dist/store-history.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { Store, StoreWithHistory } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Wraps a store (or creates one from an initial value) with explicit snapshot history
|
|
4
|
-
* for undo/redo / time-travel.
|
|
5
|
-
*
|
|
6
|
-
* **Ownership:** if called with an initial value, the adapter creates and owns
|
|
7
|
-
* the underlying store — `dispose()` will also dispose it. If called with an
|
|
8
|
-
* existing `Store<T>`, the adapter does NOT own it — `dispose()` leaves the
|
|
9
|
-
* store alive.
|
|
10
|
-
*
|
|
11
|
-
* History is **explicit** — call `push()` or `pushNamed()` after mutations you want
|
|
12
|
-
* to be undoable. This gives you full control over snapshot granularity: you can
|
|
13
|
-
* batch multiple mutations before pushing, or avoid pushing for programmatic
|
|
14
|
-
* operations like server sync or reset-and-reload.
|
|
15
|
-
*
|
|
16
|
-
* This is a pure external adapter — it does not access any store internals.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* const h = storeWithHistory({ count: 0 }, { maxHistory: 10 });
|
|
21
|
-
*
|
|
22
|
-
* h.patch({ count: 1 });
|
|
23
|
-
* h.push(); // save checkpoint
|
|
24
|
-
* h.patch({ count: 2 });
|
|
25
|
-
* h.pushNamed('increment');
|
|
26
|
-
*
|
|
27
|
-
* h.undo(); // back to { count: 1 }
|
|
28
|
-
* h.redo(); // forward to { count: 2 }
|
|
29
|
-
* h.historyAt(0); // { label: undefined, state: { count: 0 } }
|
|
30
|
-
* h.canUndo; // true
|
|
31
|
-
*
|
|
32
|
-
* // Wrap an existing store:
|
|
33
|
-
* const existingStore = store({ x: 0 });
|
|
34
|
-
* const h2 = storeWithHistory(existingStore);
|
|
35
|
-
* h2.dispose(); // existingStore is NOT disposed
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export declare const storeWithHistory: <T extends object>(storeOrInitial: Store<T> | T, options?: {
|
|
39
|
-
maxHistory?: number;
|
|
40
|
-
name?: string;
|
|
41
|
-
}) => StoreWithHistory<T>;
|
|
42
|
-
//# sourceMappingURL=store-history.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store-history.d.ts","sourceRoot":"","sources":["../src/store-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAWrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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/store-history.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { signal as e } from "./signal.js";
|
|
2
|
-
import { store as t } from "./store.js";
|
|
3
|
-
import { isStore as n } from "./utilities.js";
|
|
4
|
-
//#region src/store-history.ts
|
|
5
|
-
var r = (e, t) => ({
|
|
6
|
-
label: t,
|
|
7
|
-
state: Object.freeze(structuredClone({ ...e }))
|
|
8
|
-
}), i = (i, a) => {
|
|
9
|
-
let o = a?.maxHistory ?? 50, s = !n(i), c = s ? t(i, { name: a?.name }) : i, l = [r(c.peek())], u = e(0, { name: a?.name ? `${a.name}.cursor` : void 0 }), d = !1, f = (e) => {
|
|
10
|
-
d || (l.splice(u.peek() + 1), l.push(r(c.peek(), e)), l.length > o && l.splice(0, l.length - o), u.value = l.length - 1);
|
|
11
|
-
}, p = () => {
|
|
12
|
-
d || u.peek() <= 0 || (u.value = u.peek() - 1, c.replace(() => l[u.peek()].state));
|
|
13
|
-
}, m = () => {
|
|
14
|
-
d || u.peek() >= l.length - 1 || (u.value = u.peek() + 1, c.replace(() => l[u.peek()].state));
|
|
15
|
-
}, h = (e) => {
|
|
16
|
-
if (!(e < 0 || e >= l.length)) return l[e];
|
|
17
|
-
}, g = () => {
|
|
18
|
-
d || (d = !0, u.dispose(), s && c.dispose());
|
|
19
|
-
};
|
|
20
|
-
return {
|
|
21
|
-
get canRedo() {
|
|
22
|
-
return u.value < l.length - 1;
|
|
23
|
-
},
|
|
24
|
-
get canUndo() {
|
|
25
|
-
return u.value > 0;
|
|
26
|
-
},
|
|
27
|
-
dispose: g,
|
|
28
|
-
get disposed() {
|
|
29
|
-
return d;
|
|
30
|
-
},
|
|
31
|
-
historyAt: h,
|
|
32
|
-
get historyLength() {
|
|
33
|
-
return l.length;
|
|
34
|
-
},
|
|
35
|
-
lens: (e) => c.lens(e),
|
|
36
|
-
get name() {
|
|
37
|
-
return c.name;
|
|
38
|
-
},
|
|
39
|
-
patch: (e) => c.patch(e),
|
|
40
|
-
peek: () => c.peek(),
|
|
41
|
-
push: () => f(),
|
|
42
|
-
pushNamed: (e) => f(e),
|
|
43
|
-
redo: m,
|
|
44
|
-
replace: (e) => c.replace(e),
|
|
45
|
-
reset: () => c.reset(),
|
|
46
|
-
get store() {
|
|
47
|
-
return c;
|
|
48
|
-
},
|
|
49
|
-
subscribe: (e) => c.subscribe(e),
|
|
50
|
-
[Symbol.dispose]: g,
|
|
51
|
-
undo: p,
|
|
52
|
-
get value() {
|
|
53
|
-
return c.value;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
//#endregion
|
|
58
|
-
export { i as storeWithHistory };
|
|
59
|
-
|
|
60
|
-
//# sourceMappingURL=store-history.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store-history.js","names":[],"sources":["../src/store-history.ts"],"sourcesContent":["import type { HistoryEntry, Store, StoreWithHistory } from './types';\n\nimport { signal } from './signal';\nimport { store } from './store';\nimport { isStore } from './utilities';\n\nconst snapshot = <T extends object>(state: Readonly<T>, label?: string): HistoryEntry<T> => ({\n label,\n state: Object.freeze(structuredClone({ ...state })) as Readonly<T>,\n});\n\n/**\n * Wraps a store (or creates one from an initial value) with explicit snapshot history\n * for undo/redo / time-travel.\n *\n * **Ownership:** if called with an initial value, the adapter creates and owns\n * the underlying store — `dispose()` will also dispose it. If called with an\n * existing `Store<T>`, the adapter does NOT own it — `dispose()` leaves the\n * store alive.\n *\n * History is **explicit** — call `push()` or `pushNamed()` after mutations you want\n * to be undoable. This gives you full control over snapshot granularity: you can\n * batch multiple mutations before pushing, or avoid pushing for programmatic\n * operations like server sync or reset-and-reload.\n *\n * This is a pure external adapter — it does not access any store internals.\n *\n * @example\n * ```ts\n * const h = storeWithHistory({ count: 0 }, { maxHistory: 10 });\n *\n * h.patch({ count: 1 });\n * h.push(); // save checkpoint\n * h.patch({ count: 2 });\n * h.pushNamed('increment');\n *\n * h.undo(); // back to { count: 1 }\n * h.redo(); // forward to { count: 2 }\n * h.historyAt(0); // { label: undefined, state: { count: 0 } }\n * h.canUndo; // true\n *\n * // Wrap an existing store:\n * const existingStore = store({ x: 0 });\n * const h2 = storeWithHistory(existingStore);\n * h2.dispose(); // existingStore is NOT disposed\n * ```\n */\nexport const storeWithHistory = <T extends object>(\n storeOrInitial: Store<T> | T,\n options?: { maxHistory?: number; name?: string },\n): StoreWithHistory<T> => {\n const maxHistory = options?.maxHistory ?? 50;\n const ownsStore = !isStore(storeOrInitial);\n const base: Store<T> = ownsStore ? store(storeOrInitial as T, { name: options?.name }) : (storeOrInitial as Store<T>);\n\n const snapshots: HistoryEntry<T>[] = [snapshot(base.peek())];\n\n // cursor is a reactive signal so canUndo/canRedo participate in the reactive graph.\n const cursor = signal(0, { name: options?.name ? `${options.name}.cursor` : undefined });\n\n let disposed = false;\n\n const pushSnapshot = (label?: string): void => {\n if (disposed) return;\n\n snapshots.splice(cursor.peek() + 1);\n snapshots.push(snapshot(base.peek(), label));\n\n if (snapshots.length > maxHistory) {\n snapshots.splice(0, snapshots.length - maxHistory);\n }\n\n cursor.value = snapshots.length - 1;\n };\n\n const undo = (): void => {\n if (disposed || cursor.peek() <= 0) return;\n\n cursor.value = cursor.peek() - 1;\n base.replace(() => snapshots[cursor.peek()]!.state as T);\n };\n\n const redo = (): void => {\n if (disposed || cursor.peek() >= snapshots.length - 1) return;\n\n cursor.value = cursor.peek() + 1;\n base.replace(() => snapshots[cursor.peek()]!.state as T);\n };\n\n const historyAt = (index: number): HistoryEntry<T> | undefined => {\n if (index < 0 || index >= snapshots.length) return undefined;\n\n return snapshots[index];\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n cursor.dispose();\n\n if (ownsStore) base.dispose();\n };\n\n // Explicit delegation: all Store<T> methods are forwarded directly to base.\n // History-specific properties are defined inline.\n const adapter: StoreWithHistory<T> = {\n get canRedo() {\n return cursor.value < snapshots.length - 1;\n },\n // History-specific\n get canUndo() {\n return cursor.value > 0;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n historyAt,\n get historyLength() {\n return snapshots.length;\n },\n lens: <P extends string>(path: P) => base.lens(path),\n // Store<T> delegation\n get name() {\n return base.name;\n },\n patch: (partial) => base.patch(partial),\n peek: () => base.peek(),\n push: () => pushSnapshot(),\n pushNamed: (label: string) => pushSnapshot(label),\n redo,\n replace: (fn) => base.replace(fn),\n reset: () => base.reset(),\n get store() {\n return base;\n },\n subscribe: (listener) => base.subscribe(listener),\n [Symbol.dispose]: dispose,\n undo,\n get value() {\n return base.value;\n },\n };\n\n return adapter;\n};\n"],"mappings":";;;;AAMA,IAAM,KAA8B,GAAoB,OAAqC;CAC3F;CACA,OAAO,OAAO,OAAO,gBAAgB,EAAE,GAAG,EAAM,CAAC,CAAC;AACpD,IAsCa,KACX,GACA,MACwB;CACxB,IAAM,IAAa,GAAS,cAAc,IACpC,IAAY,CAAC,EAAQ,CAAc,GACnC,IAAiB,IAAY,EAAM,GAAqB,EAAE,MAAM,GAAS,KAAK,CAAC,IAAK,GAEpF,IAA+B,CAAC,EAAS,EAAK,KAAK,CAAC,CAAC,GAGrD,IAAS,EAAO,GAAG,EAAE,MAAM,GAAS,OAAO,GAAG,EAAQ,KAAK,WAAW,KAAA,EAAU,CAAC,GAEnF,IAAW,IAET,KAAgB,MAAyB;EACzC,MAEJ,EAAU,OAAO,EAAO,KAAK,IAAI,CAAC,GAClC,EAAU,KAAK,EAAS,EAAK,KAAK,GAAG,CAAK,CAAC,GAEvC,EAAU,SAAS,KACrB,EAAU,OAAO,GAAG,EAAU,SAAS,CAAU,GAGnD,EAAO,QAAQ,EAAU,SAAS;CACpC,GAEM,UAAmB;EACnB,KAAY,EAAO,KAAK,KAAK,MAEjC,EAAO,QAAQ,EAAO,KAAK,IAAI,GAC/B,EAAK,cAAc,EAAU,EAAO,KAAK,EAAE,CAAE,KAAU;CACzD,GAEM,UAAmB;EACnB,KAAY,EAAO,KAAK,KAAK,EAAU,SAAS,MAEpD,EAAO,QAAQ,EAAO,KAAK,IAAI,GAC/B,EAAK,cAAc,EAAU,EAAO,KAAK,EAAE,CAAE,KAAU;CACzD,GAEM,KAAa,MAA+C;EAC5D,UAAQ,KAAK,KAAS,EAAU,SAEpC,OAAO,EAAU;CACnB,GAEM,UAAsB;EACtB,MAEJ,IAAW,IACX,EAAO,QAAQ,GAEX,KAAW,EAAK,QAAQ;CAC9B;CA2CA,OAAO;EAtCL,IAAI,UAAU;GACZ,OAAO,EAAO,QAAQ,EAAU,SAAS;EAC3C;EAEA,IAAI,UAAU;GACZ,OAAO,EAAO,QAAQ;EACxB;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA,IAAI,gBAAgB;GAClB,OAAO,EAAU;EACnB;EACA,OAAyB,MAAY,EAAK,KAAK,CAAI;EAEnD,IAAI,OAAO;GACT,OAAO,EAAK;EACd;EACA,QAAQ,MAAY,EAAK,MAAM,CAAO;EACtC,YAAY,EAAK,KAAK;EACtB,YAAY,EAAa;EACzB,YAAY,MAAkB,EAAa,CAAK;EAChD;EACA,UAAU,MAAO,EAAK,QAAQ,CAAE;EAChC,aAAa,EAAK,MAAM;EACxB,IAAI,QAAQ;GACV,OAAO;EACT;EACA,YAAY,MAAa,EAAK,UAAU,CAAQ;GAC/C,OAAO,UAAU;EAClB;EACA,IAAI,QAAQ;GACV,OAAO,EAAK;EACd;CAGK;AACT"}
|
package/dist/store.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store.cjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { PathValue, Readable, Signal, Store, Subscription } from './types';\n\nimport { computed } from './computed';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleInvalidStoreError } from './errors';\nimport { batch } from './scheduling';\nimport { SignalImpl } from './signal';\nimport { IS_SIGNAL, IS_STORE } from './symbols';\n\n// ── Key safety ────────────────────────────────────────────────────────────────\n\n/**\n * Rejects prototype-pollution-prone top-level keys. Called both as an upfront\n * validation pass (patch/replace — so a later unsafe key never leaves earlier,\n * safe keys partially applied) and defensively inside applyTopLevelChange_.\n */\nconst assertSafeKey = (key: string): void => {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n throw new RippleInvalidStoreError(`Unsafe key \"${key}\" rejected to prevent prototype pollution.`);\n }\n};\n\n// ── Nested path helpers ───────────────────────────────────────────────────────\n\nconst getNestedValue = (obj: unknown, parts: string[]): unknown => {\n let current = obj;\n\n for (const key of parts) {\n if (current == null || typeof current !== 'object') return undefined;\n\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n};\n\nconst setNestedValue = <T>(obj: T, parts: string[], value: unknown): T => {\n if (parts.length === 0) return value as T;\n\n if (obj === null || typeof obj !== 'object') {\n throw new RippleInvalidStoreError('Cannot write to a nested path through a null or non-object intermediate value.');\n }\n\n const [key, ...rest] = parts;\n const currentObj = obj as Record<string, unknown>;\n\n return { ...currentObj, [key!]: setNestedValue(currentObj[key!], rest, value) } as T;\n};\n\n// ── LensSignal ───────────────────────────────────────────────────────────────\n//\n// A unified writable Signal that delegates reads to an inner Reactive<V>\n// and writes to an injected callback. Replaces the old TopLevelLens +\n// NestedLens pair — same interface, one class to update when Signal<T> changes.\n\ntype LensOptions<V> = {\n disposeSource?: () => void;\n evict: () => void;\n name?: string;\n source: Readable<V>;\n write: (v: V) => void;\n};\n\nclass LensSignal<V> implements Signal<V> {\n [IS_SIGNAL] = true as const;\n\n private source_: Readable<V>;\n private write_: (v: V) => void;\n private evict_: () => void;\n private disposeSource_: (() => void) | undefined;\n private name_: string | undefined;\n // Own flag — top-level lenses don't own their source (the store does), so\n // `dispose()` can't rely on `source_.disposed` alone to reflect \"was this\n // handle disposed?\". Nested lenses still cascade into `source_.disposed`\n // via `disposeSource_`, so the getter below ORs both signals.\n private disposed_ = false;\n\n get name(): string | undefined {\n return this.name_ ?? this.source_.name;\n }\n\n constructor(opts: LensOptions<V>) {\n this.source_ = opts.source;\n this.write_ = opts.write;\n this.evict_ = opts.evict;\n this.disposeSource_ = opts.disposeSource;\n this.name_ = opts.name;\n }\n\n get value(): V {\n return this.source_.value;\n }\n\n set value(next: V) {\n this.write_(next);\n }\n\n peek(): V {\n return this.source_.peek();\n }\n\n subscribe(listener: () => void): Subscription {\n return this.source_.subscribe(listener);\n }\n\n get disposed(): boolean {\n return this.disposed_ || this.source_.disposed;\n }\n\n dispose(): void {\n this.disposed_ = true;\n this.evict_();\n this.disposeSource_?.();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── StoreImpl ─────────────────────────────────────────────────────────────────\n//\n// Fine-grained reactive store.\n//\n// Internal layout:\n// - current_: mutable backing object — NEVER exposed directly, internal writes only\n// - readonlyProxy_: Proxy over current_ that throws on set/delete — returned by .peek()\n// - propSignals_: per-top-level-key SignalImpl, lazily created on first lens/read\n// - version_: whole-store monotonic counter (bumped on any change, used by subscribe())\n//\n// StoreImpl is a standalone class — does not extend ReactiveBase.\n// applyTopLevelChange_() has NO batch() wrapper — callers that need atomicity\n// (patch, replace, reset, and both lens write paths) wrap with batch() at their\n// own call sites; the method itself stays free of any scheduling concern.\n\nexport class StoreImpl<T extends object> implements Store<T> {\n [IS_SIGNAL] = true as const;\n [IS_STORE] = true as const;\n\n readonly name: string | undefined;\n\n private current_: T;\n private disposed_: boolean;\n private readonly initial_: T;\n private readonly lensCache_: Map<string, LensSignal<unknown>>;\n private readonly propSignals_: Map<string, SignalImpl<unknown>>;\n private readonly readonlyProxy_: Readonly<T>;\n private readonly version_: SignalImpl<number>;\n\n constructor(initial: T, name?: string) {\n this.name = name;\n this.current_ = structuredClone(initial);\n this.disposed_ = false;\n this.initial_ = structuredClone(initial);\n this.lensCache_ = new Map();\n this.propSignals_ = new Map();\n this.version_ = new SignalImpl(0, undefined, name ? `${name}.version` : undefined);\n\n // Shallow proxy: throws on any top-level mutation attempt.\n // All external reads go through this proxy; internal writes use this.current_ directly.\n // NOTE: nested objects are plain references — mutations on nested properties\n // bypass reactivity. Use store.lens('a.b') or store.replace() to update nested state.\n this.readonlyProxy_ = new Proxy(this.current_, {\n deleteProperty(_target, key): never {\n throw new RippleInvalidStoreError(\n `Direct deletion from store.value is not allowed. Use store.patch() or store.lens(). (key: \"${String(key)}\")`,\n );\n },\n set(_target, key): never {\n throw new RippleInvalidStoreError(\n `Direct mutation of store.value is not allowed. Use store.patch(), store.lens(), or store.replace(). (key: \"${String(key)}\")`,\n );\n },\n }) as Readonly<T>;\n }\n\n /** Returns (or lazily creates) the per-property signal for a top-level key. */\n propSignalFor_(key: string): SignalImpl<unknown> {\n let sig = this.propSignals_.get(key);\n\n if (sig === undefined) {\n sig = new SignalImpl(\n (this.current_ as Record<string, unknown>)[key],\n undefined,\n this.name !== undefined ? `${this.name}.${key}` : key,\n );\n this.propSignals_.set(key, sig);\n }\n\n return sig;\n }\n\n /** @internal Called by nested lens write to update the store through a path. */\n setPath_(parts: string[], value: unknown): void {\n const [rootKey, ...rest] = parts;\n const rootVal = (this.current_ as Record<string, unknown>)[rootKey!];\n const newRootVal = setNestedValue(rootVal, rest, value);\n\n this.applyTopLevelChange_(rootKey!, newRootVal);\n }\n\n /**\n * @internal Apply a change to a top-level key and propagate to prop signal + version.\n * R4: No batch() wrapper — callers that need atomicity must wrap themselves.\n */\n applyTopLevelChange_(key: string, newValue: unknown): void {\n assertSafeKey(key);\n\n const current = (this.current_ as Record<string, unknown>)[key];\n\n if (Object.is(current, newValue)) return;\n\n (this.current_ as Record<string, unknown>)[key] = newValue;\n this.propSignalFor_(key).value = newValue as never;\n this.version_.value = this.version_.peek() + 1;\n }\n\n /**\n * @internal Removes a top-level key entirely (as opposed to applyTopLevelChange_,\n * which can only ever assign a value — including `undefined` — never delete the key).\n * Companion used by applyFullState_ for keys absent from the target state.\n */\n deleteTopLevelKey_(key: string): void {\n if (!Object.hasOwn(this.current_ as object, key)) return;\n\n delete (this.current_ as Record<string, unknown>)[key];\n this.propSignalFor_(key).value = undefined as never;\n this.version_.value = this.version_.peek() + 1;\n }\n\n /**\n * @internal Applies `next` as the authoritative full top-level state: keys present in\n * `next` are set via applyTopLevelChange_, keys present in current_ but absent from\n * `next` are removed via deleteTopLevelKey_. Shared by replace() and reset() so both\n * can actually remove a key, not just null it out — see B1 in the ripple improvement plan.\n */\n applyFullState_(next: Record<string, unknown>): void {\n const current = this.current_ as Record<string, unknown>;\n const allKeys = new Set([...Object.keys(current), ...Object.keys(next)]);\n\n for (const key of allKeys) assertSafeKey(key);\n\n batch(() => {\n for (const key of allKeys) {\n if (Object.hasOwn(next, key)) {\n this.applyTopLevelChange_(key, next[key]);\n } else {\n this.deleteTopLevelKey_(key);\n }\n }\n });\n }\n\n /**\n * Returns the current state as a tracked read — registers the store as a dependency\n * inside `effect()` / `computed()`. Any mutation re-runs the subscriber.\n * For untracked one-off reads, use `peek()` instead.\n */\n get value(): Readonly<T> {\n void this.version_.value;\n\n return this.readonlyProxy_;\n }\n\n /**\n * Returns a frozen deep snapshot of the current state.\n * The returned object reflects state at call time — it does not update as the store mutates.\n * Nested objects are cloned (not aliased), so mutating nested properties on the snapshot\n * does not affect the live store state.\n * Use for serialization or one-off reads outside reactive contexts.\n * For reactive reads, use `store.lens(path)` instead.\n */\n peek(): Readonly<T> {\n return Object.freeze(structuredClone(this.current_)) as Readonly<T>;\n }\n\n readonly subscribe = (listener: () => void): Subscription => {\n return this.version_.subscribe(listener);\n };\n\n patch(partial: Partial<T>): void {\n if (typeof partial !== 'object' || partial === null || Array.isArray(partial)) {\n throw new RippleInvalidStoreError('store.patch() requires a plain object partial.');\n }\n\n const keys = Object.keys(partial) as Array<keyof T & string>;\n\n if (keys.length === 0) return;\n\n // Validate every key before mutating any — a key rejected mid-loop must not\n // leave earlier, safe keys partially applied (and un-notified — see batch()'s\n // error path, which clears the pending flush queue rather than replaying it).\n for (const key of keys) assertSafeKey(key);\n\n batch(() => {\n for (const key of keys) {\n this.applyTopLevelChange_(key, (partial as Record<string, unknown>)[key]);\n }\n });\n\n getDevToolsHook()?.mutate?.({ kind: 'patch', name: this.name });\n }\n\n replace(fn: (state: Readonly<T>) => T): void {\n const snapshot = structuredClone(this.current_) as Readonly<T>;\n const next = fn(snapshot);\n\n if (next === snapshot) return;\n\n this.applyFullState_(next as Record<string, unknown>);\n\n getDevToolsHook()?.mutate?.({ kind: 'replace', name: this.name });\n }\n\n reset(): void {\n this.applyFullState_(this.initial_ as Record<string, unknown>);\n\n getDevToolsHook()?.mutate?.({ kind: 'reset', name: this.name });\n }\n\n lens<P extends string>(path: P): Signal<PathValue<T, P>> {\n const cached = this.lensCache_.get(path);\n\n if (cached !== undefined) return cached as unknown as Signal<PathValue<T, P>>;\n\n const evict = (): void => {\n this.lensCache_.delete(path);\n };\n\n const parts = path.split('.');\n\n if (parts.length > 32) {\n throw new RippleInvalidStoreError(`Lens path exceeds maximum depth of 32 segments: \"${path}\".`);\n }\n\n for (const part of parts) {\n if (part === '') {\n throw new RippleInvalidStoreError(`Empty path segment in lens path \"${path}\". Check for consecutive dots.`);\n }\n\n if (part === '__proto__' || part === 'constructor' || part === 'prototype') {\n throw new RippleInvalidStoreError(`Unsafe path segment \"${part}\" in lens path \"${path}\".`);\n }\n }\n\n let lens: LensSignal<unknown>;\n\n if (parts.length === 1) {\n // Top-level lens: backed directly by the per-property signal.\n const propSig = this.propSignalFor_(parts[0]!) as SignalImpl<PathValue<T, P>>;\n\n lens = new LensSignal<PathValue<T, P>>({\n evict,\n // propSig lifecycle is owned by the store — do not dispose it\n source: propSig,\n write: (v) => {\n batch(() => this.applyTopLevelChange_(parts[0]!, v));\n getDevToolsHook()?.mutate?.({ kind: 'lens', name: this.name, path });\n },\n }) as unknown as LensSignal<unknown>;\n } else {\n // Nested lens: backed by a derived computed over the root property signal.\n // Write path uses batch() here so nested lens writes remain atomic.\n const readComputed = computed(\n () => getNestedValue(this.propSignalFor_(parts[0]!).value, parts.slice(1)) as PathValue<T, P>,\n );\n\n lens = new LensSignal<PathValue<T, P>>({\n disposeSource: () => readComputed.dispose(),\n evict,\n name: this.name ? `${this.name}.${path}` : path,\n source: readComputed,\n write: (v) => {\n const current = readComputed.peek();\n\n if (Object.is(current, v)) return;\n\n batch(() => this.setPath_(parts, v));\n getDevToolsHook()?.mutate?.({ kind: 'lens', name: this.name, path });\n },\n }) as unknown as LensSignal<unknown>;\n }\n\n this.lensCache_.set(path, lens);\n\n return lens as unknown as Signal<PathValue<T, P>>;\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 for (const lens of [...this.lensCache_.values()]) {\n lens.dispose();\n }\n\n this.lensCache_.clear();\n\n for (const sig of this.propSignals_.values()) {\n sig.dispose();\n }\n\n this.propSignals_.clear();\n this.version_.dispose();\n getDevToolsHook()?.dispose?.({ kind: 'store', name: this.name });\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\nexport const store = <T extends object>(initial: T, options?: { name?: string }): Store<T> => {\n if (typeof initial !== 'object' || initial === null || Array.isArray(initial)) {\n throw new RippleInvalidStoreError('store() requires a plain object initial state.');\n }\n\n // Same guard as patch()/replace() — reject an unsafe top-level key (e.g. a real\n // own \"__proto__\" property from JSON.parse) before it ever enters store state.\n for (const key of Object.keys(initial)) assertSafeKey(key);\n\n return new StoreImpl(initial, options?.name) as unknown as Store<T>;\n};\n"],"mappings":"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"}
|
package/dist/store.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { PathValue, Readable, Signal, Store, Subscription } from './types';\n\nimport { computed } from './computed';\nimport { getDevToolsHook } from './devtools-hook';\nimport { RippleInvalidStoreError } from './errors';\nimport { batch } from './scheduling';\nimport { SignalImpl } from './signal';\nimport { IS_SIGNAL, IS_STORE } from './symbols';\n\n// ── Key safety ────────────────────────────────────────────────────────────────\n\n/**\n * Rejects prototype-pollution-prone top-level keys. Called both as an upfront\n * validation pass (patch/replace — so a later unsafe key never leaves earlier,\n * safe keys partially applied) and defensively inside applyTopLevelChange_.\n */\nconst assertSafeKey = (key: string): void => {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n throw new RippleInvalidStoreError(`Unsafe key \"${key}\" rejected to prevent prototype pollution.`);\n }\n};\n\n// ── Nested path helpers ───────────────────────────────────────────────────────\n\nconst getNestedValue = (obj: unknown, parts: string[]): unknown => {\n let current = obj;\n\n for (const key of parts) {\n if (current == null || typeof current !== 'object') return undefined;\n\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n};\n\nconst setNestedValue = <T>(obj: T, parts: string[], value: unknown): T => {\n if (parts.length === 0) return value as T;\n\n if (obj === null || typeof obj !== 'object') {\n throw new RippleInvalidStoreError('Cannot write to a nested path through a null or non-object intermediate value.');\n }\n\n const [key, ...rest] = parts;\n const currentObj = obj as Record<string, unknown>;\n\n return { ...currentObj, [key!]: setNestedValue(currentObj[key!], rest, value) } as T;\n};\n\n// ── LensSignal ───────────────────────────────────────────────────────────────\n//\n// A unified writable Signal that delegates reads to an inner Reactive<V>\n// and writes to an injected callback. Replaces the old TopLevelLens +\n// NestedLens pair — same interface, one class to update when Signal<T> changes.\n\ntype LensOptions<V> = {\n disposeSource?: () => void;\n evict: () => void;\n name?: string;\n source: Readable<V>;\n write: (v: V) => void;\n};\n\nclass LensSignal<V> implements Signal<V> {\n [IS_SIGNAL] = true as const;\n\n private source_: Readable<V>;\n private write_: (v: V) => void;\n private evict_: () => void;\n private disposeSource_: (() => void) | undefined;\n private name_: string | undefined;\n // Own flag — top-level lenses don't own their source (the store does), so\n // `dispose()` can't rely on `source_.disposed` alone to reflect \"was this\n // handle disposed?\". Nested lenses still cascade into `source_.disposed`\n // via `disposeSource_`, so the getter below ORs both signals.\n private disposed_ = false;\n\n get name(): string | undefined {\n return this.name_ ?? this.source_.name;\n }\n\n constructor(opts: LensOptions<V>) {\n this.source_ = opts.source;\n this.write_ = opts.write;\n this.evict_ = opts.evict;\n this.disposeSource_ = opts.disposeSource;\n this.name_ = opts.name;\n }\n\n get value(): V {\n return this.source_.value;\n }\n\n set value(next: V) {\n this.write_(next);\n }\n\n peek(): V {\n return this.source_.peek();\n }\n\n subscribe(listener: () => void): Subscription {\n return this.source_.subscribe(listener);\n }\n\n get disposed(): boolean {\n return this.disposed_ || this.source_.disposed;\n }\n\n dispose(): void {\n this.disposed_ = true;\n this.evict_();\n this.disposeSource_?.();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── StoreImpl ─────────────────────────────────────────────────────────────────\n//\n// Fine-grained reactive store.\n//\n// Internal layout:\n// - current_: mutable backing object — NEVER exposed directly, internal writes only\n// - readonlyProxy_: Proxy over current_ that throws on set/delete — returned by .peek()\n// - propSignals_: per-top-level-key SignalImpl, lazily created on first lens/read\n// - version_: whole-store monotonic counter (bumped on any change, used by subscribe())\n//\n// StoreImpl is a standalone class — does not extend ReactiveBase.\n// applyTopLevelChange_() has NO batch() wrapper — callers that need atomicity\n// (patch, replace, reset, and both lens write paths) wrap with batch() at their\n// own call sites; the method itself stays free of any scheduling concern.\n\nexport class StoreImpl<T extends object> implements Store<T> {\n [IS_SIGNAL] = true as const;\n [IS_STORE] = true as const;\n\n readonly name: string | undefined;\n\n private current_: T;\n private disposed_: boolean;\n private readonly initial_: T;\n private readonly lensCache_: Map<string, LensSignal<unknown>>;\n private readonly propSignals_: Map<string, SignalImpl<unknown>>;\n private readonly readonlyProxy_: Readonly<T>;\n private readonly version_: SignalImpl<number>;\n\n constructor(initial: T, name?: string) {\n this.name = name;\n this.current_ = structuredClone(initial);\n this.disposed_ = false;\n this.initial_ = structuredClone(initial);\n this.lensCache_ = new Map();\n this.propSignals_ = new Map();\n this.version_ = new SignalImpl(0, undefined, name ? `${name}.version` : undefined);\n\n // Shallow proxy: throws on any top-level mutation attempt.\n // All external reads go through this proxy; internal writes use this.current_ directly.\n // NOTE: nested objects are plain references — mutations on nested properties\n // bypass reactivity. Use store.lens('a.b') or store.replace() to update nested state.\n this.readonlyProxy_ = new Proxy(this.current_, {\n deleteProperty(_target, key): never {\n throw new RippleInvalidStoreError(\n `Direct deletion from store.value is not allowed. Use store.patch() or store.lens(). (key: \"${String(key)}\")`,\n );\n },\n set(_target, key): never {\n throw new RippleInvalidStoreError(\n `Direct mutation of store.value is not allowed. Use store.patch(), store.lens(), or store.replace(). (key: \"${String(key)}\")`,\n );\n },\n }) as Readonly<T>;\n }\n\n /** Returns (or lazily creates) the per-property signal for a top-level key. */\n propSignalFor_(key: string): SignalImpl<unknown> {\n let sig = this.propSignals_.get(key);\n\n if (sig === undefined) {\n sig = new SignalImpl(\n (this.current_ as Record<string, unknown>)[key],\n undefined,\n this.name !== undefined ? `${this.name}.${key}` : key,\n );\n this.propSignals_.set(key, sig);\n }\n\n return sig;\n }\n\n /** @internal Called by nested lens write to update the store through a path. */\n setPath_(parts: string[], value: unknown): void {\n const [rootKey, ...rest] = parts;\n const rootVal = (this.current_ as Record<string, unknown>)[rootKey!];\n const newRootVal = setNestedValue(rootVal, rest, value);\n\n this.applyTopLevelChange_(rootKey!, newRootVal);\n }\n\n /**\n * @internal Apply a change to a top-level key and propagate to prop signal + version.\n * R4: No batch() wrapper — callers that need atomicity must wrap themselves.\n */\n applyTopLevelChange_(key: string, newValue: unknown): void {\n assertSafeKey(key);\n\n const current = (this.current_ as Record<string, unknown>)[key];\n\n if (Object.is(current, newValue)) return;\n\n (this.current_ as Record<string, unknown>)[key] = newValue;\n this.propSignalFor_(key).value = newValue as never;\n this.version_.value = this.version_.peek() + 1;\n }\n\n /**\n * @internal Removes a top-level key entirely (as opposed to applyTopLevelChange_,\n * which can only ever assign a value — including `undefined` — never delete the key).\n * Companion used by applyFullState_ for keys absent from the target state.\n */\n deleteTopLevelKey_(key: string): void {\n if (!Object.hasOwn(this.current_ as object, key)) return;\n\n delete (this.current_ as Record<string, unknown>)[key];\n this.propSignalFor_(key).value = undefined as never;\n this.version_.value = this.version_.peek() + 1;\n }\n\n /**\n * @internal Applies `next` as the authoritative full top-level state: keys present in\n * `next` are set via applyTopLevelChange_, keys present in current_ but absent from\n * `next` are removed via deleteTopLevelKey_. Shared by replace() and reset() so both\n * can actually remove a key, not just null it out — see B1 in the ripple improvement plan.\n */\n applyFullState_(next: Record<string, unknown>): void {\n const current = this.current_ as Record<string, unknown>;\n const allKeys = new Set([...Object.keys(current), ...Object.keys(next)]);\n\n for (const key of allKeys) assertSafeKey(key);\n\n batch(() => {\n for (const key of allKeys) {\n if (Object.hasOwn(next, key)) {\n this.applyTopLevelChange_(key, next[key]);\n } else {\n this.deleteTopLevelKey_(key);\n }\n }\n });\n }\n\n /**\n * Returns the current state as a tracked read — registers the store as a dependency\n * inside `effect()` / `computed()`. Any mutation re-runs the subscriber.\n * For untracked one-off reads, use `peek()` instead.\n */\n get value(): Readonly<T> {\n void this.version_.value;\n\n return this.readonlyProxy_;\n }\n\n /**\n * Returns a frozen deep snapshot of the current state.\n * The returned object reflects state at call time — it does not update as the store mutates.\n * Nested objects are cloned (not aliased), so mutating nested properties on the snapshot\n * does not affect the live store state.\n * Use for serialization or one-off reads outside reactive contexts.\n * For reactive reads, use `store.lens(path)` instead.\n */\n peek(): Readonly<T> {\n return Object.freeze(structuredClone(this.current_)) as Readonly<T>;\n }\n\n readonly subscribe = (listener: () => void): Subscription => {\n return this.version_.subscribe(listener);\n };\n\n patch(partial: Partial<T>): void {\n if (typeof partial !== 'object' || partial === null || Array.isArray(partial)) {\n throw new RippleInvalidStoreError('store.patch() requires a plain object partial.');\n }\n\n const keys = Object.keys(partial) as Array<keyof T & string>;\n\n if (keys.length === 0) return;\n\n // Validate every key before mutating any — a key rejected mid-loop must not\n // leave earlier, safe keys partially applied (and un-notified — see batch()'s\n // error path, which clears the pending flush queue rather than replaying it).\n for (const key of keys) assertSafeKey(key);\n\n batch(() => {\n for (const key of keys) {\n this.applyTopLevelChange_(key, (partial as Record<string, unknown>)[key]);\n }\n });\n\n getDevToolsHook()?.mutate?.({ kind: 'patch', name: this.name });\n }\n\n replace(fn: (state: Readonly<T>) => T): void {\n const snapshot = structuredClone(this.current_) as Readonly<T>;\n const next = fn(snapshot);\n\n if (next === snapshot) return;\n\n this.applyFullState_(next as Record<string, unknown>);\n\n getDevToolsHook()?.mutate?.({ kind: 'replace', name: this.name });\n }\n\n reset(): void {\n this.applyFullState_(this.initial_ as Record<string, unknown>);\n\n getDevToolsHook()?.mutate?.({ kind: 'reset', name: this.name });\n }\n\n lens<P extends string>(path: P): Signal<PathValue<T, P>> {\n const cached = this.lensCache_.get(path);\n\n if (cached !== undefined) return cached as unknown as Signal<PathValue<T, P>>;\n\n const evict = (): void => {\n this.lensCache_.delete(path);\n };\n\n const parts = path.split('.');\n\n if (parts.length > 32) {\n throw new RippleInvalidStoreError(`Lens path exceeds maximum depth of 32 segments: \"${path}\".`);\n }\n\n for (const part of parts) {\n if (part === '') {\n throw new RippleInvalidStoreError(`Empty path segment in lens path \"${path}\". Check for consecutive dots.`);\n }\n\n if (part === '__proto__' || part === 'constructor' || part === 'prototype') {\n throw new RippleInvalidStoreError(`Unsafe path segment \"${part}\" in lens path \"${path}\".`);\n }\n }\n\n let lens: LensSignal<unknown>;\n\n if (parts.length === 1) {\n // Top-level lens: backed directly by the per-property signal.\n const propSig = this.propSignalFor_(parts[0]!) as SignalImpl<PathValue<T, P>>;\n\n lens = new LensSignal<PathValue<T, P>>({\n evict,\n // propSig lifecycle is owned by the store — do not dispose it\n source: propSig,\n write: (v) => {\n batch(() => this.applyTopLevelChange_(parts[0]!, v));\n getDevToolsHook()?.mutate?.({ kind: 'lens', name: this.name, path });\n },\n }) as unknown as LensSignal<unknown>;\n } else {\n // Nested lens: backed by a derived computed over the root property signal.\n // Write path uses batch() here so nested lens writes remain atomic.\n const readComputed = computed(\n () => getNestedValue(this.propSignalFor_(parts[0]!).value, parts.slice(1)) as PathValue<T, P>,\n );\n\n lens = new LensSignal<PathValue<T, P>>({\n disposeSource: () => readComputed.dispose(),\n evict,\n name: this.name ? `${this.name}.${path}` : path,\n source: readComputed,\n write: (v) => {\n const current = readComputed.peek();\n\n if (Object.is(current, v)) return;\n\n batch(() => this.setPath_(parts, v));\n getDevToolsHook()?.mutate?.({ kind: 'lens', name: this.name, path });\n },\n }) as unknown as LensSignal<unknown>;\n }\n\n this.lensCache_.set(path, lens);\n\n return lens as unknown as Signal<PathValue<T, P>>;\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 for (const lens of [...this.lensCache_.values()]) {\n lens.dispose();\n }\n\n this.lensCache_.clear();\n\n for (const sig of this.propSignals_.values()) {\n sig.dispose();\n }\n\n this.propSignals_.clear();\n this.version_.dispose();\n getDevToolsHook()?.dispose?.({ kind: 'store', name: this.name });\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\nexport const store = <T extends object>(initial: T, options?: { name?: string }): Store<T> => {\n if (typeof initial !== 'object' || initial === null || Array.isArray(initial)) {\n throw new RippleInvalidStoreError('store() requires a plain object initial state.');\n }\n\n // Same guard as patch()/replace() — reject an unsafe top-level key (e.g. a real\n // own \"__proto__\" property from JSON.parse) before it ever enters store state.\n for (const key of Object.keys(initial)) assertSafeKey(key);\n\n return new StoreImpl(initial, options?.name) as unknown as Store<T>;\n};\n"],"mappings":"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"}
|
package/dist/subscription.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var e=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()}},t=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_}};exports.AsyncSubscriptionImpl=t,exports.SubscriptionImpl=e;
|
|
2
|
-
//# sourceMappingURL=subscription.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.cjs","names":[],"sources":["../src/subscription.ts"],"sourcesContent":["import type { AsyncSubscription, CleanupFn, Subscription } from './types';\n\n// ── SubscriptionImpl ────────────────────────────────────────────────────────\n//\n// [Symbol.dispose] delegates to dispose() instead of duplicating the logic.\n// dispose() is idempotent — calling it twice is safe (fn_ set to null after first call).\n\nexport class SubscriptionImpl implements Subscription {\n private fn_: CleanupFn | null;\n private disposed_ = false;\n\n constructor(fn: CleanupFn) {\n this.fn_ = fn;\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 const fn = this.fn_!;\n\n this.fn_ = null;\n fn();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── AsyncSubscriptionImpl ─────────────────────────────────────────────────────\n//\n// Wraps a synchronous stop handle + an async drain function.\n// run() awaits the current in-flight run without stopping the effect.\n// [Symbol.asyncDispose] stops the effect synchronously and then awaits full teardown.\n\nexport class AsyncSubscriptionImpl implements AsyncSubscription {\n private awaitDone_: () => Promise<void>;\n private getCurrentRun_: () => Promise<void> | null;\n private syncStop_: Subscription;\n private asyncDisposePromise_: Promise<void> | null = null;\n\n constructor(syncStop: Subscription, awaitDone: () => Promise<void>, getCurrentRun: () => Promise<void> | null) {\n this.awaitDone_ = awaitDone;\n this.getCurrentRun_ = getCurrentRun;\n this.syncStop_ = syncStop;\n }\n\n get disposed(): boolean {\n return this.syncStop_.disposed;\n }\n\n dispose(): void {\n this.syncStop_.dispose();\n }\n\n run(): Promise<void> {\n return this.getCurrentRun_() ?? Promise.resolve();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n if (this.asyncDisposePromise_ !== null) return this.asyncDisposePromise_;\n\n this.dispose();\n this.asyncDisposePromise_ = this.awaitDone_();\n\n return this.asyncDisposePromise_;\n }\n}\n"],"mappings":"AAOA,IAAa,EAAb,KAAsD,CACpD,IACA,UAAoB,GAEpB,YAAY,EAAe,CACzB,KAAK,IAAM,CACb,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,SACd,CAEA,SAAgB,CACd,GAAI,KAAK,UAAW,OAEpB,KAAK,UAAY,GAEjB,IAAM,EAAK,KAAK,IAEhB,KAAK,IAAM,KACX,EAAG,CACL,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,EAQa,EAAb,KAAgE,CAC9D,WACA,eACA,UACA,qBAAqD,KAErD,YAAY,EAAwB,EAAgC,EAA2C,CAC7G,KAAK,WAAa,EAClB,KAAK,eAAiB,EACtB,KAAK,UAAY,CACnB,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,UAAU,QACxB,CAEA,SAAgB,CACd,KAAK,UAAU,QAAQ,CACzB,CAEA,KAAqB,CACnB,OAAO,KAAK,eAAe,GAAK,QAAQ,QAAQ,CAClD,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CAEA,CAAC,OAAO,eAA+B,CAMrC,OALI,KAAK,uBAAyB,MAElC,KAAK,QAAQ,EACb,KAAK,qBAAuB,KAAK,WAAW,EAErC,KAAK,sBALmC,KAAK,oBAMtD,CACF"}
|
package/dist/subscription.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { AsyncSubscription, CleanupFn, Subscription } from './types';
|
|
2
|
-
export declare class SubscriptionImpl implements Subscription {
|
|
3
|
-
private fn_;
|
|
4
|
-
private disposed_;
|
|
5
|
-
constructor(fn: CleanupFn);
|
|
6
|
-
get disposed(): boolean;
|
|
7
|
-
dispose(): void;
|
|
8
|
-
[Symbol.dispose](): void;
|
|
9
|
-
}
|
|
10
|
-
export declare class AsyncSubscriptionImpl implements AsyncSubscription {
|
|
11
|
-
private awaitDone_;
|
|
12
|
-
private getCurrentRun_;
|
|
13
|
-
private syncStop_;
|
|
14
|
-
private asyncDisposePromise_;
|
|
15
|
-
constructor(syncStop: Subscription, awaitDone: () => Promise<void>, getCurrentRun: () => Promise<void> | null);
|
|
16
|
-
get disposed(): boolean;
|
|
17
|
-
dispose(): void;
|
|
18
|
-
run(): Promise<void>;
|
|
19
|
-
[Symbol.dispose](): void;
|
|
20
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=subscription.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../src/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAO1E,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,EAAE,SAAS;IAIzB,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,OAAO,IAAI,IAAI;IAWf,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB;AAQD,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,oBAAoB,CAA8B;gBAE9C,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAM7G,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,OAAO,IAAI,IAAI;IAIf,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAIxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAQvC"}
|
package/dist/subscription.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
//#region src/subscription.ts
|
|
2
|
-
var e = class {
|
|
3
|
-
fn_;
|
|
4
|
-
disposed_ = !1;
|
|
5
|
-
constructor(e) {
|
|
6
|
-
this.fn_ = e;
|
|
7
|
-
}
|
|
8
|
-
get disposed() {
|
|
9
|
-
return this.disposed_;
|
|
10
|
-
}
|
|
11
|
-
dispose() {
|
|
12
|
-
if (this.disposed_) return;
|
|
13
|
-
this.disposed_ = !0;
|
|
14
|
-
let e = this.fn_;
|
|
15
|
-
this.fn_ = null, e();
|
|
16
|
-
}
|
|
17
|
-
[Symbol.dispose]() {
|
|
18
|
-
this.dispose();
|
|
19
|
-
}
|
|
20
|
-
}, t = class {
|
|
21
|
-
awaitDone_;
|
|
22
|
-
getCurrentRun_;
|
|
23
|
-
syncStop_;
|
|
24
|
-
asyncDisposePromise_ = null;
|
|
25
|
-
constructor(e, t, n) {
|
|
26
|
-
this.awaitDone_ = t, this.getCurrentRun_ = n, this.syncStop_ = e;
|
|
27
|
-
}
|
|
28
|
-
get disposed() {
|
|
29
|
-
return this.syncStop_.disposed;
|
|
30
|
-
}
|
|
31
|
-
dispose() {
|
|
32
|
-
this.syncStop_.dispose();
|
|
33
|
-
}
|
|
34
|
-
run() {
|
|
35
|
-
return this.getCurrentRun_() ?? Promise.resolve();
|
|
36
|
-
}
|
|
37
|
-
[Symbol.dispose]() {
|
|
38
|
-
this.dispose();
|
|
39
|
-
}
|
|
40
|
-
[Symbol.asyncDispose]() {
|
|
41
|
-
return this.asyncDisposePromise_ === null ? (this.dispose(), this.asyncDisposePromise_ = this.awaitDone_(), this.asyncDisposePromise_) : this.asyncDisposePromise_;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
//#endregion
|
|
45
|
-
export { t as AsyncSubscriptionImpl, e as SubscriptionImpl };
|
|
46
|
-
|
|
47
|
-
//# sourceMappingURL=subscription.js.map
|
package/dist/subscription.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.js","names":[],"sources":["../src/subscription.ts"],"sourcesContent":["import type { AsyncSubscription, CleanupFn, Subscription } from './types';\n\n// ── SubscriptionImpl ────────────────────────────────────────────────────────\n//\n// [Symbol.dispose] delegates to dispose() instead of duplicating the logic.\n// dispose() is idempotent — calling it twice is safe (fn_ set to null after first call).\n\nexport class SubscriptionImpl implements Subscription {\n private fn_: CleanupFn | null;\n private disposed_ = false;\n\n constructor(fn: CleanupFn) {\n this.fn_ = fn;\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 const fn = this.fn_!;\n\n this.fn_ = null;\n fn();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n\n// ── AsyncSubscriptionImpl ─────────────────────────────────────────────────────\n//\n// Wraps a synchronous stop handle + an async drain function.\n// run() awaits the current in-flight run without stopping the effect.\n// [Symbol.asyncDispose] stops the effect synchronously and then awaits full teardown.\n\nexport class AsyncSubscriptionImpl implements AsyncSubscription {\n private awaitDone_: () => Promise<void>;\n private getCurrentRun_: () => Promise<void> | null;\n private syncStop_: Subscription;\n private asyncDisposePromise_: Promise<void> | null = null;\n\n constructor(syncStop: Subscription, awaitDone: () => Promise<void>, getCurrentRun: () => Promise<void> | null) {\n this.awaitDone_ = awaitDone;\n this.getCurrentRun_ = getCurrentRun;\n this.syncStop_ = syncStop;\n }\n\n get disposed(): boolean {\n return this.syncStop_.disposed;\n }\n\n dispose(): void {\n this.syncStop_.dispose();\n }\n\n run(): Promise<void> {\n return this.getCurrentRun_() ?? Promise.resolve();\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n if (this.asyncDisposePromise_ !== null) return this.asyncDisposePromise_;\n\n this.dispose();\n this.asyncDisposePromise_ = this.awaitDone_();\n\n return this.asyncDisposePromise_;\n }\n}\n"],"mappings":";AAOA,IAAa,IAAb,MAAsD;CACpD;CACA,YAAoB;CAEpB,YAAY,GAAe;EACzB,KAAK,MAAM;CACb;CAEA,IAAI,WAAoB;EACtB,OAAO,KAAK;CACd;CAEA,UAAgB;EACd,IAAI,KAAK,WAAW;EAEpB,KAAK,YAAY;EAEjB,IAAM,IAAK,KAAK;EAGhB,AADA,KAAK,MAAM,MACX,EAAG;CACL;CAEA,CAAC,OAAO,WAAiB;EACvB,KAAK,QAAQ;CACf;AACF,GAQa,IAAb,MAAgE;CAC9D;CACA;CACA;CACA,uBAAqD;CAErD,YAAY,GAAwB,GAAgC,GAA2C;EAG7G,AAFA,KAAK,aAAa,GAClB,KAAK,iBAAiB,GACtB,KAAK,YAAY;CACnB;CAEA,IAAI,WAAoB;EACtB,OAAO,KAAK,UAAU;CACxB;CAEA,UAAgB;EACd,KAAK,UAAU,QAAQ;CACzB;CAEA,MAAqB;EACnB,OAAO,KAAK,eAAe,KAAK,QAAQ,QAAQ;CAClD;CAEA,CAAC,OAAO,WAAiB;EACvB,KAAK,QAAQ;CACf;CAEA,CAAC,OAAO,gBAA+B;EAMrC,OALI,KAAK,yBAAyB,QAElC,KAAK,QAAQ,GACb,KAAK,uBAAuB,KAAK,WAAW,GAErC,KAAK,wBALmC,KAAK;CAMtD;AACF"}
|
package/dist/symbols.cjs
DELETED
package/dist/symbols.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbols.cjs","names":[],"sources":["../src/symbols.ts"],"sourcesContent":["// Brand symbols used for runtime type guards (isSignal, isComputed, isStore).\n// Declared as unique symbols so TypeScript treats them as distinct types.\n\nexport const IS_SIGNAL: unique symbol = Symbol('ripple.is-signal');\nexport const IS_COMPUTED: unique symbol = Symbol('ripple.is-computed');\nexport const IS_STORE: unique symbol = Symbol('ripple.is-store');\n\n// Internal sentinels\n\n/** Marks an uninitialized computed value — distinct from any user-provided value. */\nexport const UNINITIALIZED: unique symbol = Symbol('ripple.uninitialized');\n"],"mappings":"AAGA,IAAa,EAA2B,OAAO,kBAAkB,EACpD,EAA6B,OAAO,oBAAoB,EACxD,EAA0B,OAAO,iBAAiB,EAKlD,EAA+B,OAAO,sBAAsB"}
|
package/dist/symbols.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare const IS_SIGNAL: unique symbol;
|
|
2
|
-
export declare const IS_COMPUTED: unique symbol;
|
|
3
|
-
export declare const IS_STORE: unique symbol;
|
|
4
|
-
/** Marks an uninitialized computed value — distinct from any user-provided value. */
|
|
5
|
-
export declare const UNINITIALIZED: unique symbol;
|
|
6
|
-
//# sourceMappingURL=symbols.d.ts.map
|
package/dist/symbols.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../src/symbols.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,EAAE,OAAO,MAAmC,CAAC;AACnE,eAAO,MAAM,WAAW,EAAE,OAAO,MAAqC,CAAC;AACvE,eAAO,MAAM,QAAQ,EAAE,OAAO,MAAkC,CAAC;AAIjE,qFAAqF;AACrF,eAAO,MAAM,aAAa,EAAE,OAAO,MAAuC,CAAC"}
|
package/dist/symbols.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
//#region src/symbols.ts
|
|
2
|
-
var e = Symbol("ripple.is-signal"), t = Symbol("ripple.is-computed"), n = Symbol("ripple.is-store"), r = Symbol("ripple.uninitialized");
|
|
3
|
-
//#endregion
|
|
4
|
-
export { t as IS_COMPUTED, e as IS_SIGNAL, n as IS_STORE, r as UNINITIALIZED };
|
|
5
|
-
|
|
6
|
-
//# sourceMappingURL=symbols.js.map
|
package/dist/symbols.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbols.js","names":[],"sources":["../src/symbols.ts"],"sourcesContent":["// Brand symbols used for runtime type guards (isSignal, isComputed, isStore).\n// Declared as unique symbols so TypeScript treats them as distinct types.\n\nexport const IS_SIGNAL: unique symbol = Symbol('ripple.is-signal');\nexport const IS_COMPUTED: unique symbol = Symbol('ripple.is-computed');\nexport const IS_STORE: unique symbol = Symbol('ripple.is-store');\n\n// Internal sentinels\n\n/** Marks an uninitialized computed value — distinct from any user-provided value. */\nexport const UNINITIALIZED: unique symbol = Symbol('ripple.uninitialized');\n"],"mappings":";AAGA,IAAa,IAA2B,OAAO,kBAAkB,GACpD,IAA6B,OAAO,oBAAoB,GACxD,IAA0B,OAAO,iBAAiB,GAKlD,IAA+B,OAAO,sBAAsB"}
|
package/dist/tracking.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var e=0,t=()=>++e,n=()=>e,r=()=>({activeDirty:`a`,batchDepth:0,dirtyWithEffectSubsA:new Set,dirtyWithEffectSubsB:new Set,pendingSubscribers:new Set}),i={scheduling:r(),scopeCleanups:null,tracking:null},a=null,o=()=>a===null?i:a.get(),s=()=>o().scheduling,c=()=>a!==null,l=()=>o().tracking,u=(e,t)=>{if(a!==null){let n=a.get();return a.run({...n,tracking:e},t)}let n=i;i={...i,tracking:e};try{return t()}finally{i=n}},d=(e,t)=>{if(a!==null){let n=a.get();return a.run({...n,scopeCleanups:e},t)}let n=i;i={...i,scopeCleanups:e};try{return t()}finally{i=n}},f=()=>o().scopeCleanups,p=e=>{let t=l();t?.kind===`effect`?t.cleanups.push(e):f()?.push(e)},m=e=>{let t=a;return a=e,t},h=(e,t)=>{let n=l();return n===null?t():u({...n,sourceObserver:e},t)},g=e=>u(null,e),_=e=>{let t=l();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)}}};exports._installContextHook=m,exports.autoRegisterDisposal=p,exports.createSchedulingState=r,exports.getRevision=n,exports.getSchedulingState=s,exports.getScopeCleanups=f,exports.getTracking=l,exports.hasContextHook=c,exports.tickRevision=t,exports.trackSource=_,exports.untrack=g,exports.withScopeCleanups=d,exports.withSourceObserver=h,exports.withTracking=u;
|
|
2
|
-
//# sourceMappingURL=tracking.cjs.map
|