@graphrefly/graphrefly 0.25.0 → 0.27.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 +8 -0
- package/dist/ai-CaR_912Q.d.cts +1033 -0
- package/dist/ai-WlRltJV7.d.ts +1033 -0
- package/dist/audit-ClmqGOCx.d.cts +245 -0
- package/dist/audit-DRlSzBu9.d.ts +245 -0
- package/dist/{chunk-QOWVNWOC.js → chunk-3ZWCKRHX.js} +27 -25
- package/dist/{chunk-QOWVNWOC.js.map → chunk-3ZWCKRHX.js.map} +1 -1
- package/dist/chunk-APFNLIRG.js +62 -0
- package/dist/chunk-APFNLIRG.js.map +1 -0
- package/dist/chunk-AT5LKYNL.js +395 -0
- package/dist/chunk-AT5LKYNL.js.map +1 -0
- package/dist/{chunk-IAHGTNOZ.js → chunk-BQ6RQQFF.js} +351 -2095
- package/dist/chunk-BQ6RQQFF.js.map +1 -0
- package/dist/{chunk-L2GLW2U7.js → chunk-BVZYTZ5H.js} +9 -103
- package/dist/chunk-BVZYTZ5H.js.map +1 -0
- package/dist/{chunk-EVR6UFUV.js → chunk-DST5DKZS.js} +19 -15
- package/dist/{chunk-EVR6UFUV.js.map → chunk-DST5DKZS.js.map} +1 -1
- package/dist/{chunk-TKE3JGOH.js → chunk-GTE6PWRZ.js} +5 -692
- package/dist/chunk-GTE6PWRZ.js.map +1 -0
- package/dist/chunk-HXZEYDUR.js +94 -0
- package/dist/chunk-HXZEYDUR.js.map +1 -0
- package/dist/chunk-J22W6HV3.js +107 -0
- package/dist/chunk-J22W6HV3.js.map +1 -0
- package/dist/{chunk-PY4XCDLR.js → chunk-J2VBW3DZ.js} +6 -95
- package/dist/chunk-J2VBW3DZ.js.map +1 -0
- package/dist/{chunk-HWPIFSW2.js → chunk-JSCT3CR4.js} +6 -4
- package/dist/{chunk-HWPIFSW2.js.map → chunk-JSCT3CR4.js.map} +1 -1
- package/dist/chunk-JWBCY4NC.js +330 -0
- package/dist/chunk-JWBCY4NC.js.map +1 -0
- package/dist/chunk-K2AUJHVP.js +2251 -0
- package/dist/chunk-K2AUJHVP.js.map +1 -0
- package/dist/chunk-MJ2NKQQL.js +119 -0
- package/dist/chunk-MJ2NKQQL.js.map +1 -0
- package/dist/chunk-N6UR7YVY.js +198 -0
- package/dist/chunk-N6UR7YVY.js.map +1 -0
- package/dist/chunk-NC6S43JJ.js +456 -0
- package/dist/chunk-NC6S43JJ.js.map +1 -0
- package/dist/chunk-OFVJBJXR.js +98 -0
- package/dist/chunk-OFVJBJXR.js.map +1 -0
- package/dist/chunk-OHISZPOJ.js +97 -0
- package/dist/chunk-OHISZPOJ.js.map +1 -0
- package/dist/chunk-OU5CQKNW.js +102 -0
- package/dist/chunk-OU5CQKNW.js.map +1 -0
- package/dist/{chunk-XOFWRC73.js → chunk-PF7GRZMW.js} +316 -21
- package/dist/chunk-PF7GRZMW.js.map +1 -0
- package/dist/{chunk-5DJTTKX3.js → chunk-PHOUUNK7.js} +74 -111
- package/dist/chunk-PHOUUNK7.js.map +1 -0
- package/dist/chunk-RNHBMHKA.js +1665 -0
- package/dist/chunk-RNHBMHKA.js.map +1 -0
- package/dist/chunk-SX52TAR4.js +110 -0
- package/dist/chunk-SX52TAR4.js.map +1 -0
- package/dist/{chunk-H4RVA4VE.js → chunk-VYPWMZ6H.js} +2 -2
- package/dist/chunk-WBZOVTYK.js +171 -0
- package/dist/chunk-WBZOVTYK.js.map +1 -0
- package/dist/chunk-WKNUIZOY.js +354 -0
- package/dist/chunk-WKNUIZOY.js.map +1 -0
- package/dist/chunk-X3VMZYBT.js +713 -0
- package/dist/chunk-X3VMZYBT.js.map +1 -0
- package/dist/chunk-X5R3GL6H.js +525 -0
- package/dist/chunk-X5R3GL6H.js.map +1 -0
- package/dist/chunk-XGPU467M.js +136 -0
- package/dist/chunk-XGPU467M.js.map +1 -0
- package/dist/compat/index.cjs +7656 -0
- package/dist/compat/index.cjs.map +1 -0
- package/dist/compat/index.d.cts +18 -0
- package/dist/compat/index.d.ts +18 -0
- package/dist/compat/index.js +50 -0
- package/dist/compat/index.js.map +1 -0
- package/dist/compat/jotai/index.cjs +2048 -0
- package/dist/compat/jotai/index.cjs.map +1 -0
- package/dist/compat/jotai/index.d.cts +2 -0
- package/dist/compat/jotai/index.d.ts +2 -0
- package/dist/compat/jotai/index.js +9 -0
- package/dist/compat/jotai/index.js.map +1 -0
- package/dist/compat/nanostores/index.cjs +2175 -0
- package/dist/compat/nanostores/index.cjs.map +1 -0
- package/dist/compat/nanostores/index.d.cts +2 -0
- package/dist/compat/nanostores/index.d.ts +2 -0
- package/dist/compat/nanostores/index.js +23 -0
- package/dist/compat/nanostores/index.js.map +1 -0
- package/dist/compat/nestjs/index.cjs +350 -16
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +6 -6
- package/dist/compat/nestjs/index.d.ts +6 -6
- package/dist/compat/nestjs/index.js +11 -9
- package/dist/compat/react/index.cjs +141 -0
- package/dist/compat/react/index.cjs.map +1 -0
- package/dist/compat/react/index.d.cts +2 -0
- package/dist/compat/react/index.d.ts +2 -0
- package/dist/compat/react/index.js +12 -0
- package/dist/compat/react/index.js.map +1 -0
- package/dist/compat/solid/index.cjs +128 -0
- package/dist/compat/solid/index.cjs.map +1 -0
- package/dist/compat/solid/index.d.cts +2 -0
- package/dist/compat/solid/index.d.ts +2 -0
- package/dist/compat/solid/index.js +12 -0
- package/dist/compat/solid/index.js.map +1 -0
- package/dist/compat/svelte/index.cjs +131 -0
- package/dist/compat/svelte/index.cjs.map +1 -0
- package/dist/compat/svelte/index.d.cts +2 -0
- package/dist/compat/svelte/index.d.ts +2 -0
- package/dist/compat/svelte/index.js +12 -0
- package/dist/compat/svelte/index.js.map +1 -0
- package/dist/compat/vue/index.cjs +146 -0
- package/dist/compat/vue/index.cjs.map +1 -0
- package/dist/compat/vue/index.d.cts +3 -0
- package/dist/compat/vue/index.d.ts +3 -0
- package/dist/compat/vue/index.js +12 -0
- package/dist/compat/vue/index.js.map +1 -0
- package/dist/compat/zustand/index.cjs +4931 -0
- package/dist/compat/zustand/index.cjs.map +1 -0
- package/dist/compat/zustand/index.d.cts +5 -0
- package/dist/compat/zustand/index.d.ts +5 -0
- package/dist/compat/zustand/index.js +12 -0
- package/dist/compat/zustand/index.js.map +1 -0
- package/dist/composite-C7PcQvcs.d.cts +303 -0
- package/dist/composite-aUCvjZVR.d.ts +303 -0
- package/dist/core/index.cjs +53 -4
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +4 -3
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.js +26 -24
- package/dist/demo-shell-BDkOptd6.d.ts +102 -0
- package/dist/demo-shell-Crid1WdR.d.cts +102 -0
- package/dist/extra/index.cjs +222 -110
- package/dist/extra/index.cjs.map +1 -1
- package/dist/extra/index.d.cts +6 -4
- package/dist/extra/index.d.ts +6 -4
- package/dist/extra/index.js +72 -65
- package/dist/extra/sources.cjs +2486 -0
- package/dist/extra/sources.cjs.map +1 -0
- package/dist/extra/sources.d.cts +465 -0
- package/dist/extra/sources.d.ts +465 -0
- package/dist/extra/sources.js +57 -0
- package/dist/extra/sources.js.map +1 -0
- package/dist/graph/index.cjs +408 -14
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +5 -5
- package/dist/graph/index.d.ts +5 -5
- package/dist/graph/index.js +13 -5
- package/dist/{graph-D-3JIQme.d.cts → graph-CCwGKLCm.d.ts} +195 -4
- package/dist/{graph-B6NFqv3z.d.ts → graph-DNCrvZSn.d.cts} +195 -4
- package/dist/index-3lsddbbS.d.ts +86 -0
- package/dist/index-B1tloyhO.d.cts +34 -0
- package/dist/{index-CYkjxu3s.d.ts → index-B6D3QNSA.d.ts} +33 -4
- package/dist/index-B6EhDnjH.d.cts +37 -0
- package/dist/index-B9B7_HEY.d.ts +37 -0
- package/dist/{index-Ds23Wvou.d.ts → index-BHlKbUwO.d.cts} +131 -883
- package/dist/{index-DiobMNwE.d.ts → index-BPVt8kqc.d.ts} +3 -3
- package/dist/index-BaSM3aYt.d.ts +195 -0
- package/dist/index-BuEoe-Qu.d.ts +121 -0
- package/dist/{index-Ch0IpIO0.d.cts → index-BwfLUNw4.d.ts} +131 -883
- package/dist/index-ByQxazQJ.d.cts +86 -0
- package/dist/index-C0svESO4.d.ts +127 -0
- package/dist/{index-OXImXMq6.d.ts → index-C8oil6M6.d.ts} +18 -196
- package/dist/{index-DKE1EATr.d.cts → index-CI3DprxP.d.cts} +18 -196
- package/dist/{index-AMWewNDe.d.cts → index-CO8uBlUh.d.cts} +33 -4
- package/dist/index-CxFrXH4m.d.ts +45 -0
- package/dist/index-D8wS_PeY.d.cts +121 -0
- package/dist/index-DO_6JN9Z.d.cts +127 -0
- package/dist/index-DVGiGFGT.d.cts +195 -0
- package/dist/index-DYme44FM.d.cts +44 -0
- package/dist/{index-J7Kc0oIQ.d.cts → index-DlLp-2Xn.d.cts} +3 -3
- package/dist/index-Dzk2hrlR.d.ts +44 -0
- package/dist/index-VHqptjhu.d.cts +45 -0
- package/dist/index-VdHQMPy1.d.ts +36 -0
- package/dist/index-Xi3u0HCQ.d.cts +36 -0
- package/dist/index-wEn0eFe8.d.ts +34 -0
- package/dist/index.cjs +1780 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +784 -2082
- package/dist/index.d.ts +784 -2082
- package/dist/index.js +955 -4349
- package/dist/index.js.map +1 -1
- package/dist/memory-C6Z2tGpC.d.cts +139 -0
- package/dist/memory-li6FL5RM.d.ts +139 -0
- package/dist/messaging-Gt4LPbyA.d.cts +269 -0
- package/dist/messaging-XDoYablx.d.ts +269 -0
- package/dist/{meta-DWbkoq1s.d.cts → meta-BxCA7rcr.d.cts} +1 -1
- package/dist/{meta-CnkLA_43.d.ts → meta-CbznRPYJ.d.ts} +1 -1
- package/dist/{node-B-f-Lu-k.d.cts → node-BmerH3kS.d.cts} +26 -1
- package/dist/{node-B-f-Lu-k.d.ts → node-BmerH3kS.d.ts} +26 -1
- package/dist/{observable-uP-wy_uK.d.ts → observable-BgGUwcqp.d.ts} +1 -1
- package/dist/{observable-DBnrwcar.d.cts → observable-DJt_AxzQ.d.cts} +1 -1
- package/dist/patterns/ai.cjs +7930 -0
- package/dist/patterns/ai.cjs.map +1 -0
- package/dist/patterns/ai.d.cts +10 -0
- package/dist/patterns/ai.d.ts +10 -0
- package/dist/patterns/ai.js +71 -0
- package/dist/patterns/ai.js.map +1 -0
- package/dist/patterns/audit.cjs +5805 -0
- package/dist/patterns/audit.cjs.map +1 -0
- package/dist/patterns/audit.d.cts +6 -0
- package/dist/patterns/audit.d.ts +6 -0
- package/dist/patterns/audit.js +29 -0
- package/dist/patterns/audit.js.map +1 -0
- package/dist/patterns/demo-shell.cjs +5604 -0
- package/dist/patterns/demo-shell.cjs.map +1 -0
- package/dist/patterns/demo-shell.d.cts +6 -0
- package/dist/patterns/demo-shell.d.ts +6 -0
- package/dist/patterns/demo-shell.js +15 -0
- package/dist/patterns/demo-shell.js.map +1 -0
- package/dist/patterns/memory.cjs +5283 -0
- package/dist/patterns/memory.cjs.map +1 -0
- package/dist/patterns/memory.d.cts +5 -0
- package/dist/patterns/memory.d.ts +5 -0
- package/dist/patterns/memory.js +20 -0
- package/dist/patterns/memory.js.map +1 -0
- package/dist/patterns/reactive-layout/index.cjs +355 -13
- package/dist/patterns/reactive-layout/index.cjs.map +1 -1
- package/dist/patterns/reactive-layout/index.d.cts +6 -5
- package/dist/patterns/reactive-layout/index.d.ts +6 -5
- package/dist/patterns/reactive-layout/index.js +15 -12
- package/dist/reactive-layout-MQP--J3F.d.cts +183 -0
- package/dist/reactive-layout-u5Ulnqag.d.ts +183 -0
- package/dist/{storage-BuTdpCI1.d.cts → storage-CMjUUuxn.d.ts} +10 -2
- package/dist/{storage-F2X1U1x0.d.ts → storage-DdWlZo6U.d.cts} +10 -2
- package/dist/sugar-CCOxXK1e.d.ts +201 -0
- package/dist/sugar-D02n5JjF.d.cts +201 -0
- package/package.json +63 -3
- package/dist/chunk-5DJTTKX3.js.map +0 -1
- package/dist/chunk-IAHGTNOZ.js.map +0 -1
- package/dist/chunk-L2GLW2U7.js.map +0 -1
- package/dist/chunk-MW4VAKAO.js +0 -47
- package/dist/chunk-MW4VAKAO.js.map +0 -1
- package/dist/chunk-PY4XCDLR.js.map +0 -1
- package/dist/chunk-TKE3JGOH.js.map +0 -1
- package/dist/chunk-XOFWRC73.js.map +0 -1
- package/dist/index-BJB7t9gg.d.cts +0 -392
- package/dist/index-C-TXEa7C.d.ts +0 -392
- /package/dist/{chunk-H4RVA4VE.js.map → chunk-VYPWMZ6H.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compat/index.ts","../src/compat/signals/index.ts"],"sourcesContent":["/**\n * Compat layer: compatibility wrappers for other state management libraries (Phase 5.1b).\n *\n * Framework adapters are optional peers. Install only what you use:\n * - `@graphrefly/graphrefly-ts/compat/react` -> `react`, `react-dom`\n * - `@graphrefly/graphrefly-ts/compat/vue` -> `vue`\n * - `@graphrefly/graphrefly-ts/compat/svelte` -> `svelte`\n * - `@graphrefly/graphrefly-ts/compat/solid` -> `solid-js`\n */\nexport * as jotai from \"./jotai/index.js\";\nexport * as nanostores from \"./nanostores/index.js\";\nexport * as nestjs from \"./nestjs/index.js\";\nexport * as react from \"./react/index.js\";\nexport * as signals from \"./signals/index.js\";\nexport * as solid from \"./solid/index.js\";\nexport * as svelte from \"./svelte/index.js\";\nexport * as vue from \"./vue/index.js\";\nexport * as zustand from \"./zustand/index.js\";\n","import { batch } from \"../../core/batch.js\";\nimport { COMPLETE, DATA, DIRTY, ERROR, type Messages } from \"../../core/messages.js\";\nimport type { Node } from \"../../core/node.js\";\nimport { autoTrackNode, state, type TrackFn } from \"../../core/sugar.js\";\n\n/**\n * Options for creating signals.\n *\n * @category compat\n */\nexport interface SignalOptions {\n\t/** Optional identifier for the underlying node. */\n\tname?: string;\n\t/** Custom equality function for change detection. */\n\tequals?: (a: any, b: any) => boolean;\n}\n\n/**\n * Common interface for all reactive signals.\n *\n * @category compat\n */\nexport interface AnySignal<T> {\n\t/** Returns the current value of the signal. */\n\tget(): T;\n\t/** @internal The underlying GraphReFly node. */\n\t_node: Node<T>;\n}\n\n/**\n * Global stack of active tracking contexts.\n * Since computation evaluation is fully synchronous, we push the tracking `get`\n * function before execution and pop it after. This prevents memory leaks without\n * needing WeakRefs, as the stack is always empty when idle.\n */\nconst trackingStack: TrackFn[] = [];\n\n/**\n * Helper to pull a disconnected node, forcing a synchronous resolution\n * cycle so that `get()` returns a fresh value even if the signal is unmounted.\n */\nfunction pull<T>(n: Node<T>): T {\n\tlet val: T | undefined | null = n.cache;\n\tconst unsub = n.subscribe((msgs: Messages) => {\n\t\tfor (const [t, v] of msgs) {\n\t\t\tif (t === DATA) val = v as T;\n\t\t}\n\t});\n\tunsub();\n\treturn val as T;\n}\n\n/**\n * TC39 `Signal.State` — a writable signal backed by a GraphReFly `state` node.\n * Automatically registers itself as a dependency if read inside a `Computed`.\n *\n * @example\n * ```ts\n * const count = new Signal.State(0);\n * count.get(); // 0\n * count.set(1);\n * count.get(); // 1\n * ```\n */\nclass SignalState<T> implements AnySignal<T> {\n\t/** @internal */\n\t_node: Node<T>;\n\tprivate readonly _equals: (a: T, b: T) => boolean;\n\n\tconstructor(initial: T, opts?: SignalOptions) {\n\t\tthis._equals = (opts?.equals ?? Object.is) as (a: T, b: T) => boolean;\n\t\tthis._node = state<T>(initial, {\n\t\t\t...opts,\n\t\t\tresubscribable: true,\n\t\t\tresetOnTeardown: true,\n\t\t});\n\t}\n\n\tget(): T {\n\t\t// If we are evaluating inside a computed node, track this read!\n\t\tconst tracker = trackingStack[trackingStack.length - 1];\n\t\tif (tracker) {\n\t\t\tif (this._node.status === \"sentinel\") {\n\t\t\t\tpull(this._node);\n\t\t\t}\n\t\t\treturn tracker(this._node) as T;\n\t\t}\n\n\t\tif (this._node.status === \"sentinel\") {\n\t\t\treturn pull(this._node);\n\t\t}\n\t\treturn this._node.cache as T;\n\t}\n\n\tset(value: T): void {\n\t\tif (this._equals(this.get(), value)) return;\n\t\tbatch(() => {\n\t\t\tthis._node.down([[DIRTY], [DATA, value]]);\n\t\t});\n\t}\n}\n\n/**\n * TC39 `Signal.Computed` — a read-only signal backed by `dynamicNode`.\n * Automatically tracks dependencies when `get()` is called on other signals\n * during its computation.\n *\n * @example\n * ```ts\n * const count = new Signal.State(0);\n * const doubled = new Signal.Computed(() => count.get() * 2);\n * ```\n */\nclass SignalComputed<T> implements AnySignal<T> {\n\t/** @internal */\n\t_node: Node<T>;\n\n\tconstructor(computation: () => T, opts?: SignalOptions) {\n\t\tthis._node = autoTrackNode<T>(\n\t\t\t(track) => {\n\t\t\t\ttrackingStack.push(track);\n\t\t\t\ttry {\n\t\t\t\t\treturn computation();\n\t\t\t\t} finally {\n\t\t\t\t\ttrackingStack.pop();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t...opts,\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tresubscribable: true,\n\t\t\t\tresetOnTeardown: true,\n\t\t\t},\n\t\t);\n\t}\n\n\tget(): T {\n\t\t// Computed nodes can themselves be dependencies of other Computed nodes.\n\t\tconst tracker = trackingStack[trackingStack.length - 1];\n\t\tif (tracker) {\n\t\t\tif (this._node.status === \"sentinel\") {\n\t\t\t\tpull(this._node);\n\t\t\t}\n\t\t\treturn tracker(this._node) as T;\n\t\t}\n\n\t\tif (this._node.status === \"sentinel\") {\n\t\t\treturn pull(this._node);\n\t\t}\n\t\treturn this._node.cache as T;\n\t}\n}\n\n/**\n * TC39 Signals-compatible namespace. Wraps GraphReFly primitives.\n * Provides auto-tracking conforming to the TS39 signals proposal.\n *\n * @category compat\n */\nexport const Signal = {\n\tState: SignalState,\n\tComputed: SignalComputed,\n\n\t/**\n\t * Subscribes to changes on a signal.\n\t * Returns an unsubscribe callback.\n\t *\n\t * @example\n\t * ```ts\n\t * const count = new Signal.State(0);\n\t * const unsub = Signal.sub(count, v => console.log(v));\n\t * ```\n\t */\n\tsub: <T>(\n\t\tsignal: AnySignal<T>,\n\t\tcallback:\n\t\t\t| ((value: T) => void)\n\t\t\t| {\n\t\t\t\t\tdata?: (value: T) => void;\n\t\t\t\t\terror?: (err: unknown) => void;\n\t\t\t\t\tcomplete?: () => void;\n\t\t\t },\n\t): (() => void) => {\n\t\tconst handlers =\n\t\t\ttypeof callback === \"function\"\n\t\t\t\t? { data: callback as (value: T) => void, error: undefined, complete: undefined }\n\t\t\t\t: callback;\n\t\t// Skip the initial push-on-subscribe DATA — Signal.sub fires on changes only.\n\t\tlet initial = true;\n\t\treturn signal._node.subscribe((msgs) => {\n\t\t\tfor (const [t, v] of msgs) {\n\t\t\t\tif (t === DATA) {\n\t\t\t\t\tif (initial) {\n\t\t\t\t\t\tinitial = false;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\thandlers.data?.(v as T);\n\t\t\t\t}\n\t\t\t\tif (t === ERROR) handlers.error?.(v);\n\t\t\t\tif (t === COMPLETE) handlers.complete?.();\n\t\t\t}\n\t\t});\n\t},\n} as const;\n\nexport type { SignalComputed, SignalState };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import {
|
|
2
|
+
batch,
|
|
3
|
+
monotonicNs,
|
|
4
|
+
state
|
|
5
|
+
} from "./chunk-PHOUUNK7.js";
|
|
6
|
+
import {
|
|
7
|
+
DATA,
|
|
8
|
+
DIRTY
|
|
9
|
+
} from "./chunk-SX52TAR4.js";
|
|
10
|
+
|
|
11
|
+
// src/extra/reactive-map.ts
|
|
12
|
+
var NativeMapBackend = class {
|
|
13
|
+
_version = 0;
|
|
14
|
+
_store = /* @__PURE__ */ new Map();
|
|
15
|
+
_maxSize;
|
|
16
|
+
_defaultTtl;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
const { maxSize, defaultTtl } = options;
|
|
19
|
+
if (maxSize !== void 0 && maxSize < 1) {
|
|
20
|
+
throw new RangeError("maxSize must be >= 1");
|
|
21
|
+
}
|
|
22
|
+
if (defaultTtl !== void 0 && defaultTtl <= 0) {
|
|
23
|
+
throw new RangeError("defaultTtl must be positive");
|
|
24
|
+
}
|
|
25
|
+
this._maxSize = maxSize;
|
|
26
|
+
this._defaultTtl = defaultTtl;
|
|
27
|
+
}
|
|
28
|
+
get version() {
|
|
29
|
+
return this._version;
|
|
30
|
+
}
|
|
31
|
+
get size() {
|
|
32
|
+
return this._store.size;
|
|
33
|
+
}
|
|
34
|
+
has(key) {
|
|
35
|
+
const e = this._store.get(key);
|
|
36
|
+
if (e === void 0) return false;
|
|
37
|
+
if (this._isExpired(e)) {
|
|
38
|
+
this._store.delete(key);
|
|
39
|
+
this._version += 1;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
this._touchLru(key, e);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
get(key) {
|
|
46
|
+
const e = this._store.get(key);
|
|
47
|
+
if (e === void 0) return void 0;
|
|
48
|
+
if (this._isExpired(e)) {
|
|
49
|
+
this._store.delete(key);
|
|
50
|
+
this._version += 1;
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
this._touchLru(key, e);
|
|
54
|
+
return e.value;
|
|
55
|
+
}
|
|
56
|
+
set(key, value, ttl) {
|
|
57
|
+
const expiresAt = this._resolveExpiresAt(ttl);
|
|
58
|
+
if (this._store.has(key)) this._store.delete(key);
|
|
59
|
+
this._store.set(key, { value, expiresAt });
|
|
60
|
+
this._evictLruWhileOver();
|
|
61
|
+
this._version += 1;
|
|
62
|
+
}
|
|
63
|
+
setMany(entries, ttl) {
|
|
64
|
+
const expiresAt = this._resolveExpiresAt(ttl);
|
|
65
|
+
let count = 0;
|
|
66
|
+
try {
|
|
67
|
+
for (const [key, value] of entries) {
|
|
68
|
+
if (this._store.has(key)) this._store.delete(key);
|
|
69
|
+
this._store.set(key, { value, expiresAt });
|
|
70
|
+
count += 1;
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
if (count > 0) {
|
|
74
|
+
this._evictLruWhileOver();
|
|
75
|
+
this._version += 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
delete(key) {
|
|
80
|
+
const had = this._store.delete(key);
|
|
81
|
+
if (had) this._version += 1;
|
|
82
|
+
return had;
|
|
83
|
+
}
|
|
84
|
+
deleteMany(keys) {
|
|
85
|
+
let removed = 0;
|
|
86
|
+
try {
|
|
87
|
+
for (const k of keys) {
|
|
88
|
+
if (this._store.delete(k)) removed += 1;
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
if (removed > 0) this._version += 1;
|
|
92
|
+
}
|
|
93
|
+
return removed;
|
|
94
|
+
}
|
|
95
|
+
clear() {
|
|
96
|
+
const n = this._store.size;
|
|
97
|
+
if (n === 0) return 0;
|
|
98
|
+
this._store.clear();
|
|
99
|
+
this._version += 1;
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
pruneExpired() {
|
|
103
|
+
const now = monotonicNs();
|
|
104
|
+
let removed = 0;
|
|
105
|
+
for (const [k, e] of this._store) {
|
|
106
|
+
if (this._isExpired(e, now)) {
|
|
107
|
+
this._store.delete(k);
|
|
108
|
+
removed += 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (removed > 0) this._version += 1;
|
|
112
|
+
return removed;
|
|
113
|
+
}
|
|
114
|
+
toMap() {
|
|
115
|
+
const now = monotonicNs();
|
|
116
|
+
const out = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const [k, e] of this._store) {
|
|
118
|
+
if (!this._isExpired(e, now)) out.set(k, e.value);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
_resolveExpiresAt(ttl) {
|
|
123
|
+
const effectiveTtl = ttl ?? this._defaultTtl;
|
|
124
|
+
if (effectiveTtl === void 0) return void 0;
|
|
125
|
+
if (!Number.isFinite(effectiveTtl) || effectiveTtl <= 0) {
|
|
126
|
+
throw new RangeError(
|
|
127
|
+
`MapBackend: ttl must be a positive finite number (got ${effectiveTtl})`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return monotonicNs() + effectiveTtl * 1e9;
|
|
131
|
+
}
|
|
132
|
+
_isExpired(e, now) {
|
|
133
|
+
if (e.expiresAt === void 0) return false;
|
|
134
|
+
return (now ?? monotonicNs()) >= e.expiresAt;
|
|
135
|
+
}
|
|
136
|
+
_touchLru(key, entry) {
|
|
137
|
+
this._store.delete(key);
|
|
138
|
+
this._store.set(key, entry);
|
|
139
|
+
}
|
|
140
|
+
_evictLruWhileOver() {
|
|
141
|
+
if (this._maxSize === void 0) return;
|
|
142
|
+
while (this._store.size > this._maxSize) {
|
|
143
|
+
const first = this._store.keys().next().value;
|
|
144
|
+
if (first === void 0) break;
|
|
145
|
+
this._store.delete(first);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function reactiveMap(options = {}) {
|
|
150
|
+
const { name, maxSize, defaultTtl, versioning, backend: userBackend } = options;
|
|
151
|
+
const backend = userBackend ?? new NativeMapBackend({ maxSize, defaultTtl });
|
|
152
|
+
const n = state(backend.toMap(), {
|
|
153
|
+
name,
|
|
154
|
+
describeKind: "state",
|
|
155
|
+
equals: (a, b) => a === b,
|
|
156
|
+
...versioning != null ? { versioning } : {}
|
|
157
|
+
});
|
|
158
|
+
function pushSnapshot() {
|
|
159
|
+
const map = backend.toMap();
|
|
160
|
+
batch(() => {
|
|
161
|
+
n.down([[DIRTY]]);
|
|
162
|
+
n.down([[DATA, map]]);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function wrapMutation(op) {
|
|
166
|
+
const prev = backend.version;
|
|
167
|
+
try {
|
|
168
|
+
return op();
|
|
169
|
+
} finally {
|
|
170
|
+
if (backend.version !== prev) pushSnapshot();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
entries: n,
|
|
175
|
+
has(key) {
|
|
176
|
+
return wrapMutation(() => backend.has(key));
|
|
177
|
+
},
|
|
178
|
+
get(key) {
|
|
179
|
+
return wrapMutation(() => backend.get(key));
|
|
180
|
+
},
|
|
181
|
+
set(key, value, opts) {
|
|
182
|
+
wrapMutation(() => backend.set(key, value, opts?.ttl));
|
|
183
|
+
},
|
|
184
|
+
setMany(entries, opts) {
|
|
185
|
+
wrapMutation(() => backend.setMany(entries, opts?.ttl));
|
|
186
|
+
},
|
|
187
|
+
delete(key) {
|
|
188
|
+
wrapMutation(() => backend.delete(key));
|
|
189
|
+
},
|
|
190
|
+
deleteMany(keys) {
|
|
191
|
+
wrapMutation(() => backend.deleteMany(keys));
|
|
192
|
+
},
|
|
193
|
+
clear() {
|
|
194
|
+
wrapMutation(() => backend.clear());
|
|
195
|
+
},
|
|
196
|
+
pruneExpired() {
|
|
197
|
+
wrapMutation(() => backend.pruneExpired());
|
|
198
|
+
},
|
|
199
|
+
/**
|
|
200
|
+
* Current raw entry count — O(1), **pure read**. May include
|
|
201
|
+
* not-yet-pruned expired entries on TTL maps until the next mutation
|
|
202
|
+
* or an explicit `pruneExpired()` / `has` / `get` triggers a prune.
|
|
203
|
+
*
|
|
204
|
+
* Previously this getter ran `pruneExpired()` inline and emitted a
|
|
205
|
+
* snapshot as a side effect — that violated spec §5.8 "no
|
|
206
|
+
* side-effectful reads" and created a re-entrancy hazard when a
|
|
207
|
+
* subscriber to `entries` read `.size` from its own callback. D2(a).
|
|
208
|
+
*
|
|
209
|
+
* For a live count that excludes expired entries, call
|
|
210
|
+
* `bundle.pruneExpired()` first.
|
|
211
|
+
*/
|
|
212
|
+
get size() {
|
|
213
|
+
return backend.size;
|
|
214
|
+
},
|
|
215
|
+
dispose() {
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/extra/reactive-list.ts
|
|
221
|
+
var NativeListBackend = class {
|
|
222
|
+
_version = 0;
|
|
223
|
+
_buf;
|
|
224
|
+
constructor(initial) {
|
|
225
|
+
this._buf = initial ? [...initial] : [];
|
|
226
|
+
}
|
|
227
|
+
get version() {
|
|
228
|
+
return this._version;
|
|
229
|
+
}
|
|
230
|
+
get size() {
|
|
231
|
+
return this._buf.length;
|
|
232
|
+
}
|
|
233
|
+
at(index) {
|
|
234
|
+
if (!Number.isInteger(index)) return void 0;
|
|
235
|
+
const i = index >= 0 ? index : this._buf.length + index;
|
|
236
|
+
if (i < 0 || i >= this._buf.length) return void 0;
|
|
237
|
+
return this._buf[i];
|
|
238
|
+
}
|
|
239
|
+
append(value) {
|
|
240
|
+
this._buf.push(value);
|
|
241
|
+
this._version += 1;
|
|
242
|
+
}
|
|
243
|
+
appendMany(values) {
|
|
244
|
+
if (values.length === 0) return;
|
|
245
|
+
const oldLen = this._buf.length;
|
|
246
|
+
this._buf.length = oldLen + values.length;
|
|
247
|
+
for (let i = 0; i < values.length; i++) {
|
|
248
|
+
this._buf[oldLen + i] = values[i];
|
|
249
|
+
}
|
|
250
|
+
this._version += 1;
|
|
251
|
+
}
|
|
252
|
+
insert(index, value) {
|
|
253
|
+
if (!Number.isInteger(index) || index < 0 || index > this._buf.length) {
|
|
254
|
+
throw new RangeError(`insert: index ${index} out of range [0, ${this._buf.length}]`);
|
|
255
|
+
}
|
|
256
|
+
this._buf.splice(index, 0, value);
|
|
257
|
+
this._version += 1;
|
|
258
|
+
}
|
|
259
|
+
insertMany(index, values) {
|
|
260
|
+
if (!Number.isInteger(index) || index < 0 || index > this._buf.length) {
|
|
261
|
+
throw new RangeError(`insertMany: index ${index} out of range [0, ${this._buf.length}]`);
|
|
262
|
+
}
|
|
263
|
+
if (values.length === 0) return;
|
|
264
|
+
this._buf.splice(index, 0, ...values);
|
|
265
|
+
this._version += 1;
|
|
266
|
+
}
|
|
267
|
+
pop(index) {
|
|
268
|
+
if (this._buf.length === 0) {
|
|
269
|
+
throw new RangeError("pop from empty list");
|
|
270
|
+
}
|
|
271
|
+
if (!Number.isInteger(index)) {
|
|
272
|
+
throw new RangeError(`pop: index ${index} must be an integer`);
|
|
273
|
+
}
|
|
274
|
+
const i = index >= 0 ? index : this._buf.length + index;
|
|
275
|
+
if (i < 0 || i >= this._buf.length) {
|
|
276
|
+
throw new RangeError(`pop: index ${index} out of range`);
|
|
277
|
+
}
|
|
278
|
+
const [v] = this._buf.splice(i, 1);
|
|
279
|
+
this._version += 1;
|
|
280
|
+
return v;
|
|
281
|
+
}
|
|
282
|
+
clear() {
|
|
283
|
+
const n = this._buf.length;
|
|
284
|
+
if (n === 0) return 0;
|
|
285
|
+
this._buf.length = 0;
|
|
286
|
+
this._version += 1;
|
|
287
|
+
return n;
|
|
288
|
+
}
|
|
289
|
+
toArray() {
|
|
290
|
+
return [...this._buf];
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
function reactiveList(initial, options = {}) {
|
|
294
|
+
const { name, versioning, backend: userBackend } = options;
|
|
295
|
+
const backend = userBackend ?? new NativeListBackend(initial);
|
|
296
|
+
const items = state(backend.toArray(), {
|
|
297
|
+
name,
|
|
298
|
+
describeKind: "state",
|
|
299
|
+
equals: (a, b) => a === b,
|
|
300
|
+
...versioning != null ? { versioning } : {}
|
|
301
|
+
});
|
|
302
|
+
function pushSnapshot() {
|
|
303
|
+
const snapshot = backend.toArray();
|
|
304
|
+
batch(() => {
|
|
305
|
+
items.down([[DIRTY]]);
|
|
306
|
+
items.down([[DATA, snapshot]]);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
function wrapMutation(op) {
|
|
310
|
+
const prev = backend.version;
|
|
311
|
+
try {
|
|
312
|
+
return op();
|
|
313
|
+
} finally {
|
|
314
|
+
if (backend.version !== prev) pushSnapshot();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
items,
|
|
319
|
+
get size() {
|
|
320
|
+
return backend.size;
|
|
321
|
+
},
|
|
322
|
+
at(index) {
|
|
323
|
+
return backend.at(index);
|
|
324
|
+
},
|
|
325
|
+
append(value) {
|
|
326
|
+
wrapMutation(() => backend.append(value));
|
|
327
|
+
},
|
|
328
|
+
appendMany(values) {
|
|
329
|
+
wrapMutation(() => backend.appendMany(values));
|
|
330
|
+
},
|
|
331
|
+
insert(index, value) {
|
|
332
|
+
wrapMutation(() => backend.insert(index, value));
|
|
333
|
+
},
|
|
334
|
+
insertMany(index, values) {
|
|
335
|
+
wrapMutation(() => backend.insertMany(index, values));
|
|
336
|
+
},
|
|
337
|
+
pop(index = -1) {
|
|
338
|
+
return wrapMutation(() => backend.pop(index));
|
|
339
|
+
},
|
|
340
|
+
clear() {
|
|
341
|
+
wrapMutation(() => backend.clear());
|
|
342
|
+
},
|
|
343
|
+
dispose() {
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export {
|
|
349
|
+
NativeMapBackend,
|
|
350
|
+
reactiveMap,
|
|
351
|
+
NativeListBackend,
|
|
352
|
+
reactiveList
|
|
353
|
+
};
|
|
354
|
+
//# sourceMappingURL=chunk-WKNUIZOY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extra/reactive-map.ts","../src/extra/reactive-list.ts"],"sourcesContent":["/**\n * Reactive key–value map (roadmap §3.2) — emits `ReadonlyMap` snapshots directly.\n *\n * Internal version counter drives efficient equality without leaking `Versioned`\n * into the public API (spec §5.12).\n *\n * **Wave 4 refactor (2026-04-15):** Introduces the `MapBackend<K, V>` pluggable-backend\n * interface. The default `NativeMapBackend` owns LRU ordering, TTL expiry, and a\n * monotonic `version` counter. Reads that discover expired entries prune them and\n * emit (fixes the former `size`-getter stale-snapshot gap).\n */\nimport { batch } from \"../core/batch.js\";\nimport { monotonicNs } from \"../core/clock.js\";\nimport { DATA, DIRTY } from \"../core/messages.js\";\nimport type { Node, NodeOptions } from \"../core/node.js\";\nimport { state } from \"../core/sugar.js\";\nimport type { VersioningLevel } from \"../core/versioning.js\";\n\nexport type ReactiveMapOptions<K, V> = {\n\t/** Optional registry name for `describe()` / debugging. */\n\tname?: string;\n\t/**\n\t * LRU cap. When set, evicts least-recently-used keys after inserts that exceed this size.\n\t * Forwarded to the default `NativeMapBackend`. Ignored if a custom `backend` is provided.\n\t */\n\tmaxSize?: number;\n\t/**\n\t * Default TTL in seconds. Used when `set`/`setMany` omits per-call `ttl`.\n\t * Forwarded to the default `NativeMapBackend`. Ignored if a custom `backend` is provided.\n\t */\n\tdefaultTtl?: number;\n\t/**\n\t * Storage backend. Defaults to `NativeMapBackend`. Users can plug in persistent\n\t * (HAMT / Immutable.js) or shared-state backends via the {@link MapBackend} interface.\n\t */\n\tbackend?: MapBackend<K, V>;\n\t/**\n\t * Optional versioning level for the underlying `entries` state node. Set at\n\t * construction time; cannot be changed later. Pass `0` for V0 identity +\n\t * monotonic version counter, or `1` for V1 + content-addressed cid.\n\t */\n\tversioning?: VersioningLevel;\n} & Omit<NodeOptions, \"initial\" | \"describeKind\" | \"equals\" | \"versioning\">;\n\nexport type ReactiveMapBundle<K, V> = {\n\t/** Emits `ReadonlyMap<K, V>` on each structural change (two-phase). */\n\tentries: Node<ReadonlyMap<K, V>>;\n\t/**\n\t * Checks existence. O(1) for live keys. If the key is expired, prunes it AND\n\t * emits a snapshot so the reactive surface stays consistent with the return\n\t * value. Reads on expired keys are therefore **observable side effects**.\n\t *\n\t * **LRU touch (F4):** When `maxSize` is configured, a live-key `has` also\n\t * marks the entry as most-recently-used — which rearranges internal insertion\n\t * order without bumping `version` or emitting. If you care about iteration\n\t * order in a downstream subscriber, rely on the `entries` snapshot (a fresh\n\t * `ReadonlyMap` per mutation) rather than iterating the backend directly.\n\t */\n\thas: (key: K) => boolean;\n\t/**\n\t * Gets value. O(1) for live keys. If the key is expired, prunes it AND emits\n\t * a snapshot. Reads on expired keys are therefore **observable side effects**.\n\t *\n\t * **LRU touch (F4):** When `maxSize` is configured, a live-key `get` also\n\t * marks the entry as most-recently-used (no version bump, no emission). See\n\t * `has` for the full note on iteration order.\n\t */\n\tget: (key: K) => V | undefined;\n\t/**\n\t * Sets value with optional TTL (seconds). Throws on `ttl <= 0`. Applies LRU eviction\n\t * if `maxSize` is set. Always emits.\n\t */\n\tset: (key: K, value: V, opts?: { ttl?: number }) => void;\n\t/**\n\t * Bulk set — emits one snapshot for the whole batch. Applies `opts.ttl` (falls back\n\t * to `defaultTtl`) to every entry. No-op if `entries` is empty.\n\t *\n\t * **Iterable consumption:** Consumes `entries` once (single-pass). Pass an array\n\t * or `Set` for multi-shot consumers. If the iterator throws mid-iteration,\n\t * entries already applied remain committed and a snapshot IS emitted (via the\n\t * wrapper's finally-block).\n\t */\n\tsetMany: (entries: Iterable<readonly [K, V]>, opts?: { ttl?: number }) => void;\n\tdelete: (key: K) => void;\n\t/**\n\t * Bulk delete — emits one snapshot. No-op if no keys were present.\n\t *\n\t * **Iterable consumption:** Consumes `keys` once (single-pass).\n\t */\n\tdeleteMany: (keys: Iterable<K>) => void;\n\tclear: () => void;\n\t/**\n\t * Current entry count — O(1), **pure read** (no emission). May include\n\t * expired entries on TTL maps until a mutation or explicit\n\t * `pruneExpired()` / `has(key)` / `get(key)` prunes them. Call\n\t * `pruneExpired()` first if you need a live count.\n\t */\n\treadonly size: number;\n\t/** Explicitly prunes all expired entries. Emits if any were removed. */\n\tpruneExpired: () => void;\n\t/**\n\t * Releases any internal keepalive subscriptions so the bundle can be\n\t * GC'd. `reactiveMap` currently holds none (the `entries` node lives only\n\t * as long as external subscribers keep it alive), so `dispose()` is a\n\t * no-op today — exposed for API parity with `reactiveIndex.dispose` /\n\t * `reactiveList.dispose` / `reactiveLog.dispose`. Idempotent. D6(a).\n\t */\n\tdispose: () => void;\n};\n\n// ── Backend interface ─────────────────────────────────────────────────────\n\n/**\n * Storage contract for {@link reactiveMap}. Implementations own the mutable state,\n * including optional TTL and LRU semantics, and expose a monotonic `version` counter\n * that increments on every change to visible state.\n *\n * The reactive layer reads `version` before and after each backend call; when it\n * advances, a snapshot is emitted. Reads (`has`, `get`) may internally prune the\n * target key if expired and advance `version` — in which case the layer emits so\n * subscribers see state consistent with the read's return value.\n *\n * @remarks Post-1.0 op-log changesets will extend this interface with a\n * `changesSince(version: number): Iterable<Change>` method. Current consumers\n * should treat all methods here as stable.\n *\n * @category extra\n */\nexport interface MapBackend<K, V> {\n\t/** Monotonic mutation counter; increments on every visible state change. */\n\treadonly version: number;\n\t/** Raw entry count (may include expired entries until a read / prune removes them). */\n\treadonly size: number;\n\t/** Checks existence. May prune target key if expired; advances `version` if pruned. */\n\thas(key: K): boolean;\n\t/** Gets value. May prune target key if expired; advances `version` if pruned. */\n\tget(key: K): V | undefined;\n\t/**\n\t * Sets a value with optional TTL (seconds). Throws `RangeError` if `ttl <= 0`.\n\t * Applies LRU eviction if `maxSize` is configured. Advances `version`.\n\t *\n\t * **Atomicity contract:** Either fully succeeds or throws before any state\n\t * change; `version` advances only on success.\n\t */\n\tset(key: K, value: V, ttl?: number): void;\n\t/**\n\t * Atomic bulk set. Pre-validates TTL once, then applies all entries. Advances\n\t * `version` at most once (even for N entries). No-op if iterable is empty.\n\t *\n\t * **Consumes `entries` once** — pass an array if you want repeatability.\n\t *\n\t * **Atomicity contract:** TTL validation throws before any mutation. If the\n\t * iterable itself throws mid-iteration, entries committed before the throw\n\t * remain persisted AND `version` is bumped once (surfaced via finally) so\n\t * the reactive wrapper emits a snapshot reflecting the partial state. \"At\n\t * most once\" invariant is preserved.\n\t */\n\tsetMany(entries: Iterable<readonly [K, V]>, ttl?: number): void;\n\t/** Removes a key. Returns `true` if the key existed. Advances `version` only if true. */\n\tdelete(key: K): boolean;\n\t/**\n\t * Atomic bulk delete. Returns count removed. Advances `version` at most once\n\t * (even for N keys). No-op if no keys were present. Consumes `keys` once.\n\t */\n\tdeleteMany(keys: Iterable<K>): number;\n\t/** Removes all entries. Returns count removed. Advances `version` only if non-zero. */\n\tclear(): number;\n\t/** Removes all expired entries. Returns count removed. Advances `version` only if non-zero. */\n\tpruneExpired(): number;\n\t/** Fresh snapshot of non-expired entries (does NOT mutate state). */\n\ttoMap(): ReadonlyMap<K, V>;\n}\n\nexport type NativeMapBackendOptions = {\n\tmaxSize?: number;\n\t/** Default TTL in seconds. */\n\tdefaultTtl?: number;\n};\n\ntype MapEntry<V> = { value: V; expiresAt?: number };\n\n/**\n * Default `Map<K, {value, expiresAt}>` backend with optional per-key TTL and LRU cap.\n *\n * **Complexity:**\n * - `has`, `get`, `delete`, `size`: O(1)\n * - `set`: O(1) amortized (LRU touch + eviction)\n * - `pruneExpired`, `toMap`: O(n)\n *\n * LRU order uses native `Map` insertion order. `get` / `has` on a live key \"touches\"\n * it by delete-then-reinsert (moving it to the end). This touch does NOT advance\n * `version` — it's an internal optimization; the externally visible snapshot\n * preserves iteration order as of the last mutation. **Note:** because touch\n * reorders the internal `_store` without emitting, an in-process consumer iterating\n * `_store` directly (custom subclasses) could observe changing order; external\n * subscribers only see `toMap()` snapshots which are defensively copied and stable.\n *\n * @category extra\n */\nexport class NativeMapBackend<K, V> implements MapBackend<K, V> {\n\tprivate _version = 0;\n\tprivate readonly _store = new Map<K, MapEntry<V>>();\n\tprivate readonly _maxSize?: number;\n\tprivate readonly _defaultTtl?: number;\n\n\tconstructor(options: NativeMapBackendOptions = {}) {\n\t\tconst { maxSize, defaultTtl } = options;\n\t\tif (maxSize !== undefined && maxSize < 1) {\n\t\t\tthrow new RangeError(\"maxSize must be >= 1\");\n\t\t}\n\t\tif (defaultTtl !== undefined && defaultTtl <= 0) {\n\t\t\tthrow new RangeError(\"defaultTtl must be positive\");\n\t\t}\n\t\tthis._maxSize = maxSize;\n\t\tthis._defaultTtl = defaultTtl;\n\t}\n\n\tget version(): number {\n\t\treturn this._version;\n\t}\n\n\tget size(): number {\n\t\treturn this._store.size;\n\t}\n\n\thas(key: K): boolean {\n\t\tconst e = this._store.get(key);\n\t\tif (e === undefined) return false;\n\t\tif (this._isExpired(e)) {\n\t\t\tthis._store.delete(key);\n\t\t\tthis._version += 1;\n\t\t\treturn false;\n\t\t}\n\t\tthis._touchLru(key, e);\n\t\treturn true;\n\t}\n\n\tget(key: K): V | undefined {\n\t\tconst e = this._store.get(key);\n\t\tif (e === undefined) return undefined;\n\t\tif (this._isExpired(e)) {\n\t\t\tthis._store.delete(key);\n\t\t\tthis._version += 1;\n\t\t\treturn undefined;\n\t\t}\n\t\tthis._touchLru(key, e);\n\t\treturn e.value;\n\t}\n\n\tset(key: K, value: V, ttl?: number): void {\n\t\tconst expiresAt = this._resolveExpiresAt(ttl);\n\t\t// Delete-then-insert to place key at LRU end.\n\t\tif (this._store.has(key)) this._store.delete(key);\n\t\tthis._store.set(key, { value, expiresAt });\n\t\tthis._evictLruWhileOver();\n\t\tthis._version += 1;\n\t}\n\n\tsetMany(entries: Iterable<readonly [K, V]>, ttl?: number): void {\n\t\t// Pre-validate TTL once (throws before any mutation).\n\t\tconst expiresAt = this._resolveExpiresAt(ttl);\n\t\tlet count = 0;\n\t\ttry {\n\t\t\tfor (const [key, value] of entries) {\n\t\t\t\tif (this._store.has(key)) this._store.delete(key);\n\t\t\t\tthis._store.set(key, { value, expiresAt });\n\t\t\t\tcount += 1;\n\t\t\t}\n\t\t} finally {\n\t\t\t// D3: if the iterable threw mid-iteration, entries committed before\n\t\t\t// the throw must still advance `version` so subscribers see the\n\t\t\t// partial state consistently. \"At most once\" is preserved.\n\t\t\tif (count > 0) {\n\t\t\t\tthis._evictLruWhileOver();\n\t\t\t\tthis._version += 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tdelete(key: K): boolean {\n\t\tconst had = this._store.delete(key);\n\t\tif (had) this._version += 1;\n\t\treturn had;\n\t}\n\n\tdeleteMany(keys: Iterable<K>): number {\n\t\tlet removed = 0;\n\t\ttry {\n\t\t\tfor (const k of keys) {\n\t\t\t\tif (this._store.delete(k)) removed += 1;\n\t\t\t}\n\t\t} finally {\n\t\t\tif (removed > 0) this._version += 1;\n\t\t}\n\t\treturn removed;\n\t}\n\n\tclear(): number {\n\t\tconst n = this._store.size;\n\t\tif (n === 0) return 0;\n\t\tthis._store.clear();\n\t\tthis._version += 1;\n\t\treturn n;\n\t}\n\n\tpruneExpired(): number {\n\t\tconst now = monotonicNs();\n\t\tlet removed = 0;\n\t\tfor (const [k, e] of this._store) {\n\t\t\tif (this._isExpired(e, now)) {\n\t\t\t\tthis._store.delete(k);\n\t\t\t\tremoved += 1;\n\t\t\t}\n\t\t}\n\t\tif (removed > 0) this._version += 1;\n\t\treturn removed;\n\t}\n\n\ttoMap(): ReadonlyMap<K, V> {\n\t\tconst now = monotonicNs();\n\t\tconst out = new Map<K, V>();\n\t\tfor (const [k, e] of this._store) {\n\t\t\tif (!this._isExpired(e, now)) out.set(k, e.value);\n\t\t}\n\t\treturn out;\n\t}\n\n\tprivate _resolveExpiresAt(ttl?: number): number | undefined {\n\t\tconst effectiveTtl = ttl ?? this._defaultTtl;\n\t\tif (effectiveTtl === undefined) return undefined;\n\t\tif (!Number.isFinite(effectiveTtl) || effectiveTtl <= 0) {\n\t\t\tthrow new RangeError(\n\t\t\t\t`MapBackend: ttl must be a positive finite number (got ${effectiveTtl})`,\n\t\t\t);\n\t\t}\n\t\treturn monotonicNs() + effectiveTtl * 1_000_000_000;\n\t}\n\n\tprivate _isExpired(e: MapEntry<V>, now?: number): boolean {\n\t\tif (e.expiresAt === undefined) return false;\n\t\treturn (now ?? monotonicNs()) >= e.expiresAt;\n\t}\n\n\tprivate _touchLru(key: K, entry: MapEntry<V>): void {\n\t\t// Move to LRU end. Does NOT advance `version` — internal optimization.\n\t\tthis._store.delete(key);\n\t\tthis._store.set(key, entry);\n\t}\n\n\tprivate _evictLruWhileOver(): void {\n\t\tif (this._maxSize === undefined) return;\n\t\twhile (this._store.size > this._maxSize) {\n\t\t\tconst first = this._store.keys().next().value as K | undefined;\n\t\t\tif (first === undefined) break;\n\t\t\tthis._store.delete(first);\n\t\t}\n\t}\n}\n\n// ── Reactive wrapper ──────────────────────────────────────────────────────\n\n/**\n * Creates a reactive `Map` with optional per-key TTL and optional LRU max size.\n *\n * @param options - `name`, `maxSize`, `defaultTtl` (seconds), or custom `backend`.\n * @returns `ReactiveMapBundle` — imperative methods (`has`/`get`/`set`/`setMany`/`delete`/\n * `deleteMany`/`clear`/`pruneExpired`), reactive `entries` node, and O(1)-ish `size`.\n *\n * @remarks\n * **TTL:** Expiry is checked on `get`, `has`, `size`, `pruneExpired`, and before each\n * snapshot emission (expired keys are pruned first). Reads that discover expired keys\n * emit a snapshot so subscribers see state consistent with the read's return value.\n * There is no background timer; monotonic-clock expiry is immune to wall-clock changes.\n *\n * **LRU:** Uses native `Map` insertion order — `get` / `has` refreshes position via\n * delete-then-reinsert; under `maxSize` pressure the first key in iteration order is\n * evicted. LRU touching does NOT trigger emission (internal optimization).\n *\n * **Backend:** The default {@link NativeMapBackend} owns LRU/TTL. For persistent /\n * HAMT / shared-state semantics plug in a custom {@link MapBackend}. `maxSize` and\n * `defaultTtl` on the options object are only applied to the default backend — if\n * you supply `backend`, configure those on your backend directly.\n *\n * @example\n * ```ts\n * import { reactiveMap } from \"@graphrefly/graphrefly-ts\";\n *\n * const m = reactiveMap<string, number>({ name: \"cache\", maxSize: 100, defaultTtl: 60 });\n * m.set(\"x\", 1);\n * m.setMany([[\"y\", 2], [\"z\", 3]]);\n * m.entries.subscribe((msgs) => { console.log(msgs); });\n * ```\n *\n * @category extra\n */\nexport function reactiveMap<K, V>(options: ReactiveMapOptions<K, V> = {}): ReactiveMapBundle<K, V> {\n\tconst { name, maxSize, defaultTtl, versioning, backend: userBackend } = options;\n\tconst backend: MapBackend<K, V> =\n\t\tuserBackend ?? new NativeMapBackend<K, V>({ maxSize, defaultTtl });\n\n\tconst n = state<ReadonlyMap<K, V>>(backend.toMap(), {\n\t\tname,\n\t\tdescribeKind: \"state\",\n\t\tequals: (a, b) => a === b,\n\t\t...(versioning != null ? { versioning } : {}),\n\t});\n\n\tfunction pushSnapshot(): void {\n\t\tconst map = backend.toMap();\n\t\tbatch(() => {\n\t\t\tn.down([[DIRTY]]);\n\t\t\tn.down([[DATA, map]]);\n\t\t});\n\t}\n\n\t/**\n\t * Defense-in-depth emission guard: compares `version` before/after `op` and\n\t * emits a snapshot if advanced. Uses `try/finally` so partial-mutation state\n\t * from a custom non-atomic backend is still surfaced to subscribers if the\n\t * op throws mid-way (native backends are atomic by contract and won't trip\n\t * this path).\n\t */\n\tfunction wrapMutation<T>(op: () => T): T {\n\t\tconst prev = backend.version;\n\t\ttry {\n\t\t\treturn op();\n\t\t} finally {\n\t\t\tif (backend.version !== prev) pushSnapshot();\n\t\t}\n\t}\n\n\treturn {\n\t\tentries: n,\n\n\t\thas(key: K): boolean {\n\t\t\treturn wrapMutation(() => backend.has(key));\n\t\t},\n\n\t\tget(key: K): V | undefined {\n\t\t\treturn wrapMutation(() => backend.get(key));\n\t\t},\n\n\t\tset(key: K, value: V, opts?: { ttl?: number }): void {\n\t\t\twrapMutation(() => backend.set(key, value, opts?.ttl));\n\t\t},\n\n\t\tsetMany(entries: Iterable<readonly [K, V]>, opts?: { ttl?: number }): void {\n\t\t\twrapMutation(() => backend.setMany(entries, opts?.ttl));\n\t\t},\n\n\t\tdelete(key: K): void {\n\t\t\twrapMutation(() => backend.delete(key));\n\t\t},\n\n\t\tdeleteMany(keys: Iterable<K>): void {\n\t\t\twrapMutation(() => backend.deleteMany(keys));\n\t\t},\n\n\t\tclear(): void {\n\t\t\twrapMutation(() => backend.clear());\n\t\t},\n\n\t\tpruneExpired(): void {\n\t\t\twrapMutation(() => backend.pruneExpired());\n\t\t},\n\n\t\t/**\n\t\t * Current raw entry count — O(1), **pure read**. May include\n\t\t * not-yet-pruned expired entries on TTL maps until the next mutation\n\t\t * or an explicit `pruneExpired()` / `has` / `get` triggers a prune.\n\t\t *\n\t\t * Previously this getter ran `pruneExpired()` inline and emitted a\n\t\t * snapshot as a side effect — that violated spec §5.8 \"no\n\t\t * side-effectful reads\" and created a re-entrancy hazard when a\n\t\t * subscriber to `entries` read `.size` from its own callback. D2(a).\n\t\t *\n\t\t * For a live count that excludes expired entries, call\n\t\t * `bundle.pruneExpired()` first.\n\t\t */\n\t\tget size(): number {\n\t\t\treturn backend.size;\n\t\t},\n\n\t\tdispose(): void {\n\t\t\t// D6(a): no internal keepalives yet — no-op for API parity with\n\t\t\t// reactive-index / reactive-list / reactive-log. If a future\n\t\t\t// refactor adds a keepalive (e.g. on `entries`), wire its\n\t\t\t// disposer here.\n\t\t},\n\t};\n}\n","/**\n * Reactive positional list (roadmap §3.2) — emits `readonly T[]` snapshots directly.\n *\n * Internal version counter drives efficient equality without leaking `Versioned`\n * into the public API (spec §5.12).\n *\n * **Wave 4 refactor (2026-04-15):** Introduces the `ListBackend<T>` pluggable-backend\n * interface. The default `NativeListBackend` uses a mutable array with a monotonic\n * `version` counter. No `maxSize` cap — bounded append-heavy workloads should use\n * `reactiveLog` (head-trim under cap is unambiguous for append-only; insert-anywhere\n * with a cap is not).\n */\nimport { batch } from \"../core/batch.js\";\nimport { DATA, DIRTY } from \"../core/messages.js\";\nimport type { Node } from \"../core/node.js\";\nimport { state } from \"../core/sugar.js\";\nimport type { VersioningLevel } from \"../core/versioning.js\";\n\nexport type ReactiveListOptions<T> = {\n\tname?: string;\n\t/**\n\t * Storage backend. Defaults to `NativeListBackend` (flat mutable array).\n\t * Users can plug in persistent / RRB-tree backends via the {@link ListBackend} interface.\n\t */\n\tbackend?: ListBackend<T>;\n\t/**\n\t * Optional versioning level for the underlying `items` state node. Set at\n\t * construction time; cannot be changed later. Pass `0` for V0 identity +\n\t * monotonic version counter, or `1` for V1 + content-addressed cid.\n\t */\n\tversioning?: VersioningLevel;\n};\n\nexport type ReactiveListBundle<T> = {\n\t/** Emits `readonly T[]` on each structural change (two-phase). */\n\treadonly items: Node<readonly T[]>;\n\t/** Current entry count (O(1)). */\n\treadonly size: number;\n\t/** Positional access (O(1)); supports negative indices (Python-style). Returns `undefined` on out-of-range. */\n\tat: (index: number) => T | undefined;\n\tappend: (value: T) => void;\n\t/** Push all values, emit one snapshot. No-op if `values` is empty. */\n\tappendMany: (values: readonly T[]) => void;\n\t/** Insert a value at `index`. Throws `RangeError` on out-of-range. */\n\tinsert: (index: number, value: T) => void;\n\t/** Insert all values at `index` as one bulk op; emits one snapshot. No-op if `values` is empty. */\n\tinsertMany: (index: number, values: readonly T[]) => void;\n\t/** Remove and return the value at `index` (default: last). Negative indices Python-style. Throws on empty / out-of-range. */\n\tpop: (index?: number) => T;\n\tclear: () => void;\n\t/**\n\t * Releases any internal keepalive subscriptions so the bundle can be\n\t * GC'd. `reactiveList` currently holds none (no internal derived nodes),\n\t * so `dispose()` is a no-op today — exposed for API parity with\n\t * `reactiveIndex.dispose` / `reactiveMap.dispose` / `reactiveLog.dispose`.\n\t * Idempotent. D6(a).\n\t */\n\tdispose: () => void;\n};\n\n// ── Backend interface ─────────────────────────────────────────────────────\n\n/**\n * Storage contract for {@link reactiveList}. Implementations own the mutable state\n * and expose a monotonic `version` counter that increments on every structural change.\n *\n * The reactive layer reads `version` before and after each backend call; when it\n * advances, a snapshot is emitted.\n *\n * @remarks Post-1.0 op-log changesets will extend this interface with a\n * `changesSince(version: number): Iterable<Change>` method. Current consumers\n * should treat all methods here as stable.\n *\n * @category extra\n */\nexport interface ListBackend<T> {\n\t/** Monotonic mutation counter; increments on every structural change. */\n\treadonly version: number;\n\t/** Number of items currently stored. */\n\treadonly size: number;\n\t/** Positional access; `undefined` on out-of-range. */\n\tat(index: number): T | undefined;\n\t/** Append a single value. Advances `version`. */\n\tappend(value: T): void;\n\t/** Append a batch. Advances `version` once. No-op if empty. */\n\tappendMany(values: readonly T[]): void;\n\t/** Insert at index; throws `RangeError` on out-of-range `0 <= index <= size`. Advances `version`. */\n\tinsert(index: number, value: T): void;\n\t/** Bulk insert at index; throws on out-of-range. Advances `version` once. No-op if `values` empty. */\n\tinsertMany(index: number, values: readonly T[]): void;\n\t/** Remove and return value at index; throws on empty / out-of-range. Advances `version`. */\n\tpop(index: number): T;\n\t/** Clear all entries. Returns count removed. Advances `version` only if non-zero. */\n\tclear(): number;\n\t/** Full snapshot as a fresh array. */\n\ttoArray(): readonly T[];\n}\n\n/**\n * Default mutable-array backend.\n *\n * **Complexity:**\n * - `at`, `size`: O(1)\n * - `append`: O(1) amortized\n * - `appendMany(values)`, `insertMany(index, values)`: O(n + k) where k = values.length\n * - `insert`, `pop` (middle): O(n) due to splice\n * - `pop` (last): O(1)\n * - `clear`: O(1)\n * - `toArray`: O(n)\n *\n * @category extra\n */\nexport class NativeListBackend<T> implements ListBackend<T> {\n\tprivate _version = 0;\n\tprivate readonly _buf: T[];\n\n\tconstructor(initial?: readonly T[]) {\n\t\tthis._buf = initial ? [...initial] : [];\n\t}\n\n\tget version(): number {\n\t\treturn this._version;\n\t}\n\n\tget size(): number {\n\t\treturn this._buf.length;\n\t}\n\n\tat(index: number): T | undefined {\n\t\tif (!Number.isInteger(index)) return undefined;\n\t\tconst i = index >= 0 ? index : this._buf.length + index;\n\t\tif (i < 0 || i >= this._buf.length) return undefined;\n\t\treturn this._buf[i];\n\t}\n\n\tappend(value: T): void {\n\t\tthis._buf.push(value);\n\t\tthis._version += 1;\n\t}\n\n\tappendMany(values: readonly T[]): void {\n\t\tif (values.length === 0) return;\n\t\t// Extra: avoid `this._buf.push(...values)` — spread-push has an\n\t\t// engine-dependent stack-argument limit (typically ~100k–500k), so\n\t\t// very large bulk inserts can throw \"Maximum call stack size exceeded\".\n\t\t// Indexed write is O(n) and has no spread limit.\n\t\tconst oldLen = this._buf.length;\n\t\tthis._buf.length = oldLen + values.length;\n\t\tfor (let i = 0; i < values.length; i++) {\n\t\t\tthis._buf[oldLen + i] = values[i] as T;\n\t\t}\n\t\tthis._version += 1;\n\t}\n\n\tinsert(index: number, value: T): void {\n\t\tif (!Number.isInteger(index) || index < 0 || index > this._buf.length) {\n\t\t\tthrow new RangeError(`insert: index ${index} out of range [0, ${this._buf.length}]`);\n\t\t}\n\t\tthis._buf.splice(index, 0, value);\n\t\tthis._version += 1;\n\t}\n\n\tinsertMany(index: number, values: readonly T[]): void {\n\t\tif (!Number.isInteger(index) || index < 0 || index > this._buf.length) {\n\t\t\tthrow new RangeError(`insertMany: index ${index} out of range [0, ${this._buf.length}]`);\n\t\t}\n\t\tif (values.length === 0) return;\n\t\tthis._buf.splice(index, 0, ...values);\n\t\tthis._version += 1;\n\t}\n\n\tpop(index: number): T {\n\t\tif (this._buf.length === 0) {\n\t\t\tthrow new RangeError(\"pop from empty list\");\n\t\t}\n\t\tif (!Number.isInteger(index)) {\n\t\t\tthrow new RangeError(`pop: index ${index} must be an integer`);\n\t\t}\n\t\tconst i = index >= 0 ? index : this._buf.length + index;\n\t\tif (i < 0 || i >= this._buf.length) {\n\t\t\tthrow new RangeError(`pop: index ${index} out of range`);\n\t\t}\n\t\tconst [v] = this._buf.splice(i, 1);\n\t\tthis._version += 1;\n\t\treturn v as T;\n\t}\n\n\tclear(): number {\n\t\tconst n = this._buf.length;\n\t\tif (n === 0) return 0;\n\t\tthis._buf.length = 0;\n\t\tthis._version += 1;\n\t\treturn n;\n\t}\n\n\ttoArray(): readonly T[] {\n\t\treturn [...this._buf];\n\t}\n}\n\n// ── Reactive wrapper ──────────────────────────────────────────────────────\n\n/**\n * Creates a reactive list with immutable array snapshots.\n *\n * @param initial - Optional initial items (copied).\n * @param options - Optional `name` for `describe()` / debugging, or pluggable `backend`.\n * @returns Bundle with `items` (state node), `size` / `at`, `append` / `appendMany` / `insert` /\n * `insertMany` / `pop` / `clear`.\n *\n * @remarks\n * **No `maxSize`:** insert/pop-anywhere semantics make eviction-under-cap ambiguous.\n * For bounded append-heavy workloads use `reactiveLog` (head-trim is well-defined for\n * append-only).\n *\n * **Backend:** Default {@link NativeListBackend}. For persistent / RRB-tree semantics\n * supply a custom {@link ListBackend}. If you provide a `backend`, `initial` is ignored\n * — seed the backend directly.\n *\n * @example\n * ```ts\n * import { reactiveList } from \"@graphrefly/graphrefly-ts\";\n *\n * const list = reactiveList<string>([\"a\"], { name: \"queue\" });\n * list.append(\"b\");\n * list.insertMany(1, [\"x\", \"y\"]);\n * ```\n *\n * @category extra\n */\nexport function reactiveList<T>(\n\tinitial?: readonly T[],\n\toptions: ReactiveListOptions<T> = {},\n): ReactiveListBundle<T> {\n\tconst { name, versioning, backend: userBackend } = options;\n\tconst backend: ListBackend<T> = userBackend ?? new NativeListBackend<T>(initial);\n\n\tconst items = state<readonly T[]>(backend.toArray(), {\n\t\tname,\n\t\tdescribeKind: \"state\",\n\t\tequals: (a, b) => a === b,\n\t\t...(versioning != null ? { versioning } : {}),\n\t});\n\n\tfunction pushSnapshot(): void {\n\t\tconst snapshot = backend.toArray();\n\t\tbatch(() => {\n\t\t\titems.down([[DIRTY]]);\n\t\t\titems.down([[DATA, snapshot]]);\n\t\t});\n\t}\n\n\t/**\n\t * D4(a): try/finally defense-in-depth — if a custom backend op throws\n\t * mid-mutation, surface the partial state via pushSnapshot so subscribers\n\t * don't see a stale cache. Native ops are atomic by contract; this only\n\t * matters for user-supplied backends. Mirrors the pattern in reactive-map,\n\t * reactive-index, and reactive-log.\n\t */\n\tfunction wrapMutation<R>(op: () => R): R {\n\t\tconst prev = backend.version;\n\t\ttry {\n\t\t\treturn op();\n\t\t} finally {\n\t\t\tif (backend.version !== prev) pushSnapshot();\n\t\t}\n\t}\n\n\treturn {\n\t\titems,\n\n\t\tget size(): number {\n\t\t\treturn backend.size;\n\t\t},\n\n\t\tat(index: number): T | undefined {\n\t\t\treturn backend.at(index);\n\t\t},\n\n\t\tappend(value: T): void {\n\t\t\twrapMutation(() => backend.append(value));\n\t\t},\n\n\t\tappendMany(values: readonly T[]): void {\n\t\t\twrapMutation(() => backend.appendMany(values));\n\t\t},\n\n\t\tinsert(index: number, value: T): void {\n\t\t\twrapMutation(() => backend.insert(index, value));\n\t\t},\n\n\t\tinsertMany(index: number, values: readonly T[]): void {\n\t\t\twrapMutation(() => backend.insertMany(index, values));\n\t\t},\n\n\t\tpop(index = -1): T {\n\t\t\treturn wrapMutation(() => backend.pop(index));\n\t\t},\n\n\t\tclear(): void {\n\t\t\twrapMutation(() => backend.clear());\n\t\t},\n\n\t\tdispose(): void {\n\t\t\t// D6(a): no internal keepalives — no-op for API parity. If a future\n\t\t\t// refactor adds a keepalive, wire its disposer here.\n\t\t},\n\t};\n}\n"],"mappings":"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","names":[]}
|