@yoltra/core 0.4.0 → 0.5.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 +57 -0
- package/dist/types/eventBus/EventBus.d.ts +1 -1
- package/dist/types/eventBus/index.d.ts +2 -2
- package/dist/types/index.d.ts +16 -16
- package/dist/types/persistence/adapters.d.ts +1 -1
- package/dist/types/reducer/Reducer.d.ts +1 -1
- package/dist/types/store/Store.d.ts +6 -1
- package/dist/types/types.d.ts +35 -3
- package/dist/types/utils/detectChangedProps.d.ts +6 -0
- package/dist/types/utils/immutability.d.ts +1 -1
- package/dist/types/utils/index.d.ts +2 -2
- package/dist/yoltra.cjs +11 -0
- package/dist/yoltra.cjs.map +1 -0
- package/dist/{yoltra.esm.js → yoltra.mjs} +37 -20
- package/dist/yoltra.mjs.map +1 -0
- package/dist/yoltra.umd.js +2 -2
- package/dist/yoltra.umd.js.map +1 -0
- package/package.json +13 -13
- package/dist/yoltra.cjs.js +0 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yoltra.cjs","sources":["../src/eventBus/EventBus.ts","../src/eventBus/LooseEventBus.ts","../src/reducer/Reducer.ts","../src/utils/detectChangedProps.ts","../src/utils/immutability.ts","../src/store/Store.ts","../src/types.ts","../src/entity/entityAdapter.ts","../src/serialize/codec.ts","../src/persistence/persist.ts","../src/persistence/adapters.ts"],"sourcesContent":["/**\n * @module @yoltra/core\n */\n\nimport type { Event, EventMapBase } from \"../types\";\n\n/**\n * Minimal, synchronous pub/sub event bus keyed by **channel** and **type**.\n *\n * @typeParam EM - Event map shape:\n * ```ts\n * type EventMapBase = Record<string, Record<string, unknown>>;\n * // Example:\n * type EM = {\n * ui: { toggle: boolean };\n * data: { loaded: { items: string[] } };\n * };\n * ```\n *\n * @remarks\n * - Handlers are stored per `(channel, type)` and invoked **synchronously** in subscription order.\n * - Exceptions thrown by a handler are **caught and logged**, and do **not** stop other handlers.\n * - Intended for in-memory, single-process usage (no cross-tab/process broadcasting).\n *\n * @example\n * ```ts\n * type EM = {\n * ui: { toggle: boolean };\n * data: { loaded: { items: string[] } };\n * };\n *\n * const bus = new EventBus<EM>();\n *\n * // Subscribe\n * const off = bus.on('ui', 'toggle', (on) => {\n * console.log('UI toggled:', on);\n * });\n *\n * // Emit\n * bus.emit('ui', 'toggle', true); // logs: \"UI toggled: true\"\n *\n * // Unsubscribe\n * off();\n * ```\n *\n * @public\n */\nexport class EventBus<EM extends EventMapBase> {\n /**\n * Internal registry: `channel → type → Set<handler>`.\n * @internal\n */\n private handlers: Map<string, Map<string, Set<(payload: any, event?: any) => void>>> = new Map();\n\n /**\n * Subscribes a handler to an exact `(channel, type)`.\n *\n * @typeParam C - Channel key (must be a string key of `EM`).\n * @typeParam T - Type key within channel `C` (must be a string key of `EM[C]`).\n * @param channel - Channel name to subscribe to.\n * @param type - Event type within the channel.\n * @param handler - Function invoked with the payload type `EM[C][T]`. It optionally\n * receives the **source event** as a second argument when the emitter supplies one, so\n * subscribers can read the true `id` (and any `meta`) instead of reconstructing an event\n * from the payload alone. Handlers that declare only `payload` remain valid.\n * @returns An **unsubscribe** function that removes this handler.\n *\n * @example\n * ```ts\n * const off = bus.on('data', 'loaded', ({ items }) => {\n * console.log('Loaded', items.length, 'items');\n * });\n *\n * // Later, stop listening:\n * off();\n * ```\n *\n * @example Reading the source event\n * ```ts\n * bus.on('data', 'loaded', (payload, event) => {\n * console.log('event id:', event?.id);\n * });\n * ```\n *\n * @public\n */\n public on<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n handler: (payload: EM[C][T], event?: Event<EM, C, T>) => void,\n ): () => void {\n let byType = this.handlers.get(channel);\n if (!byType) {\n byType = new Map();\n this.handlers.set(channel, byType);\n }\n\n let set = byType.get(type);\n if (!set) {\n set = new Set();\n byType.set(type, set);\n }\n\n set.add(handler as any);\n\n return () => this.off(channel, type, handler);\n }\n\n /**\n * Removes a specific handler previously added with {@link EventBus.on | `on`}.\n *\n * @typeParam C - Channel key (string key of `EM`).\n * @typeParam T - Type key within channel `C` (string key of `EM[C]`).\n * @param channel - Channel name of the subscription to remove.\n * @param type - Event type of the subscription to remove.\n * @param handler - The same handler reference that was passed to `on`.\n *\n * @example\n * ```ts\n * const h = (n: number) => console.log('inc', n);\n * bus.on('math', 'inc', h);\n *\n * // Explicitly remove this handler:\n * bus.off('math', 'inc', h);\n * ```\n *\n * @public\n */\n public off<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n handler: (payload: EM[C][T], event?: Event<EM, C, T>) => void,\n ): void {\n const byType = this.handlers.get(channel);\n if (!byType) return;\n\n const set = byType.get(type);\n if (!set) return;\n\n set.delete(handler as any);\n\n if (set.size === 0) byType.delete(type);\n if (byType.size === 0) this.handlers.delete(channel);\n }\n\n /**\n * Emits an event to all subscribers of the exact `(channel, type)`.\n *\n * Handlers are invoked **synchronously**. Any exception thrown by a handler is\n * caught and logged, and other handlers still run.\n *\n * @typeParam C - Channel key (string key of `EM`).\n * @typeParam T - Type key within channel `C` (string key of `EM[C]`).\n * @param channel - Channel name to emit on.\n * @param type - Event type to emit.\n * @param payload - Payload matching `EM[C][T]`.\n * @param event - Optional **source event**, forwarded to handlers as a second argument.\n * Supply it whenever the caller already holds the real event so subscribers observe its\n * true `id` rather than reconstructing one; omitting it keeps the original behaviour.\n *\n * @example\n * ```ts\n * bus.emit('ui', 'toggle', false);\n * ```\n *\n * @public\n */\n public emit<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n payload: EM[C][T],\n event?: Event<EM, C, T>,\n ): void {\n const byType = this.handlers.get(channel);\n if (!byType) return;\n\n const set = byType.get(type);\n if (!set || set.size === 0) return;\n\n for (const h of [...set]) {\n try {\n (h as any)(payload, event);\n } catch (err) {\n console.error(\"EventBus handler error:\", err);\n }\n }\n }\n\n /**\n * Clears **all** listeners across all channels/types.\n *\n * Useful for tests or during HMR teardown to avoid duplicate handlers.\n *\n * @example\n * ```ts\n * // In a test teardown:\n * afterEach(() => bus.clear());\n * ```\n *\n * @public\n */\n public clear(): void {\n this.handlers.clear();\n }\n}","/**\n * @module @yoltra/core\n */\n\n/**\n * Flexible, synchronous pub/sub bus that supports **exact** and **pattern** event subscriptions.\n *\n * @typeParam C - Channel name type (defaults to `string`).\n * @typeParam T - Event type name type (defaults to `string`). Types are treated as **dot-separated paths** (e.g. `\"a.b.c\"`).\n * @typeParam P - Payload type for all events (defaults to `any`).\n *\n * @remarks\n * - **Exact handlers** subscribe to a specific `(channel, type)` pair. Type keys are **normalized** by stripping a single leading dot (`\".foo\"` → `\"foo\"`).\n * - **Pattern handlers** subscribe using wildcards over dot-separated segments:\n * - `*` matches **one** segment.\n * - `**` matches **zero or more** segments (greedy).\n * - On {@link LooseEventBus.emit | `emit`}, exact handlers fire first, then any matching pattern handlers.\n * - Handlers are **de-duplicated**: if the same function is both exact and pattern-registered, it is called **once**.\n * - Handler invocation is **synchronous**. Exceptions are caught and logged; remaining handlers still run.\n *\n * @example\n * ```ts\n * type C = 'ui' | 'data';\n * type T = string;\n * type P = unknown;\n *\n * const bus = new LooseEventBus<C, T, P>();\n *\n * // Exact\n * const offA = bus.on('ui', 'panel.open', () => console.log('panel opened'));\n *\n * // Patterns\n * const offB = bus.on('ui', 'panel.*', () => console.log('any single sub-event under panel'));\n * const offC = bus.on('ui', 'panel.**', () => console.log('any depth under panel'));\n *\n * bus.emit('ui', 'panel.open', null);\n * // => exact fires, then 'panel.*', then 'panel.**'\n *\n * offA(); offB(); offC(); // unsubscribe\n * ```\n *\n * @public\n */\n/**\n * One registered pattern, kept pre-split.\n * @internal\n */\ninterface PatternEntry {\n readonly pattern: string;\n readonly segments: readonly string[];\n}\n\n/**\n * The patterns on one channel, arranged by what a subject's first segment can match.\n * @internal\n */\ninterface PatternIndex {\n /** Keyed by a literal first segment. */\n readonly byHead: Map<string, PatternEntry[]>;\n /** Patterns beginning with `*` or `**`, which every subject has to test. */\n readonly anyHead: PatternEntry[];\n}\n\nexport class LooseEventBus<C extends string = string, T extends string = string, P = any> {\n /**\n * Exact handlers: `channel → type → [handlers]`.\n * @internal\n */\n private handlers = new Map<C, Map<T, Array<(p: P) => void>>>();\n\n /**\n * Pattern handlers with `*` and `**`: `channel → pattern(string) → [handlers]`.\n * @internal\n */\n private patternHandlers = new Map<C, Map<string, Array<(p: P) => void>>>();\n\n /**\n * Patterns bucketed by their first segment, so an emit tests only what could match.\n *\n * @remarks\n * Delivery used to walk every pattern registered on the channel and run the full segment\n * matcher against each. That is linear in the number of patterns rather than in the number\n * that match, and it re-split both the pattern and the subject on every test — for a thousand\n * patterns, two thousand string splits to deliver one event.\n *\n * A subject's first segment can only be matched by a pattern whose first segment is that same\n * literal, or is `*` or `**`. Bucketing on that turns the common shape — distinct event\n * families like `panel.*` and `order.**` — from a scan of everything into a map lookup plus\n * the handful that begin with a wildcard.\n *\n * It buys nothing for a channel where every pattern starts with `**`, since all of those must\n * still be tested. That is the honest worst case, and it is unchanged rather than worsened.\n */\n private patternIndex = new Map<C, PatternIndex>();\n\n /**\n * Subscribes a handler to either an **exact** type or a **pattern**.\n *\n * @param channel - Channel to subscribe on.\n * @param type - Exact event type (e.g. `\"a.b\"`) or pattern (contains `*`/`**`).\n * @param handler - Function invoked with the emitted payload.\n * @returns An **unsubscribe** function that removes this handler.\n *\n * @remarks\n * - Exact subscriptions are stored under a **normalized** key (leading `.` removed).\n * - Pattern subscriptions are stored **as provided**; matching normalizes the subject.\n *\n * @example Exact subscription\n * ```ts\n * const off = bus.on('data', 'items.loaded', ({ count }) => {\n * console.log('Loaded', count);\n * });\n * // Later\n * off();\n * ```\n *\n * @example Pattern subscription\n * ```ts\n * // Match any single sub-event: 'panel.open', 'panel.close', etc.\n * const offStar = bus.on('ui', 'panel.*', () => {});\n *\n * // Match any depth: 'panel.open', 'panel.items.add', 'panel', etc.\n * const offGlob = bus.on('ui', 'panel.**', () => {});\n * ```\n *\n * @public\n */\n on(channel: C, type: T, handler: (payload: P) => void): () => void {\n const typeStr = String(type);\n if (!this.isPattern(typeStr)) {\n // Exact subscription with normalized key (strip leading dot)\n const key = this.normalizeTypeKey(typeStr) as T;\n\n if (!this.handlers.has(channel)) this.handlers.set(channel, new Map());\n const map = this.handlers.get(channel)!;\n\n if (!map.has(key)) map.set(key, []);\n map.get(key)!.push(handler);\n\n // capture normalized key for off()\n return () => this.offExactNormalized(channel, key, handler);\n } else {\n // Pattern subscription (stored as provided; matcher handles normalization)\n const pattern = typeStr;\n\n if (!this.patternHandlers.has(channel)) this.patternHandlers.set(channel, new Map());\n const pmap = this.patternHandlers.get(channel)!;\n\n if (!pmap.has(pattern)) {\n pmap.set(pattern, []);\n // Split once here rather than on every emit, and file it under the segment that decides\n // whether it is even a candidate.\n this.indexPattern(channel, pattern);\n }\n pmap.get(pattern)!.push(handler);\n\n return () => this.offPattern(channel, pattern, handler);\n }\n }\n\n /**\n * Unsubscribes an **exact** handler. The `type` key is normalized internally,\n * so callers can pass `\"foo\"` or `\".foo\"` interchangeably.\n *\n * @param channel - Channel name.\n * @param type - Exact event type key to remove (normalization applied).\n * @param handler - The same handler reference previously passed to {@link LooseEventBus.on | `on`}.\n *\n * @example\n * ```ts\n * const h = () => {};\n * bus.on('ui', 'panel.open', h);\n * // Remove it (with or without leading dot)\n * bus.off('ui', '.panel.open', h);\n * ```\n *\n * @public\n */\n off(channel: C, type: T, handler: (payload: P) => void): void {\n const key = this.normalizeTypeKey(String(type)) as T;\n this.offExactNormalized(channel, key, handler);\n }\n\n /**\n * Internal exact unsubscription using an already **normalized** type key.\n *\n * @param channel - Channel name.\n * @param normalizedType - Event type key with leading dot removed.\n * @param handler - Handler to remove.\n * @internal\n */\n private offExactNormalized(\n channel: C,\n normalizedType: T,\n handler: (payload: P) => void,\n ): void {\n const cMap = this.handlers.get(channel);\n if (!cMap) return;\n const list = cMap.get(normalizedType);\n if (!list) return;\n\n const i = list.indexOf(handler);\n if (i !== -1) list.splice(i, 1);\n\n // cleanup empties\n if (list.length === 0) cMap.delete(normalizedType);\n if (cMap.size === 0) this.handlers.delete(channel);\n }\n\n /**\n * Internal removal for a **pattern** subscription. No-ops if missing.\n *\n * @param channel - Channel name.\n * @param pattern - Pattern string as originally subscribed.\n * @param handler - Handler to remove.\n * @internal\n */\n private offPattern(channel: C, pattern: string, handler: (payload: P) => void): void {\n const pMap = this.patternHandlers.get(channel);\n if (!pMap) return;\n\n const list = pMap.get(pattern);\n if (!list) return;\n\n const i = list.indexOf(handler);\n if (i !== -1) list.splice(i, 1);\n\n // cleanup empties\n if (list.length === 0) {\n pMap.delete(pattern);\n this.unindexPattern(channel, pattern);\n }\n if (pMap.size === 0) {\n this.patternHandlers.delete(channel);\n this.patternIndex.delete(channel);\n }\n }\n\n /**\n * Emits an event to all exact subscribers first, then to **matching pattern** subscribers.\n * Duplicate handler references are called **once** (de-duped).\n *\n * @param channel - Channel to emit on.\n * @param type - Event type (subject). A leading dot is ignored for matching.\n * @param payload - Payload delivered to handlers.\n *\n * @example\n * ```ts\n * // Suppose:\n * // - on('ui', 'panel.open', h)\n * // - on('ui', 'panel.*', h) // same handler ref!\n * // - on('ui', 'panel.**', other)\n * bus.emit('ui', 'panel.open', { id: 1 });\n * // => 'h' runs once (de-duped), then 'other'\n * ```\n *\n * @public\n */\n emit(channel: C, type: T, payload: P): void {\n const typeStr = String(type);\n const normalizedType = this.normalizeTypeKey(typeStr) as T;\n\n // Exact delivery (normalized)\n const exactList = this.handlers.get(channel)?.get(normalizedType) ?? [];\n\n // Pattern delivery (normalize subject before matching)\n const patternLists = this.matchingPatternHandlers(channel, typeStr);\n\n const called = new Set<(p: P) => void>();\n const deliver = (arr: Array<(p: P) => void>) => {\n for (const h of [...arr]) {\n if (called.has(h)) continue;\n\n called.add(h);\n\n try {\n h(payload);\n } catch (exc) {\n console.error(exc);\n continue;\n }\n }\n };\n\n deliver(exactList);\n for (const list of patternLists) deliver(list);\n }\n\n /**\n * Emits a payload that is only built if somebody is listening.\n *\n * @param channel - Channel to emit on.\n * @param type - Concrete event type.\n * @param make - Builds the payload. Called at most once, and only when a handler matched.\n *\n * @remarks\n * Same matching as {@link LooseEventBus.emit}; the difference is *when* the payload exists.\n * The store's change notification carries the old and new value at a path, and reading those\n * means walking the state tree twice per path. Doing that eagerly meant a slice nobody had\n * subscribed to paid the full cost of describing changes to an audience of nobody — the\n * matching work was already being done to discover there were no handlers.\n *\n * @public\n */\n emitWith(channel: C, type: T, make: () => P): void {\n const typeStr = String(type);\n const normalizedType = this.normalizeTypeKey(typeStr) as T;\n\n const exactList = this.handlers.get(channel)?.get(normalizedType) ?? [];\n\n const patternLists = this.matchingPatternHandlers(channel, typeStr);\n\n if (exactList.length === 0 && patternLists.length === 0) return;\n\n // Exactly one construction, shared by every handler — the same guarantee `emit` gives.\n const payload = make();\n\n const called = new Set<(p: P) => void>();\n const deliver = (arr: Array<(p: P) => void>) => {\n for (const h of [...arr]) {\n if (called.has(h)) continue;\n called.add(h);\n try {\n h(payload);\n } catch (exc) {\n console.error(exc);\n continue;\n }\n }\n };\n\n deliver(exactList);\n for (const list of patternLists) deliver(list);\n }\n\n /**\n * Determines if a string is a **pattern** (contains `*`).\n * @param s - Event type or pattern string.\n * @returns `true` if it contains at least one `*`, else `false`.\n * @internal\n */\n private isPattern(s: string): boolean {\n return s.includes(\"*\");\n }\n\n /**\n * Normalizes event type keys for exact matching by stripping a **single** leading dot.\n *\n * @param s - Event type key.\n * @returns Normalized key without a leading dot.\n * @example\n * ```ts\n * normalizeTypeKey('.a.b') // 'a.b'\n * normalizeTypeKey('a.b') // 'a.b'\n * ```\n * @internal\n */\n private normalizeTypeKey(s: string): string {\n return s.replace(/^\\./, \"\");\n }\n\n /**\n * Splits a path into dot-separated segments after normalization and removes empties.\n * @param p - Event type or pattern string.\n * @internal\n */\n private splitPath(p: string): string[] {\n return this.normalizeTypeKey(p).split(\".\").filter(Boolean);\n }\n\n /**\n * Files a pattern under the first segment that could select it.\n * @internal\n */\n private indexPattern(channel: C, pattern: string): void {\n let index = this.patternIndex.get(channel);\n if (index === undefined) {\n index = { byHead: new Map(), anyHead: [] };\n this.patternIndex.set(channel, index);\n }\n const segments = this.splitPath(pattern);\n const entry: PatternEntry = { pattern, segments };\n const head = segments[0];\n // A pattern with no segments at all, or one starting with a wildcard, cannot be narrowed by\n // the subject's first segment — so it goes in the list every emit walks.\n if (head === undefined || head === \"*\" || head === \"**\") {\n index.anyHead.push(entry);\n return;\n }\n const bucket = index.byHead.get(head);\n if (bucket === undefined) index.byHead.set(head, [entry]);\n else bucket.push(entry);\n }\n\n /**\n * Removes a pattern from the index. Paired with {@link LooseEventBus.offPattern}.\n * @internal\n */\n private unindexPattern(channel: C, pattern: string): void {\n const index = this.patternIndex.get(channel);\n if (index === undefined) return;\n const head = this.splitPath(pattern)[0];\n const bucket =\n head === undefined || head === \"*\" || head === \"**\"\n ? index.anyHead\n : index.byHead.get(head);\n if (bucket === undefined) return;\n const at = bucket.findIndex((e) => e.pattern === pattern);\n if (at !== -1) bucket.splice(at, 1);\n if (bucket.length === 0 && bucket !== index.anyHead && head !== undefined) {\n index.byHead.delete(head);\n }\n }\n\n /**\n * The handler lists of every pattern matching this subject.\n *\n * @remarks\n * Shared by `emit` and `emitWith` so the two cannot drift on what \"matching\" means — which\n * they could, being two copies of the same walk before.\n *\n * The subject is split once here rather than once per pattern tested.\n *\n * @internal\n */\n private matchingPatternHandlers(channel: C, typeStr: string): Array<Array<(p: P) => void>> {\n const patternMap = this.patternHandlers.get(channel);\n const index = this.patternIndex.get(channel);\n if (patternMap === undefined || patternMap.size === 0 || index === undefined) return [];\n\n const subject = this.splitPath(typeStr);\n const lists: Array<Array<(p: P) => void>> = [];\n\n const test = (entries: readonly PatternEntry[]): void => {\n for (const entry of entries) {\n if (!this.matchSegments(entry.segments, subject)) continue;\n const handlers = patternMap.get(entry.pattern);\n if (handlers !== undefined) lists.push(handlers);\n }\n };\n\n const head = subject[0];\n if (head !== undefined) {\n const bucket = index.byHead.get(head);\n if (bucket !== undefined) test(bucket);\n }\n test(index.anyHead);\n\n return lists;\n }\n\n /**\n * Pattern matcher over dot-separated segments, which arrive already split.\n *\n * Rules:\n * - **literal**: exact match.\n * - `*` : matches exactly **one** segment.\n * - `**` : matches **zero or more** remaining segments (including empty).\n *\n * @remarks\n * Takes segments rather than strings so delivery can split each pattern once at registration\n * and the subject once per emit, instead of both once per test. Re-splitting per test was most\n * of what made wildcard delivery expensive: a thousand patterns meant two thousand string\n * splits to deliver one event.\n *\n * @param pSegs - Pattern segments (may include `*`/`**`).\n * @param sSegs - Subject segments to test.\n * @returns `true` if the pattern matches; otherwise `false`.\n *\n * @example\n * ```ts\n * matchSegments(['a', '*'], ['a', 'b']) // true\n * matchSegments(['a', '*'], ['a', 'b', 'c']) // false\n * matchSegments(['a', '**'], ['a']) // true\n * matchSegments(['**', 'end'], ['x', 'y', 'end']) // true\n * ```\n *\n * @internal\n */\n private matchSegments(pSegs: readonly string[], sSegs: readonly string[]): boolean {\n\n // Iterative segment glob with backtracking — no per-suffix recursion or\n // string re-joining. `*` matches exactly one segment; `**` matches zero or\n // more. Standard wildcard algorithm (`*`≈`?`, `**`≈`*`).\n let i = 0; // pattern index\n let j = 0; // subject index\n let star = -1; // pSegs index of the most recent '**' seen\n let matchIdx = 0; // sSegs index captured when that '**' was seen\n\n while (j < sSegs.length) {\n if (i < pSegs.length && (pSegs[i] === \"*\" || pSegs[i] === sSegs[j])) {\n i++;\n j++;\n } else if (i < pSegs.length && pSegs[i] === \"**\") {\n // '**' initially absorbs zero segments; remember it for backtracking.\n star = i;\n matchIdx = j;\n i++;\n } else if (star !== -1) {\n // Backtrack: let the last '**' absorb one more subject segment.\n i = star + 1;\n j = ++matchIdx;\n } else {\n return false;\n }\n }\n\n // Any leftover pattern tokens must all be '**' (each matching zero segments).\n while (i < pSegs.length && pSegs[i] === \"**\") i++;\n return i === pSegs.length;\n }\n\n /**\n * Removes **all** listeners (exact and pattern). Useful for tests/HMR teardown.\n *\n * @example\n * ```ts\n * afterEach(() => bus.clear());\n * ```\n *\n * @public\n */\n clear(): void {\n this.handlers.clear();\n this.patternHandlers.clear();\n // The index is derived state; leaving it behind would re-register a pattern twice on the\n // next `on()` and hold every cleared pattern string alive for the life of the bus.\n this.patternIndex.clear();\n }\n\n /**\n * Returns a snapshot of all registered subscriptions for DevTools introspection.\n *\n * @returns An array of `{ channel, type, count }` entries for each distinct\n * (channel, type/pattern) pair with at least one handler.\n *\n * @internal\n */\n __introspect(): Array<{ channel: string; type: string; count: number }> {\n const result: Array<{ channel: string; type: string; count: number }> = [];\n for (const [channel, map] of this.handlers) {\n for (const [type, list] of map) {\n if (list.length > 0) {\n result.push({ channel: channel as string, type: type as string, count: list.length });\n }\n }\n }\n for (const [channel, map] of this.patternHandlers) {\n for (const [pattern, list] of map) {\n if (list.length > 0) {\n result.push({ channel: channel as string, type: pattern, count: list.length });\n }\n }\n }\n return result;\n }\n}","/**\n * @module @yoltra/core\n */\n\nimport type { EventMapBase, EventUnion, ReducerFunction } from \"../types\";\n\n/**\n * Thin wrapper around a pure reducer function (stateful event consumer):\n * given a state `S` and an event (from {@link EventUnion | `EventUnion<EM>`}),\n * returns the next state `S`.\n *\n * @typeParam S - State shape handled by this reducer.\n * @typeParam EM - Event map describing the valid event keys and payload types.\n *\n * @remarks\n * - The reducer function is expected to be **pure** and **side-effect free**.\n * - Use this class when you want to pass a reducer around as a value, or to\n * unify the reducer interface across the core API.\n *\n * @example Basic counter\n * ```ts\n * type State = { count: number };\n * type EM = { math: { add: number; set: number } };\n *\n * const rf: ReducerFunction<State, EM> = (s, evt) => {\n * if (evt.channel === 'math' && evt.type === 'add') {\n * return { count: s.count + evt.payload };\n * }\n * if (evt.channel === 'math' && evt.type === 'set') {\n * return { count: evt.payload };\n * }\n * return s;\n * };\n *\n * const r = new Reducer<State, EM>(rf);\n *\n * const s0 = { count: 0 };\n * const s1 = r.reduce(s0, {\n * channel: 'math',\n * type: 'add',\n * payload: 2,\n * id: crypto.randomUUID()\n * } as EventUnion<EM>);\n * // s1.count === 2\n * ```\n *\n * @public\n */\nexport class Reducer<S, EM extends EventMapBase = EventMapBase> {\n /**\n * The underlying pure reducer function.\n * @internal\n */\n private readonly _reduce: ReducerFunction<S, EM>;\n\n /**\n * Creates a new {@link Reducer} from a pure reducer function.\n *\n * @param reduce - A function `(state, event) => nextState` that implements your update logic.\n *\n * @example\n * ```ts\n * const reducer = new Reducer<MyState, MyEM>((state, event) => {\n * // implement your transitions here\n * return state;\n * });\n * ```\n *\n * @public\n */\n constructor(reduce: ReducerFunction<S, EM>) {\n this._reduce = reduce;\n }\n\n /**\n * Applies the reducer to produce the next state.\n *\n * @param state - Current state.\n * @param event - An event drawn from {@link EventUnion | `EventUnion<EM>`}.\n * @returns The next state produced by the underlying reducer function.\n *\n * @example\n * ```ts\n * const next = reducer.reduce(curr, someEvent as EventUnion<MyEM>);\n * ```\n *\n * @public\n */\n reduce(state: S, event: EventUnion<EM>): S {\n return this._reduce(state, event);\n }\n}","/**\n * @module @yoltra/core\n */\n\n/**\n * Keys already warned about, so a hot path does not turn into a log.\n *\n * @internal\n */\nconst warnedDottedKeys = new Set<string>();\n\n/** @internal */\nfunction warnDottedKey(path: string, key: string): void {\n const full = path ? `${path}.${key}` : key;\n if (warnedDottedKeys.has(full)) return;\n warnedDottedKeys.add(full);\n console.warn(\n `[yoltra] State key \"${key}\"${path ? ` under \"${path}\"` : \"\"} contains a dot. Paths are ` +\n `dotted, so this key is indistinguishable from nested objects of the same name: a ` +\n `subscription to \"${full}\" may match the wrong value, and DevTools patches for it will ` +\n `address the wrong node. Rename the key, or nest it.`,\n );\n}\n\n\n/**\n * Computes the list of **dotted leaf paths** that changed between two values.\n *\n * The algorithm performs a deep structural comparison with special handling for:\n * - **Primitives / null** → treated as leafs (change = current `path`; two `NaN`s are equal)\n * - **Date** → compares `getTime()`\n * - **RegExp** → compares `source` and `flags`\n * - **Arrays** → if lengths differ, the whole array path is marked changed; otherwise compares\n * element-by-element producing paths like `\"items.0.title\"`\n * - **Objects** → compares by the **union of keys**, recursing into shared keys and marking\n * added/removed keys as changed at their **full path**\n *\n * Cycles are handled by tracking the `(old, new)` pairs currently on the **recursion path**\n * (added on entry, removed on unwind). A pair is skipped only when it is a genuine ancestor of\n * itself (a real cycle) — a pair that merely appears again at a *sibling* path (legitimate\n * aliasing, e.g. the same object referenced from two keys) is still diffed, so real changes at\n * the second site are never dropped.\n *\n * @param oldState - Previous value to diff.\n * @param newState - Next value to diff.\n * @param path - Current dotted path (callers pass `\"\"` for root; recursion appends segments).\n * @param ancestors - (Advanced) Pairs on the current recursion path, for cycle detection. You\n * generally never pass this.\n * @returns An array of **dotted leaf paths** that changed. Paths use `\".\"` as a separator and\n * indices for arrays (e.g., `\"todos.0.title\"`). If nothing changed, returns `[]`.\n *\n * @example Basic object leaf\n * ```ts\n * detectChangedProps(\n * { user: { name: 'Ada', age: 37 } },\n * { user: { name: 'Grace', age: 37 } }\n * );\n * // => ['user.name']\n * ```\n *\n * @example Array element change\n * ```ts\n * detectChangedProps(\n * { items: [{ title: 'A' }, { title: 'B' }] },\n * { items: [{ title: 'A+' }, { title: 'B' }] }\n * );\n * // => ['items.0.title']\n * ```\n *\n * @example Array length change (marks the array path)\n * ```ts\n * detectChangedProps({ nums: [1,2] }, { nums: [1,2,3] });\n * // => ['nums']\n * ```\n *\n * @example Dates & RegExps\n * ```ts\n * detectChangedProps(new Date(0), new Date(0), 'createdAt'); // => []\n * detectChangedProps(new Date(0), new Date(1), 'createdAt'); // => ['createdAt']\n * detectChangedProps(/a/i, /a/i, 'pattern'); // => []\n * detectChangedProps(/a/i, /a/g, 'pattern'); // => ['pattern']\n * ```\n *\n * @remarks\n * - If `oldState === newState` (same reference), returns `[]` immediately.\n * - A change at the **root** — the values themselves differ and neither is a walkable object,\n * as for a primitive, a `Map`/`Set`, or two `Date`s — is reported at the `path` given, which\n * is `\"\"` for the default root call. `[\"\"]` therefore means *\"the whole value changed\"*, and\n * is emphatically **not** the same as `[]`. Callers must not filter it out for falsiness:\n * doing so is indistinguishable from \"nothing changed\", which is how a store slice holding a\n * primitive once silently refused every update it was given.\n * - For objects, only **own enumerable** keys are compared (via `Object.keys`).\n * - Returned paths are **leaf paths** where a primitive/terminal difference was detected; for arrays,\n * a length change is treated as a leaf change at the array path.\n *\n * @public\n */\nexport function detectChangedProps(\n oldState: any,\n newState: any,\n path = \"\",\n ancestors: Map<object, Set<object>> = new Map(),\n): string[] {\n const out: string[] = [];\n walk(oldState, newState, path, ancestors, out);\n return out;\n}\n\n/**\n * The recursion, writing into one array rather than returning a new one per node.\n *\n * @remarks\n * Every node used to allocate its own `string[]` and every parent spread its children's back in.\n * On a thousand-entity normalised map that is roughly four thousand short-lived arrays per diff,\n * for a result that is usually a single path — the allocation dwarfed the comparison it existed\n * to report.\n *\n * @internal\n */\nfunction walk(\n oldState: any,\n newState: any,\n path: string,\n ancestors: Map<object, Set<object>>,\n out: string[],\n): void {\n if (oldState === newState) return;\n\n if (\n typeof oldState !== \"object\" ||\n typeof newState !== \"object\" ||\n oldState === null ||\n newState === null\n ) {\n // Two NaNs are never `===` but represent no change — don't report a spurious diff.\n if (typeof oldState === \"number\" && Number.isNaN(oldState) && Number.isNaN(newState as number)) {\n return;\n }\n out.push(path);\n return;\n }\n\n if (oldState instanceof Date && newState instanceof Date) {\n if (oldState.getTime() !== newState.getTime()) out.push(path);\n return;\n }\n\n if (oldState instanceof RegExp && newState instanceof RegExp) {\n if (oldState.source !== newState.source || newState.flags !== oldState.flags) out.push(path);\n return;\n }\n\n // `Map` and `Set` keep their contents outside own enumerable keys, so the key-walk below sees\n // two empty objects and reports no change at all. The store treats \"no changed paths\" as a\n // no-op and skips the commit entirely, so a reducer returning a new Map produced no state\n // update, no subscriber notification and no error — the update simply vanished.\n //\n // Reported at this path rather than diffed internally: the references differ, which under the\n // immutability contract means the value changed. Reactivity for such a value is therefore\n // reference-level, not per-entry.\n if (oldState instanceof Map || newState instanceof Map) {\n out.push(path);\n return;\n }\n if (oldState instanceof Set || newState instanceof Set) {\n out.push(path);\n return;\n }\n\n const oldObj = oldState as object;\n const newObj = newState as object;\n\n // Cycle guard: skip a pair only when it is currently an ANCESTOR on this\n // recursion path (a genuine cycle). A pair seen earlier at a sibling path is\n // legitimate aliasing and must still be diffed.\n const active = ancestors.get(oldObj);\n if (active?.has(newObj)) return;\n const onPath = active ?? new Set<object>();\n onPath.add(newObj);\n if (!active) ancestors.set(oldObj, onPath);\n\n try {\n const isArrOld = Array.isArray(oldState);\n const isArrNew = Array.isArray(newState);\n if (isArrOld !== isArrNew) {\n out.push(path);\n return;\n }\n\n if (isArrOld) {\n const a = oldState;\n const b = newState as any[];\n\n // A length change reports the array path — the array's own identity changed, so a\n // subscriber watching `items` must hear about it — and then keeps going. Returning early\n // here used to be the whole story, which meant an `unshift` or `splice` notified `items`\n // and nothing beneath it: a component subscribed to the exact path `items.0.title`, the\n // very example the documentation leads with, kept rendering the previous row's title.\n // Guarded rather than filtered afterwards: at the root there is no path to report, and an\n // empty string in the output would read downstream as \"the whole slice\".\n if (a.length !== b.length && path) out.push(path);\n\n // Overlapping indices are compared as usual. With positional paths a shift genuinely\n // changes the value at nearly every index, so this is honest rather than noisy — the\n // remedy for that cost is identity-keyed state, not a diff that stays quiet.\n // The identity check happens *before* the path is built. `walk` would short-circuit on it\n // a line later anyway, but only after this frame had already concatenated a string for a\n // child that turns out to be unchanged — which for the overwhelmingly common shape of an\n // update (one element of many) is one allocation per element that nobody reads.\n const overlap = Math.min(a.length, b.length);\n for (let i = 0; i < overlap; i++) {\n if (a[i] === b[i]) continue;\n walk(a[i], b[i], path ? `${path}.${i}` : `${i}`, ancestors, out);\n }\n\n // Indices present in only one of the two: the element as a whole appeared or vanished,\n // which is the same treatment an added or removed object key gets below.\n for (let i = overlap; i < Math.max(a.length, b.length); i++) {\n out.push(path ? `${path}.${i}` : `${i}`);\n }\n\n return;\n }\n\n const oldKeys = Object.keys(oldState);\n const newKeys = Object.keys(newState);\n\n // Two distinct references with nothing enumerable to compare: any class instance holding its\n // state in private fields or behind accessors lands here. Assume changed rather than equal —\n // the alternative is the silent no-op that `Map` and `Set` used to produce, and a false\n // \"changed\" costs a render while a false \"unchanged\" costs correctness.\n if (oldKeys.length === 0 && newKeys.length === 0) {\n out.push(path);\n return;\n }\n\n // Whether both sides carry exactly the same keys, which is the overwhelmingly common case:\n // an update changes values, not shape. Equal counts plus one-way containment is enough to\n // conclude it — a key of `newState` missing from `oldState` would have to be balanced by a\n // key of `oldState` missing from `newState`, and the counts forbid that.\n //\n // Worth establishing because the alternative is materialising the union, and that union used\n // to be built unconditionally: two key arrays and a `Set` per object, at every level of the\n // tree. On a thousand-entity normalised map — the exact shape `createEntityAdapter` steers\n // people toward — that allocation was most of the diff's cost.\n let sameKeys = oldKeys.length === newKeys.length;\n if (sameKeys) {\n for (let i = 0; i < newKeys.length; i++) {\n if (!Object.prototype.hasOwnProperty.call(oldState, newKeys[i]!)) {\n sameKeys = false;\n break;\n }\n }\n }\n\n if (sameKeys) {\n for (const key of newKeys) {\n // Skip before building a path. `walk` would short-circuit on this identity a line later\n // anyway, but only after this frame had already concatenated a string for a child that\n // turns out to be unchanged — one allocation per key that nobody reads, which for the\n // common shape of an update (one field of many) is nearly all of them.\n if (oldState[key] === newState[key]) continue;\n // A key containing a dot cannot survive the join: `{ \"a.b\": 1 }` and `{ a: { b: 1 } }`\n // both produce \"a.b\", so a subscription and a devtools patch pointing at one silently\n // address the other. Nothing downstream can recover the difference from the string, which\n // is why this is said here, where the key is still intact.\n if (process.env.NODE_ENV !== \"production\" && key.includes(\".\")) warnDottedKey(path, key);\n walk(oldState[key], newState[key], path ? `${path}.${key}` : key, ancestors, out);\n }\n return;\n }\n\n // The shapes differ, so both sides have to be visited — but still without materialising a\n // union. Two passes over the key lists find additions and removals directly; building a\n // `Set` of every key on both sides to iterate once costs more than walking each list.\n for (const key of newKeys) {\n const hasOld = Object.prototype.hasOwnProperty.call(oldState, key);\n // Only compare values once presence is established: with differing shapes, `oldState[key]`\n // and `newState[key]` both read `undefined` for a key genuinely absent from one side, and\n // that is a change rather than a match.\n if (hasOld && oldState[key] === newState[key]) continue;\n if (process.env.NODE_ENV !== \"production\" && key.includes(\".\")) warnDottedKey(path, key);\n const nextPath = path ? `${path}.${key}` : key;\n if (!hasOld) {\n out.push(nextPath);\n continue;\n }\n walk(oldState[key], newState[key], nextPath, ancestors, out);\n }\n\n for (const key of oldKeys) {\n if (Object.prototype.hasOwnProperty.call(newState, key)) continue;\n if (process.env.NODE_ENV !== \"production\" && key.includes(\".\")) warnDottedKey(path, key);\n out.push(path ? `${path}.${key}` : key);\n }\n } finally {\n // Unwind: leave the current recursion path so sibling branches can revisit\n // this pair (legitimate aliasing) without being suppressed as a cycle.\n onPath.delete(newObj);\n if (onPath.size === 0) ancestors.delete(oldObj);\n }\n}\n","/**\n * @module @yoltra/core\n */\n\nimport type { DeepReadonly } from \"../types\";\n\n/**\n * Deep-freezes a value **in place** and returns it as {@link DeepReadonly | `DeepReadonly<T>`}.\n *\n * @typeParam T - The input value type to freeze.\n * @param obj - Any value; objects and arrays are frozen recursively.\n * @param seen - (Advanced) A `WeakSet` used to track visited objects for cycle/alias safety.\n * @returns The **same** reference as `obj`, but frozen and typed as `DeepReadonly<T>`.\n *\n * @remarks\n * - **In-place**: this function mutates the input by freezing it and its children, then returns it.\n * - **Early exits**:\n * - Primitives and `null` are returned as-is.\n * - Already-frozen objects (`Object.isFrozen(obj)`) are returned as-is.\n * - Previously seen objects (by identity) are returned as-is to avoid infinite recursion on cycles.\n * - **Arrays**: freezes each element, then `Object.freeze(array)`. Length/property descriptors are not rewritten.\n * - **Objects**: iterates **own** string and symbol keys. Only **data properties** are recursed (getters/setters are skipped).\n * - **Strict mode**: Mutating a frozen object throws; in non-strict mode it is a no-op (per JS semantics).\n *\n * @example Basic usage\n * ```ts\n * const state = { user: { name: 'Ada' }, items: [1, { id: 1 }] };\n * const frozen = freezeState(state);\n *\n * Object.isFrozen(frozen); // true\n * Object.isFrozen(frozen.user); // true\n * Object.isFrozen(frozen.items); // true\n * Object.isFrozen(frozen.items[1]); // true\n * ```\n *\n * @example Safe with cycles\n * ```ts\n * const a: any = {};\n * a.self = a; // cycle\n * freezeState(a); // does not recurse infinitely\n * ```\n *\n * @example Already frozen objects are returned as-is\n * ```ts\n * const o = Object.freeze({ x: 1 });\n * const out = freezeState(o);\n * out === o; // true\n * ```\n *\n * @public\n */\nexport function freezeState<T>(\n obj: T,\n seen = new WeakSet<object>(),\n alias?: AliasWatch,\n): DeepReadonly<T> {\n if (obj === null || typeof obj !== \"object\") return obj as any;\n if (seen.has(obj as any)) return obj as any;\n\n // Reported before the early-exit on already-frozen values, so a payload stored twice is still\n // named the second time.\n if (alias !== undefined && obj === alias.watch) alias.onFound();\n\n if (Object.isFrozen(obj)) return obj as any;\n\n seen.add(obj as any);\n\n // Arrays: handle indices only (skip length descriptor churn)\n if (Array.isArray(obj)) {\n const arr = obj as unknown as any[];\n for (let i = 0; i < arr.length; i++) {\n arr[i] = freezeState(arr[i], seen, alias);\n }\n return Object.freeze(arr) as any;\n }\n\n // Plain objects: freeze string and symbol props (value descriptors only)\n for (const key of Object.getOwnPropertyNames(obj)) {\n const desc = Object.getOwnPropertyDescriptor(obj, key);\n if (!desc || !(\"value\" in desc)) continue; // skip getters/setters\n (obj as any)[key] = freezeState((obj as any)[key], seen, alias);\n }\n for (const sym of Object.getOwnPropertySymbols(obj)) {\n const desc = Object.getOwnPropertyDescriptor(obj, sym);\n if (!desc || !(\"value\" in desc)) continue;\n (obj as any)[sym as any] = freezeState((obj as any)[sym as any], seen, alias);\n }\n\n return Object.freeze(obj) as any;\n}\n\n/**\n * Watches the freeze walk for one specific reference.\n *\n * @remarks\n * Exists to turn a dev-only heisenbug into a named warning. Because the freeze is deep and\n * in place, anything a reducer stores **by reference** is frozen too — the event payload, a\n * module-level default, a cached response. Mutating that object afterwards then throws, only in\n * development, from a stack that has nothing to do with the store, and the same code works in\n * production because the freeze is compiled out.\n *\n * Freezing it is not the mistake: an object reachable from state genuinely must not be mutated,\n * or state changes behind the store's back. Keeping the reference is. The walk already visits\n * every node, so recognising one of them costs an identity comparison and lets the store say so\n * at the moment it happens.\n *\n * @public\n */\nexport interface AliasWatch {\n /** The reference to look for while freezing. */\n readonly watch: object;\n /** Called if `watch` is reachable from the value being frozen. */\n readonly onFound: () => void;\n}","/**\n * @module @yoltra/core\n */\n\nimport { Reducer } from \"../reducer/Reducer\";\nimport { detectChangedProps } from \"../utils/detectChangedProps\";\nimport { EventBus } from \"../eventBus/EventBus\";\nimport { LooseEventBus } from \"../eventBus/LooseEventBus\";\nimport type {\n Event,\n EventMapBase,\n EventKey,\n EventUnion,\n Change,\n DeepReadonly,\n EffectFunction,\n EffectSpec,\n EventConsumerMeta,\n EventMeta,\n MiddlewareFunction,\n MiddlewareInput,\n MiddlewareSpec,\n ReducersMapAny,\n ReducerSpec,\n StateFromReducers,\n StoreInstance,\n StoreSpec,\n Unsubscribe,\n EMFromReducersStrict,\n Emit,\n EmitOptions,\n InstrumentationObserver,\n InstrumentedEvent,\n EventPhase,\n EventSubscriptionHandler,\n NarrowedEventHandler,\n When,\n} from \"../types\";\nimport { freezeState } from \"../utils/immutability\";\nimport type { AliasWatch } from \"../utils/immutability\";\n\n/**\n * Deep-freezes a value **in development only**, returning it untouched in\n * production.\n *\n * @remarks\n * Deep-freezing is a dev-time guard against accidental state mutation; in\n * production it is pure overhead. Because {@link freezeState} freezes in place\n * and early-exits on already-frozen nodes, freezing a structurally-shared value\n * touches only the **newly-created** nodes — O(change), not O(state size). This\n * is why the write path does **not** deep-clone before freezing.\n *\n * @internal\n */\n/**\n * Copies a slice's initial state so the store owns it, naming the slice if it cannot.\n *\n * @remarks\n * `structuredClone` refuses functions and drops class prototypes, and its `DataCloneError`\n * says only that something was uncloneable — not which slice, and not which key. For a store\n * built from several slices at once that leaves the developer bisecting their own\n * configuration. The message here names the slice and points at the usual cause.\n *\n * @internal\n */\nfunction cloneInitialState<T>(sliceName: unknown, state: T): T {\n try {\n return structuredClone(state);\n } catch (err) {\n throw new Error(\n `[yoltra] Initial state for slice \"${String(sliceName)}\" could not be copied: ` +\n `${err instanceof Error ? err.message : String(err)}. State must be structured-cloneable ` +\n `— functions, class instances and DOM nodes are not. Keep behaviour out of state and ` +\n `store plain data.`,\n );\n }\n}\n\nfunction freezeInDev<T>(value: T, alias?: AliasWatch): DeepReadonly<T> {\n return process.env.NODE_ENV === \"production\"\n ? (value as unknown as DeepReadonly<T>)\n : freezeState(value, new WeakSet<object>(), alias);\n}\n\n/**\n * Default window (ms) for identity-based dedup via {@link EmitOptions.dedupKey}\n * when content-based dedup (`dedupWindowMs`) is disabled. Large enough to absorb\n * a synchronous re-fire (e.g. React Strict Mode's mount → unmount → mount),\n * small enough not to swallow genuine user repeats.\n */\nconst DEFAULT_DEDUP_KEY_WINDOW_MS = 100;\n\n/**\n * High-resolution monotonic clock in milliseconds for instrumentation timing;\n * falls back to `Date.now()` where `performance` is unavailable.\n */\nconst now = (): number =>\n typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now();\n\nexport class Store<EM extends EventMapBase, R extends string, S extends Record<R, any>>\n implements StoreInstance<R, S, EM> {\n /**\n * Store name (used by DevTools & diagnostics).\n *\n * @public\n */\n name: string;\n\n /**\n * Registered middleware pipeline (run **before** reducers).\n * Stores either raw functions (legacy) or MiddlewareSpec objects.\n * Return `false` from the middleware function to stop propagation.\n *\n * @internal\n */\n private readonly middleware: MiddlewareInput<DeepReadonly<S>, EM>[];\n\n /**\n * Installed slice reducers keyed by slice name.\n *\n * @internal\n */\n private readonly reducers: Record<R, Reducer<S[R], EM>>;\n\n /**\n * Current immutable snapshot of the store state.\n * This reference changes whenever any slice changes (shallow immutability).\n *\n * @internal\n */\n private state: DeepReadonly<S>;\n\n /**\n * Bus for reducer wiring (emit by `(channel, type)`).\n *\n * @internal\n */\n private readonly reducerBus: EventBus<EM>;\n\n /**\n * Bus for **granular** connector events (emit by **dotted path** inside a slice).\n *\n * @internal\n */\n private readonly connectorBus: LooseEventBus<R, string, Change>;\n\n /**\n * Coarse-grained listeners (called once per committed event, only if state changed).\n *\n * @internal\n */\n private readonly listeners: Set<() => void> = new Set();\n\n /**\n * Registered effect handlers keyed by `\"channel::type\"` for O(1) lookup.\n * Used for effects with explicit `keys` targeting.\n *\n * @internal\n */\n private readonly effects = new Map<string, Set<EffectFunction<DeepReadonly<S>, EM>>>();\n\n /**\n * Pattern-based effects that need runtime matching.\n * Used for effects with `when: { any }`, `{ channel }`, or `{ channels }`.\n * Stores tuples of [effect function, when matcher].\n *\n * @internal\n */\n private readonly patternEffects = new Set<{\n effect: EffectFunction<DeepReadonly<S>, EM>;\n when: When<EM>;\n }>();\n\n /**\n * Committed event subscribers keyed by `\"channel::type\"` for O(1) lookup.\n * Notified after reducers, before effects, for events that passed middleware.\n *\n * @internal\n */\n private readonly committedEventSubscribers = new Map<\n string,\n Set<EventSubscriptionHandler<DeepReadonly<S>, EM>>\n >();\n\n /**\n * Uncommitted event subscribers keyed by `\"channel::type\"` for O(1) lookup.\n * Notified when middleware rejects an event.\n *\n * @internal\n */\n private readonly uncommittedEventSubscribers = new Map<\n string,\n Set<EventSubscriptionHandler<DeepReadonly<S>, EM>>\n >();\n\n /**\n * All-events subscribers keyed by `\"channel::type\"` for O(1) lookup.\n * Notified for both committed and uncommitted events with phase parameter.\n *\n * @internal\n */\n private readonly allEventSubscribers = new Map<\n string,\n Set<EventSubscriptionHandler<DeepReadonly<S>, EM>>\n >();\n\n /**\n * Track reducerBus unsubs per slice for HMR/register/unregister.\n *\n * @internal\n */\n private readonly sliceUnsubs = new Map<string, Array<() => void>>();\n\n /**\n * Pattern-based reducers that need runtime matching.\n * Used for reducers with `when: { any }`, `{ channel }`, or `{ channels }`.\n * Maps slice name to the `when` matcher.\n *\n * @internal\n */\n private readonly patternReducers = new Map<R, When<EM>>();\n\n /**\n * Whether `__replayEvents()` is allowed.\n * Set from `spec.devtools.allowReplay`.\n *\n * @internal\n */\n private readonly replayEnabled: boolean;\n\n /**\n * Produces the `id` for each emitted event. Defaults to `crypto.randomUUID()`; overridable\n * via {@link StoreSpec.idFactory} for runtimes lacking it or for deterministic tests.\n *\n * @internal\n */\n private readonly idFactory: () => string;\n\n /**\n * Optional hook invoked when an effect throws/rejects. See\n * {@link StoreSpec.onEffectError}. `await emit()` never rejects on effect\n * failure — this is how callers observe effect errors.\n */\n private readonly onEffectError?: (error: unknown, event: EventUnion<EM>) => void;\n\n /**\n * Optional hook invoked when a reducer throws. See {@link StoreSpec.onReducerError}. The\n * failing slice is isolated rather than the event being rolled back, so this is the only\n * signal that a reducer misbehaved.\n */\n private readonly onReducerError?: (\n error: unknown,\n event: EventUnion<EM>,\n slice: string,\n ) => void;\n\n /**\n * `slice:channel:type` combinations already warned about for payload aliasing.\n *\n * @remarks\n * Development-only diagnostics have to stay quiet enough to be read. One warning names the\n * pattern; repeating it once per event would bury it.\n */\n private readonly warnedPayloadAliases = new Set<string>();\n\n /**\n * Pending events awaiting the **synchronous** reduce phase (middleware +\n * reducers + subscribers + coarse listeners). Drained by {@link drainReduce}.\n *\n * @internal\n */\n private readonly reduceQueue: Array<{\n channel: string;\n type: string;\n payload: any;\n id: string;\n meta?: EventMeta;\n resolve: () => void;\n }> = [];\n\n /**\n * Re-entrancy guard for the synchronous reduce phase.\n *\n * @internal\n */\n private isReducing = false;\n\n /**\n * Registered instrumentation observers (DevTools seam). See {@link instrument}.\n *\n * @internal\n */\n private readonly instrumentObservers = new Set<InstrumentationObserver<EM>>();\n\n /**\n * Scratch array collecting slice-prefixed changed leaf paths during an\n * instrumented reduce. Set by {@link drainReduce} while observers are active;\n * appended to by {@link forwardEvent}. `null` when not instrumenting.\n *\n * @internal\n */\n private changedPathSink: string[] | null = null;\n\n /**\n * Count of effect tasks currently in flight; surfaced as queue depth by\n * {@link __devtoolsIntrospect}.\n *\n * @internal\n */\n private inFlightEffects = 0;\n\n /**\n * Tracks processed events by fingerprint with timestamps for TTL-based deduplication.\n *\n * **Deduplication Behavior:**\n * - Events are fingerprinted using `channel::type::JSON(payload)`\n * - If an identical fingerprint is seen within the dedup window, it's skipped\n * - The window is 50ms in development, 100ms in production\n *\n * **Limitations:**\n * - Non-serializable payloads (functions, symbols, circular refs) get unique\n * fingerprints and won't be deduplicated\n * - Legitimate rapid-fire identical events may be incorrectly deduplicated\n * - The cache is bounded to 1000 entries with lazy pruning\n *\n * @internal\n */\n private readonly processedEvents = new Map<string, number>();\n\n /**\n * Lifetime count of events suppressed by the deduplication cache.\n * Exposed via {@link __devtoolsIntrospect} so the DevTools agent can\n * surface it in the STORE_METRICS response without further core changes.\n *\n * @internal\n */\n private dedupCount = 0;\n\n /**\n * Store-owned metadata for registered effects, keyed by the effect function.\n * Kept **off** the caller's function object: mutating a user-owned function\n * (the old `fn.__quoMeta`) bled metadata across stores that share a handler\n * and left it attached after unregister. Cleared on {@link dispose}.\n *\n * @internal\n */\n private effectMeta = new WeakMap<object, EventConsumerMeta<\"effect\">>();\n\n /**\n * Configuration for event deduplication.\n * @internal\n */\n private readonly dedupConfig: {\n /** Time window in ms for considering events as duplicates */\n windowMs: number;\n /** Maximum cache size to prevent unbounded growth */\n maxCacheSize: number;\n };\n\n /**\n * Timer for periodic cleanup of processed events.\n *\n * @internal\n */\n private eventCleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n /**\n * Creates a store from a {@link StoreSpec}.\n *\n * @param spec - Store configuration (name, reducers, middleware, optional effects).\n *\n * @public\n */\n constructor(spec: StoreSpec<R, S, EM>) {\n this.name = spec.name ?? \"yoltra Store\";\n this.reducerBus = new EventBus<EM>();\n this.connectorBus = new LooseEventBus();\n this.middleware = [...(spec.middleware ?? [])];\n this.reducers = {} as Record<R, Reducer<S[R], EM>>;\n this.state = {} as any;\n this.replayEnabled = spec.devtools?.allowReplay ?? false;\n this.idFactory = spec.idFactory ?? (() => crypto.randomUUID());\n this.onEffectError = spec.onEffectError;\n this.onReducerError = spec.onReducerError;\n\n // Deduplication is OPT-IN. Content-based dedup is OFF by default because it\n // can silently drop legitimate rapid-fire identical events; enable it with\n // `dedupWindowMs > 0`, or use per-emit `dedupKey` for identity-based dedup.\n this.dedupConfig = {\n windowMs: spec.dedupWindowMs ?? 0,\n maxCacheSize: 1000,\n };\n\n /**\n * Reducer wiring\n */\n Object.entries(spec.reducer).forEach(([name, rSpec]) => {\n this.mountSlice(name as R, rSpec as ReducerSpec<S[R], EM>, { preserveState: false });\n });\n\n /**\n * Effects from spec (optional)\n */\n if (spec.effects?.length) {\n for (const effSpec of spec.effects) {\n this.registerEffect(effSpec);\n }\n }\n\n // Event dedup cleanup runs on a lazily-started interval: it begins the first\n // time an entry is cached (content dedup OR identity `dedupKey`) and stops\n // when the cache empties (see ensureCleanupTimer / pruneProcessedEvents).\n // When no dedup is used the cache stays empty, so no timer is ever started\n // and the store never keeps the event loop alive unnecessarily.\n\n /**\n * Method bindings\n */\n this.dispose = this.dispose.bind(this);\n this.notifyEffects = this.notifyEffects.bind(this);\n\n // private API\n this.forwardEvent = this.forwardEvent.bind(this);\n this.__applyExternalState = this.__applyExternalState.bind(this);\n this.__replayEvents = this.__replayEvents.bind(this);\n this.__devtoolsIntrospect = this.__devtoolsIntrospect.bind(this);\n this.mountSlice = this.mountSlice.bind(this);\n this.unmountSlice = this.unmountSlice.bind(this);\n this.getAtPath = this.getAtPath.bind(this);\n\n // public API\n this.emit = this.emit.bind(this);\n this.subscribe = this.subscribe.bind(this);\n this.connect = this.connect.bind(this);\n this.onEffect = this.onEffect.bind(this);\n this.onEvent = this.onEvent.bind(this);\n this.getState = this.getState.bind(this);\n this.registerEffect = this.registerEffect.bind(this);\n this.registerMiddleware = this.registerMiddleware.bind(this);\n this.registerReducer = this.registerReducer.bind(this);\n this.replaceMiddleware = this.replaceMiddleware.bind(this);\n this.replaceEffects = this.replaceEffects.bind(this);\n this.replaceReducers = this.replaceReducers.bind(this);\n this.hotReplace = this.hotReplace.bind(this);\n }\n\n /**\n * Cleanup resources (timers, etc.) when disposing the store.\n * Call this if you're dynamically creating/destroying stores.\n *\n * @example\n * ```ts\n * const store = createStore({ ... });\n * // later\n * store.dispose();\n * ```\n *\n * @public\n */\n public dispose(): void {\n if (this.eventCleanupTimer) {\n clearInterval(this.eventCleanupTimer);\n this.eventCleanupTimer = null;\n }\n\n this.processedEvents.clear();\n this.effects.clear();\n this.patternEffects.clear();\n this.effectMeta = new WeakMap();\n\n // The once-per-slice-and-event latch for the payload-aliasing warning. Left populated, a\n // disposed-and-recreated store — per-route stores, HMR, a test suite building one per case —\n // inherits the suppression and stays quiet about aliasing in code that has never been warned\n // about. The latch exists to stop a hot path becoming a log, not to silence the next store.\n this.warnedPayloadAliases.clear();\n\n // Release every subscription and observer. Without this, the closures they\n // hold (React fibers, DevTools sockets, effect handlers) pin the store and\n // leak on per-route / SSR / test / HMR stores that create and dispose stores.\n this.listeners.clear();\n this.committedEventSubscribers.clear();\n this.uncommittedEventSubscribers.clear();\n this.allEventSubscribers.clear();\n this.instrumentObservers.clear();\n this.connectorBus.clear();\n this.reducerBus.clear();\n this.patternReducers.clear();\n this.sliceUnsubs.clear();\n this.changedPathSink = null;\n }\n\n /**\n * Generates a fingerprint for an event for deduplication purposes.\n * Falls back gracefully for non-serializable payloads.\n *\n * @param channel - Event channel.\n * @param type - Event type.\n * @param payload - Event payload.\n * @returns A string fingerprint for the event.\n *\n * @internal\n */\n private fingerprint(channel: string, type: string, payload: unknown): string {\n const base = `${channel}::${type}`;\n\n try {\n // Fast path for primitives\n if (payload === null || payload === undefined) {\n return `${base}::null`;\n }\n if (typeof payload !== \"object\") {\n return `${base}::${String(payload)}`;\n }\n\n // Attempt JSON serialization (handles most cases)\n const json = JSON.stringify(payload);\n return `${base}::${json}`;\n } catch {\n // Non-serializable payload - use timestamp to avoid false positives\n // This means non-serializable payloads won't be deduplicated\n return `${base}::${Date.now()}::${Math.random()}`;\n }\n }\n\n /**\n * Checks if an event should be deduplicated.\n * Returns true if this is a duplicate that should be skipped.\n *\n * @param fp - Event fingerprint.\n * @returns `true` if duplicate (should skip), `false` otherwise.\n *\n * @internal\n */\n private shouldDedupe(fp: string, windowMs: number): boolean {\n const now = Date.now();\n const existing = this.processedEvents.get(fp);\n\n if (existing !== undefined) {\n // Check if within dedup window\n if (now - existing < windowMs) {\n this.dedupCount++;\n return true; // Duplicate, skip\n }\n }\n\n // Record this event and make sure the periodic prune is running (it may not\n // be — e.g. identity `dedupKey` dedup at windowMs 0 never started it at\n // construction). The timer stops itself once the cache drains.\n this.processedEvents.set(fp, now);\n this.ensureCleanupTimer();\n\n // Lazy cleanup if cache is getting large\n if (this.processedEvents.size > this.dedupConfig.maxCacheSize) {\n this.pruneProcessedEvents(now);\n }\n\n return false; // Not a duplicate\n }\n\n /**\n * Starts the periodic prune interval if it isn't already running. Called when\n * the first entry is cached so the timer's lifetime tracks actual dedup use\n * (content window or identity `dedupKey`), independent of `dedupWindowMs`.\n *\n * @internal\n */\n private ensureCleanupTimer(): void {\n if (this.eventCleanupTimer !== null) return;\n this.eventCleanupTimer = setInterval(() => {\n this.pruneProcessedEvents(Date.now());\n }, 5000);\n // Never let the cleanup interval by itself keep a Node process alive.\n (this.eventCleanupTimer as { unref?: () => void }).unref?.();\n }\n\n /**\n * Removes expired entries from the processed events cache.\n *\n * @param now - Current timestamp.\n *\n * @internal\n */\n private pruneProcessedEvents(now: number): void {\n // Keep 2x the largest window in play (content window or the keyed-dedup\n // default) so entries aren't evicted before their dedup window elapses.\n const effectiveWindow = Math.max(this.dedupConfig.windowMs, DEFAULT_DEDUP_KEY_WINDOW_MS);\n const cutoff = now - effectiveWindow * 2;\n\n for (const [key, timestamp] of this.processedEvents) {\n if (timestamp < cutoff) {\n this.processedEvents.delete(key);\n }\n }\n\n // Once the cache has drained, stop the interval so an idle store doesn't\n // hold a repeating timer. It restarts on the next cached event.\n if (this.processedEvents.size === 0 && this.eventCleanupTimer !== null) {\n clearInterval(this.eventCleanupTimer);\n this.eventCleanupTimer = null;\n }\n }\n\n /**\n * Checks if an event matches a `When` matcher.\n *\n * @param when - The When matcher (or undefined for \"all events\").\n * @param event - The event to check.\n * @returns `true` if the event matches, `false` otherwise.\n *\n * @remarks\n * - `undefined` or missing `when` matches ALL events.\n * - `{ any: true }` matches ALL events.\n * - `{ keys: [...] }` matches if event's `[channel, type]` is in the array.\n * - `{ channel: 'x' }` matches if event's channel equals 'x'.\n * - `{ channels: ['x', 'y'] }` matches if event's channel is in the array.\n *\n * @internal\n */\n private matchesWhen(when: When<EM> | undefined, event: EventUnion<EM>): boolean {\n // No targeting = match all events\n if (!when) return true;\n\n // Match all events\n if (\"any\" in when && when.any === true) {\n return true;\n }\n\n // Match specific event keys\n if (\"keys\" in when) {\n return when.keys.some(\n ([channel, type]) => event.channel === channel && event.type === type,\n );\n }\n\n // Match single channel (all types within that channel)\n if (\"channel\" in when) {\n return event.channel === when.channel;\n }\n\n // Match multiple channels\n if (\"channels\" in when) {\n return when.channels.includes(event.channel as keyof EM & string);\n }\n\n return false;\n }\n\n /**\n * Extracts the middleware function from a MiddlewareInput.\n * Handles both raw functions (legacy) and MiddlewareSpec objects.\n *\n * @param input - MiddlewareInput (function or spec).\n * @returns The middleware function.\n *\n * @internal\n */\n private getMiddlewareFunction(\n input: MiddlewareInput<DeepReadonly<S>, EM>,\n ): MiddlewareFunction<DeepReadonly<S>, EM> {\n if (typeof input === \"function\") {\n return input;\n }\n return input.middleware;\n }\n\n /**\n * Gets the `when` matcher from a MiddlewareInput.\n *\n * @param input - MiddlewareInput (function or spec).\n * @returns The `when` matcher, or `undefined` for raw functions (match all).\n *\n * @internal\n */\n private getMiddlewareWhen(\n input: MiddlewareInput<DeepReadonly<S>, EM>,\n ): When<EM> | undefined {\n if (typeof input === \"function\") {\n // Raw functions match all events\n return undefined;\n }\n return input.when;\n }\n\n /**\n * Invokes all registered **effects** for a given event.\n * Handles both key-based effects (O(1) lookup) and pattern-based effects (runtime matching).\n * Errors are caught and logged.\n *\n * @param event - The event that was reduced.\n * @internal\n */\n private async notifyEffects(event: EventUnion<EM>) {\n // 1. Call key-based effects (O(1) lookup)\n const key = `${String(event.channel)}::${String(event.type)}`;\n const effectSet = this.effects.get(key);\n\n if (effectSet && effectSet.size > 0) {\n for (const h of [...effectSet]) {\n try {\n await h(event, this.getState, this.emit);\n } catch (e) {\n console.error(\"Effect error:\", e);\n this.onEffectError?.(e, event);\n }\n }\n }\n\n // 2. Call pattern-based effects (runtime matching)\n for (const { effect, when } of this.patternEffects) {\n if (this.matchesWhen(when, event)) {\n try {\n await effect(event, this.getState, this.emit);\n } catch (e) {\n console.error(\"Effect error:\", e);\n this.onEffectError?.(e, event);\n }\n }\n }\n }\n\n /**\n * Notifies event subscribers for a specific phase.\n *\n * Calls both phase-specific subscribers and 'all' subscribers.\n * Errors are caught and logged, allowing other subscribers to continue.\n *\n * @param event - The event to notify about.\n * @param phase - The phase ('committed' or 'uncommitted').\n * @internal\n */\n private notifyEventSubscribers(\n event: EventUnion<EM>,\n phase: \"committed\" | \"uncommitted\",\n ): void {\n const key = `${String(event.channel)}::${String(event.type)}`;\n\n // Notify phase-specific subscribers\n const phaseMap =\n phase === \"committed\"\n ? this.committedEventSubscribers\n : this.uncommittedEventSubscribers;\n const phaseSet = phaseMap.get(key);\n\n if (phaseSet?.size) {\n for (const handler of [...phaseSet]) this.invokeEventSubscriber(handler, event, phase);\n }\n\n // Notify 'all' subscribers\n const allSet = this.allEventSubscribers.get(key);\n if (allSet?.size) {\n for (const handler of [...allSet]) this.invokeEventSubscriber(handler, event, phase);\n }\n }\n\n /**\n * Invokes a single event-subscription handler **fire-and-forget**: synchronous\n * throws and async rejections are logged but never block the emit pipeline.\n * Event subscribers are notifications, not part of the committed reduce result.\n *\n * @internal\n */\n private invokeEventSubscriber(\n handler: EventSubscriptionHandler<DeepReadonly<S>, EM>,\n event: EventUnion<EM>,\n phase: \"committed\" | \"uncommitted\",\n ): void {\n try {\n const result = handler(event, this.getState, this.emit, phase) as unknown;\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n (result as Promise<unknown>).catch((e) => console.error(\"Event subscription error:\", e));\n }\n } catch (e) {\n console.error(\"Event subscription error:\", e);\n }\n }\n\n /**\n * Applies a reduced event to a slice and emits **precise** connector events.\n *\n * For each changed **leaf path** (via {@link detectChangedProps}), emits that leaf and\n * all of its **ancestors** once (e.g., `\"data\"`, `\"data.123\"`, `\"data.123.title\"`).\n *\n * A slice whose state **is** a single value — a primitive, a `Map`/`Set`, a `Date` — has no\n * leaf below its root, and `detectChangedProps` reports its change as the empty path `\"\"`.\n * That path is emitted as-is, so `connect({ reducer, property: \"\" })` (and any `**` pattern)\n * hears it. It has no ancestors to walk.\n *\n * **State Immutability**: When a slice changes, a new state object is created via\n * shallow spread: `{ ...this.state, [sliceName]: newSlice }`. This ensures that\n * `this.state` reference changes, enabling efficient change detection via `===`.\n *\n * @param rName - Slice name being updated.\n * @param event - Reduced event with typed payload.\n * @returns `true` if the slice actually changed, `false` otherwise.\n *\n * @internal\n */\n /**\n * Reduces one slice and contains any error it raises.\n *\n * @returns `true` when the slice changed.\n *\n * @remarks\n * The single funnel both dispatch paths go through, which is the point. Keyed reducers run\n * through `reducerBus`, whose handler loop caught and logged; pattern reducers were called\n * straight from the drain, so their errors escaped to the caller instead. The same bug in the\n * same reducer therefore produced two different outcomes depending on how the slice happened\n * to be targeted — a keyed reducer's throw let the event commit and its effects run, while a\n * pattern reducer's throw aborted the commit and notified nobody, not even the uncommitted\n * subscribers a veto would have reached.\n *\n * The semantics are now the same either way: **the failing slice is isolated.** Its state is\n * unchanged, every other slice still reduces, and the event still commits if anything else\n * changed. Rolling the whole event back would be tidier in principle, but fine-grained\n * subscribers are notified inside `forwardEvent` as each slice commits, so an event that\n * reverted afterwards would have already told components about a value that no longer exists.\n * Isolation keeps every notification truthful.\n *\n * @internal\n */\n private forwardEventGuarded<C extends keyof EM & string, T extends keyof EM[C] & string>(\n rName: R,\n event: Event<EM, C, T>,\n ): boolean {\n try {\n return this.forwardEvent(rName, event);\n } catch (err) {\n // Reported through a hook as well as the console: a reducer throwing is a bug in\n // application code, and until now the only trace of it was a console line in one case and\n // an exception surfacing somewhere unrelated in the other.\n console.error(`Reducer error in slice \"${rName as string}\":`, err);\n this.onReducerError?.(err, event as EventUnion<EM>, rName as string);\n return false;\n }\n }\n\n private forwardEvent<C extends keyof EM & string, T extends keyof EM[C] & string>(\n rName: R,\n event: Event<EM, C, T>,\n ): boolean {\n // @ts-expect-error R indexing on DeepReadonly<S> is valid at runtime\n const prev = this.state[rName] as S[R];\n const next = this.reducers[rName].reduce(prev, event as any);\n\n // if reducer returned same ref, definitely no change\n if (prev === next) return false;\n\n // Compute precise leaf paths that changed (relative to slice root).\n //\n // Not filtered for truthiness. `\"\"` is how `detectChangedProps` reports a change at the\n // slice ROOT — a slice that *is* one value: a primitive, a `Map`/`Set`, a `Date`, or an\n // object replaced by something of a different shape. Discarding it as falsy made the\n // length check below read \"nothing changed\", so `forwardEvent` returned before assigning\n // `this.state`: the reducer ran, its result was thrown away, and nothing said so. A store\n // holding `state: 0` could never leave `0`.\n const leafPaths = detectChangedProps(prev, next);\n\n // if nothing actually changed at the leaves, treat as a no-op\n if (leafPaths.length === 0) return false;\n\n // Commit the new slice under a NEW top-level state reference (shallow spread).\n // No deep clone: the reducer already returned a fresh `next` (purity contract),\n // so structural sharing is preserved and freezeInDev only touches new nodes.\n // In development the freeze walk also watches for the event payload appearing in the new\n // state by reference. That is the aliasing that makes a deep in-place freeze surprising:\n // the caller still holds the object, mutating it later throws from an unrelated stack, and\n // the same code works in production because the freeze is compiled out. Warned once per\n // slice and event so a hot path does not become a log.\n const payload = (event as { payload?: unknown }).payload;\n const alias: AliasWatch | undefined =\n process.env.NODE_ENV !== \"production\" && payload !== null && typeof payload === \"object\"\n ? {\n watch: payload,\n onFound: () => {\n const key = `${rName as string}:${event.channel}:${event.type}`;\n if (this.warnedPayloadAliases.has(key)) return;\n this.warnedPayloadAliases.add(key);\n console.warn(\n `[yoltra] Slice \"${rName as string}\" stored the payload of ` +\n `\"${event.channel}/${event.type}\" by reference. It is now frozen along with ` +\n `the rest of the state, so the emitter mutating it later will throw in ` +\n `development and silently corrupt state in production. Copy the payload in ` +\n `the reducer instead.`,\n );\n },\n }\n : undefined;\n\n const frozen = freezeInDev(next, alias) as DeepReadonly<S[R]>;\n this.state = { ...this.state, [rName]: frozen } as DeepReadonly<S>;\n\n // Record slice-prefixed changed leaf paths for any active instrumentation\n // (lets DevTools agents build precise patches without re-diffing state).\n if (this.changedPathSink) {\n for (const p of leafPaths) {\n this.changedPathSink.push(p ? `${rName as string}.${p}` : (rName as string));\n }\n }\n\n // emit deep + ancestor paths once each\n const toEmit = new Set<string>();\n for (const p of leafPaths) {\n // The slice root has no ancestors to walk — `buildAncestorPaths(\"\")` is `[]` by contract,\n // which is what callers holding a real path rely on — so it is added directly. Without\n // this a slice that is entirely one value changes and tells nobody, which is the\n // subscription half of the same bug the missing filter caused in the commit half.\n if (p === \"\") {\n toEmit.add(\"\");\n continue;\n }\n for (const a of Store.buildAncestorPaths(p)) toEmit.add(a);\n }\n\n for (const prop of toEmit) {\n // Built only if a handler matched. Reading the old and new value walks the state tree\n // twice per path, and a slice nobody subscribes to used to pay that for every path it\n // changed — describing the change in detail to an audience of nobody.\n this.connectorBus.emitWith(rName, prop, () => ({\n oldValue: this.getAtPath(prev, prop),\n newValue: this.getAtPath(frozen, prop),\n path: prop,\n }));\n }\n\n return true; // slice changed\n }\n\n /**\n * Returns a structured introspection snapshot for DevTools UIs.\n *\n * @remarks\n * Reads the internal middleware, effects, reducers, and subscriber\n * registries and returns a plain-object summary matching the\n * `STORE_SUBSCRIPTIONS` protocol message shape.\n *\n * @public\n */\n public __devtoolsIntrospect() {\n // Reducers\n const reducers = (Object.keys(this.reducers) as Array<R>).map((name) => {\n const when = this.patternReducers.get(name);\n return { name: name as string, when };\n });\n\n // Effects (keyed) — metadata looked up from the store-owned effectMeta map\n const effects: Array<{ channel: string; type: string; name?: string; description?: string }> = [];\n for (const [key, set] of this.effects) {\n if (set.size === 0) continue;\n const [channel, type] = key.split(\"::\");\n for (const fn of set) {\n const meta = this.effectMeta.get(fn);\n effects.push({ channel, type, name: meta?.name, description: meta?.description });\n }\n }\n // Effects (pattern-based) — entry is { effect, when }; metadata in effectMeta\n for (const entry of this.patternEffects) {\n const meta = this.effectMeta.get(entry.effect);\n effects.push({\n channel: \"*\",\n type: \"*\",\n name: meta?.name,\n description: meta?.description,\n });\n }\n\n // Middleware\n const middleware: Array<{ name?: string; description?: string; when?: unknown }> = [];\n for (const mwInput of this.middleware) {\n if (typeof mwInput === \"function\") {\n middleware.push({ name: mwInput.name || undefined });\n } else {\n middleware.push({\n name: (mwInput as any).meta?.name,\n description: (mwInput as any).meta?.description,\n when: (mwInput as any).when,\n });\n }\n }\n\n // Atomic (connect) subscriptions — enumerate from the connectorBus\n const atomic: Array<{ reducer: string; property: string }> = [];\n for (const entry of this.connectorBus.__introspect()) {\n for (let i = 0; i < entry.count; i++) {\n atomic.push({ reducer: entry.channel, property: entry.type });\n }\n }\n\n // Event subscriptions\n const event: Array<{ channel: string; type: string; phase: string }> = [];\n for (const [key, set] of this.committedEventSubscribers) {\n if (set.size === 0) continue;\n const [channel, type] = key.split(\"::\");\n for (let i = 0; i < set.size; i++) {\n event.push({ channel, type, phase: \"committed\" });\n }\n }\n for (const [key, set] of this.uncommittedEventSubscribers) {\n if (set.size === 0) continue;\n const [channel, type] = key.split(\"::\");\n for (let i = 0; i < set.size; i++) {\n event.push({ channel, type, phase: \"uncommitted\" });\n }\n }\n for (const [key, set] of this.allEventSubscribers) {\n if (set.size === 0) continue;\n const [channel, type] = key.split(\"::\");\n for (let i = 0; i < set.size; i++) {\n event.push({ channel, type, phase: \"all\" });\n }\n }\n\n // Coarse subscribers count\n const coarse = this.listeners.size;\n\n return {\n reducers,\n effects,\n middleware,\n atomic,\n event,\n coarse,\n dedupHits: this.dedupCount,\n queueDepth: this.reduceQueue.length + this.inFlightEffects,\n };\n }\n\n /**\n * Applies an externally provided **whole-state** (e.g., DevTools time travel) and emits\n * fine-grained path changes for each slice.\n *\n * **State Immutability**: If any slices change, a new state object is created via\n * shallow spread. This ensures consistent immutability with {@link forwardEvent}.\n *\n * **Missing slices**: the snapshot should contain every slice. A slice absent\n * from `nextPlain` is **retained at its current value** (not blanked to\n * `undefined`, which would make `getState().<slice>` throw on next access).\n *\n * @param nextPlain - Plain JS object to become the new state.\n *\n * @internal\n */\n public __applyExternalState(nextPlain: any) {\n // Gate on the same runtime flag as __replayEvents: time-travel replaces the\n // whole state tree, so it must stay off unless the app opted in via\n // createStore({ devtools: { allowReplay: true } }). Enforced here at the\n // seam so a devtools agent (or a client driving it) cannot bypass it.\n if (!this.replayEnabled) {\n // Throws, like `__replayEvents`. Both replace state wholesale on behalf of a devtools\n // client; one refusing loudly while the other returned quietly meant a disabled seam\n // looked like a working one that had simply found nothing to do.\n throw new Error(\n \"[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })\",\n );\n }\n\n const prev = this.state as any;\n const next = nextPlain;\n\n const newState = { ...this.state } as any;\n let anyChanged = false;\n\n (Object.keys(this.reducers) as Array<R>).forEach((rName) => {\n const prevSlice = prev?.[rName];\n const nextSlice = next?.[rName];\n\n // A snapshot missing this slice must not blank it out — retain the current\n // slice (storing `undefined` would make getState().<slice>.x throw later).\n if (nextSlice === undefined) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n `[yoltra] External state is missing slice \"${String(\n rName,\n )}\"; retaining its current value. Time-travel snapshots should contain all slices.`,\n );\n }\n return;\n }\n\n // if reference equal, nothing to emit\n if (prevSlice === nextSlice) return;\n\n // freeze the incoming slice before storing (dev-only; no deep clone — the\n // external snapshot is freshly deserialized and owned by the store)\n const frozenNextSlice = freezeInDev(nextSlice) as DeepReadonly<S[typeof rName]>;\n newState[rName] = frozenNextSlice;\n anyChanged = true;\n\n // Full dotted leaf paths relative to the slice. Unfiltered, for the reason given in\n // `forwardEvent`: `\"\"` is a genuine root-level change, not an absent one. Time travel\n // onto a primitive slice committed the state here but emitted nothing, so a component\n // subscribed through `connect` kept rendering the value it had before the jump.\n const leafPaths = detectChangedProps(prevSlice, nextSlice);\n if (leafPaths.length === 0) return;\n\n // emit every leaf AND its ancestors once\n const toEmit = new Set<string>();\n for (const p of leafPaths) {\n if (p === \"\") {\n toEmit.add(\"\");\n continue;\n }\n for (const a of Store.buildAncestorPaths(p)) toEmit.add(a);\n }\n\n for (const path of toEmit) {\n const oldValue = this.getAtPath(prevSlice, path);\n const newValue = this.getAtPath(frozenNextSlice, path);\n this.connectorBus.emit(rName, path as any, { oldValue, newValue, path });\n }\n });\n\n // commit new state if any slices changed\n if (anyChanged) {\n this.state = newState as DeepReadonly<S>;\n }\n\n // coerse subscribers after all fine-grained emits (only if changed)\n if (anyChanged) {\n this.listeners.forEach((l) => l());\n }\n }\n\n /**\n * Replays a sequence of events from a snapshot through reducers and event\n * subscribers ONLY. Skips dedup, middleware, and effects.\n *\n * This method is gated by the `devtools.allowReplay` runtime config.\n * If replay is not enabled, this method throws.\n *\n * @param snapshot - The state snapshot to restore before replaying.\n * @param events - Array of events to replay (in order).\n *\n * @internal\n */\n public __replayEvents(\n snapshot: any,\n events: Array<{ channel: string; type: string; payload: any; id: string; meta?: EventMeta }>,\n ): void {\n if (!this.replayEnabled) {\n throw new Error(\n \"[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })\",\n );\n }\n\n // 1. Apply snapshot (restores base state)\n this.__applyExternalState(snapshot);\n\n // 2. Replay each event through reducers + event subscribers only\n for (const evt of events) {\n const event = evt as EventUnion<EM>;\n\n // Track state before reducers\n const stateBefore = this.state;\n\n // Run key-based reducers via reducerBus. The event travels alongside the payload so\n // keyed reducers observe the replayed event's real id, exactly like pattern reducers.\n this.reducerBus.emit(event.channel as any, event.type as any, event.payload, event as any);\n\n // Run pattern-based reducers. Guarded like the live path: one bad event in a replayed log\n // should cost that event, not abandon the replay halfway through with the store left at\n // whatever state it happened to reach.\n for (const [sliceName, when] of this.patternReducers) {\n if (this.matchesWhen(when, event)) {\n this.forwardEventGuarded(sliceName, event as any);\n }\n }\n\n const stateAfter = this.state;\n const anySliceChanged = stateBefore !== stateAfter;\n\n // Notify committed event subscribers (sync, fire-and-forget)\n this.notifyEventSubscribers(event, \"committed\");\n\n // Notify coarse subscribers if state changed\n if (anySliceChanged) {\n this.listeners.forEach((l) => l());\n }\n\n // NOTE: No middleware, no effects, no dedup, no DevTools logging\n }\n }\n\n /**\n * Emits a typed event `(channel, type, payload)`.\n * Events are queued and processed **sequentially** (FIFO).\n *\n * **Pipeline per event:** the *reduce phase* (steps 1-4) runs **synchronously**,\n * so `getState()` reflects the change as soon as `emit()` returns; the *effect\n * phase* (step 5) runs afterwards, asynchronously.\n * 1. **Deduplication** (opt-in) - Skip when content-dedup is enabled (`dedupWindowMs > 0`) or a matching `dedupKey` recurs; off by default\n * 2. **Middleware** (sync) - Pre-reducer hooks; may cancel by returning `false`\n * 3. **Reducers** (sync) - state updates + fine-grained path notifications\n * 4. **Subscribers + coarse** (sync) - event subscribers (fire-and-forget) then coarse listeners (only if state changed)\n * 5. **Effects** (async) - side-effects keyed by `(channel, type)`; the returned promise resolves once they complete\n *\n * **Change Detection**: Uses reference equality (`===`) on `this.state` to determine\n * if any slice changed. Works because {@link forwardEvent} creates a new state reference\n * via shallow spread when any slice changes.\n *\n * @typeParam C - Channel key in `EM`.\n * @typeParam T - Type key within channel `C`.\n * @param channel - Channel name.\n * @param type - Event type name.\n * @param payload - Payload typed as `EM[C][T]`.\n * @param opts - Optional per-emit options (e.g. `dedupKey` for identity-based dedup).\n * @returns A promise that resolves once this event's effects have finished.\n * State is already updated synchronously before `emit()` returns.\n *\n * @example Basic usage\n * ```ts\n * await store.emit('ui', 'increment', 1);\n * ```\n *\n * @example With middleware cancellation\n * ```ts\n * store.registerMiddleware((state, event) => {\n * if (event.type === 'dangerous') return false; // cancel\n * return true; // allow\n * });\n *\n * await store.emit('ui', 'dangerous', null); // cancelled, no state change\n * ```\n *\n * @public\n */\n public async emit<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n payload: EM[C][T],\n opts?: EmitOptions,\n ): Promise<void> {\n // Deduplication is OPT-IN (see EmitOptions / StoreSpec.dedupWindowMs).\n // Content-based dedup runs only when `dedupWindowMs > 0`; identity-based\n // dedup runs when an explicit `dedupKey` is supplied. By default neither is\n // active, so legitimate rapid-fire identical events are never silently dropped.\n const dedupKey = opts?.dedupKey;\n const contentWindow = this.dedupConfig.windowMs;\n // `skipDedup` wins over both the per-emit key and the store-level window: callers that\n // already guarantee distinctness must not have events silently coalesced by payload.\n if (opts?.skipDedup !== true && (contentWindow > 0 || dedupKey !== undefined)) {\n const windowMs =\n dedupKey !== undefined && contentWindow <= 0 ? DEFAULT_DEDUP_KEY_WINDOW_MS : contentWindow;\n const fp =\n dedupKey !== undefined\n ? `${channel}::${type}::#${dedupKey}`\n : this.fingerprint(channel as string, type as string, payload);\n if (this.shouldDedupe(fp, windowMs)) {\n return; // Skip duplicate\n }\n }\n\n // Assign a unique id and a completion deferred, resolved after this event's\n // effects run. Reducers run synchronously (see drainReduce), so state is\n // already updated before emit() returns; the returned promise tracks the\n // async effect phase for `await emit(...)`.\n const id = opts?.id ?? this.idFactory();\n let resolve!: () => void;\n const done = new Promise<void>((r) => {\n resolve = r;\n });\n\n this.reduceQueue.push({\n channel: channel as string,\n type: type as string,\n payload,\n id,\n meta: opts?.meta,\n resolve,\n });\n\n // Synchronous reduce phase (drains re-entrant emits too), then async effects.\n this.drainReduce();\n\n return done;\n }\n\n /**\n * Drains the reduce queue **synchronously**. For each event it runs middleware,\n * reducers, event subscribers, and coarse listeners in the same tick, so\n * `getState()` reflects the change the moment {@link emit} returns. Re-entrant\n * emits (from middleware or subscribers) are appended and drained in the same\n * pass — preserving FIFO order without interleaving reducers. Each committed\n * event's effects then run in an independent task (see {@link runEventEffects}).\n *\n * @internal\n */\n private drainReduce(): void {\n if (this.isReducing) return;\n this.isReducing = true;\n try {\n while (this.reduceQueue.length > 0) {\n const { channel, type, payload, id, meta, resolve } = this.reduceQueue.shift()!;\n // Conditional spread, not `meta` unconditionally: when no metadata was supplied the\n // event object stays byte-identical to one built before `meta` existed, so\n // Object.keys / JSON.stringify / toStrictEqual behaviour is unchanged.\n const event = {\n channel,\n type,\n payload,\n id,\n ...(meta !== undefined ? { meta } : {}),\n } as EventUnion<EM>;\n\n // Instrumentation: capture prev state, collect changed paths, and time\n // the synchronous reduce — all skipped entirely when no observers.\n const instrumenting = this.instrumentObservers.size > 0;\n const prevState = instrumenting ? this.state : undefined;\n const sink: string[] | undefined = instrumenting ? [] : undefined;\n if (sink !== undefined) this.changedPathSink = sink;\n const t0 = instrumenting ? now() : 0;\n\n let committed = false;\n try {\n committed = this.applyEventSync(event);\n } catch (err) {\n console.error(\"Emit reduce error:\", err);\n } finally {\n if (instrumenting) this.changedPathSink = null;\n }\n\n if (instrumenting) {\n this.emitInstrumentation(\n event,\n committed,\n sink ?? [],\n prevState as DeepReadonly<S>,\n now() - t0,\n );\n }\n\n // Run this event's effects as an independent task and resolve its\n // completion deferred when they finish. Independent per-event tasks\n // (rather than one shared serialized loop) let an effect `await` a\n // re-entrant emit without deadlocking.\n void this.runEventEffects(event, committed, resolve);\n }\n } finally {\n this.isReducing = false;\n }\n }\n\n /**\n * Runs the **synchronous** part of the pipeline for a single event: middleware\n * (may veto), key- and pattern-based reducers, committed/uncommitted event\n * subscribers (fire-and-forget), and coarse listeners.\n *\n * @returns `true` if the event was committed (passed middleware), `false` if a\n * middleware vetoed it.\n *\n * @internal\n */\n private applyEventSync(event: EventUnion<EM>): boolean {\n // Middleware (synchronous). Return false to veto; async work belongs in effects.\n for (const mwInput of this.middleware) {\n const when = this.getMiddlewareWhen(mwInput);\n if (!this.matchesWhen(when, event)) continue;\n const mw = this.getMiddlewareFunction(mwInput);\n let ok: boolean;\n try {\n ok = mw(this.state, event, this.emit);\n if (\n process.env.NODE_ENV !== \"production\" &&\n typeof (ok as unknown as { then?: unknown })?.then === \"function\"\n ) {\n // A Promise is truthy, so an async middleware silently allows everything: the event\n // commits while the middleware is still deciding, and the veto it was written to\n // perform can never fire. Caught here because the symptom — a rule that simply does\n // not apply — looks nothing like its cause.\n console.error(\n `[yoltra] Middleware for \"${event.channel}/${event.type}\" returned a Promise. ` +\n `Middleware is synchronous: a Promise is truthy, so this event was allowed ` +\n `without waiting and a \"return false\" inside it can never veto. Do the check ` +\n `synchronously, and put anything that must await in an effect.`,\n );\n }\n } catch (err) {\n console.error(\"Middleware error:\", err);\n ok = false;\n }\n if (!ok) {\n // Rejected by middleware — notify uncommitted subscribers, do not commit.\n this.notifyEventSubscribers(event, \"uncommitted\");\n return false;\n }\n }\n\n // Reducers — track whether any slice changed via reference equality.\n const stateBefore = this.state;\n // Pass the event itself, not just the payload: keyed reducers are wired through\n // `reducerBus` in `mountSlice` and would otherwise have to invent an id.\n this.reducerBus.emit(\n event.channel as any,\n event.type as any,\n event.payload as any,\n event as any,\n );\n for (const [sliceName, when] of this.patternReducers) {\n if (this.matchesWhen(when, event)) {\n this.forwardEventGuarded(sliceName, event as any);\n }\n }\n const changed = stateBefore !== this.state;\n\n // Committed event subscribers (fire-and-forget), then coarse listeners.\n this.notifyEventSubscribers(event, \"committed\");\n if (changed) {\n this.listeners.forEach((l) => l());\n }\n return true;\n }\n\n /**\n * Runs a single committed event's effects as an **independent async task**,\n * then resolves that event's completion deferred so `await emit(...)` settles\n * once its effects finish. Per-event tasks (rather than one shared serialized\n * loop) let an effect `await` a re-entrant emit without deadlocking.\n *\n * @internal\n */\n private async runEventEffects(\n event: EventUnion<EM>,\n committed: boolean,\n resolve: () => void,\n ): Promise<void> {\n this.inFlightEffects++;\n try {\n if (committed) await this.notifyEffects(event);\n } catch (err) {\n console.error(\"Effect error:\", err);\n } finally {\n this.inFlightEffects--;\n resolve();\n }\n }\n\n /**\n * Registers an instrumentation observer. See {@link StoreInstance.instrument}.\n *\n * @public\n */\n public instrument(observer: InstrumentationObserver<EM>): Unsubscribe {\n this.instrumentObservers.add(observer);\n return () => {\n this.instrumentObservers.delete(observer);\n };\n }\n\n /**\n * Builds an {@link InstrumentedEvent} from the reduce result and notifies\n * observers. `changedPaths` are the exact slice-prefixed leaf paths recorded\n * by {@link forwardEvent} during this reduce, so DevTools patches need no\n * re-diff.\n *\n * @internal\n */\n private emitInstrumentation(\n event: EventUnion<EM>,\n committed: boolean,\n changedPaths: string[],\n prevState: DeepReadonly<S>,\n reduceTimeMs: number,\n ): void {\n const prevValues: Record<string, unknown> = {};\n const nextValues: Record<string, unknown> = {};\n for (const path of changedPaths) {\n prevValues[path] = this.getAtPath(prevState, path);\n nextValues[path] = this.getAtPath(this.state, path);\n }\n const info: InstrumentedEvent<EM> = {\n event: {\n id: event.id,\n channel: event.channel as string,\n type: event.type as string,\n payload: event.payload,\n // Conditional, so an event without metadata produces an observer payload\n // byte-identical to the pre-`meta` shape.\n ...(event.meta !== undefined ? { meta: event.meta } : {}),\n },\n committed,\n changedPaths,\n prevValues,\n nextValues,\n reduceTimeMs,\n };\n for (const observer of [...this.instrumentObservers]) {\n try {\n observer(info);\n } catch (e) {\n console.error(\"Instrumentation observer error:\", e);\n }\n }\n }\n\n /**\n * Connects a **fine-grained** listener to a dotted path under a slice.\n *\n * @param spec - `{ reducer, property }` where `property` is a dotted path (e.g., `\"items.0.title\"`).\n * Supports wildcards: `*` (one segment) and `**` (zero or more segments).\n * @param h - Handler receiving a {@link Change} with `{ oldValue, newValue, path }`.\n * @returns Unsubscribe function.\n *\n * @example Exact path\n * ```ts\n * const off = store.connect(\n * { reducer: 'todos', property: 'items.0.title' },\n * (chg) => console.log('title changed:', chg.newValue)\n * );\n * off();\n * ```\n *\n * @example Wildcard pattern\n * ```ts\n * // Listen to any item title change\n * const off = store.connect(\n * { reducer: 'todos', property: 'items.*.title' },\n * (chg) => console.log('some title changed')\n * );\n * ```\n *\n * @public\n */\n public connect(spec: { reducer: R; property: string }, h: (chg: Change) => void): () => void {\n return this.connectorBus.on(spec.reducer, spec.property, h);\n }\n\n /**\n * Subscribe to events by channel and type.\n *\n * Event subscriptions are intended for the View layer (e.g., React components)\n * to react to events without affecting the event flow. They are fire-and-forget\n * and cannot cancel event propagation.\n *\n * **Phases:**\n * - `'committed'` (default): Events that passed middleware and reached reducers.\n * Notified after reducers, before effects.\n * - `'uncommitted'`: Events rejected by middleware. Notified immediately after rejection.\n * - `'all'`: Both committed and uncommitted events. Handler receives the phase parameter\n * to distinguish between the two.\n *\n * @typeParam C - Channel key within `EM`.\n * @typeParam T - Event type key within channel `C`.\n * @param channel - Channel to subscribe to.\n * @param type - Event type to subscribe to.\n * @param handler - Handler function `(event, getState, emit, phase)`.\n * @param phase - Event phase to subscribe to (default: `'committed'`).\n * @returns Unsubscribe function.\n *\n * @example Committed events (default)\n * ```ts\n * const off = store.onEvent('ui', 'save', (event, getState, emit, phase) => {\n * console.log('Save committed:', event.payload);\n * });\n * off();\n * ```\n *\n * @example Uncommitted (rejected) events\n * ```ts\n * store.onEvent('ui', 'delete', (event, getState, emit, phase) => {\n * console.log('Delete was rejected by middleware');\n * }, 'uncommitted');\n * ```\n *\n * @example All events\n * ```ts\n * store.onEvent('ui', 'action', (event, getState, emit, phase) => {\n * console.log('Action:', phase); // 'committed' or 'uncommitted'\n * }, 'all');\n * ```\n *\n * @public\n */\n public onEvent<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n handler: NarrowedEventHandler<DeepReadonly<S>, EM, C, T>,\n phase: EventPhase = \"committed\",\n ): Unsubscribe {\n const key = `${channel}::${String(type)}`;\n\n const targetMap =\n phase === \"committed\"\n ? this.committedEventSubscribers\n : phase === \"uncommitted\"\n ? this.uncommittedEventSubscribers\n : this.allEventSubscribers;\n\n if (!targetMap.has(key)) {\n targetMap.set(key, new Set());\n }\n // Store handler with type cast since internal storage uses the broad type\n targetMap.get(key)!.add(handler as EventSubscriptionHandler<DeepReadonly<S>, EM>);\n\n return () => {\n const set = targetMap.get(key);\n if (set) {\n set.delete(handler as EventSubscriptionHandler<DeepReadonly<S>, EM>);\n if (set.size === 0) targetMap.delete(key);\n }\n };\n }\n\n /**\n * Subscribes to **coarse-grained** commits (called once per successful event, only if state changed).\n *\n * **Use Case**: React's `useSyncExternalStore` or similar external store integrations.\n *\n * @param fn - Listener invoked after reducers/effects have run and state has changed.\n * @returns Unsubscribe function.\n *\n * @example\n * ```ts\n * const off = store.subscribe(() => console.log('state committed'));\n * // Later:\n * off();\n * ```\n *\n * @public\n */\n public subscribe(fn: () => void): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n /**\n * Returns the current immutable state snapshot.\n *\n * @returns Deep-readonly state object.\n *\n * @example\n * ```ts\n * const state = store.getState();\n * console.log(state.counter.value);\n * ```\n *\n * @public\n */\n public getState(): DeepReadonly<S> {\n return this.state;\n }\n\n /**\n * Registers a middleware (runs **before** reducers).\n *\n * @param mw - Middleware `(state, event, emit) => boolean`. Return `false` to cancel event\n * propagation.\n * @returns Unsubscribe function that removes this middleware.\n *\n * @remarks\n * **Synchronous, and that is the contract.** The reduce phase completes before `emit()`\n * returns, so the commit decision has to be available in the same tick. An `async` middleware\n * returns a Promise, every Promise is truthy, and the veto would therefore never fire — the\n * event would commit while the middleware was still deciding. The type rejects it; this note\n * exists because the examples here used to teach it. Do authorization and validation here, and\n * anything that needs to await in an effect.\n *\n * @example Logging middleware\n * ```ts\n * const off = store.registerMiddleware((state, event) => {\n * console.log('Event:', event.channel, event.type, event.payload);\n * return true; // allow\n * });\n * off();\n * ```\n *\n * @example Cancellation middleware\n * ```ts\n * store.registerMiddleware((state, event) => {\n * if (event.type === 'forbidden') return false; // cancel\n * return true;\n * });\n * ```\n *\n * @public\n */\n public registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): Unsubscribe {\n this.middleware.push(mw as any);\n return () => {\n const i = this.middleware.indexOf(mw as any);\n if (i !== -1) this.middleware.splice(i, 1);\n };\n }\n\n /**\n * Dynamically **adds** a named slice reducer at runtime.\n *\n * @param name - New slice name (must not already exist).\n * @param spec - Reducer spec (state, when, reducer).\n * @returns Disposer function that **removes** the slice (and its state).\n *\n * @example\n * ```ts\n * const dispose = store.registerReducer('filters', {\n * state: { q: '' },\n * events: [['ui', 'setQuery']],\n * reducer(s, evt) {\n * return evt.type === 'setQuery' ? { q: evt.payload } : s;\n * }\n * });\n * // Later:\n * dispose();\n * ```\n *\n * @public\n */\n public registerReducer(name: string, spec: ReducerSpec<any, EM>): () => void {\n // `hasOwnProperty`, not `in`: the registry is a plain object, so `in` also answers true for\n // everything on `Object.prototype`. A slice legitimately named `toString`, `constructor` or\n // `valueOf` was refused as already existing — with a message naming a reducer that does not\n // exist, which is the least useful place to send someone.\n if (Object.prototype.hasOwnProperty.call(this.reducers, name)) {\n throw new Error(`Reducer ${name} already exists`);\n }\n\n this.mountSlice(name as R, spec as ReducerSpec<S[R], EM>, {\n preserveState: false,\n });\n\n this.listeners.forEach((l) => l()); // broadcast new slice\n\n return () => {\n // disposer\n this.unmountSlice(name as R, { deleteState: true });\n this.listeners.forEach((l) => l());\n };\n }\n\n /**\n * Registers an **effect** (stateless async event consumer) that runs after reducers.\n *\n * Effects are **keyed** by `(channel, type)` for O(1) lookup (no scanning all effects).\n *\n * @param spec - Effect specification with `when` targeting and `effect` (handler).\n * @returns Unsubscribe function.\n *\n * @example Logging effect\n * ```ts\n * const off = store.registerEffect({\n * events: [['ui', 'increment']],\n * effect: async (evt, getState, emit) => {\n * console.log('increment', evt.payload, getState().counter.value);\n * }\n * });\n * off();\n * ```\n *\n * @example Multi-event effect\n * ```ts\n * store.registerEffect({\n * events: [['ui', 'increment'], ['ui', 'decrement']],\n * effect: async (evt, getState, emit) => {\n * // Runs for both increment and decrement\n * await saveToServer(getState());\n * }\n * });\n * ```\n *\n * @public\n */\n public registerEffect(spec: EffectSpec<DeepReadonly<S>, EM>): () => void {\n const { effect, meta, when } = spec;\n const unsubs: Array<() => void> = [];\n\n // Record metadata in a store-owned map keyed by the effect function, rather\n // than mutating the caller's function (which would bleed across stores).\n if (meta) {\n this.effectMeta.set(effect, meta);\n }\n\n // Check if this is a pattern-based effect (any, channel, channels)\n // or a key-based effect (keys, or no targeting = all events)\n const isPatternBased =\n when &&\n ((\"any\" in when && when.any === true) ||\n \"channel\" in when ||\n \"channels\" in when);\n\n if (isPatternBased) {\n // Store as pattern-based effect for runtime matching\n const entry = { effect, when: when! };\n this.patternEffects.add(entry);\n\n return () => {\n this.patternEffects.delete(entry);\n };\n }\n\n // Key-based effect: normalize to event keys\n const eventKeys = this.normalizeEventKeys(spec);\n\n // If no keys (no targeting at all), this effect matches ALL events\n // We treat it as a pattern-based effect with `any: true`\n if (eventKeys.length === 0 && !when) {\n const entry = { effect, when: { any: true } as When<EM> };\n this.patternEffects.add(entry);\n\n return () => {\n this.patternEffects.delete(entry);\n };\n }\n\n // Register for specific event keys\n for (const [channel, type] of eventKeys) {\n const key = `${String(channel)}::${String(type)}`;\n if (!this.effects.has(key)) {\n this.effects.set(key, new Set());\n }\n this.effects.get(key)!.add(effect);\n\n // Create disposer\n unsubs.push(() => {\n const set = this.effects.get(key);\n if (set) {\n set.delete(effect);\n if (set.size === 0) this.effects.delete(key);\n }\n });\n }\n\n return () => {\n for (const u of unsubs) u();\n };\n }\n\n\n\n /**\n * Convenience helper to register an **effect** filtered by a single `(channel, type)` pair.\n *\n * @typeParam C - Channel key within `EM`.\n * @typeParam T - Event type key within channel `C`.\n * @param channel - Channel to filter.\n * @param type - Event type to filter.\n * @param handler - Effect handler `(payload, getState, emit, event)`.\n * @returns Unsubscribe/teardown function.\n *\n * @example\n * ```ts\n * const off = store.onEffect('ui', 'increment', async (n, get, emit) => {\n * if (n > 10) await emit('ui', 'increment', -10);\n * });\n * // later\n * off();\n * ```\n *\n * @public\n */\n public onEffect<\n C extends keyof EM & string,\n T extends keyof EM[C] & string\n >(\n channel: C,\n type: T,\n handler: (\n payload: EM[C][T],\n getState: () => DeepReadonly<S>,\n emit: Emit<EM>,\n event: Event<EM, C, T>,\n ) => void | Promise<void>,\n ): () => void {\n const effect: EffectFunction<DeepReadonly<S>, EM> = async (evt, getState, emit) => {\n if (evt.channel !== channel || evt.type !== type) return;\n\n const typed = evt as Event<EM, C, T>;\n return handler(typed.payload, getState, emit, typed);\n };\n\n return this.registerEffect({\n when: { keys: [[channel, type] as EventKey<EM>] },\n effect,\n });\n }\n\n /**\n * Replaces the **entire** middleware pipeline (HMR-friendly).\n *\n * @param next - New middleware array.\n *\n * @example Hot module replacement\n * ```ts\n * if (import.meta.hot) {\n * import.meta.hot.accept('./middleware', (newModule) => {\n * store.replaceMiddleware(newModule.middleware);\n * });\n * }\n * ```\n *\n * @public\n */\n public replaceMiddleware(next: MiddlewareInput<DeepReadonly<S>, EM>[]): void {\n // Accepts either form. Taking only the bare function meant a hot reload silently discarded\n // the `when` targeting and `meta` of every spec-form middleware, so after an HMR pass a\n // middleware scoped to one channel began running on all of them.\n (this.middleware as any).length = 0;\n for (const mw of next) this.middleware.push(mw as any);\n }\n\n /**\n * Replaces all registered **effects** (HMR-friendly).\n *\n * @param next - New effects array (as EffectSpecs).\n *\n * @example Hot module replacement\n * ```ts\n * if (import.meta.hot) {\n * import.meta.hot.accept('./effects', (newModule) => {\n * store.replaceEffects(newModule.effects);\n * });\n * }\n * ```\n *\n * @public\n */\n public replaceEffects(next: Array<EffectSpec<DeepReadonly<S>, EM>>): void {\n this.effects.clear();\n this.patternEffects.clear();\n for (const spec of next) {\n this.registerEffect(spec);\n }\n }\n\n /**\n * Replaces the entire **reducer set** (HMR-friendly).\n *\n * @param next - Map of slice specs keyed by slice name.\n * @param opts - `{ preserveState?: boolean }` (default `true`).\n *\n * @example Hot module replacement\n * ```ts\n * if (import.meta.hot) {\n * import.meta.hot.accept('./reducers', (newModule) => {\n * store.replaceReducers(newModule.reducers, { preserveState: true });\n * });\n * }\n * ```\n *\n * @public\n */\n public replaceReducers(\n next: Record<R, ReducerSpec<S[R], EM>>,\n opts: { preserveState?: boolean } = {},\n ): void {\n const preserveState = opts.preserveState !== false; // default true\n\n const currentKeys = new Set(Object.keys(this.reducers as any));\n const nextEntries = Object.entries(next);\n const nextKeys = new Set(nextEntries.map(([k]) => k));\n\n // Remove slices that no longer exist\n for (const k of currentKeys) {\n if (!nextKeys.has(k)) this.unmountSlice(k as R, { deleteState: true });\n }\n\n // Add or update slices\n for (const [k, rSpec] of nextEntries) {\n if (currentKeys.has(k)) {\n // Update reducer impl + event wiring; preserve current state\n this.unmountSlice(k as R, { deleteState: false });\n this.mountSlice(k as R, rSpec as any, { preserveState });\n } else {\n // New slice\n this.mountSlice(k as R, rSpec as any, { preserveState: false });\n }\n }\n\n }\n\n /**\n * Convenience API to replace **any subset** of store parts (HMR patterns).\n *\n * @param partial - Partial replacement set.\n *\n * @example Replace everything\n * ```ts\n * store.hotReplace({\n * reducer: newReducers,\n * middleware: newMiddleware,\n * effects: newEffects,\n * preserveState: true\n * });\n * ```\n *\n * @public\n */\n public hotReplace(partial: {\n reducer?: Record<R, ReducerSpec<S[R], EM>>;\n middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];\n effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;\n preserveState?: boolean;\n }): void {\n if (partial.middleware) this.replaceMiddleware(partial.middleware);\n if (partial.effects) this.replaceEffects(partial.effects);\n if (partial.reducer)\n this.replaceReducers(partial.reducer, { preserveState: partial.preserveState });\n }\n\n /**\n * Mounts a slice: installs reducer, initializes state (unless preserved),\n * and wires `(channel, type)` listeners on the reducer bus.\n *\n * @param name - Slice name.\n * @param rSpec - Reducer spec (state, when, reducer).\n * @param opts - `{ preserveState: boolean }` whether to keep existing state.\n *\n * @internal\n */\n private mountSlice(\n name: R,\n rSpec: ReducerSpec<S[R], EM>,\n opts: { preserveState: boolean },\n ): void {\n const rName = name as unknown as string;\n const { reducer, state, when } = rSpec;\n\n // Install reducer instance (FIXED: only pass reducer function)\n this.reducers[name] = new Reducer(reducer);\n\n // Initialize state unless preserving an existing value\n if (!opts.preserveState || (this.state as any)[rName] === undefined) {\n // A NEW root, not a write into the existing one. Mounting a slice is a state change, and\n // anything keyed on root identity — `useSelector` bailing out on `Object.is`, a memo, a\n // devtools snapshot differ — could not see it when the root object stayed the same.\n // Clone the caller's initial state so the store owns an independent copy; freeze is\n // dev-only.\n this.state = {\n ...(this.state as object),\n [rName]: freezeInDev(cloneInitialState(rName, state)),\n } as DeepReadonly<S>;\n }\n\n // Check if this is a pattern-based reducer (any, channel, channels)\n const isPatternBased =\n when &&\n ((\"any\" in when && when.any === true) ||\n \"channel\" in when ||\n \"channels\" in when);\n\n if (isPatternBased) {\n // Store as pattern-based reducer for runtime matching\n this.patternReducers.set(name, when);\n // No unsubs needed for pattern reducers - they're called from emit loop\n this.sliceUnsubs.set(rName, []);\n return;\n }\n\n // Normalize event keys from `when: { keys }`\n const eventKeys = this.normalizeEventKeys(rSpec);\n\n // If no targeting at all, treat as \"all events\" (pattern-based)\n if (eventKeys.length === 0 && !when) {\n this.patternReducers.set(name, { any: true });\n this.sliceUnsubs.set(rName, []);\n return;\n }\n\n // Wire reducerBus listeners and save disposers for HMR\n const unsubs: Array<() => void> = [];\n for (const [ch, tp] of eventKeys) {\n const u = this.reducerBus.on(ch, tp, (payload, sourceEvent) => {\n // Prefer the source event so keyed reducers see the same `id` (and `meta`) as\n // pattern reducers, effects, event subscribers and instrumentation. The fallback\n // only applies when something emits on `reducerBus` without an event.\n const event = (sourceEvent ?? {\n channel: ch,\n type: tp,\n payload,\n id: this.idFactory(),\n }) as Event<EM, typeof ch, typeof tp>;\n this.forwardEventGuarded(name, event as any);\n });\n\n unsubs.push(u);\n }\n\n this.sliceUnsubs.set(rName, unsubs);\n }\n\n /**\n * Unmounts a slice: disposes reducer-bus listeners, removes reducer,\n * and optionally deletes the slice state.\n *\n * @param name - Slice name.\n * @param opts - `{ deleteState: boolean }`.\n *\n * @internal\n */\n private unmountSlice(name: R, opts: { deleteState: boolean }): void {\n const rName = name as unknown as string;\n\n // Remove from pattern reducers if present\n this.patternReducers.delete(name);\n\n // Dispose reducerBus listeners\n const unsubs = this.sliceUnsubs.get(rName);\n if (unsubs) {\n for (const u of unsubs)\n try {\n u();\n } catch (e) {\n console.error(`[Store error]: ${e}`);\n }\n\n this.sliceUnsubs.delete(rName);\n }\n\n // Remove reducer instance\n delete this.reducers[name];\n\n // Optionally drop state\n if (opts.deleteState) {\n const { [rName]: _removed, ...rest } = this.state as Record<string, unknown>;\n this.state = rest as DeepReadonly<S>;\n }\n }\n\n /**\n * Normalizes event targeting from `when` to an array of EventKeys.\n *\n * @param spec - Object with an optional `when` matcher.\n * @returns Array of `[channel, type]` pairs.\n *\n * @internal\n */\n private normalizeEventKeys(spec: {\n when?: When<EM>;\n events?: ReadonlyArray<EventKey<EM>>;\n }): ReadonlyArray<EventKey<EM>> {\n\n if (spec.when) {\n const when = spec.when;\n\n // Only `keys` can reach this point: both callers intercept pattern-based matchers\n // (`any`, `channel`, `channels`) before normalizing, because those register against the\n // emit loop rather than against per-key handler maps.\n if (\"keys\" in when) {\n return when.keys;\n }\n }\n\n // No targeting specified\n return [];\n }\n\n /**\n * Reads a dotted path from an object (supports numeric array indices via string keys).\n *\n * @param obj - Root object (slice or value).\n * @param path - Dotted path; leading dot is ignored.\n * @returns The value at the path, or `undefined`.\n *\n * @internal\n */\n private getAtPath(obj: any, path: string): any {\n if (!path) return obj;\n\n // Normalize any accidental leading dots\n const clean = path[0] === \".\" ? path.slice(1) : path;\n const parts = clean.split(\".\");\n\n let cur = obj;\n for (const seg of parts) {\n if (cur == null) return undefined;\n cur = cur[seg as any];\n }\n return cur;\n }\n\n /**\n * Builds ancestor paths for a dotted path.\n *\n * For `\"a.b.c\"`, returns `[\"a\", \"a.b\", \"a.b.c\"]`. Leading dots are trimmed.\n *\n * @param path - Dotted path string.\n * @returns Array of ancestor paths.\n *\n * @example\n * ```ts\n * Store.buildAncestorPaths('x.y.z'); // ['x','x.y','x.y.z']\n * ```\n *\n * @public\n */\n static buildAncestorPaths(path: string): string[] {\n if (!path) return [];\n\n const clean = path[0] === \".\" ? path.slice(1) : path;\n const parts = clean.split(\".\");\n const out: string[] = [];\n\n for (let i = 0; i < parts.length; i++) {\n out.push(parts.slice(0, i + 1).join(\".\"));\n }\n\n return out;\n }\n}\n\n/**\n * Creates a store with explicit State and EventMap types.\n *\n * Use this overload for:\n * - **Event-only stores** (no reducers, just middleware/effects)\n * - When TypeScript inference from reducers isn't sufficient\n * - When you want to define the EventMap independently of reducers\n *\n * @typeParam S - State record type (can be empty `{}` for event-only stores).\n * @typeParam EM - Event map type defining all `channel → type → payload` combinations.\n * @param cfg - Configuration with `name`, optional `reducer`, optional `middleware`, optional `effects`.\n * @returns A typed {@link StoreInstance}.\n *\n * @example Event-only store\n * ```ts\n * type AppEM = {\n * notifications: { show: { message: string }; hide: void };\n * };\n *\n * const store = createStore<{}, AppEM>({\n * name: 'NotificationBus',\n * effects: [{\n * when: { channel: 'notifications' },\n * effect: (evt) => {\n * if (evt.type === 'show') showToast(evt.payload.message);\n * },\n * }],\n * });\n * ```\n *\n * @example Explicit generics with reducers\n * ```ts\n * const store = createStore<AppState, AppEM>({\n * name: 'App',\n * reducer: { counter: counterSpec },\n * middleware: [loggingMiddleware],\n * });\n * ```\n *\n * @public\n */\nexport function createStore<\n S extends Record<string, any>,\n EM extends EventMapBase,\n>(cfg: {\n name: string;\n reducer?: { [K in keyof S]?: ReducerSpec<S[K], EM> };\n middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];\n effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;\n dedupWindowMs?: number;\n idFactory?: () => string;\n devtools?: { allowReplay?: boolean };\n onEffectError?: (error: unknown, event: EventUnion<EM>) => void;\n onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;\n}): StoreInstance<keyof S & string, S, EM>;\n\n/**\n * Creates a store with types inferred from the reducers map.\n *\n * This is the primary overload for most use cases where reducers define\n * both the state shape and the event map.\n *\n * @typeParam RM - Reducers map object with each slice's `ReducerSpec`.\n * @param cfg - Configuration with `name`, `reducer`, optional `middleware`, optional `effects`.\n * @returns A typed {@link StoreInstance}.\n *\n * @example\n * ```ts\n * const store = createStore({\n * name: 'App',\n * reducer: {\n * counter: {\n * state: { value: 0 },\n * when: { keys: eventKeys<MyEM>()([['ui', 'increment']]) },\n * reducer: (s, evt) => evt.type === 'increment' ? { value: s.value + evt.payload } : s\n * }\n * },\n * middleware: [],\n * effects: []\n * });\n * ```\n *\n * @public\n */\nexport function createStore<RM extends ReducersMapAny>(cfg: {\n name: string;\n reducer: RM;\n middleware?: MiddlewareInput<\n DeepReadonly<StateFromReducers<RM>>,\n EMFromReducersStrict<RM>\n >[];\n effects?: Array<EffectSpec<DeepReadonly<StateFromReducers<RM>>, EMFromReducersStrict<RM>>>;\n dedupWindowMs?: number;\n idFactory?: () => string;\n devtools?: { allowReplay?: boolean };\n onEffectError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>) => void;\n onReducerError?: (\n error: unknown,\n event: EventUnion<EMFromReducersStrict<RM>>,\n slice: string,\n ) => void;\n}): StoreInstance<keyof RM & string, StateFromReducers<RM>, EMFromReducersStrict<RM>>;\n\nexport function createStore(cfg: any) {\n type RM = typeof cfg.reducer;\n type S = StateFromReducers<RM>;\n type EM = EMFromReducersStrict<RM>;\n type RN = keyof RM & string;\n\n return new Store<EM, RN, S>({\n name: cfg.name,\n reducer: (cfg.reducer ?? {}) as unknown as Record<RN, ReducerSpec<S[RN], EM>>,\n middleware: (cfg.middleware ?? []) as any,\n effects: (cfg.effects ?? []) as any,\n dedupWindowMs: cfg.dedupWindowMs,\n idFactory: cfg.idFactory,\n devtools: cfg.devtools,\n onEffectError: cfg.onEffectError,\n onReducerError: cfg.onReducerError,\n });\n}\n\n/**\n * Utility to define **typed** `(channel, events[])` definitions for reducer specs.\n *\n * @typeParam EM - Event map for the store.\n * @param _ - Internal marker parameter (usually `events` array placeholder). Not used at runtime.\n * @returns A helper that, given a `channel` and a readonly `events` array, returns typed event keys.\n *\n * @example\n * ```ts\n * // In a ReducerSpec:\n * const events = typedEvents<EM>([])('ui', ['increment', 'decrement'] as const);\n * // events: ReadonlyArray<EventKey<EM>>\n * ```\n *\n * @public\n */\nexport const typedEvents = <EM extends EventMapBase>(_: string[][]) =>\n <C extends keyof EM & string, Evt extends readonly (keyof EM[C] & string)[]>(\n channel: C,\n events: Evt,\n ): ReadonlyArray<EventKey<EM>> => events.map((e) => [channel, e] as const);","/**\n * @module @yoltra/core\n */\n\n/**\n * A minimal \"record of record\" constraint for EventMaps.\n *\n * @example\n * ```ts\n * type EM = {\n * ui: { toggle: boolean; setTheme: string };\n * data: { loaded: { items: string[] } };\n * };\n * ```\n *\n * @public\n */\nexport type EventMapBase = {\n [C in string]: { [T in string]: unknown };\n};\n\n/**\n * Canonical routing concept: a readonly tuple `[channel, type]` that uniquely identifies an event.\n *\n * @typeParam EM - Event map.\n *\n * @remarks\n * - Used consistently across ReducerSpec, EffectSpec, and React hooks.\n * - Literal key lists narrow channel/type/payload in reducers and effects.\n * - Non-literal usage degrades safely to unions.\n *\n * @example\n * ```ts\n * type EM = {\n * ui: { increment: number; decrement: number };\n * data: { loaded: string[] };\n * };\n *\n * type K = EventKey<EM>;\n * // K = ['ui', 'increment'] | ['ui', 'decrement'] | ['data', 'loaded']\n *\n * const key: EventKey<EM> = ['ui', 'increment'];\n * ```\n *\n * @public\n */\nexport type EventKey<EM extends EventMapBase> = {\n [C in keyof EM & string]: [C, keyof EM[C] & string];\n}[keyof EM & string];\n\n/**\n * Opaque, optional envelope metadata carried alongside an {@link Event}.\n *\n * @remarks\n * The store never reads, validates or acts on this — it only carries it end to end, so\n * reducers, middleware, effects, event subscribers and instrumentation all observe the same\n * value. It is deliberately untyped at this level: consumers namespace their own keys (for\n * example a tracing integration keeping provenance under `meta.trace`) rather than\n * extending core with domain concepts.\n *\n * It is **not** part of the deduplication fingerprint, which is computed from\n * `(channel, type, payload)` only. Two events differing solely in `meta` still dedupe.\n *\n * @example\n * ```ts\n * await store.emit('orders', 'created', payload, {\n * meta: { trace: { origin: 'checkout-service', hop: 1 } },\n * });\n * ```\n *\n * @public\n */\nexport type EventMeta = Readonly<Record<string, unknown>>;\n\n/**\n * A single event object: `{ channel, type, payload, id }`, plus optional `meta`.\n *\n * @typeParam EM - Event map.\n * @typeParam C - Channel key.\n * @typeParam T - Type key within channel `C`.\n * @typeParam P - Payload type (defaults to `EM[C][T]`).\n *\n * @remarks\n * - The `id` field is automatically added by the store to enable deduplication, unless the\n * emitter supplies one via {@link EmitOptions.id}.\n * - Used for preventing duplicate event processing (e.g., React Strict Mode).\n * - `meta` is present only when {@link EmitOptions.meta} was supplied. See {@link EventMeta}.\n *\n * @example\n * ```ts\n * type EM = { ui: { toggle: boolean } };\n * type Evt = Event<EM, 'ui', 'toggle'>;\n * // { channel: 'ui'; type: 'toggle'; payload: boolean; id: string; meta?: EventMeta }\n * ```\n *\n * @public\n */\nexport interface Event<\n EM extends EventMapBase = EventMapBase,\n C extends keyof EM & string = keyof EM & string,\n T extends keyof EM[C] & string = keyof EM[C] & string,\n P = EM[C][T],\n> {\n channel: C;\n type: T;\n payload: P;\n /** Unique identifier for deduplication and devtools tracking (automatically added by store) */\n id: string;\n /**\n * Optional caller-supplied metadata, carried through the pipeline untouched.\n * Absent entirely unless {@link EmitOptions.meta} was supplied. See {@link EventMeta}.\n */\n readonly meta?: EventMeta;\n}\n\n/**\n * Generic \"old → new\" wrapper for fine-grained change notifications.\n * Carries the dotted `path` that changed.\n *\n * @typeParam V - Value type at the changed path.\n *\n * @example\n * ```ts\n * const change: Change<string> = {\n * oldValue: 'foo',\n * newValue: 'bar',\n * path: 'user.name'\n * };\n * ```\n *\n * @public\n */\nexport interface Change<V = any> {\n oldValue: V;\n newValue: V;\n /** Dotted path for fine-grained listeners; e.g., \"data.items.0.title\" */\n path?: string;\n}\n\n/**\n * Emit function narrowed to the developer's EventMap.\n * Returns a Promise that resolves when the event has been fully processed.\n *\n * @typeParam EM - Event map.\n *\n * @example\n * ```ts\n * type EM = { ui: { increment: number } };\n * const emit: Emit<EM> = async (channel, type, payload) => { /* ... *\\/ };\n * await emit('ui', 'increment', 1);\n * ```\n *\n * @public\n */\n/**\n * Per-emit options.\n *\n * @public\n */\nexport interface EmitOptions {\n /**\n * Opt this specific emit into **identity-based** deduplication: if another\n * event with the same `(channel, type, dedupKey)` was emitted within the dedup\n * window, this one is skipped. Unlike content-based dedup\n * ({@link StoreSpec.dedupWindowMs}), it never coalesces two *distinct* logical\n * emits that merely share a payload — only re-fires of the *same* keyed emit\n * (e.g. a React Strict Mode double-invoke). Works even when `dedupWindowMs`\n * is 0, using a short default window.\n */\n dedupKey?: string;\n\n /**\n * Use this exact id for the event instead of generating one.\n *\n * @remarks\n * Intended for **idempotent re-emission**: a caller replaying an event from elsewhere (a\n * peer store, a durable log) can preserve the original id so the same logical event keeps\n * one identity everywhere, which makes it traceable across systems and in DevTools.\n *\n * The store does **not** enforce uniqueness — supplying a duplicate id does not dedupe the\n * event. Deduplication is a separate, opt-in concern; see {@link EmitOptions.dedupKey}.\n */\n id?: string;\n\n /**\n * Metadata to attach to this event, carried through the pipeline untouched and visible to\n * reducers, middleware, effects, subscribers and instrumentation. See {@link EventMeta}.\n *\n * @remarks\n * Omitting this leaves `event.meta` genuinely absent rather than `undefined`, so event\n * objects are byte-identical to those produced before this option existed.\n */\n meta?: EventMeta;\n\n /**\n * Bypass deduplication for this emit entirely, even when the store was created with\n * {@link StoreSpec.dedupWindowMs} greater than 0.\n *\n * @remarks\n * Content-based dedup fingerprints `(channel, type, payload)`, so a store with a dedup\n * window silently collapses genuinely distinct events that happen to share a payload —\n * repeated ticks with an empty payload, or the same event legitimately arriving twice from\n * two different sources. Set this when the caller already guarantees distinctness by other\n * means and needs every emit to land.\n *\n * Takes precedence over both {@link EmitOptions.dedupKey} and the store-level window.\n */\n skipDedup?: boolean;\n}\n\nexport type Emit<EM extends EventMapBase> = <\n C extends keyof EM & string,\n T extends keyof EM[C] & string,\n>(\n channel: C,\n type: T,\n payload: EM[C][T],\n opts?: EmitOptions,\n) => Promise<void>;\n\n/**\n * Basic unsubscribe handle.\n *\n * @public\n */\nexport type Unsubscribe = () => void;\n\n/**\n * A single observed event delivered to an {@link InstrumentationObserver}.\n *\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport interface InstrumentedEvent<EM extends EventMapBase = EventMapBase> {\n /**\n * The processed event, including its `id` and any {@link EventMeta} the emitter attached.\n * `meta` is absent unless it was supplied.\n */\n event: { id: string; channel: string; type: string; payload: unknown; meta?: EventMeta };\n /** `true` if the event passed middleware and ran reducers; `false` if vetoed. */\n committed: boolean;\n /**\n * Dotted **leaf** paths that changed, prefixed with the slice name (e.g.\n * `\"todos.items.0.title\"`). Empty when nothing changed. These are the exact\n * paths the store computed while reducing — no re-diff required.\n */\n changedPaths: string[];\n /** Old value at each changed path, keyed by path. */\n prevValues: Record<string, unknown>;\n /** New value at each changed path, keyed by path. */\n nextValues: Record<string, unknown>;\n /** Wall-clock milliseconds spent in the synchronous reduce phase for this event. */\n reduceTimeMs: number;\n}\n\n/**\n * Observer for {@link StoreInstance.instrument}. Called once per emitted event\n * (committed or vetoed), after the synchronous reduce phase.\n *\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport type InstrumentationObserver<EM extends EventMapBase = EventMapBase> = (\n info: InstrumentedEvent<EM>,\n) => void;\n\n/**\n * Store spec - what you feed into the constructor / factory.\n *\n * @typeParam R - Reducer name union (string literal union).\n * @typeParam S - State record keyed by `R`.\n * @typeParam EM - Event map.\n *\n * @example\n * ```ts\n * type S = { counter: { value: number } };\n * type EM = { ui: { increment: number } };\n *\n * const spec: StoreSpec<'counter', S, EM> = {\n * name: 'App',\n * reducer: {\n * counter: {\n * state: { value: 0 },\n * events: [['ui', 'increment']],\n * reducer(s, evt) {\n * if (evt.type === 'increment') return { value: s.value + evt.payload };\n * return s;\n * }\n * }\n * }\n * };\n * ```\n *\n * @public\n */\n/**\n * Middleware input: accepts either a function (legacy) or a spec object (recommended).\n *\n * @typeParam S - Store state (readonly).\n * @typeParam EM - Event map.\n *\n * @example Function form (legacy)\n * ```ts\n * const mw: MiddlewareInput<AppState, AppEM> = (state, event, emit) => {\n * console.log(event.type);\n * return true;\n * };\n * ```\n *\n * @example Spec form (recommended)\n * ```ts\n * const mw: MiddlewareInput<AppState, AppEM> = {\n * when: { channel: 'admin' },\n * middleware: (state, event, emit) => state.auth.isAdmin,\n * meta: { type: 'middleware', name: 'authGuard' },\n * };\n * ```\n *\n * @public\n */\nexport type MiddlewareInput<S = any, EM extends EventMapBase = EventMapBase> =\n | MiddlewareFunction<S, EM>\n | MiddlewareSpec<S, EM>;\n\n/**\n * Store configuration object passed to the {@link Store} constructor or {@link createStore}.\n *\n * @typeParam R - Reducer name union (string literal union).\n * @typeParam S - State record keyed by `R`.\n * @typeParam EM - Event map.\n *\n * @example\n * ```ts\n * type S = { counter: { value: number } };\n * type EM = { ui: { increment: number } };\n *\n * const spec: StoreSpec<'counter', S, EM> = {\n * name: 'App',\n * reducer: {\n * counter: {\n * state: { value: 0 },\n * when: { keys: eventKeys<EM>()([['ui', 'increment']]) },\n * reducer(s, evt) {\n * if (evt.type === 'increment') return { value: s.value + evt.payload };\n * return s;\n * }\n * }\n * }\n * };\n * ```\n *\n * @public\n */\nexport type StoreSpec<R extends string, S extends Record<R, any>, EM extends EventMapBase> = {\n /**\n * Store name (used by DevTools to identify the instance).\n */\n name: string;\n\n /**\n * Map of slice name → reducer spec.\n * Each entry declares initial state, the reducer function, and the event targeting.\n */\n reducer: Record<R, ReducerSpec<S[R], EM>>;\n\n /**\n * Middleware chain executed before reducers/effects.\n * Accepts either functions (legacy) or MiddlewareSpec objects (recommended).\n * If any middleware returns false (or resolves to false), the event will not propagate.\n */\n middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];\n\n /**\n * Optional side-effect handlers registered at construction time.\n * Runs after reducers for every propagated event.\n */\n effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;\n\n /**\n * Time window in milliseconds for **content-based** event deduplication.\n * When greater than 0, events with identical fingerprints\n * (channel + type + serialized payload) within this window are treated as\n * duplicates and skipped.\n *\n * **Off by default.** Content-based dedup can silently drop legitimate\n * rapid-fire identical events (double-clicks, repeated `+1`, sliders emitting\n * the same value), so it is opt-in. To safely coalesce a *specific* re-fired\n * emit (e.g. React Strict Mode), prefer the per-emit {@link EmitOptions.dedupKey}.\n *\n * @default 0 (disabled)\n */\n dedupWindowMs?: number;\n\n /**\n * Generates the `id` for each emitted event. Defaults to `crypto.randomUUID()`.\n *\n * @remarks\n * Two reasons to override it. First, portability: `crypto.randomUUID` requires a **secure\n * context** in browsers and is absent on some runtimes (React Native / Hermes), where the\n * default would throw on every emit. Second, determinism: injecting a counter makes event\n * ids stable across runs, which is what allows byte-exact assertions in tests.\n *\n * The factory must return a string. Uniqueness is the caller's responsibility.\n *\n * @default () => crypto.randomUUID()\n *\n * @example\n * ```ts\n * let n = 0;\n * const store = createStore({ name: 'Test', reducer, idFactory: () => `evt-${++n}` });\n * ```\n */\n idFactory?: () => string;\n\n /**\n * DevTools configuration options.\n *\n * @remarks\n * These options control runtime DevTools capabilities such as event replay.\n */\n devtools?: {\n /**\n * Enable event replay via `__replayEvents()`.\n * When `false` (default), calling `__replayEvents()` throws.\n *\n * @default false\n */\n allowReplay?: boolean;\n };\n\n /**\n * Called when an effect throws or its returned promise rejects.\n *\n * @remarks\n * `await emit(...)` **never rejects** on effect failure: the reduce phase has\n * already committed synchronously, and effects run as independent per-event\n * tasks. Effect errors are logged to the console and delivered here (when\n * provided), so this is the single place to observe and route them — e.g.\n * report to a service or emit a failure event. Other effects still run.\n *\n * @param error - The thrown value or rejection reason.\n * @param event - The event whose effect failed.\n */\n onEffectError?: (error: unknown, event: EventUnion<EM>) => void;\n\n /**\n * Invoked when a reducer throws.\n *\n * @remarks\n * A reducer is meant to be pure and total, so a throw is a bug in application code — and it\n * used to be almost invisible. Keyed reducers ran through a bus that logged and moved on,\n * letting the event commit and its effects run; pattern reducers threw straight out of the\n * drain, aborting the commit and notifying nobody. Both paths now isolate the failing slice\n * and report here.\n *\n * The failing slice keeps its previous state; every other slice still reduces, and the event\n * still commits if anything else changed. `emit()` never rejects because of a reducer error,\n * so this hook is how a caller observes one.\n *\n * @param error - The thrown value.\n * @param event - The event being reduced when it threw.\n * @param slice - Name of the slice whose reducer threw.\n */\n onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;\n};\n\n/**\n * Public Store surface.\n *\n * @typeParam R - Reducer name union.\n * @typeParam S - State record (already readonly at the call site).\n * @typeParam EM - Event map.\n *\n * @remarks\n * The concrete Store implements this as `StoreInstance<R, DeepReadonly<S>, EM>`.\n *\n * @public\n */\nexport interface StoreInstance<\n R extends string = string,\n S extends Record<R, any> = Record<string, any>,\n EM extends EventMapBase = EventMapBase,\n> {\n /**\n * Store name (used by DevTools to identify the instance).\n */\n name: string;\n\n /**\n * Read the full state (already readonly).\n */\n getState(): DeepReadonly<S>;\n\n /**\n * Emit a typed event `(channel, type, payload)`.\n * Returns a promise that resolves when the event has been processed.\n */\n emit: Emit<EM>;\n\n /**\n * Coarse subscription: runs after any state change (once per committed event).\n */\n subscribe(listener: () => void): Unsubscribe;\n\n /**\n * Fine-grained subscription: listen to a specific `reducer.property` path.\n * Accepts a dotted path string (e.g., \"data.123.title\").\n * Fires when that path (or its ancestors) actually changes.\n *\n * @param spec - `{ reducer, property }` where `property` is a single dotted path string.\n * @param handler - Handler receiving a {@link Change} with `{ oldValue, newValue, path }`.\n */\n connect(spec: { reducer: R; property: string }, handler: (change: Change) => void): Unsubscribe;\n\n /**\n * Convenience helper to register an **effect** filtered by a single `(channel, type)` pair.\n *\n * @typeParam C - Channel key within `EM`.\n * @typeParam T - Event type key within channel `C`.\n * @param channel - Channel to filter.\n * @param type - Event type to filter.\n * @param handler - Effect handler `(payload, getState, emit, event)`.\n * \n * @returns Unsubscribe/teardown function.\n */\n onEffect<\n C extends keyof EM & string,\n T extends keyof EM[C] & string\n >(\n channel: C,\n type: T,\n handler: (\n payload: EM[C][T],\n getState: () => DeepReadonly<S>,\n emit: Emit<EM>,\n event: Event<EM, C, T>,\n ) => void | Promise<void>,\n ): Unsubscribe;\n\n /**\n * Register a post-reducer effect (sees final state). Returns an unsubscribe.\n */\n registerEffect(spec: EffectSpec<DeepReadonly<S>, EM>): Unsubscribe;\n\n /**\n * Dynamically add middleware, in either the function or the spec form.\n */\n registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): Unsubscribe;\n\n /**\n * Dynamically add/remove a namespaced reducer slice at runtime.\n */\n registerReducer(name: string, spec: ReducerSpec<any, EM>): Unsubscribe;\n\n /**\n * Cleanup resources (timers, etc.) when disposing the store.\n * Call this if you're dynamically creating/destroying stores.\n */\n dispose(): void;\n\n /**\n * Subscribe to events by channel and type.\n *\n * Event subscriptions are intended for the View layer (e.g., React components)\n * to react to events without affecting the event flow. They are fire-and-forget\n * and cannot cancel event propagation.\n *\n * **Phases:**\n * - `'committed'` (default): Events that passed middleware and reached reducers\n * - `'uncommitted'`: Events rejected by middleware\n * - `'all'`: Both committed and uncommitted events (handler receives phase parameter)\n *\n * @typeParam C - Channel key within `EM`.\n * @typeParam T - Event type key within channel `C`.\n * @param channel - Channel to subscribe to.\n * @param type - Event type to subscribe to.\n * @param handler - Handler function `(event, getState, emit, phase)`.\n * @param phase - Event phase to subscribe to (default: `'committed'`).\n * @returns Unsubscribe function.\n *\n * @example Committed events (default)\n * ```ts\n * const off = store.onEvent('ui', 'save', (event, getState, emit, phase) => {\n * console.log('Save committed:', event.payload);\n * });\n * ```\n *\n * @example Uncommitted (rejected) events\n * ```ts\n * store.onEvent('ui', 'delete', (event, getState, emit, phase) => {\n * console.log('Delete was rejected by middleware');\n * }, 'uncommitted');\n * ```\n *\n * @example All events\n * ```ts\n * store.onEvent('ui', 'action', (event, getState, emit, phase) => {\n * console.log('Action:', phase); // 'committed' or 'uncommitted'\n * }, 'all');\n * ```\n */\n onEvent<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n handler: NarrowedEventHandler<DeepReadonly<S>, EM, C, T>,\n phase?: EventPhase,\n ): Unsubscribe;\n\n /**\n * Replaces the entire middleware pipeline (HMR-friendly).\n *\n * @param next - New middleware array.\n */\n replaceMiddleware(next: MiddlewareFunction<DeepReadonly<S>, EM>[]): void;\n\n /**\n * Replaces all registered effects (HMR-friendly).\n *\n * @param next - New effects array (as EffectSpecs).\n */\n replaceEffects(next: Array<EffectSpec<DeepReadonly<S>, EM>>): void;\n\n /**\n * Replaces the entire reducer set (HMR-friendly).\n *\n * @param next - Map of slice specs keyed by slice name.\n * @param opts - `{ preserveState?: boolean }` (default `true`).\n */\n replaceReducers(\n next: Record<R, ReducerSpec<S[R], EM>>,\n opts?: { preserveState?: boolean },\n ): void;\n\n /**\n * Convenience API to replace any subset of store parts (HMR patterns).\n *\n * @param partial - Partial replacement set.\n */\n hotReplace(partial: {\n reducer?: Record<R, ReducerSpec<S[R], EM>>;\n middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];\n effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;\n preserveState?: boolean;\n }): void;\n\n /**\n * Replays a sequence of events from a snapshot through reducers and event\n * subscribers ONLY. Skips dedup, middleware, and effects.\n *\n * Gated by `createStore({ devtools: { allowReplay: true } })`.\n * Throws if replay is not enabled.\n *\n * @param snapshot - The state snapshot to restore before replaying.\n * @param events - Array of events to replay (in order).\n *\n * @internal\n */\n __replayEvents(\n snapshot: any,\n events: Array<{ channel: string; type: string; payload: any; id: string; meta?: EventMeta }>,\n ): void;\n\n /**\n * Returns a structured introspection snapshot for DevTools UIs.\n *\n * @returns Reducers, effects, middleware, event subscriptions, coarse\n * subscriber count, dedup hit count, and current queue depth.\n *\n * @internal\n */\n __devtoolsIntrospect(): {\n reducers: Array<{ name: string; when?: unknown }>;\n effects: Array<{ channel: string; type: string; name?: string; description?: string }>;\n middleware: Array<{ name?: string; description?: string; when?: unknown }>;\n atomic: Array<{ reducer: string; property: string }>;\n event: Array<{ channel: string; type: string; phase: string }>;\n coarse: number;\n dedupHits: number;\n queueDepth: number;\n };\n\n /**\n * Registers an instrumentation observer, called once per emitted event\n * (committed or vetoed) after the synchronous reduce phase, with the exact\n * changed paths, their old/new values, and reduce timing. This is the typed\n * seam DevTools agents consume — no `as any` bridging required.\n *\n * @param observer - Receives an {@link InstrumentedEvent} per emit.\n * @returns Unsubscribe function.\n */\n instrument(observer: InstrumentationObserver<EM>): Unsubscribe;\n\n /**\n * Applies an externally-provided whole-state snapshot (DevTools time-travel),\n * emitting fine-grained path changes and notifying coarse subscribers.\n *\n * @param next - Plain state object to apply.\n *\n * @internal\n */\n __applyExternalState(next: unknown): void;\n}\n\n\n/**\n * One reducer's definition blob (stateful event consumer).\n *\n * @typeParam S - State managed by this reducer.\n * @typeParam EM - Event map.\n *\n * @remarks\n * Use `when` for event targeting (preferred). The `events` property is\n * kept for backward compatibility but `when` is recommended for new code.\n *\n * @example Using `when` (recommended)\n * ```ts\n * const counterSpec: ReducerSpec<{ value: number }, MyEM> = {\n * state: { value: 0 },\n * when: { keys: eventKeys<MyEM>()([['ui', 'increment'], ['ui', 'decrement']]) },\n * reducer(s, evt) {\n * if (evt.type === 'increment') return { value: s.value + evt.payload };\n * if (evt.type === 'decrement') return { value: s.value - evt.payload };\n * return s;\n * },\n * meta: { type: 'reducer', name: 'counter' },\n * };\n * ```\n *\n * @public\n */\nexport interface ReducerSpec<S = any, EM extends EventMapBase = EventMapBase> {\n /**\n * Initial state for this reducer.\n */\n state: S;\n\n /**\n * Event targeting using the unified `When` matcher.\n */\n when?: When<EM>;\n\n /**\n * Pure reducer function: `(state, event) => nextState`.\n */\n reducer: ReducerFunction<S, EM>;\n\n /**\n * Optional metadata for debugging tools and DevTools integration.\n */\n meta?: EventConsumerMeta<\"reducer\">;\n}\n\n/**\n * Pure reducer function (stateful event consumer).\n *\n * @typeParam S - State type.\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport type ReducerFunction<S = any, EM extends EventMapBase = EventMapBase> = (\n state: S,\n event: EventUnion<EM>,\n) => S;\n\n/**\n * Effect specification (stateless async event consumer).\n *\n * @typeParam S - Store state type (readonly).\n * @typeParam EM - Event map.\n *\n * @remarks\n * - Effects run after reducers see the event.\n * - Effects are async-safe and do not own state.\n * - Effects are keyed by event for O(1) lookup (no scanning).\n * - Use `when` for event targeting (preferred over `events`).\n *\n * @example Using `when` (recommended)\n * ```ts\n * const logEffect: EffectSpec<AppState, MyEM> = {\n * when: { keys: eventKeys<MyEM>()([['ui', 'increment']]) },\n * effect: async (evt, getState, emit) => {\n * console.log('increment', evt.payload, getState().counter.value);\n * },\n * meta: { type: 'effect', name: 'logEffect', description: 'Logs increment events' },\n * };\n * ```\n *\n * @example Match all events in a channel\n * ```ts\n * const notificationEffect: EffectSpec<AppState, MyEM> = {\n * when: { channel: 'notifications' },\n * effect: (evt, getState, emit) => {\n * if (evt.type === 'show') showToast(evt.payload.message);\n * },\n * };\n * ```\n *\n * @public\n */\nexport interface EffectSpec<S = any, EM extends EventMapBase = EventMapBase> {\n /**\n * Event targeting using the unified `When` matcher.\n */\n when?: When<EM>;\n\n /**\n * Async effect handler: `(event, getState, emit) => void | Promise<void>`.\n */\n effect: EffectFunction<S, EM>;\n\n /**\n * Optional metadata for debugging tools and DevTools integration.\n */\n meta?: EventConsumerMeta<\"effect\">;\n}\n\n/**\n * Every legal `{ channel, type, payload, id }` as a *distinct* object type.\n *\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport type EventUnion<EM extends EventMapBase> = {\n [C in keyof EM & string]: {\n [T in keyof EM[C] & string]: Event<EM, C, T>;\n }[keyof EM[C] & string];\n}[keyof EM & string];\n\n/**\n * Middleware function: log, guard, or veto an event **synchronously**.\n * Return `true` to continue, `false` to swallow / cancel propagation.\n *\n * @remarks\n * Middleware runs in the synchronous reduce phase (so `getState()` is correct\n * immediately after `emit()`), and therefore must be synchronous. Perform async\n * work in effects instead.\n *\n * @typeParam S - Store state (readonly).\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport type MiddlewareFunction<S = any, EM extends EventMapBase = EventMapBase> = (\n state: S,\n event: EventUnion<EM>,\n emit: Emit<EM>,\n) => boolean;\n\n/**\n * Middleware specification with optional event targeting and metadata.\n *\n * @typeParam S - Store state (readonly).\n * @typeParam EM - Event map.\n *\n * @remarks\n * - If `when` is omitted, middleware receives ALL events.\n * - Use `when` to filter which events the middleware processes.\n * - Middleware runs BEFORE reducers and can cancel event propagation.\n *\n * @example Global logging middleware (all events)\n * ```ts\n * const loggingMiddleware: MiddlewareSpec<AppState, AppEM> = {\n * middleware: (state, event, emit) => {\n * console.log('Event:', event.channel, event.type);\n * return true; // allow propagation\n * },\n * meta: { type: 'middleware', name: 'logger' },\n * };\n * ```\n *\n * @example Filtered middleware (specific events)\n * ```ts\n * const authMiddleware: MiddlewareSpec<AppState, AppEM> = {\n * when: { channel: 'admin' },\n * middleware: (state, event, emit) => {\n * if (!state.auth.isAdmin) return false; // cancel\n * return true;\n * },\n * meta: { type: 'middleware', name: 'authGuard', description: 'Guards admin events' },\n * };\n * ```\n *\n * @public\n */\nexport interface MiddlewareSpec<S = any, EM extends EventMapBase = EventMapBase> {\n /**\n * Event targeting (optional). If omitted, middleware receives ALL events.\n */\n when?: When<EM>;\n\n /**\n * Middleware function: `(state, event, emit) => boolean` (synchronous).\n * Return `false` to cancel event propagation.\n */\n middleware: MiddlewareFunction<S, EM>;\n\n /**\n * Optional metadata for debugging tools and DevTools integration.\n */\n meta?: EventConsumerMeta<\"middleware\">;\n}\n\n/**\n * Effect handler: runs AFTER reducers, sees the final state.\n *\n * @typeParam S - Store state (readonly).\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport type EffectFunction<S = any, EM extends EventMapBase = EventMapBase> = (\n event: EventUnion<EM>,\n getState: () => S,\n emit: Emit<EM>,\n) => void | Promise<void>;\n\n/**\n * Helper: extract state shape from a reducers map.\n *\n * @internal\n */\nexport type ReducersMapAny = Record<string, ReducerSpec<any, any>>;\n\n/**\n * Helper: derive state type from a reducers map.\n *\n * @internal\n */\nexport type StateFromReducers<R> = {\n [K in keyof R]: R[K] extends ReducerSpec<infer S, any> ? S : never;\n};\n\n/**\n * Helper: turn a union into an intersection.\n *\n * @internal\n */\nexport type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (\n k: infer I,\n) => void\n ? I\n : never;\n\n/**\n * Helper: the event map of a single reducer spec.\n *\n * @internal\n */\nexport type EMOfSpec<Spec> = Spec extends ReducerSpec<any, infer EM> ? EM : never;\n\n/**\n * Helper: derive the combined event map from a reducers map (strict).\n * Used by the createStore inference overload.\n *\n * Each slice contributes its own event map; those maps are **merged** (channels,\n * and each channel's `type → payload` entries, combined across slices) rather\n * than collapsed to a single slice's map. `EMOfSpec` distributes over the union\n * of specs to yield the union of per-slice event maps, and `UnionToIntersection`\n * merges them — so a store whose slices declare divergent event maps still types\n * `emit` against the union of every slice's channels/types.\n *\n * @internal\n */\nexport type EMFromReducersStrict<RM extends ReducersMapAny> = UnionToIntersection<\n EMOfSpec<RM[keyof RM]>\n> extends infer Merged\n ? Merged extends EventMapBase\n ? Merged\n : EventMapBase\n : EventMapBase;\n\n// ============================================\n// Event Targeting (When Matcher)\n// ============================================\n\n/**\n * Matcher for event targeting across reducers, effects, middleware, and subscriptions.\n *\n * Supports four targeting modes:\n * - `{ any: true }` — match all events\n * - `{ keys: [...] }` — match specific `[channel, type]` pairs (correlated)\n * - `{ channel: 'x' }` — match all events in a channel\n * - `{ channels: ['x', 'y'] }` — match all events in multiple channels\n *\n * @typeParam EM - Event map.\n *\n * @example Match all events\n * ```ts\n * const mw: MiddlewareSpec<S, EM> = {\n * when: { any: true },\n * middleware: (state, event, emit) => true,\n * };\n * ```\n *\n * @example Match specific event keys\n * ```ts\n * const reducer: ReducerSpec<S, EM> = {\n * state: { value: 0 },\n * when: { keys: eventKeys<EM>()([['ui', 'increment'], ['ui', 'decrement']]) },\n * reducer: (s, e) => { ... },\n * };\n * ```\n *\n * @example Match entire channel\n * ```ts\n * const effect: EffectSpec<S, EM> = {\n * when: { channel: 'notifications' },\n * effect: (e, getState, emit) => { ... },\n * };\n * ```\n *\n * @public\n */\nexport type When<EM extends EventMapBase> =\n | { any: true }\n | { keys: ReadonlyArray<EventKey<EM>> }\n | { channel: keyof EM & string }\n | { channels: ReadonlyArray<keyof EM & string> };\n\n/**\n * Helper to create type-safe EventKey arrays without requiring `as const`.\n * Preserves literal tuple types for proper type correlation in handlers.\n *\n * @typeParam EM - Event map.\n *\n * @example\n * ```ts\n * type AppEM = {\n * ui: { increment: number; decrement: number };\n * data: { loaded: string[] };\n * };\n *\n * // Without helper (requires `as const`):\n * const keys = [['ui', 'increment'], ['ui', 'decrement']] as const;\n *\n * // With helper (no `as const` needed):\n * const keys = eventKeys<AppEM>()([\n * ['ui', 'increment'],\n * ['ui', 'decrement'],\n * ]);\n * // Type: readonly [['ui', 'increment'], ['ui', 'decrement']]\n * ```\n *\n * @public\n */\nexport const eventKeys =\n <EM extends EventMapBase>() =>\n <const K extends ReadonlyArray<EventKey<EM>>>(keys: K): K =>\n keys;\n\n/**\n * Extracts the event union from a `When` matcher.\n * Used internally to narrow handler `event` parameter types based on the matcher.\n *\n * @typeParam EM - Event map.\n * @typeParam W - When matcher type.\n *\n * @internal\n */\nexport type EventFromWhen<EM extends EventMapBase, W extends When<EM>> = W extends { any: true }\n ? EventUnion<EM>\n : W extends { keys: ReadonlyArray<infer K> }\n ? K extends readonly [infer C, infer T]\n ? C extends keyof EM & string\n ? T extends keyof EM[C] & string\n ? Event<EM, C, T>\n : never\n : never\n : never\n : W extends { channel: infer C }\n ? C extends keyof EM & string\n ? { [T in keyof EM[C] & string]: Event<EM, C, T> }[keyof EM[C] & string]\n : never\n : W extends { channels: ReadonlyArray<infer C> }\n ? C extends keyof EM & string\n ? { [T in keyof EM[C] & string]: Event<EM, C, T> }[keyof EM[C] & string]\n : never\n : never;\n\n// ============================================\n// Path Value Resolution\n// ============================================\n\n/**\n * Resolves the value type at a dotted path `P` inside object/array `T`.\n * Supports numeric segments for array indexing (e.g., `\"items.0.title\"`).\n *\n * @typeParam T - Root type to index into.\n * @typeParam P - Dotted path string.\n *\n * @example\n * ```ts\n * type S = { todos: Array<{ title: string; done: boolean }> };\n * type T1 = PathValue<S['todos'], '0.title'>; // string\n * type T2 = PathValue<S, 'todos.0'>; // { title: string; done: boolean }\n * type T3 = PathValue<S, 'todos'>; // Array<{ title: string; done: boolean }>\n * ```\n *\n * @remarks\n * The empty path resolves to `T` itself, matching what the code has always done: both the\n * store's internal path reader and the React one return the object unchanged for `\"\"`. The type\n * used to say `never`, so a subscription to a root-value slice was typed as nothing at all.\n *\n * @public\n */\nexport type PathValue<T, P extends string> = P extends \"\"\n ? T\n : P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? PathValue<T[K], Rest>\n : K extends `${number}`\n ? T extends readonly (infer E)[]\n ? PathValue<E, Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : P extends `${number}`\n ? T extends readonly (infer E)[]\n ? E\n : never\n : never;\n\n// ============================================\n// Metadata for Debugging Tools\n// ============================================\n\n/**\n * Type discriminator for event consumers.\n *\n * @public\n */\nexport type EventConsumerType = \"reducer\" | \"middleware\" | \"effect\";\n\n/**\n * Metadata for event consumers (reducers, effects, middleware).\n * Useful for debugging tools, DevTools integration, and introspection.\n *\n * @typeParam T - Consumer type discriminator.\n *\n * @example\n * ```ts\n * const counterReducer: ReducerSpec<CounterState, AppEM> = {\n * state: { value: 0 },\n * when: { keys: eventKeys<AppEM>()([['ui', 'increment']]) },\n * reducer: (s, e) => ({ value: s.value + e.payload }),\n * meta: {\n * type: 'reducer',\n * name: 'counterReducer',\n * description: 'Handles counter increment/decrement events',\n * },\n * };\n * ```\n *\n * @public\n */\nexport interface EventConsumerMeta<T extends EventConsumerType = EventConsumerType> {\n /** Consumer type discriminator */\n type: T;\n\n /** Unique identifier for this consumer */\n name: string;\n\n /** Brief one-liner description of what this consumer does */\n description?: string;\n}\n\n/**\n * Alias for DeepReadonly.\n *\n * @public\n */\nexport type DeepRO<T> = DeepReadonly<T>;\n\n/**\n * Primitive types (terminal leaves in deep traversal).\n *\n * @public\n */\nexport type Primitive =\n | string\n | number\n | boolean\n | bigint\n | symbol\n | null\n | undefined\n | Date\n | RegExp;\n\n/**\n * A value with **no addressable interior**: its changes are reported at the slice root rather\n * than at a path beneath it.\n *\n * @remarks\n * The distinction the path types were missing. `Map` and `Set` keep their contents outside own\n * enumerable keys, so walking them with `keyof` yields the names of their *methods* — which is\n * how `\"byId.get\"` and `\"byId.size\"` came to be offered as subscribable paths, and why a slice\n * holding a plain number autocompleted `\"toFixed\"`. Neither ever notified anything, because\n * `detectChangedProps` reports such a value at its own path and never descends into it.\n *\n * This is the type-level counterpart of that runtime rule: what the diff reports at the root,\n * the types address at the root, with the empty path.\n *\n * @public\n */\nexport type RootValue = Primitive | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>;\n\n/**\n * Compute dotted paths of T, including nested objects and arrays.\n *\n * @typeParam T - Type to compute paths for.\n *\n * @public\n */\nexport type Path<T> = T extends RootValue\n ? never\n : T extends readonly (infer U)[]\n ? `${number}` | (Path<U> extends never ? never : `${number}.${Path<U>}`)\n : {\n [K in keyof T & string]: T[K] extends Primitive\n ? K\n : K | (Path<T[K]> extends never ? never : `${K}.${Path<T[K]>}`);\n }[keyof T & string];\n\n/**\n * Allow wildcard patterns like \"*\" and \"**\" anywhere in the string.\n *\n * @typeParam T - Base string type.\n *\n * @public\n */\nexport type WithGlob<T extends string> = T | `${string}*${string}`;\n\n/**\n * Dotted keys of a slice: top-level keys or any nested path.\n *\n * @typeParam Slice - Slice state type.\n *\n * @remarks\n * A slice that **is** one value — a primitive, a `Map`, a `Set`, a `Date` — has no key to\n * address, and its only subscribable path is the empty one. Saying so is what makes\n * `{ reducer, property: \"\" }` type-check where it can actually fire, instead of falling through\n * to the untyped `property: string` overload and returning `unknown`.\n *\n * The conditional distributes over unions, which is why a nullable object slice gets both:\n * `Dotted<{ a: number } | null>` is `\"\" | \"a\"`. That is exactly right — such a slice really does\n * change at its root when it becomes `null`, and at `\"a\"` otherwise.\n *\n * @public\n */\nexport type Dotted<Slice> = Slice extends RootValue\n ? \"\"\n : (keyof Slice & string) | Path<Slice>;\n\n/**\n * Deep readonly type: recursively makes all properties readonly.\n *\n * @remarks\n * The built-in object types are handled before the general mapped-object case, because\n * mapping over one destroys it. `{ readonly [K in keyof Map<K, V>]: ... }` produces an object\n * carrying the *names* of a Map's methods with their signatures rewritten, so reading a Map\n * out of state and calling `.get()` on it was a type error even though the value at runtime\n * is an ordinary Map. The same applied to `Set`, `Date`, `RegExp` and any function stored in\n * state.\n *\n * Collections become their `Readonly*` counterparts, which is the same treatment arrays\n * already had. Functions are returned untouched: a function's properties are not state, and\n * mapping over them makes it uncallable.\n *\n * @typeParam T - Type to make readonly.\n *\n * @public\n */\nexport type DeepReadonly<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends (infer A)[]\n ? ReadonlyArray<DeepReadonly<A>>\n : T extends ReadonlyMap<infer K, infer V>\n ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n : T extends ReadonlySet<infer V>\n ? ReadonlySet<DeepReadonly<V>>\n : T extends Date | RegExp | Promise<unknown> | Error\n ? T\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T;\n\n/**\n * Phase of event subscription notification.\n *\n * - `'committed'`: Events that passed middleware and reached reducers (default)\n * - `'uncommitted'`: Events rejected by middleware\n * - `'all'`: Both committed and uncommitted events\n *\n * @public\n */\nexport type EventPhase = \"committed\" | \"uncommitted\" | \"all\";\n\n/**\n * Handler function for event subscriptions (receives full event union).\n *\n * Event subscriptions are intended for the View layer (e.g., React components)\n * to react to events without affecting the event flow. They are fire-and-forget\n * and cannot cancel event propagation.\n *\n * @typeParam S - Store state type (readonly).\n * @typeParam EM - Event map.\n *\n * @param event - The event that was emitted\n * @param getState - Function to get current state\n * @param emit - Function to emit new events\n * @param phase - The phase ('committed' or 'uncommitted') indicating how the event was processed\n *\n * @example\n * ```ts\n * const handler: EventSubscriptionHandler<AppState, AppEM> = (event, getState, emit, phase) => {\n * if (phase === 'committed') {\n * console.log('Event committed:', event.type);\n * } else {\n * console.log('Event rejected:', event.type);\n * }\n * };\n * ```\n *\n * @public\n */\nexport type EventSubscriptionHandler<S = any, EM extends EventMapBase = EventMapBase> = (\n event: EventUnion<EM>,\n getState: () => S,\n emit: Emit<EM>,\n phase: \"committed\" | \"uncommitted\",\n) => void | Promise<void>;\n\n/**\n * Narrowed event subscription handler for specific `(channel, type)` pairs.\n * Provides better type inference when subscribing to a single event type.\n *\n * @typeParam S - Store state type (readonly).\n * @typeParam EM - Event map.\n * @typeParam C - Channel key within `EM`.\n * @typeParam T - Event type key within channel `C`.\n *\n * @example\n * ```ts\n * const handler: NarrowedEventHandler<AppState, AppEM, 'ui', 'increment'> = (\n * event, // Event<AppEM, 'ui', 'increment'> - narrowed!\n * getState,\n * emit,\n * phase,\n * ) => {\n * // event.payload is typed as number (from EM['ui']['increment'])\n * console.log('Increment by:', event.payload);\n * };\n * ```\n *\n * @public\n */\nexport type NarrowedEventHandler<\n S,\n EM extends EventMapBase,\n C extends keyof EM & string,\n T extends keyof EM[C] & string,\n> = (\n event: Event<EM, C, T>,\n getState: () => S,\n emit: Emit<EM>,\n phase: \"committed\" | \"uncommitted\",\n) => void | Promise<void>;","/**\n * Normalised collections, so a list stops paying O(N) for an O(1) change.\n *\n * @remarks\n * Path notification is positional for arrays. `detectChangedProps` walks indices and reports\n * `items.0.title`, which names a *slot*, not a thing. So `unshift`, `splice(0, 1)` and `sort`\n * move nearly every element into a different slot, and the diff correctly reports that nearly\n * every leaf changed. Inserting one row at the front of a thousand wakes a thousand\n * subscribers.\n *\n * The remedy is the state shape, not a quieter diff. A key-stable array diff would need an\n * identity key the diff has no business knowing, and even then the *paths* would still be\n * positional — `items.0.title` names position zero, and so does the RFC-6902 pointer the\n * devtools agents build from it.\n *\n * Normalising to `{ ids, entities }` makes `entities.abc.title` stable across insert, remove\n * and reorder.\n *\n * **What this does not do:** `ids` is still an array, so a reorder still reports `ids.0`,\n * `ids.1` and so on. That cost is confined rather than removed. A list container subscribes to\n * `ids` and reorders its children; rows subscribe to `entities.<id>.<field>` and stay asleep.\n * The promise is cost proportional to what actually changed.\n *\n * @module @yoltra/core\n */\n\n/** What an entity may be keyed by. */\nexport type EntityId = string | number;\n\n/**\n * A normalised collection.\n *\n * @typeParam T - The entity.\n * @typeParam Id - Its key type.\n *\n * @public\n */\nexport interface EntityState<T, Id extends EntityId = string> {\n /** Order. Reordering touches this and nothing under `entities`. */\n readonly ids: readonly Id[];\n /** Identity-keyed, so a path to one entity survives every change to the others. */\n readonly entities: Readonly<Record<Id, T>>;\n}\n\n/** A change to apply to one entity. */\nexport interface EntityUpdate<T, Id extends EntityId> {\n readonly id: Id;\n readonly changes: Partial<T>;\n}\n\n/** How an adapter identifies and orders its entities. */\nexport interface EntityAdapterOptions<T, Id extends EntityId> {\n /** Defaults to reading `id`. */\n readonly selectId?: (entity: T) => Id;\n /**\n * Keeps `ids` sorted.\n *\n * @remarks\n * Omit it and `ids` holds insertion order, which is cheaper: with a comparer, any change\n * that could affect position re-sorts. The sorted array is only adopted when it actually\n * differs, so a sort that changes nothing reports nothing.\n */\n readonly sortComparer?: (a: T, b: T) => number;\n}\n\n/**\n * Reducer helpers, selectors, and the subscription paths that make the shape worth having.\n *\n * @public\n */\nexport interface EntityAdapter<T, Id extends EntityId = string> {\n getInitialState(): EntityState<T, Id>;\n getInitialState<Extra extends object>(extra: Extra): EntityState<T, Id> & Extra;\n\n /** Adds an entity. Existing ids are left alone — this is not an upsert. */\n addOne<S extends EntityState<T, Id>>(state: S, entity: T): S;\n addMany<S extends EntityState<T, Id>>(state: S, entities: readonly T[]): S;\n /** Adds or replaces one entity wholesale. */\n setOne<S extends EntityState<T, Id>>(state: S, entity: T): S;\n setMany<S extends EntityState<T, Id>>(state: S, entities: readonly T[]): S;\n /** Replaces the whole collection. */\n setAll<S extends EntityState<T, Id>>(state: S, entities: readonly T[]): S;\n /** Merges `changes` into one entity. Unknown ids are ignored. */\n updateOne<S extends EntityState<T, Id>>(state: S, update: EntityUpdate<T, Id>): S;\n updateMany<S extends EntityState<T, Id>>(state: S, updates: readonly EntityUpdate<T, Id>[]): S;\n /** Adds, or merges into an existing entity. */\n upsertOne<S extends EntityState<T, Id>>(state: S, entity: T): S;\n upsertMany<S extends EntityState<T, Id>>(state: S, entities: readonly T[]): S;\n removeOne<S extends EntityState<T, Id>>(state: S, id: Id): S;\n removeMany<S extends EntityState<T, Id>>(state: S, ids: readonly Id[]): S;\n removeAll<S extends EntityState<T, Id>>(state: S): S;\n\n selectIds(state: EntityState<T, Id>): readonly Id[];\n selectEntities(state: EntityState<T, Id>): Readonly<Record<Id, T>>;\n selectAll(state: EntityState<T, Id>): readonly T[];\n selectById(state: EntityState<T, Id>, id: Id): T | undefined;\n selectTotal(state: EntityState<T, Id>): number;\n\n /** Path to the order array. Subscribe here for a list that reorders. */\n readonly idsPath: string;\n /** Path to one entity, or to a field of it. */\n pathTo(id: Id, field?: string): string;\n /** Wildcard across every entity's `field`, for the loose subscription registry. */\n anyField(field: string): string;\n}\n\n/** @internal */\nconst warnedDottedIds = new Set<string>();\n\n/** @internal */\nfunction warnDottedId(id: EntityId): void {\n const key = String(id);\n if (warnedDottedIds.has(key)) return;\n warnedDottedIds.add(key);\n console.warn(\n `[yoltra] Entity id \"${key}\" contains a dot. Paths are dotted, so a subscription to ` +\n `\"entities.${key}\" is indistinguishable from one to a nested object of the same name. ` +\n `Use ids without dots.`,\n );\n}\n\n/**\n * Returns `next` only when it differs from `current`, element by element.\n *\n * @remarks\n * Reusing the existing array when the order did not change is what keeps `ids` out of the\n * changed-path list. Without it, every update to a sorted collection would report the order\n * as changed and wake the list container for nothing.\n *\n * @internal\n */\nfunction sameOrder<Id extends EntityId>(\n current: readonly Id[],\n next: readonly Id[],\n): readonly Id[] {\n if (current.length !== next.length) return next;\n for (let i = 0; i < current.length; i++) {\n if (current[i] !== next[i]) return next;\n }\n return current;\n}\n\n/**\n * Builds an adapter for one entity type.\n *\n * @example\n * ```ts\n * const todos = createEntityAdapter<Todo>();\n *\n * const spec: ReducerSpec<EntityState<Todo>, EM> = {\n * state: todos.getInitialState(),\n * when: { keys: eventKeys<EM>()([['todos', 'toggled']]) },\n * reducer: (state, event) =>\n * todos.updateOne(state, { id: event.payload.id, changes: { done: event.payload.done } }),\n * };\n *\n * // and in a component\n * useAtomicProp({ reducer: 'todos', property: todos.pathTo(id, 'title') });\n * ```\n *\n * @public\n */\nexport function createEntityAdapter<T, Id extends EntityId = string>(\n options: EntityAdapterOptions<T, Id> = {},\n): EntityAdapter<T, Id> {\n const selectId = options.selectId ?? ((entity: T) => (entity as { id: Id }).id);\n const { sortComparer } = options;\n\n const order = <S extends EntityState<T, Id>>(state: S, ids: readonly Id[]): readonly Id[] => {\n if (sortComparer === undefined) return ids;\n const sorted = [...ids].sort((a, b) => {\n const left = state.entities[a];\n const right = state.entities[b];\n if (left === undefined || right === undefined) return 0;\n return sortComparer(left, right);\n });\n return sameOrder(ids, sorted);\n };\n\n const write = <S extends EntityState<T, Id>>(\n state: S,\n entities: Record<Id, T>,\n ids: readonly Id[],\n ): S => {\n const next = { ...state, entities, ids } as S;\n return { ...next, ids: order(next, ids) };\n };\n\n const put = <S extends EntityState<T, Id>>(\n state: S,\n incoming: readonly T[],\n mode: \"add\" | \"set\" | \"upsert\",\n ): S => {\n let entities: Record<Id, T> | null = null;\n let ids: Id[] | null = null;\n\n for (const entity of incoming) {\n const id = selectId(entity);\n if (process.env.NODE_ENV !== \"production\" && String(id).includes(\".\")) warnDottedId(id);\n\n const existing = (entities ?? state.entities)[id];\n if (existing !== undefined && mode === \"add\") continue;\n\n const value =\n existing !== undefined && mode === \"upsert\" ? { ...existing, ...entity } : entity;\n\n entities ??= { ...state.entities };\n entities[id] = value;\n if (existing === undefined) {\n ids ??= [...state.ids];\n ids.push(id);\n }\n }\n\n if (entities === null) return state;\n return write(state, entities, ids ?? state.ids);\n };\n\n const merge = <S extends EntityState<T, Id>>(\n state: S,\n updates: readonly EntityUpdate<T, Id>[],\n ): S => {\n let entities: Record<Id, T> | null = null;\n\n for (const { id, changes } of updates) {\n const existing = (entities ?? state.entities)[id];\n if (existing === undefined) continue;\n entities ??= { ...state.entities };\n // Only the touched entity gets a new reference. Cloning the rest would report every\n // entity as changed, which is the defect this whole module exists to remove.\n entities[id] = { ...existing, ...changes };\n }\n\n if (entities === null) return state;\n return write(state, entities, state.ids);\n };\n\n const drop = <S extends EntityState<T, Id>>(state: S, ids: readonly Id[]): S => {\n const doomed = new Set<Id>(ids.filter((id) => state.entities[id] !== undefined));\n if (doomed.size === 0) return state;\n\n const entities = { ...state.entities };\n for (const id of doomed) delete entities[id];\n return write(\n state,\n entities,\n state.ids.filter((id) => !doomed.has(id)),\n );\n };\n\n return {\n getInitialState<Extra extends object>(extra?: Extra) {\n const base: EntityState<T, Id> = { ids: [], entities: {} as Record<Id, T> };\n return (extra === undefined ? base : { ...base, ...extra }) as EntityState<T, Id> & Extra;\n },\n\n addOne: (state, entity) => put(state, [entity], \"add\"),\n addMany: (state, entities) => put(state, entities, \"add\"),\n setOne: (state, entity) => put(state, [entity], \"set\"),\n setMany: (state, entities) => put(state, entities, \"set\"),\n setAll: (state, entities) => {\n const next = {} as Record<Id, T>;\n const ids: Id[] = [];\n for (const entity of entities) {\n const id = selectId(entity);\n if (next[id] === undefined) ids.push(id);\n next[id] = entity;\n }\n return write(state, next, ids);\n },\n updateOne: (state, update) => merge(state, [update]),\n updateMany: (state, updates) => merge(state, updates),\n upsertOne: (state, entity) => put(state, [entity], \"upsert\"),\n upsertMany: (state, entities) => put(state, entities, \"upsert\"),\n removeOne: (state, id) => drop(state, [id]),\n removeMany: (state, ids) => drop(state, ids),\n removeAll: (state) => (state.ids.length === 0 ? state : write(state, {} as Record<Id, T>, [])),\n\n selectIds: (state) => state.ids,\n selectEntities: (state) => state.entities,\n selectAll: (state) => state.ids.map((id) => state.entities[id]!),\n selectById: (state, id) => state.entities[id],\n selectTotal: (state) => state.ids.length,\n\n idsPath: \"ids\",\n pathTo: (id, field) => (field === undefined ? `entities.${id}` : `entities.${id}.${field}`),\n anyField: (field) => `entities.*.${field}`,\n };\n}\n","/**\n * Lossless encoding of store state for the wire.\n *\n * @remarks\n * The wire is JSON, and `JSON.stringify` is not a safe way to put arbitrary state on it. It does\n * not fail on the values it cannot represent — it quietly destroys them. A `Map` becomes `{}`, a\n * `Set` becomes `{}`, a `Date` becomes a string, `undefined` disappears from objects entirely,\n * and a `BigInt` or a cycle throws from inside a handler nobody awaits.\n *\n * Silent destruction is the dangerous half. The panel showed `{}` where a `Map` lived, which is\n * merely wrong; but time-travel then sent that `{}` back and applied it to the running store,\n * replacing a live `Map` with an empty object in the user's own application. A debugging tool\n * corrupting the program it is inspecting is the worst failure available to it.\n *\n * Values are therefore tagged rather than coerced. Anything JSON can carry travels unchanged;\n * anything it cannot is wrapped in a marker object that {@link decodeState} reverses exactly.\n *\n * @module\n */\n\n/** Marker key identifying an encoded value. Chosen to be improbable in application state. */\nconst TAG = \"$yoltra\" as const;\n\n/** What an encoded non-JSON value looks like on the wire. */\ntype Tagged =\n | { readonly [TAG]: \"map\"; readonly entries: Array<[unknown, unknown]> }\n | { readonly [TAG]: \"set\"; readonly values: unknown[] }\n | { readonly [TAG]: \"date\"; readonly iso: string }\n | { readonly [TAG]: \"bigint\"; readonly value: string }\n | { readonly [TAG]: \"undefined\" }\n | { readonly [TAG]: \"nan\" }\n | { readonly [TAG]: \"infinity\"; readonly sign: 1 | -1 }\n | { readonly [TAG]: \"regexp\"; readonly source: string; readonly flags: string }\n | { readonly [TAG]: \"error\"; readonly name: string; readonly message: string }\n | { readonly [TAG]: \"ref\"; readonly path: string }\n | { readonly [TAG]: \"unsupported\"; readonly kind: string }\n | { readonly [TAG]: \"escaped\"; readonly value: Record<string, unknown> };\n\n/** Options for {@link encodeState}. */\nexport interface EncodeOptions {\n /**\n * Redacts a value before it leaves the process.\n *\n * @remarks\n * State frequently holds tokens, session material and personal data, and devtools traffic\n * crosses a socket to another process. Return the replacement value, or the value itself to\n * keep it. Applied before encoding, so a redacted value is encoded like any other.\n */\n readonly sanitize?: (path: string, value: unknown) => unknown;\n /**\n * Maximum number of nodes to encode. Beyond it, subtrees are replaced by a truncation marker.\n *\n * @remarks\n * A snapshot larger than the hub's frame cap is rejected outright, which reads to the user as\n * a panel that hangs. Truncating visibly is a better failure: the panel renders, and says\n * where it stopped. Defaults to 100000.\n */\n readonly maxNodes?: number;\n}\n\n/** Reports what an encode had to compromise. Empty when nothing was lost. */\nexport interface EncodeReport {\n /** Node budget was exhausted and some subtrees were replaced by markers. */\n readonly truncated: boolean;\n /** Values no JSON representation exists for, by path — functions, symbols, DOM nodes. */\n readonly unsupported: readonly string[];\n}\n\n/** Result of {@link encodeState}. */\nexport interface EncodeResult {\n readonly value: unknown;\n readonly report: EncodeReport;\n}\n\n/**\n * Encodes a value into something `JSON.stringify` can carry losslessly.\n *\n * @param input - Any value, including one holding `Map`, `Set`, `Date`, `BigInt` or cycles.\n * @param options - Redaction and size limits.\n * @returns The encoded value plus a report of anything that could not be represented.\n *\n * @example\n * ```ts\n * const { value } = encodeState({ index: new Map([['a', 1]]) });\n * JSON.stringify(value); // safe, and decodeState restores the Map\n * ```\n *\n * @public\n */\nexport function encodeState(input: unknown, options: EncodeOptions = {}): EncodeResult {\n const maxNodes = options.maxNodes ?? 100_000;\n const sanitize = options.sanitize;\n const unsupported: string[] = [];\n\n // Identity → JSON Pointer of the first place it was seen. A cycle then encodes as a reference\n // to that path rather than recursing forever, and repeated references stay repeated rather\n // than being silently expanded into copies.\n const seen = new Map<object, string>();\n let nodes = 0;\n let truncated = false;\n\n function walk(value: unknown, path: string): unknown {\n if (sanitize !== undefined) value = sanitize(path, value);\n\n nodes += 1;\n if (nodes > maxNodes) {\n truncated = true;\n return { [TAG]: \"unsupported\", kind: \"truncated\" } satisfies Tagged;\n }\n\n switch (typeof value) {\n case \"undefined\":\n return { [TAG]: \"undefined\" } satisfies Tagged;\n case \"bigint\":\n return { [TAG]: \"bigint\", value: value.toString() } satisfies Tagged;\n case \"number\":\n if (Number.isNaN(value)) return { [TAG]: \"nan\" } satisfies Tagged;\n if (value === Infinity) return { [TAG]: \"infinity\", sign: 1 } satisfies Tagged;\n if (value === -Infinity) return { [TAG]: \"infinity\", sign: -1 } satisfies Tagged;\n return value;\n case \"function\":\n case \"symbol\":\n unsupported.push(path);\n return { [TAG]: \"unsupported\", kind: typeof value } satisfies Tagged;\n case \"string\":\n case \"boolean\":\n return value;\n default:\n break;\n }\n\n if (value === null) return null;\n\n const asObject = value as object;\n const previous = seen.get(asObject);\n if (previous !== undefined) return { [TAG]: \"ref\", path: previous } satisfies Tagged;\n seen.set(asObject, path);\n\n if (value instanceof Date) {\n return { [TAG]: \"date\", iso: value.toISOString() } satisfies Tagged;\n }\n if (value instanceof RegExp) {\n return { [TAG]: \"regexp\", source: value.source, flags: value.flags } satisfies Tagged;\n }\n if (value instanceof Error) {\n return { [TAG]: \"error\", name: value.name, message: value.message } satisfies Tagged;\n }\n if (value instanceof Map) {\n const entries: Array<[unknown, unknown]> = [];\n let i = 0;\n for (const [k, v] of value) {\n entries.push([walk(k, `${path}/@k${i}`), walk(v, `${path}/${i}`)]);\n i += 1;\n }\n return { [TAG]: \"map\", entries } satisfies Tagged;\n }\n if (value instanceof Set) {\n const values: unknown[] = [];\n let i = 0;\n for (const v of value) {\n values.push(walk(v, `${path}/${i}`));\n i += 1;\n }\n return { [TAG]: \"set\", values } satisfies Tagged;\n }\n if (Array.isArray(value)) {\n return value.map((item, index) => walk(item, `${path}/${index}`));\n }\n\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n out[key] = walk(item, `${path}/${escapePointer(key)}`);\n }\n // An application object that happens to carry the marker key would decode as a tagged value\n // and come back as something else entirely. Wrap it so the decoder knows it is ordinary.\n if (TAG in out) return { [TAG]: \"escaped\", value: out } satisfies Tagged;\n return out;\n }\n\n const value = walk(input, \"\");\n return { value, report: { truncated, unsupported } };\n}\n\n/**\n * Reverses {@link encodeState}.\n *\n * @param input - A value produced by `encodeState` (typically after a JSON round trip).\n * @returns The original structure, with `Map`, `Set`, `Date` and friends restored.\n *\n * @remarks\n * Unsupported markers decode to `undefined`: a function cannot be reconstructed, and inventing a\n * placeholder would be worse than an absent value. Cycles are restored by resolving references\n * after the tree is built, so a decoded structure is cyclic exactly where the original was.\n *\n * @public\n */\nexport function decodeState(input: unknown): unknown {\n // Built during the walk so a reference can resolve to a node that may not exist yet.\n const byPath = new Map<string, unknown>();\n const pending: Array<{ target: unknown; key: string | number; path: string }> = [];\n\n function walk(value: unknown, path: string): unknown {\n if (value === null || typeof value !== \"object\") return value;\n\n if (Array.isArray(value)) {\n const arr: unknown[] = [];\n byPath.set(path, arr);\n value.forEach((item, index) => {\n if (isRef(item)) {\n // Left undefined for now; the second pass fills it once every node exists.\n pending.push({ target: arr, key: index, path: item.path });\n arr[index] = undefined;\n return;\n }\n arr[index] = walk(item, `${path}/${index}`);\n });\n return arr;\n }\n\n const tag = (value as Record<string, unknown>)[TAG];\n if (typeof tag === \"string\") {\n const tagged = value as unknown as Tagged;\n switch (tagged[TAG]) {\n case \"undefined\":\n return undefined;\n case \"nan\":\n return Number.NaN;\n case \"infinity\":\n return tagged.sign === 1 ? Infinity : -Infinity;\n case \"bigint\":\n return BigInt(tagged.value);\n case \"date\":\n return new Date(tagged.iso);\n case \"regexp\":\n return new RegExp(tagged.source, tagged.flags);\n case \"error\": {\n const error = new Error(tagged.message);\n error.name = tagged.name;\n return error;\n }\n case \"unsupported\":\n // Nothing faithful to return. `undefined` says \"not representable\" without pretending.\n return undefined;\n case \"ref\":\n // Resolved by the caller once the whole tree exists.\n return undefined;\n case \"map\": {\n const map = new Map<unknown, unknown>();\n byPath.set(path, map);\n tagged.entries.forEach(([k, v], index) => {\n map.set(walk(k, `${path}/@k${index}`), walk(v, `${path}/${index}`));\n });\n return map;\n }\n case \"set\": {\n const set = new Set<unknown>();\n byPath.set(path, set);\n tagged.values.forEach((v, index) => set.add(walk(v, `${path}/${index}`)));\n return set;\n }\n case \"escaped\":\n return walkPlain(tagged.value, path);\n default:\n return undefined;\n }\n }\n\n return walkPlain(value as Record<string, unknown>, path);\n }\n\n function walkPlain(value: Record<string, unknown>, path: string): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n byPath.set(path, out);\n for (const [key, item] of Object.entries(value)) {\n const childPath = `${path}/${escapePointer(key)}`;\n if (isRef(item)) {\n pending.push({ target: out, key, path: item.path });\n out[key] = undefined;\n continue;\n }\n out[key] = walk(item, childPath);\n }\n return out;\n }\n\n const root = walk(input, \"\");\n byPath.set(\"\", root);\n\n // Second pass: every reference now has a node to point at.\n for (const { target, key, path } of pending) {\n (target as Record<string | number, unknown>)[key] = byPath.get(path);\n }\n\n return root;\n}\n\n/** @internal */\nfunction isRef(value: unknown): value is { [TAG]: \"ref\"; path: string } {\n return (\n value !== null &&\n typeof value === \"object\" &&\n (value as Record<string, unknown>)[TAG] === \"ref\" &&\n typeof (value as Record<string, unknown>).path === \"string\"\n );\n}\n\n/**\n * Escapes a key for use in a JSON Pointer segment (RFC 6901).\n *\n * @internal\n */\nfunction escapePointer(key: string): string {\n return key.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\n\n/** Outcome of {@link encodeStateBounded}. */\nexport interface BoundedEncodeResult {\n /** The encoded value, small enough to send. */\n readonly value: unknown;\n /** `true` when the state did not fit and parts were replaced by markers. */\n readonly truncated: boolean;\n /** Explains what was dropped, for display beside a partial tree. */\n readonly note?: string;\n}\n\n/**\n * Encodes a value, shrinking it until its serialized form fits within `maxBytes`.\n *\n * @param input - Any value.\n * @param maxBytes - Byte budget for the serialized form.\n * @param options - Passed through to {@link encodeState}.\n *\n * @returns The encoded value and whether anything had to be dropped.\n *\n * @remarks\n * A frame larger than the hub's cap is not merely slow — it is rejected, and the connection with\n * it, so the client reconnects, asks again, is refused again, and the panel sits waiting through\n * a loop with nothing on screen to explain it. The size therefore has to be bounded before the\n * frame is sent rather than discovered afterwards.\n *\n * Node count is a poor proxy for bytes: a hundred nodes holding base64 blobs outweigh a hundred\n * thousand holding integers. So this measures the encoded output and, when it is too large,\n * scales the node budget by how far over it went and measures again. Scaling by the overshoot\n * rather than halving matters: from a default of a hundred thousand nodes, repeated halving\n * needs a dozen rounds to reach the hundreds, so a state that could have been shown in part\n * would have been abandoned instead.\n *\n * Truncation is reported rather than performed silently. A partial tree presented as the state is\n * worse than no tree at all: a debugger that quietly lies about state is not a debugger.\n *\n * @public\n */\nexport function encodeStateBounded(\n input: unknown,\n maxBytes: number,\n options: EncodeOptions = {},\n): BoundedEncodeResult {\n let nodeBudget = options.maxNodes ?? 100_000;\n\n for (let attempt = 0; attempt < 8; attempt += 1) {\n const { value, report } = encodeState(input, { ...options, maxNodes: nodeBudget });\n // `JSON.stringify` can still refuse a value the encoder passed through untouched, so a\n // failure here is measured as \"does not fit\" rather than thrown at the caller.\n let size: number;\n try {\n size = JSON.stringify(value)?.length ?? 0;\n } catch {\n size = Number.POSITIVE_INFINITY;\n }\n\n if (size <= maxBytes) {\n return report.truncated\n ? {\n value,\n truncated: true,\n note: `State was too large to send in full; parts beyond ${nodeBudget} nodes are omitted.`,\n }\n : { value, truncated: false };\n }\n\n // Aim at 80% of the budget so the next attempt has room for the tagging overhead that\n // shrinking cannot remove, and always make progress even when the estimate is optimistic.\n const scaled = Math.floor((nodeBudget * maxBytes * 0.8) / size);\n nodeBudget = Math.max(1, Math.min(scaled, nodeBudget - 1));\n if (nodeBudget <= 1 && attempt > 0) {\n // Already at the floor and still too large: the remaining bytes are one enormous value,\n // not many small ones, and no node budget will cut it down.\n break;\n }\n }\n\n // Nothing fit, even at the smallest budget. Say so instead of sending a frame that will be\n // refused and leaving the panel to retry against a wall.\n return {\n value: { [TAG]: \"unsupported\", kind: \"truncated\" } satisfies Tagged,\n truncated: true,\n note: `State exceeds the ${maxBytes}-byte transport limit and could not be reduced to fit.`,\n };\n}\n","/**\n * Saving state, and starting from saved state.\n *\n * @remarks\n * The two halves happen on opposite sides of the store's existence, which is why this is two\n * functions rather than one. {@link hydrate} produces *initial slice state*, so the store is\n * born hydrated; {@link persist} subscribes to a store that already exists.\n *\n * Restoring after construction is the obvious alternative and the wrong one. It means applying\n * a whole-state snapshot to a live store, which emits a change across every path: a visible\n * flash on boot, a burst of instrumentation entries describing changes nobody made, and\n * effects observing a transition that never happened.\n *\n * @module @yoltra/core\n */\n\nimport { decodeState, encodeState } from \"../serialize/codec\";\n\n/** Where persisted state lives. Bring your own; core imports no platform global. */\nexport interface PersistenceAdapter {\n read(key: string): string | null | Promise<string | null>;\n write(key: string, value: string): void | Promise<void>;\n remove(key: string): void | Promise<void>;\n}\n\n/** Where a failure happened, so a handler can tell a bad write from a bad payload. */\nexport type PersistencePhase = \"read\" | \"write\" | \"decode\" | \"migrate\";\n\n/** Shared configuration. */\nexport interface PersistOptions {\n /** Storage key. */\n readonly key: string;\n readonly adapter: PersistenceAdapter;\n /**\n * Schema version of what is written.\n *\n * @remarks\n * Compared on read. A mismatch is handed to {@link PersistOptions.migrate}, and without one\n * the stored value is discarded rather than trusted — reducers change, and a snapshot\n * written against an older shape is not merely stale, it may not be valid state at all.\n */\n readonly version: number;\n /** Slices to persist. Every slice by default. */\n readonly slices?: readonly string[];\n /** Coalescing window for writes, in milliseconds. Defaults to 250. */\n readonly throttleMs?: number;\n /**\n * Upgrades a payload written by an older version.\n *\n * @returns The slices to restore, or `null` to start fresh.\n */\n readonly migrate?: (persisted: unknown, from: number) => Record<string, unknown> | null;\n /**\n * Called on any failure.\n *\n * @remarks\n * Persistence never throws into the application it is persisting. A store that will not\n * start because storage holds stale JSON is worse than one that starts fresh, and a full\n * disk should not take down a page.\n */\n readonly onError?: (error: unknown, phase: PersistencePhase) => void;\n}\n\n/** What {@link hydrate} recovered. */\nexport interface Hydration {\n /** Slice states to start from. Empty when there was nothing usable to restore. */\n readonly slices: Readonly<Record<string, unknown>>;\n /** `true` when a payload was found, decoded and accepted. */\n readonly restored: boolean;\n}\n\n/** What is written to storage. */\ninterface Envelope {\n readonly version: number;\n readonly slices: Record<string, unknown>;\n}\n\n/** @internal */\nfunction report(options: PersistOptions, error: unknown, phase: PersistencePhase): void {\n options.onError?.(error, phase);\n}\n\n/**\n * Reads persisted state, ready to seed a store.\n *\n * @remarks\n * Every read-side failure — missing, unparseable, wrong version with no migration, a\n * migration that declines — resolves to \"nothing to restore\" and reports through\n * {@link PersistOptions.onError}. Nothing throws.\n *\n * @example\n * ```ts\n * const hydration = await hydrate({ key: 'app', adapter, version: 3 });\n * const store = createStore({\n * name: 'App',\n * reducer: withHydration({ todos: todosSpec }, hydration),\n * });\n * ```\n *\n * @public\n */\nexport async function hydrate(\n options: PersistOptions & { readonly source?: string },\n): Promise<Hydration> {\n const empty: Hydration = { slices: {}, restored: false };\n\n let raw: string | null | undefined;\n try {\n raw = options.source ?? (await options.adapter.read(options.key));\n } catch (error) {\n report(options, error, \"read\");\n return empty;\n }\n if (raw === null || raw === undefined || raw === \"\") return empty;\n\n let envelope: Envelope;\n try {\n envelope = decodeState(JSON.parse(raw)) as Envelope;\n } catch (error) {\n report(options, error, \"decode\");\n return empty;\n }\n\n if (envelope === null || typeof envelope !== \"object\" || typeof envelope.version !== \"number\") {\n report(options, new Error(\"persisted payload is not a recognisable envelope\"), \"decode\");\n return empty;\n }\n\n if (envelope.version !== options.version) {\n if (options.migrate === undefined) {\n report(\n options,\n new Error(\n `persisted state is version ${envelope.version}, this build expects ${options.version}, and no migrate was supplied`,\n ),\n \"migrate\",\n );\n return empty;\n }\n try {\n const migrated = options.migrate(envelope.slices, envelope.version);\n if (migrated === null) return empty;\n return { slices: migrated, restored: true };\n } catch (error) {\n report(options, error, \"migrate\");\n return empty;\n }\n }\n\n return { slices: envelope.slices ?? {}, restored: true };\n}\n\n/** A reducer spec, as far as hydration cares: something carrying an initial `state`. */\ninterface HasState {\n state: unknown;\n}\n\n/**\n * Replaces each reducer's initial state with what was restored for it.\n *\n * @remarks\n * Slices absent from the payload keep their declared defaults, so adding a reducer does not\n * invalidate everything written before it existed.\n *\n * @public\n */\nexport function withHydration<R extends Record<string, HasState>>(\n reducers: R,\n hydration: Hydration,\n): R {\n if (!hydration.restored) return reducers;\n\n const next = {} as Record<string, HasState>;\n for (const [name, spec] of Object.entries(reducers)) {\n const restored = hydration.slices[name];\n next[name] = restored === undefined ? spec : { ...spec, state: restored };\n }\n return next as R;\n}\n\n/** The store surface persistence needs, which is two methods wide. */\nexport interface PersistableStore {\n getState(): unknown;\n instrument(observer: (info: { changedPaths?: readonly string[] }) => void): () => void;\n}\n\n/** Serializes the slices being persisted. */\nfunction encodeEnvelope(state: unknown, options: Pick<PersistOptions, \"version\" | \"slices\">): string {\n const all = (state ?? {}) as Record<string, unknown>;\n const slices: Record<string, unknown> =\n options.slices === undefined\n ? all\n : Object.fromEntries(options.slices.filter((s) => s in all).map((s) => [s, all[s]]));\n\n return JSON.stringify(encodeState({ version: options.version, slices }).value);\n}\n\n/**\n * Writes state as it changes.\n *\n * @returns A function that stops persisting and flushes anything pending.\n *\n * @remarks\n * Driven by `instrument` rather than the coarse subscription, so a change confined to a slice\n * that is not persisted costs nothing at all. Writes are coalesced on the trailing edge.\n *\n * @public\n */\nexport function persist(store: PersistableStore, options: PersistOptions): () => void {\n const throttleMs = options.throttleMs ?? 250;\n const watched = options.slices;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let pending = false;\n\n const flush = (): void => {\n if (!pending) return;\n pending = false;\n try {\n const written = options.adapter.write(options.key, encodeEnvelope(store.getState(), options));\n if (written instanceof Promise) {\n void written.catch((error: unknown) => report(options, error, \"write\"));\n }\n } catch (error) {\n // Storage being full, or unavailable in private mode, must not surface to the caller.\n report(options, error, \"write\");\n }\n };\n\n const schedule = (): void => {\n pending = true;\n if (throttleMs <= 0) {\n flush();\n return;\n }\n if (timer !== null) return;\n timer = setTimeout(() => {\n timer = null;\n flush();\n }, throttleMs);\n // Never hold a process open for a pending write.\n (timer as unknown as { unref?: () => void }).unref?.();\n };\n\n const stop = store.instrument((info) => {\n if (watched === undefined) {\n schedule();\n return;\n }\n // A changed path is `slice.rest`; only a watched slice is worth a write.\n const touched = (info.changedPaths ?? []).some((path) =>\n watched.some((slice) => path === slice || path.startsWith(`${slice}.`)),\n );\n if (touched) schedule();\n });\n\n return () => {\n stop();\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n flush();\n };\n}\n\n/**\n * Serializes a store for handoff, for example from a server render to the client.\n *\n * @public\n */\nexport function dehydrate(\n store: Pick<PersistableStore, \"getState\">,\n options: Pick<PersistOptions, \"version\" | \"slices\">,\n): string {\n return encodeEnvelope(store.getState(), options);\n}\n","/**\n * Storage adapters for the environments core can reach without importing them.\n *\n * @remarks\n * Each is built by a factory that takes the storage object rather than reaching for a global,\n * so this module stays isomorphic: nothing here breaks a Worker, a server render or a test.\n *\n * @module @yoltra/core\n */\n\nimport type { PersistenceAdapter } from \"./persist\";\n\n/** The slice of the Web Storage API used here. */\nexport interface WebStorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * Wraps a Web Storage object.\n *\n * @remarks\n * Pass `localStorage` or `sessionStorage` explicitly. Reading the global here would make this\n * module unusable anywhere one does not exist, which includes a server render — exactly where\n * hydration payloads are produced.\n *\n * @example\n * ```ts\n * const adapter = createWebStorageAdapter(localStorage);\n * ```\n *\n * @public\n */\nexport function createWebStorageAdapter(storage: WebStorageLike): PersistenceAdapter {\n return {\n read: (key) => storage.getItem(key),\n write: (key, value) => storage.setItem(key, value),\n remove: (key) => storage.removeItem(key),\n };\n}\n\n/**\n * Keeps state in memory.\n *\n * @remarks\n * For tests, and for a server render that wants the persistence path exercised without a\n * store behind it. It forgets on restart, which is the whole of what it claims.\n *\n * @public\n */\nexport function createMemoryAdapter(initial?: Record<string, string>): PersistenceAdapter {\n const store = new Map<string, string>(Object.entries(initial ?? {}));\n return {\n read: (key) => store.get(key) ?? null,\n write: (key, value) => {\n store.set(key, value);\n },\n remove: (key) => {\n store.delete(key);\n },\n };\n}\n"],"names":["EventBus","__publicField","channel","type","handler","byType","set","payload","event","h","err","LooseEventBus","typeStr","pattern","pmap","key","map","normalizedType","cMap","list","i","pMap","exactList","patternLists","called","deliver","arr","exc","make","s","p","index","segments","entry","head","bucket","at","e","patternMap","subject","lists","test","entries","handlers","pSegs","sSegs","j","star","matchIdx","result","Reducer","reduce","state","warnedDottedKeys","warnDottedKey","path","full","detectChangedProps","oldState","newState","ancestors","out","walk","oldObj","newObj","active","onPath","isArrOld","isArrNew","a","b","overlap","oldKeys","newKeys","sameKeys","hasOld","nextPath","freezeState","obj","seen","alias","desc","sym","cloneInitialState","sliceName","freezeInDev","value","DEFAULT_DEDUP_KEY_WINDOW_MS","now","Store","spec","name","rSpec","effSpec","base","json","fp","windowMs","existing","effectiveWindow","cutoff","timestamp","when","input","effectSet","effect","phase","phaseSet","allSet","rName","prev","next","leafPaths","frozen","toEmit","prop","reducers","effects","fn","meta","middleware","mwInput","atomic","coarse","nextPlain","anyChanged","prevSlice","nextSlice","frozenNextSlice","oldValue","newValue","l","snapshot","events","evt","stateBefore","stateAfter","anySliceChanged","opts","dedupKey","contentWindow","id","resolve","done","r","instrumenting","prevState","sink","t0","committed","mw","ok","changed","observer","changedPaths","reduceTimeMs","prevValues","nextValues","info","targetMap","unsubs","eventKeys","u","getState","emit","typed","preserveState","currentKeys","nextEntries","nextKeys","k","partial","reducer","ch","tp","sourceEvent","_removed","rest","parts","cur","seg","createStore","cfg","typedEvents","_","keys","warnedDottedIds","warnDottedId","sameOrder","current","createEntityAdapter","options","selectId","entity","sortComparer","order","ids","sorted","left","right","write","entities","put","incoming","mode","merge","updates","changes","drop","doomed","extra","update","field","TAG","encodeState","maxNodes","sanitize","unsupported","nodes","truncated","asObject","previous","v","values","item","escapePointer","decodeState","byPath","pending","isRef","tagged","error","walkPlain","childPath","root","target","encodeStateBounded","maxBytes","nodeBudget","attempt","report","size","scaled","hydrate","empty","raw","envelope","migrated","withHydration","hydration","restored","encodeEnvelope","all","slices","persist","store","throttleMs","watched","timer","flush","written","schedule","stop","slice","dehydrate","createWebStorageAdapter","storage","createMemoryAdapter","initial"],"mappings":"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"}
|