@yoltra/core 0.5.0 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"yoltra.umd.js","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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"}
1
+ {"version":3,"file":"yoltra.umd.js","sources":["../src/eventBus/EventBus.ts","../src/eventBus/LooseEventBus.ts","../src/reducer/Reducer.ts","../src/utils/detectChangedProps.ts","../src/utils/immutability.ts","../src/store/rejection.ts","../src/store/call.ts","../src/store/callQueue.ts","../src/store/performCall.ts","../src/store/paths.ts","../src/store/matching.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\";\nimport type { Rejection } from \"../store/rejection\";\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, or a {@link Rejection} if the reducer refused the write.\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 | Rejection {\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\n/**\n * Brand identifying a {@link Rejection}.\n *\n * @remarks\n * `Symbol.for` rather than `Symbol()`, so the brand survives two copies of this package meeting\n * at runtime — a duplicated dependency, a bundle that inlined a second copy, a consumer that\n * pinned an older minor. With a unique symbol the check would silently answer `false` across that boundary and a\n * refusal would read as ordinary state, which is the failure this whole feature exists to end.\n *\n * @internal\n */\nconst REJECTED = Symbol.for(\"yoltra.rejected\");\n\n/**\n * A reducer's refusal to apply a write, carrying the reason.\n *\n * @remarks\n * Distinct from a reducer returning its state unchanged, which is indistinguishable from \"the\n * event did not concern me\". A `Rejection` says *this write was considered and declined*, and it\n * says why — which is what a contended store needs and what a lost update otherwise costs.\n *\n * @public\n */\nexport interface Rejection {\n readonly [REJECTED]: true;\n /** Why the write was refused. Surfaced to the caller and to `onRejected`. */\n readonly reason: string;\n}\n\n/**\n * Builds a {@link Rejection} for a reducer to return instead of state.\n *\n * @param reason - Why the write is refused; surfaced verbatim to the caller.\n *\n * @remarks\n * Rejecting is a whole-event act: no slice commits, no change notifications fire, and the\n * caller's `emit` resolves reporting the refusal. A reducer that merely has nothing to do should\n * return its state, not this.\n *\n * @example Compare-and-swap on a contended slice\n * ```ts\n * reducer: (state, event) =>\n * event.payload.expectedVersion === state.version\n * ? { ...state, ...event.payload.patch, version: state.version + 1 }\n * : Rejected(`stale write: expected v${event.payload.expectedVersion}, have v${state.version}`)\n * ```\n *\n * @public\n */\nexport function Rejected(reason: string): Rejection {\n return { [REJECTED]: true, reason };\n}\n\n/**\n * Whether a reducer returned a {@link Rejection} rather than state.\n *\n * @public\n */\nexport function isRejected(value: unknown): value is Rejection {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { [REJECTED]?: unknown })[REJECTED] === true\n );\n}\n","/**\n * @module @yoltra/core\n */\n\nimport type { EventMapBase, EventUnion } from \"../types\";\n\n/**\n * Which reply events end a {@link StoreInstance.call | call}, and therefore what it resolves to.\n *\n * @remarks\n * Given as `[channel]` or `[channel, type]` or `[channel, [type, type]]`. The named types are\n * **terminal**: the first one to arrive settles the call. Every other correlated event on that\n * channel is progress.\n *\n * Naming a channel alone makes every event on it terminal, which suits a responder with a single\n * kind of answer. Naming types is what lets a responder stream: `[\"rpc\", [\"answer\", \"error\"]]`\n * ends on either, and anything else — `progress`, `partial`, `log` — flows to the consumer.\n *\n * @public\n */\nexport type ReplySpec<EM extends EventMapBase> =\n | readonly [channel: keyof EM & string]\n | readonly [channel: keyof EM & string, type: string]\n | readonly [channel: keyof EM & string, types: readonly string[]];\n\n/**\n * Options for {@link StoreInstance.call}.\n *\n * @public\n */\nexport interface CallOptions<EM extends EventMapBase> {\n /** Which reply events end the call. See {@link ReplySpec}. */\n readonly reply: ReplySpec<EM>;\n\n /**\n * How long the call may sit **idle** before it gives up, in milliseconds.\n *\n * @remarks\n * Idle, not total: every correlated event resets it, progress included. A job that streams for\n * two minutes must not fail a thirty-second call, and a total deadline would make the timeout a\n * function of how much work the responder had to do rather than whether it is still alive.\n *\n * For a genuine deadline — \"this must be finished by then, however lively\" — use\n * {@link CallOptions.signal} with an `AbortSignal.timeout()`.\n *\n * @default 30000\n */\n readonly timeoutMs?: number;\n\n /**\n * Aborts the call. The returned promise rejects and the iterator ends.\n *\n * @remarks\n * Unlike `timeoutMs` this is absolute, so it is the right tool for a request deadline, a\n * user-cancelled action, or a component unmounting.\n */\n readonly signal?: AbortSignal;\n\n /**\n * How many progress events may buffer before the producer is made to wait.\n *\n * @remarks\n * Only meaningful once the caller is iterating. See {@link StoreInstance.call} for what\n * backpressure means here and when it engages.\n *\n * @default 16\n */\n readonly highWaterMark?: number;\n\n /**\n * Correlate on this id instead of on causality.\n *\n * @remarks\n * Causal matching — a reply is correlated because the store stamped it as *caused by* the\n * request — is free and cannot be forged, but only holds in one process. A reply arriving from\n * another node, a worker, or any transport carries no causal link, so for those the responder\n * echoes an id and both sides agree on it here.\n *\n * When set, the id is sent as `meta.correlationId` and a reply matches if it echoes the same\n * value **or** is causally descended. Causality still wins where it applies, so a local\n * responder needs no changes to be compatible with a remote one.\n */\n readonly correlationId?: string;\n}\n\n/**\n * The result of {@link StoreInstance.call}: awaitable for the terminal reply, async-iterable for\n * progress.\n *\n * @typeParam TReply - The terminal reply event.\n * @typeParam TProgress - Non-terminal correlated events.\n *\n * @remarks\n * One object serving both shapes, rather than two functions, because the caller's intent is not\n * known at the call site — the same request may be awaited in one place and streamed in another,\n * and the responder should not have to care which.\n *\n * ```ts\n * // Await the answer, ignore the running commentary.\n * const done = await store.call(\"rpc\", \"ask\", { q }, { reply: [\"rpc\", \"answer\"] });\n *\n * // Or consume the commentary, then take the answer.\n * const call = store.call(\"rpc\", \"ask\", { q }, { reply: [\"rpc\", \"answer\"] });\n * for await (const step of call) render(step.payload);\n * const answer = await call;\n * ```\n *\n * Awaiting the same call twice is safe and yields the same reply; the terminal event is retained.\n *\n * @public\n */\nexport interface CallHandle<TReply, TProgress> extends Promise<TReply>, AsyncIterable<TProgress> {\n /**\n * Progress events discarded because nothing was iterating.\n *\n * @remarks\n * Zero unless the call was awaited without being iterated *and* the responder streamed more\n * than `highWaterMark` events. Non-zero is not an error — it is the honest count of what a\n * caller chose not to read, and is worth logging rather than guessing at.\n */\n readonly dropped: number;\n\n /** Stops listening and settles the call. Safe to call more than once. */\n cancel(reason?: string): void;\n}\n\n/**\n * Raised when a call goes {@link CallOptions.timeoutMs} without a correlated event.\n *\n * @public\n */\nexport class CallTimeoutError extends Error {\n readonly channel: string;\n readonly type: string;\n readonly idleMs: number;\n\n constructor(channel: string, type: string, idleMs: number) {\n super(\n `[yoltra] call to \"${channel}/${type}\" saw no correlated reply for ${idleMs}ms. ` +\n `The timeout is idle rather than total, so this means the responder went quiet, not ` +\n `that it was slow. Check that something handles \"${channel}/${type}\" and that its reply ` +\n `is emitted through the \\`emit\\` it was handed — a reply emitted from an unrelated ` +\n `context carries no causal link, and needs an explicit correlationId instead.`,\n );\n this.name = \"CallTimeoutError\";\n this.channel = channel;\n this.type = type;\n this.idleMs = idleMs;\n }\n}\n\n/**\n * Raised when a call is cancelled, or its {@link CallOptions.signal} aborts.\n *\n * @public\n */\nexport class CallAbortedError extends Error {\n constructor(reason: string) {\n super(`[yoltra] call aborted: ${reason}`);\n this.name = \"CallAbortedError\";\n }\n}\n\n/**\n * Normalises a {@link ReplySpec} into a channel and a terminal-type test.\n *\n * @internal\n */\nexport function parseReply<EM extends EventMapBase>(\n reply: ReplySpec<EM>,\n): { channel: string; isTerminal: (type: string) => boolean } {\n const [channel, types] = reply as readonly [string, (string | readonly string[])?];\n\n // A channel on its own means every reply on it ends the call — the shape a responder with one\n // kind of answer takes, and the one where naming the type would be noise.\n if (types === undefined) return { channel, isTerminal: () => true };\n\n if (typeof types === \"string\") return { channel, isTerminal: (t) => t === types };\n\n const set = new Set(types);\n return { channel, isTerminal: (t) => set.has(t) };\n}\n\n/**\n * Whether `event` is a reply to the request identified by `requestId` / `correlationId`.\n *\n * @remarks\n * Causality first: the store stamps `parentId` on anything emitted while handling an event, so a\n * responder that answers through the `emit` it was given is correlated without doing anything.\n * The explicit id is the fallback for replies that crossed a boundary causality cannot.\n *\n * @internal\n */\nexport function isReplyTo<EM extends EventMapBase>(\n event: EventUnion<EM>,\n requestId: string,\n correlationId: string | undefined,\n): boolean {\n if (event.parentId === requestId) return true;\n if (correlationId === undefined) return false;\n return (event.meta as { correlationId?: unknown } | undefined)?.correlationId === correlationId;\n}\n","/**\n * @module @yoltra/core\n */\n\n/**\n * A bounded hand-off queue between one producer and one consumer, where **the producer waits**.\n *\n * @remarks\n * This is what makes {@link StoreInstance.call}'s backpressure real rather than decorative. A\n * plain buffer accepts everything and grows; this one hands the producer a promise that does not\n * resolve until the consumer has taken an item. Because the store awaits effects, and `emit`\n * resolves only once its effects have finished, a producer writing\n *\n * ```ts\n * await emit(\"rpc\", \"progress\", chunk);\n * ```\n *\n * genuinely blocks until the consumer catches up — end to end, through machinery that already\n * existed, with nothing polling and nothing dropped.\n *\n * **Backpressure only engages once the consumer has begun iterating.** Before that, items buffer\n * up to `highWaterMark` and further ones are counted and discarded. That asymmetry is deliberate:\n * a caller that only awaits the terminal reply never pulls, so blocking the producer would\n * deadlock the very call it is feeding — the producer would be waiting to deliver progress\n * nobody will read, and would therefore never emit the terminal event that ends the wait.\n *\n * @internal\n */\nexport class CallQueue<T> {\n private readonly buffer: T[] = [];\n\n /** Consumers parked in `take`, oldest first. */\n private readonly takers: Array<(value: IteratorResult<T>) => void> = [];\n\n /** Producers parked in `put`, each with the item they are waiting to hand over. */\n private readonly putters: Array<{ item: T; release: () => void }> = [];\n\n private consuming = false;\n\n /** No more items will be accepted, but what is already here is still owed to the consumer. */\n private ended = false;\n\n /** Abandoned: nothing further is owed to anybody. */\n private closed = false;\n\n /** Items discarded because nobody was iterating and the buffer was full. */\n private dropped = 0;\n\n constructor(private readonly highWaterMark: number) {}\n\n /** How many items were discarded for want of a consumer. */\n get droppedCount(): number {\n return this.dropped;\n }\n\n /**\n * Marks that a consumer has started pulling. From here on, a full buffer parks the producer\n * rather than dropping.\n */\n beginConsuming(): void {\n this.consuming = true;\n }\n\n /**\n * Offers an item. The returned promise settles when the item has been taken — or immediately,\n * if it fit in the buffer or was dropped.\n */\n put(item: T): Promise<void> {\n if (this.closed || this.ended) return Promise.resolve();\n\n // A parked consumer takes it directly; no buffering, no waiting either way.\n const taker = this.takers.shift();\n if (taker !== undefined) {\n taker({ value: item, done: false });\n return Promise.resolve();\n }\n\n if (this.buffer.length < this.highWaterMark) {\n this.buffer.push(item);\n return Promise.resolve();\n }\n\n if (!this.consuming) {\n // Nobody is reading and nobody has said they will. Dropping is the only option that does\n // not deadlock the producer — see the note on this class.\n this.dropped++;\n return Promise.resolve();\n }\n\n return new Promise<void>((release) => {\n this.putters.push({ item, release });\n });\n }\n\n /** Takes the next item, waiting if none is available. Resolves `done` once closed and drained. */\n take(): Promise<IteratorResult<T>> {\n this.consuming = true;\n\n const buffered = this.buffer.shift();\n if (buffered !== undefined) {\n // A parked producer can now hand its item to the space just freed.\n const putter = this.putters.shift();\n if (putter !== undefined) {\n this.buffer.push(putter.item);\n putter.release();\n }\n return Promise.resolve({ value: buffered, done: false });\n }\n\n // Nothing buffered, but a producer is parked: take directly from it.\n const putter = this.putters.shift();\n if (putter !== undefined) {\n putter.release();\n return Promise.resolve({ value: putter.item, done: false });\n }\n\n // Nothing left to hand over. `ended` counts here as well as `closed`: the terminal reply has\n // arrived and the buffer is drained, so the stream is genuinely over.\n if (this.closed || this.ended) return Promise.resolve({ value: undefined, done: true });\n\n return new Promise<IteratorResult<T>>((taker) => {\n this.takers.push(taker);\n });\n }\n\n /**\n * Stops accepting items, but keeps owing the consumer everything already queued.\n *\n * @remarks\n * What the terminal reply does. Closing outright at that moment would throw away progress the\n * responder had already handed over and the consumer had not yet read — which is exactly what\n * happened before this existed: a six-step job delivered five steps, because the sixth was in\n * the buffer when `done` arrived and the buffer was cleared. The terminal event says \"no more\n * is coming\", not \"forget what you were given\".\n */\n end(): void {\n if (this.ended || this.closed) return;\n this.ended = true;\n\n // Anything a producer is still parked with was sent before the terminal, so it is owed.\n let putter = this.putters.shift();\n while (putter !== undefined) {\n this.buffer.push(putter.item);\n putter.release();\n putter = this.putters.shift();\n }\n\n // Hand the buffer to anyone already waiting, then tell the rest we are done.\n let taker = this.takers.shift();\n while (taker !== undefined) {\n const next = this.buffer.shift();\n taker(\n next !== undefined\n ? { value: next, done: false }\n : { value: undefined, done: true },\n );\n taker = this.takers.shift();\n }\n }\n\n /**\n * Closes the queue: waiting consumers are told `done`, and **every parked producer is\n * released**.\n *\n * @remarks\n * Releasing producers is not tidying up. A producer parked on `put` is a pending `await emit`\n * somewhere; leaving it parked when the call has already settled would hang the responder for\n * good — turning a timed-out call into a wedged process, which is worse than the problem\n * backpressure was added to solve.\n */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.buffer.length = 0;\n\n let taker = this.takers.shift();\n while (taker !== undefined) {\n taker({ value: undefined, done: true });\n taker = this.takers.shift();\n }\n\n let putter = this.putters.shift();\n while (putter !== undefined) {\n putter.release();\n putter = this.putters.shift();\n }\n }\n}\n","/**\n * The orchestration behind `store.call()`.\n *\n * @remarks\n * Moved out of `Store.ts` unchanged, and it lands beside the types and the queue it already\n * used. The seam is three members wide, which is what made this one extractable: the body mints\n * an id, registers a collector effect, and emits the request. It reaches nothing else.\n *\n * `registerEffect` and `emit` arrive as bound references, since `Store` binds both in its\n * constructor. `Store.call` keeps its signature and its explicit return type.\n *\n * @module\n */\n\nimport type {\n DeepReadonly,\n EffectSpec,\n EmitOptions,\n EmitResult,\n EventMapBase,\n EventUnion,\n} from \"../types\";\nimport {\n CallAbortedError,\n CallTimeoutError,\n type CallHandle,\n type CallOptions,\n parseReply,\n isReplyTo,\n} from \"./call\";\nimport { CallQueue } from \"./callQueue\";\n\n/** Idle time a {@link performCall} tolerates before giving up. */\nconst DEFAULT_CALL_TIMEOUT_MS = 30_000;\n\n/** Progress events a call buffers before pacing the producer. */\nconst DEFAULT_CALL_WATERMARK = 16;\n\n/**\n * What `performCall` needs from the store.\n *\n * @remarks\n * Three members, named rather than structural over the whole class, because three is few enough\n * that naming them documents the coupling instead of hiding it.\n */\nexport interface CallDeps<St, EM extends EventMapBase> {\n readonly idFactory: () => string;\n readonly registerEffect: (spec: EffectSpec<DeepReadonly<St>, EM>) => () => void;\n readonly 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<EmitResult>;\n}\n\nexport function performCall<\n St,\n EM extends EventMapBase,\n C extends keyof EM & string,\n T extends keyof EM[C] & string,\n>(\n deps: CallDeps<St, EM>,\n channel: C,\n type: T,\n payload: EM[C][T],\n opts: CallOptions<EM>,\n): CallHandle<EventUnion<EM>, EventUnion<EM>> {\n const { channel: replyChannel, isTerminal } = parseReply<EM>(opts.reply);\n const idleMs = opts.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;\n const queue = new CallQueue<EventUnion<EM>>(opts.highWaterMark ?? DEFAULT_CALL_WATERMARK);\n\n // Minted here rather than left to `emit`, because the correlation has to be known before the\n // request goes out — a reply can arrive during the emit itself, synchronously.\n const requestId = deps.idFactory();\n\n let settle!: (event: EventUnion<EM>) => void;\n let fail!: (error: Error) => void;\n let settled = false;\n const terminal = new Promise<EventUnion<EM>>((resolve, reject) => {\n settle = resolve;\n fail = reject;\n });\n // Attached immediately so a rejection that nobody has awaited yet is not reported as\n // unhandled; the caller's own await still sees it.\n terminal.catch(() => undefined);\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n let unregister: (() => void) | null = null;\n\n /**\n * Settles the call once. `graceful` distinguishes a terminal reply — after which the\n * consumer is still owed whatever progress it has not read — from an abort, after which\n * nothing is owed to anyone.\n */\n const finish = (fn: () => void, graceful = false): void => {\n if (settled) return;\n settled = true;\n if (timer !== null) clearTimeout(timer);\n timer = null;\n unregister?.();\n unregister = null;\n if (graceful) queue.end();\n else queue.close();\n opts.signal?.removeEventListener(\"abort\", onAbort);\n fn();\n };\n\n function onAbort(): void {\n finish(() => fail(new CallAbortedError(String(opts.signal?.reason ?? \"signal aborted\"))));\n }\n\n const arm = (): void => {\n if (timer !== null) clearTimeout(timer);\n // Idle: every correlated event pushes the deadline out, so a streaming responder is not\n // punished for having a lot to say.\n timer = setTimeout(() => {\n finish(() => fail(new CallTimeoutError(channel, type, idleMs)));\n }, idleMs);\n (timer as { unref?: () => void }).unref?.();\n };\n\n unregister = deps.registerEffect({\n // A pattern effect on the reply channel: which types are terminal is known, which are\n // progress is not, so the filter cannot be a key list.\n when: { channel: replyChannel as keyof EM & string },\n effect: async (event) => {\n if (settled) return;\n if (!isReplyTo<EM>(event, requestId, opts.correlationId)) return;\n\n arm();\n\n if (isTerminal(String(event.type))) {\n finish(() => settle(event), true);\n return;\n }\n\n // The await is the backpressure. This runs inside the store's effect phase, so the\n // responder's own `await emit(...)` does not resolve until it returns.\n await queue.put(event);\n },\n });\n\n if (opts.signal !== undefined) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n arm();\n\n void deps.emit(channel, type, payload, {\n id: requestId,\n ...(opts.correlationId !== undefined\n ? { meta: { correlationId: opts.correlationId } }\n : {}),\n });\n\n const handle = {\n then: (onOk?: never, onErr?: never) => terminal.then(onOk, onErr),\n catch: (onErr?: never) => terminal.catch(onErr),\n finally: (onDone?: () => void) => terminal.finally(onDone),\n get dropped() {\n return queue.droppedCount;\n },\n cancel: (reason = \"cancelled\") => {\n finish(() => fail(new CallAbortedError(reason)));\n },\n [Symbol.asyncIterator]: (): AsyncIterator<EventUnion<EM>> => {\n queue.beginConsuming();\n return {\n next: () => queue.take(),\n // Called by `for await` on `break`, `return` or a throw. Without it, abandoning the\n // loop would leave the effect registered and the producer parked for good.\n return: async () => {\n queue.close();\n return { value: undefined, done: true };\n },\n };\n },\n } as CallHandle<EventUnion<EM>, EventUnion<EM>>;\n\n return handle;\n}\n","/**\n * Reading and expanding dotted state paths.\n *\n * @remarks\n * Moved out of `Store.ts` unchanged. Neither function touched an instance field.\n *\n * `Store` still exposes both as members, and deliberately so. `Store.buildAncestorPaths` is\n * public API that appears in the committed reference, and `getAtPath` is replaced on the\n * instance by a test that counts the walks a change description costs, so the internal callers\n * have to keep reaching it through `this`.\n *\n * @module\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 */\nexport function 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 * buildAncestorPaths('x.y.z'); // ['x','x.y','x.y.z']\n * ```\n *\n * @public\n */\nexport function 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 * Event targeting: deciding whether an event matches a `When` matcher, and reading the parts of\n * a middleware declaration.\n *\n * @remarks\n * Moved out of `Store.ts` unchanged. These four functions never touched an instance field, so\n * they were already free functions wearing method clothing, and the class kept them only because\n * that is where they were written.\n *\n * @module\n */\n\nimport type {\n EventKey,\n EventMapBase,\n EventUnion,\n MiddlewareFunction,\n MiddlewareInput,\n When,\n} from \"../types\";\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 */\nexport function matchesWhen<EM extends EventMapBase>(\n when: When<EM> | undefined,\n event: EventUnion<EM>,\n): 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 */\nexport function getMiddlewareFunction<St, EM extends EventMapBase>(\n input: MiddlewareInput<St, EM>,\n): MiddlewareFunction<St, 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 */\nexport function getMiddlewareWhen<St, EM extends EventMapBase>(\n input: MiddlewareInput<St, 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 * 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 */\nexport function normalizeEventKeys<EM extends EventMapBase>(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 * @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 EmitResult,\n ConnectOptions,\n InstrumentationObserver,\n CascadeInfo,\n InstrumentedEvent,\n EventPhase,\n EventSubscriptionHandler,\n NarrowedEventHandler,\n When,\n} from \"../types\";\nimport { freezeState } from \"../utils/immutability\";\nimport { isRejected } from \"./rejection\";\nimport type { CallHandle, CallOptions } from \"./call\";\nimport { performCall } from \"./performCall\";\nimport type { Rejection } from \"./rejection\";\nimport type { AliasWatch } from \"../utils/immutability\";\nimport {\n buildAncestorPaths as ancestorPaths,\n getAtPath as readAtPath,\n} from \"./paths\";\nimport {\n getMiddlewareFunction,\n getMiddlewareWhen,\n matchesWhen,\n normalizeEventKeys,\n} from \"./matching\";\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 * Causal depth at which the store stops extending an event chain.\n *\n * @remarks\n * Chosen to be uncontroversial rather than tight. An event caused by an event caused by an event\n * is ordinary application wiring; sixty-four deep is a cycle. The cost of being wrong in the\n * generous direction is a cascade that runs a few more hops before it is named; the cost of being\n * wrong in the strict direction is refusing correct code, which would teach people to raise the\n * limit reflexively and defeat it.\n */\nconst DEFAULT_MAX_REDUCE_DEPTH = 64;\n\n/**\n * How many ancestor ids {@link CascadeInfo.chain} carries.\n *\n * @remarks\n * A cascade is long by definition. The diagnostic value is in the cycle at the end — which\n * handler emitted back into which — not in the several thousand identical hops that preceded it,\n * and retaining all of them would make the guard against runaway memory itself retain\n * unboundedly.\n */\nconst CASCADE_CHAIN_LIMIT = 16;\n\n/**\n * One slice's pending write: computed, frozen, and not yet visible to anybody.\n *\n * @remarks\n * `prev` is retained because change notifications report old and new, and by the time they are\n * built the slice has already been replaced in `this.state` — the whole point of staging.\n *\n * @internal\n */\nconst NOT_COMMITTED: EmitResult = Object.freeze({ committed: false, written: false });\nconst COMMITTED_UNWRITTEN: EmitResult = Object.freeze({ committed: true, written: false });\nconst WRITTEN: EmitResult = Object.freeze({ committed: true, written: true });\n\ninterface StagedSlice {\n readonly name: string;\n readonly prev: unknown;\n readonly frozen: unknown;\n readonly leafPaths: string[];\n}\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 /**\n * Subscribers to events that actually changed state, notified after the commit.\n *\n * @remarks\n * Separate from `committedEventSubscribers` rather than a filter over it, because the two\n * answer different questions and one of them is load bearing: `committed` means \"not vetoed\"\n * and fires for every event a store accepts, including every event in a store with no\n * reducers. Narrowing it would have silently stopped toasts and analytics firing.\n *\n * @internal\n */\n private readonly writtenEventSubscribers = new Map<\n string,\n Set<EventSubscriptionHandler<DeepReadonly<S>, EM>>\n >();\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: (result: EmitResult) => void;\n parentId?: string;\n depth?: number;\n /** Ancestor ids, for {@link CascadeInfo.chain}. Never surfaced on the event itself. */\n chain?: readonly string[];\n }> = [];\n\n /**\n * Re-entrancy guard for the synchronous reduce phase.\n *\n * @internal\n */\n private isReducing = false;\n\n /**\n * The event currently being reduced, or `null` outside the drain.\n *\n * @remarks\n * This is what makes causality exact rather than best-effort. The drain is synchronous — no\n * `await` can interleave — so any `emit` that arrives while it is set is, without ambiguity, a\n * consequence of this event. That catches the case a scoped `emit` closure cannot: a\n * middleware or subscriber that captured the store and calls `store.emit` directly instead of\n * using the injected one. Attribution should not depend on which reference a consumer reached\n * for.\n *\n * @internal\n */\n private currentEvent: { id: string; depth: number; chain: readonly string[] } | null = null;\n\n /**\n * Events processed by the drain currently in progress. Compared against\n * `maxTransitionsPerDrain`, which is off unless configured.\n *\n * @internal\n */\n private transitionsThisDrain = 0;\n\n /**\n * Ceilings that stop a cascade from becoming a hung process. See {@link StoreSpec.maxReduceDepth}.\n *\n * @internal\n */\n private readonly maxReduceDepth: number;\n private readonly maxTransitionsPerDrain: number;\n private readonly onCascade?: (info: CascadeInfo<EM>) => void;\n private readonly onRejected?: (\n rejection: Rejection,\n event: EventUnion<EM>,\n slice: string,\n ) => void;\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 commitStaged}. `null` when not instrumenting.\n *\n * @internal\n */\n private changedPathSink: string[] | null = null;\n\n /**\n * Where keyed reducers put their pending writes during a reduce, and the refusal one of them\n * returned.\n *\n * @remarks\n * Keyed reducers are invoked through `reducerBus`, which delivers to handlers and has no way\n * to hand a value back — the same reason `changedPathSink` exists. `null` outside a reduce.\n *\n * @internal\n */\n private stagingSink: StagedSlice[] | null = null;\n private stagedRejection: Rejection | null = null;\n private stagedRejectedBy = \"\";\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 // Depth is bounded whether or not anybody asked. The queue drains synchronously, so an\n // unbounded cascade is a frozen tab or a pinned core with no error to point at — a failure\n // mode a library should not require configuration to avoid.\n //\n // Width stays opt-in, because wide and deep mean different things: a fan-out (one event whose\n // subscriber emits five hundred siblings) is legitimate and wide, while a cascade is narrow\n // and deep. Depth separates them; a count cannot. See StoreSpec.maxTransitionsPerDrain.\n this.maxReduceDepth = spec.maxReduceDepth ?? DEFAULT_MAX_REDUCE_DEPTH;\n this.maxTransitionsPerDrain = spec.maxTransitionsPerDrain ?? Infinity;\n this.onCascade = spec.onCascade;\n this.onRejected = spec.onRejected;\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.__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.writtenEventSubscribers.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 * Reports a breached ceiling and refuses the emit.\n *\n * @remarks\n * Console *and* hook, matching how reducer and effect errors are reported: a cascade is a\n * wiring bug, and the console line is what a developer who has not registered a hook will\n * actually see. Without one, refusing the emit would look exactly like the event never having\n * been emitted at all — which is the invisibility this whole guard exists to end.\n *\n * @internal\n */\n private reportCascade(\n limit: \"maxReduceDepth\" | \"maxTransitionsPerDrain\",\n limitValue: number,\n event: EventUnion<EM>,\n depth: number,\n chain: readonly string[],\n ): void {\n console.error(\n `[yoltra] Cascade stopped: \"${event.channel}/${event.type}\" would exceed ${limit} ` +\n `(${limitValue}). This event was refused and the chain ends here. A chain this long is ` +\n `almost always two consumers emitting into each other — check what reacts to ` +\n `\"${event.channel}/${event.type}\" and what that emits in turn.` +\n (chain.length > 0 ? ` Recent causal chain: ${chain.join(\" → \")} → (refused).` : \"\"),\n );\n\n try {\n this.onCascade?.({ limit, limitValue, event, depth, chain });\n } catch (err) {\n // A throwing diagnostic must not become the failure it was reporting.\n console.error(\"onCascade handler error:\", err);\n }\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 // Effects resume in their own task, after the drain that produced this event has ended, so\n // `currentEvent` is null by the time they run and cannot speak for them. This closure is how\n // an effect's emits stay attached to the event that triggered them — which is what bounds a\n // cascade that crosses drains rather than staying inside one.\n const emit = this.scopedEmit(event);\n\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, 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 (matchesWhen(when, event)) {\n try {\n await effect(event, this.getState, emit);\n } catch (e) {\n console.error(\"Effect error:\", e);\n this.onEffectError?.(e, event);\n }\n }\n }\n }\n\n /**\n * An `emit` that attributes whatever it sends to `cause`.\n *\n * @remarks\n * Built per event rather than per effect: every effect reacting to one event shares a cause,\n * and one closure is cheaper than one per handler on a path that runs for every committed\n * event.\n *\n * @internal\n */\n private scopedEmit(cause: EventUnion<EM>): Emit<EM> {\n const parent = {\n id: cause.id,\n depth: cause.depth ?? 0,\n chain: [...(this.currentEvent?.chain ?? []), cause.id].slice(-CASCADE_CHAIN_LIMIT),\n };\n return ((channel, type, payload, opts) =>\n this.emitCaused(parent, channel, type, payload, opts)) as Emit<EM>;\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\" | \"written\",\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 : phase === \"written\"\n ? this.writtenEventSubscribers\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 //\n // Deliberately not reached for `written`. An event that writes is also committed, so folding\n // it in would hand every existing 'all' subscriber a second notification for the same event\n // and quietly double their counts — a silent change to code that never asked for the new\n // phase. `all` means committed-or-uncommitted, as it always has.\n if (phase === \"written\") return;\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\" | \"written\",\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 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.\n *\n * That is deliberately *not* what a {@link Rejected} refusal does, which discards the whole\n * event. A crash and a refusal are different acts: a reducer that throws has a bug and should\n * not be able to veto its neighbours' work, while a reducer that refuses has made a decision\n * and must be able to.\n *\n * This once argued that rolling back was untenable, because subscribers were notified as each\n * slice committed and a later revert would have told them about a value that no longer\n * existed. Staging removed that obstacle — nothing is notified until every slice is written —\n * which is what made refusal possible at all.\n *\n * @internal\n */\n private stageSliceGuarded<C extends keyof EM & string, T extends keyof EM[C] & string>(\n rName: R,\n event: Event<EM, C, T>,\n staged: StagedSlice[],\n ): Rejection | null {\n try {\n return this.stageSlice(rName, event, staged);\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 null;\n }\n }\n\n /**\n * Runs one slice's reducer and records what it *would* write. Writes nothing.\n *\n * @returns The reducer's {@link Rejection} if it refused, otherwise `null`.\n *\n * @remarks\n * The staging half of the write path. Nothing here touches `this.state` or notifies anybody,\n * which is what lets the event be refused after every reducer has had its say — a decision\n * that has to see the whole diff cannot be made one slice at a time.\n *\n * Freezing happens here rather than at commit because it is where the new value is built, and\n * the freeze is a no-op on anything already frozen; a staged slice that never commits is\n * discarded frozen, which costs nothing and keeps the committed path free of a second walk.\n *\n * @internal\n */\n private stageSlice<C extends keyof EM & string, T extends keyof EM[C] & string>(\n rName: R,\n event: Event<EM, C, T>,\n staged: StagedSlice[],\n ): Rejection | null {\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 // A refusal, not a value. Checked before the identity comparison below, because a rejection\n // object is never the previous state and would otherwise be staged as one.\n if (isRejected(next)) return next;\n\n // if reducer returned same ref, definitely no change\n if (prev === next) return null;\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 the write path 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 null;\n\n // No deep clone: the reducer already returned a fresh `next` (purity contract), so\n // 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 staged.push({\n name: rName as string,\n prev,\n frozen: freezeInDev(next, alias),\n leafPaths,\n });\n\n return null;\n }\n\n /**\n * Writes every staged slice, then tells the world — in that order.\n *\n * @remarks\n * The commit half. Assigning all slices under a single new root before any notification goes\n * out is what closes the window this used to leave open: notifications fired per slice as each\n * committed, so a subscriber to slice A that read `getState()` could observe slice B of the\n * *same event* not yet applied. In React that window is real, because the atomic hooks use a\n * change as a bare signal and then re-read the whole store.\n *\n * It is also what makes refusal possible at all. The previous code documented rollback as\n * untenable precisely because \"an event that reverted afterwards would have already told\n * components about a value that no longer exists\" — true when notification and commit were the\n * same step, and no longer true now that they are not.\n *\n * @returns `true` if anything was written.\n *\n * @internal\n */\n private commitStaged(staged: StagedSlice[], event: EventUnion<EM>): boolean {\n if (staged.length === 0) return false;\n\n // One new root for the whole event, not one per slice.\n const nextState = { ...(this.state as object) } as Record<string, unknown>;\n for (const slice of staged) nextState[slice.name] = slice.frozen;\n this.state = nextState 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 slice of staged) {\n for (const p of slice.leafPaths) {\n this.changedPathSink.push(p ? `${slice.name}.${p}` : slice.name);\n }\n }\n }\n\n // Every notification happens after every write, so any handler reading `getState()` sees the\n // event applied in full.\n for (const slice of staged) {\n // emit deep + ancestor paths once each\n const toEmit = new Set<string>();\n for (const p of slice.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(slice.name as R, prop, () => ({\n oldValue: this.getAtPath(slice.prev, prop),\n newValue: this.getAtPath(slice.frozen, prop),\n path: prop,\n // Provenance, built inside the same lazy factory as the values: a subscriber that\n // needs to know why a value moved no longer has to mirror the cause into state and\n // keep it there twice.\n eventId: event.id,\n channel: event.channel as string,\n type: event.type as string,\n }));\n }\n }\n\n return true;\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 commitStaged}.\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 // `stageSlice`: `\"\"` 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 // Staged and committed exactly as a live event is, so a replay reproduces the same state\n // by the same path — including a reducer that refuses, which must refuse identically or\n // the replayed history is not the history.\n const staged: StagedSlice[] = [];\n this.stagingSink = staged;\n let rejection: Rejection | null = null;\n\n try {\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 rejection = this.stagedRejection;\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 (rejection !== null) break;\n if (matchesWhen(when, event)) {\n const refused = this.stageSliceGuarded(sliceName, event as any, staged);\n if (refused !== null) rejection = refused;\n }\n }\n } finally {\n this.stagingSink = null;\n this.stagedRejection = null;\n this.stagedRejectedBy = \"\";\n }\n\n const anySliceChanged = rejection === null && this.commitStaged(staged, event);\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.notifyEventSubscribers(event, \"written\");\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) - every matching slice is *staged*; nothing is written yet, so a refusal from the last reducer still stops the first one's write\n * 4. **Commit + subscribers** (sync) - all staged slices are assigned under one new root, then event subscribers (`committed`, then `written` when state actually changed), then coarse listeners\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 the commit builds a new state reference via\n * 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<EmitResult> {\n return this.emitCaused(null, channel, type, payload, opts);\n }\n\n /**\n * The real emit, with an explicitly supplied cause.\n *\n * @remarks\n * Exists so the parent can be passed without a pseudo-private field on the public\n * {@link EmitOptions}. Two callers supply one: the public {@link emit} passes `null` and lets\n * `currentEvent` speak for the synchronous case, and the scoped `emit` handed to effects passes\n * the event that triggered them — effects resume after the drain has ended, so nothing else\n * could still know what caused them.\n *\n * @internal\n */\n private async emitCaused<C extends keyof EM & string, T extends keyof EM[C] & string>(\n scopedParent: { id: string; depth: number; chain: readonly string[] } | null,\n channel: C,\n type: T,\n payload: EM[C][T],\n opts?: EmitOptions,\n ): Promise<EmitResult> {\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 // A suppressed duplicate never reaches middleware or a reducer, so it is neither\n // committed nor written — the same answer a vetoed event gives, which is correct: in\n // both cases the caller's event had no effect.\n return NOT_COMMITTED;\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\n // Causality. `currentEvent` is set only inside the synchronous drain, so if it is set this\n // emit is a consequence of that event — no matter whether the caller used the injected\n // `emit` or reached for `store.emit` directly. Outside the drain, an explicitly scoped\n // parent (given to effects, which resume after the drain has ended) supplies it instead.\n const parent = this.currentEvent ?? scopedParent;\n const depth = parent === null ? 0 : parent.depth + 1;\n\n if (parent !== null && depth > this.maxReduceDepth) {\n // Refused, not thrown: the throw would land in whichever frame happened to be emitting.\n // Guarded on `parent` as well as depth — a root is depth 0 and can only breach a ceiling\n // set below zero, and refusing the caller's own emit is never the right answer.\n this.reportCascade(\n \"maxReduceDepth\",\n this.maxReduceDepth,\n {\n channel,\n type,\n payload,\n id,\n ...(opts?.meta !== undefined ? { meta: opts.meta } : {}),\n parentId: parent.id,\n depth,\n } as EventUnion<EM>,\n depth,\n parent.chain,\n );\n return NOT_COMMITTED;\n }\n\n let resolve!: (result: EmitResult) => void;\n const done = new Promise<EmitResult>((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 // Only carried for caused events, so a root event's object stays byte-identical to one\n // built before causality existed — the same rule `meta` follows.\n ...(parent !== null ? { parentId: parent.id, depth, chain: parent.chain } : {}),\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 this.transitionsThisDrain = 0;\n try {\n while (this.reduceQueue.length > 0) {\n const next = this.reduceQueue.shift()!;\n const { channel, type, payload, id, meta, resolve, parentId, depth, chain } = next;\n\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. `parentId` and\n // `depth` follow the same rule, and are absent on a root event.\n const event = {\n channel,\n type,\n payload,\n id,\n ...(meta !== undefined ? { meta } : {}),\n ...(parentId !== undefined ? { parentId, depth } : {}),\n } as EventUnion<EM>;\n\n // Width ceiling, checked as the event is dequeued rather than as it is emitted: a burst\n // is only excessive relative to the pass draining it, and at emit time there is no pass\n // yet. Off unless configured — see StoreSpec.maxTransitionsPerDrain.\n //\n // The root is never refused. It is the caller's own emit, not part of any burst, and a\n // ceiling that rejected it would turn \"this store's cascades are bounded\" into \"this\n // store randomly drops the event you just sent\". Only what the drain caused can be\n // excessive, which is also why `depth` and `chain` are known to be set here.\n if (parentId !== undefined && ++this.transitionsThisDrain > this.maxTransitionsPerDrain) {\n this.reportCascade(\n \"maxTransitionsPerDrain\",\n this.maxTransitionsPerDrain,\n event,\n depth as number,\n chain as readonly string[],\n );\n // Resolve rather than abandon: a caller awaiting this emit would otherwise hang, which\n // is the failure the ceiling exists to prevent, arriving by another door.\n resolve(NOT_COMMITTED);\n continue;\n }\n\n // Anything emitted from here until the end of this iteration is caused by this event.\n // The drain is synchronous, so this is exact rather than a heuristic — and it holds even\n // when a consumer calls `store.emit` directly instead of the injected `emit`.\n this.currentEvent = {\n id,\n depth: depth ?? 0,\n chain: [...(chain ?? []), id].slice(-CASCADE_CHAIN_LIMIT),\n };\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 result: EmitResult = NOT_COMMITTED;\n try {\n result = this.applyEventSync(event);\n } catch (err) {\n console.error(\"Emit reduce error:\", err);\n } finally {\n if (instrumenting) this.changedPathSink = null;\n // Cleared before effects are scheduled. Effects resume in a later task, when this\n // event is no longer what the drain is processing; they carry their cause explicitly\n // through the scoped emit instead.\n this.currentEvent = null;\n }\n\n if (instrumenting) {\n this.emitInstrumentation(\n event,\n result,\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, result, 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>): EmitResult {\n // Middleware (synchronous). Return false to veto; async work belongs in effects.\n for (const mwInput of this.middleware) {\n const when = getMiddlewareWhen(mwInput);\n if (!matchesWhen(when, event)) continue;\n const mw = 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 NOT_COMMITTED;\n }\n }\n\n // Reduce every matching slice into a staging list. Nothing is written yet, so a refusal\n // arriving from the last reducer can still stop the first one's write.\n const staged: StagedSlice[] = [];\n this.stagingSink = staged;\n let rejection: Rejection | null = null;\n let rejectedBy = \"\";\n\n try {\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 rejection = this.stagedRejection;\n rejectedBy = this.stagedRejectedBy;\n\n for (const [sliceName, when] of this.patternReducers) {\n if (rejection !== null) break;\n if (matchesWhen(when, event)) {\n const refused = this.stageSliceGuarded(sliceName, event as any, staged);\n if (refused !== null) {\n rejection = refused;\n rejectedBy = sliceName as string;\n }\n }\n }\n } finally {\n this.stagingSink = null;\n this.stagedRejection = null;\n this.stagedRejectedBy = \"\";\n }\n\n // A refusal discards every staged slice, not just the refusing one. Authorising a write to\n // one slice while a sibling records it as accepted is not authorisation — and a caller told\n // \"rejected\" must not find half of its event applied.\n if (rejection !== null) {\n this.onRejected?.(rejection, event, rejectedBy);\n this.notifyEventSubscribers(event, \"committed\");\n return { committed: true, written: false, rejected: rejection };\n }\n\n const written = this.commitStaged(staged, event);\n\n // Committed subscribers fire whether or not anything was written — `committed` means \"not\n // vetoed\", which is what a notification or analytics bus depends on. `written` is the\n // stricter fact, and fires after the commit so a handler reading getState() sees it.\n this.notifyEventSubscribers(event, \"committed\");\n if (written) {\n this.notifyEventSubscribers(event, \"written\");\n this.listeners.forEach((l) => l());\n }\n return written ? WRITTEN : COMMITTED_UNWRITTEN;\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 result: EmitResult,\n resolve: (result: EmitResult) => void,\n ): Promise<void> {\n this.inFlightEffects++;\n try {\n if (result.committed) await this.notifyEffects(event);\n } catch (err) {\n console.error(\"Effect error:\", err);\n } finally {\n this.inFlightEffects--;\n resolve(result);\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 commitStaged} during this reduce, so DevTools patches need no\n * re-diff.\n *\n * @internal\n */\n private emitInstrumentation(\n event: EventUnion<EM>,\n result: EmitResult,\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: result.committed,\n changedPaths,\n prevValues,\n nextValues,\n reduceTimeMs,\n // Present only when a reducer refused, so an observer can tell a refusal from a veto —\n // identical in state, entirely different in cause.\n ...(result.rejected !== undefined ? { rejected: result.rejected } : {}),\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(\n spec: { reducer: R; property: string },\n h: (chg: Change) => void,\n options?: ConnectOptions,\n ): () => void {\n const off = this.connectorBus.on(spec.reducer, spec.property, h);\n\n if (options?.immediate === true) {\n // @ts-expect-error R indexing on DeepReadonly<S> is valid at runtime\n const slice = this.state[spec.reducer] as unknown;\n // A pattern matches a set of paths, and a set has no single current value — so the slice\n // root is delivered instead, at the path a whole-slice subscription would use.\n // Same test the bus uses to tell a pattern from an exact path.\n const path = spec.property.includes(\"*\") ? \"\" : spec.property;\n\n // No `eventId`, `channel` or `type`: nothing caused this, and inventing a cause would be\n // a lie a subscriber could act on. `oldValue` is undefined for the same reason — there is\n // no previous value, only a first one.\n h({ oldValue: undefined, newValue: this.getAtPath(slice, path), path });\n }\n\n return off;\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 : phase === \"written\"\n ? this.writtenEventSubscribers\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 /**\n * Sends a request and waits for the reply, correlating the two automatically.\n *\n * @typeParam C - Request channel.\n * @typeParam T - Request type within `C`.\n * @param channel - Channel to send on.\n * @param type - Event type to send.\n * @param payload - The **request** payload. This is what you are sending; what comes back is\n * described by {@link CallOptions.reply}, not by this.\n * @param opts - Which replies end the call, and how long to wait. See {@link CallOptions}.\n * @returns A {@link CallHandle}: `await` it for the terminal reply, or `for await` it for\n * progress events as they arrive.\n *\n * @remarks\n * Every consumer of an event bus eventually writes request/reply by hand — mint an id,\n * subscribe, match, time out, unsubscribe — and every one of them writes the same eighty lines\n * with the same two bugs: the subscription outlives the call, and a responder that forgets to\n * echo the id produces a timeout with nothing to point at. This is that, once.\n *\n * **Correlation is causal.** The store stamps `parentId` on anything emitted while an event is\n * being handled, so a responder that replies through the `emit` it was handed is already\n * correlated. There is no id to mint, echo, or forget:\n *\n * ```ts\n * store.registerEffect({\n * when: { keys: [[\"rpc\", \"ask\"]] },\n * effect: async (event, _get, emit) => {\n * await emit(\"rpc\", \"answer\", await lookup(event.payload.q));\n * },\n * });\n * ```\n *\n * **The reply carries its own discriminant.** A call resolves to the *event*, not the payload,\n * because a caller often cannot know which kind of reply it will get:\n *\n * ```ts\n * const res = await store.call(\"rpc\", \"ask\", { q }, { reply: [\"rpc\", [\"answer\", \"error\"]] });\n * switch (res.type) {\n * case \"answer\": return res.payload;\n * case \"error\": throw new Error(res.payload.reason);\n * }\n * ```\n *\n * **Progress streams, with backpressure.** Any correlated event that is not terminal is\n * progress, and iterating the call consumes it. The producer genuinely waits: `emit` resolves\n * only once its effects have run, and the collector is an effect that does not return until the\n * consumer has taken the item. A responder writing `await emit(\"rpc\", \"progress\", chunk)` is\n * therefore paced by the reader, with nothing buffering without bound.\n *\n * ```ts\n * const call = store.call(\"job\", \"start\", { id }, {\n * reply: [\"job\", \"done\"],\n * highWaterMark: 4,\n * });\n * for await (const step of call) await render(step.payload); // producer waits on this\n * const { payload } = await call;\n * ```\n *\n * Backpressure engages **once you begin iterating**. A call that is only awaited never pulls,\n * so blocking its producer would deadlock the call itself — progress nobody reads would stop\n * the terminal event from ever being sent. Un-iterated progress therefore buffers to\n * `highWaterMark` and is then counted on {@link CallHandle.dropped} rather than blocking.\n *\n * **This is a local primitive.**\n *\n * @example Timeout is idle, not total\n * ```ts\n * // Survives a job that streams for minutes; fails a responder that goes quiet for 5s.\n * await store.call(\"job\", \"start\", { id }, { reply: [\"job\", \"done\"], timeoutMs: 5_000 });\n * ```\n *\n * @example Cancelling\n * ```ts\n * const call = store.call(\"rpc\", \"ask\", { q }, { reply: [\"rpc\", \"answer\"] });\n * useEffect(() => () => call.cancel(\"unmounted\"), [call]);\n * ```\n *\n * @public\n */\n public call<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n payload: EM[C][T],\n opts: CallOptions<EM>,\n ): CallHandle<EventUnion<EM>, EventUnion<EM>> {\n return performCall<S, EM, C, T>(\n { idFactory: this.idFactory, registerEffect: this.registerEffect, emit: this.emit },\n channel,\n type,\n payload,\n opts,\n );\n }\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 = 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 = 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 // Staged, not committed. `reducerBus` delivers to handlers and has no return channel,\n // so a refusal is recorded on the store for `applyEventSync` to read — the same reason\n // `changedPathSink` exists. The sink is null outside a reduce, which is the only path\n // that can reach here.\n if (this.stagingSink === null) return;\n const refused = this.stageSliceGuarded(name, event as any, this.stagingSink);\n if (refused !== null && this.stagedRejection === null) {\n this.stagedRejection = refused;\n this.stagedRejectedBy = name as string;\n }\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 * 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 * @remarks\n * A member rather than a bare import: a test replaces this on the instance to count how many\n * walks describing a change costs, which only works while the callers go through `this`.\n *\n * @internal\n */\n private getAtPath(obj: any, path: string): any {\n return readAtPath(obj, path);\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 return ancestorPaths(path);\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 maxReduceDepth?: number;\n maxTransitionsPerDrain?: number;\n onCascade?: (info: CascadeInfo<EM>) => void;\n onRejected?: (rejection: Rejection, 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 maxReduceDepth?: number;\n maxTransitionsPerDrain?: number;\n onCascade?: (info: CascadeInfo<EMFromReducersStrict<RM>>) => void;\n onRejected?: (\n rejection: Rejection,\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 // Spread, then override the three fields that need a default. Copying the option list by hand\n // meant every option added to `StoreSpec` had to be added here too, and forgetting was silent:\n // the option type-checked at the call site, reached `createStore`, and was dropped on the\n // floor. `maxReduceDepth` was lost exactly that way. The Store constructor reads named fields,\n // so anything extra in `cfg` is ignored rather than harmful.\n return new Store<EM, RN, S>({\n ...cfg,\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 });\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\nimport type { Rejection } from \"./store/rejection\";\nimport type { CallHandle, CallOptions } from \"./store/call\";\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 * The `id` of the event whose handling caused this one, when there was one.\n *\n * @remarks\n * Absent on a **root** event — one emitted by application code rather than by a middleware,\n * subscriber or effect reacting to another event. Together with {@link Event.depth} this makes\n * a cascade legible after the fact: without it, a runaway chain is a pile of unrelated events\n * with no way to tell which caused which.\n */\n readonly parentId?: string;\n /**\n * How many events deep in a causal chain this one is. A root event is depth `0`; an event\n * emitted while handling it is `1`, and so on.\n *\n * @remarks\n * Absent on a root event rather than present as `0`, so an event emitted by application code\n * stays byte-identical to one built before causality tracking existed — the same treatment\n * {@link Event.meta} gets, and for the same reason: `Object.keys` and `toStrictEqual` are load\n * bearing in consumer tests.\n *\n * This is the value {@link StoreSpec.maxReduceDepth} bounds.\n */\n readonly depth?: number;\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 * The `id` of the event that caused this change.\n *\n * @remarks\n * A change used to be anonymous, so a subscriber that needed to know *why* a value moved had\n * to mirror the cause into state and store it twice. Absent when the change did not come from\n * an event — a DevTools time-travel snapshot, for instance — which is itself the signal that\n * no event caused it.\n */\n eventId?: string;\n /** Channel of the causing event. Absent for the same reason as {@link Change.eventId}. */\n channel?: string;\n /** Type of the causing event. Absent for the same reason as {@link Change.eventId}. */\n type?: 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 * What an `emit` resolves to once its effects have run.\n *\n * @remarks\n * `emit` used to resolve to `void`, so a caller could not tell \"the reducer applied my write\"\n * from \"the reducer looked at my write and returned the state unchanged\". On a single-writer\n * store that distinction is academic; on a contended one it is a lost update the API could not\n * report.\n *\n * Deliberately does **not** carry the changed paths. Building that list costs a string\n * concatenation per changed path on every emit, and almost no caller reads it — the same reason\n * change notifications are built lazily. Instrumentation already provides them to the observers\n * that do want them.\n *\n * @public\n */\nexport interface EmitResult {\n /**\n * The event was not vetoed by middleware.\n *\n * @remarks\n * Unchanged in meaning, and deliberately not narrowed to \"state changed\" — an event-only store\n * commits every event and writes nothing, by construction.\n */\n readonly committed: boolean;\n /** A reducer actually changed state. */\n readonly written: boolean;\n /** Present when a reducer refused the write. See {@link Rejection}. */\n readonly rejected?: Rejection;\n}\n\n/**\n * Options for {@link StoreInstance.connect}.\n *\n * @public\n */\nexport interface ConnectOptions {\n /**\n * Deliver the current value once, immediately, before any change arrives.\n *\n * @remarks\n * A subscription otherwise starts at \"from now on\", so a subscriber's first render has to read\n * the path separately — the same path, spelled twice, which is one place for them to drift.\n *\n * The synthetic change has `oldValue: undefined` and no `eventId`, `channel` or `type`: no\n * event caused it, and claiming one would be a lie a subscriber could act on.\n *\n * For a wildcard pattern the \"current value\" of a match set is not a thing, so the slice root\n * is delivered with `path: \"\"`. React's hooks do not need this at all — `useSyncExternalStore`\n * already reads a snapshot on mount — so it is aimed at imperative subscribers.\n */\n readonly immediate?: boolean;\n}\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 (another\n * 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<EmitResult>;\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 * Present when a reducer refused the write, carrying its reason.\n *\n * @remarks\n * Distinct from `committed: false`, which means middleware vetoed the event before any reducer\n * saw it. This is a reducer having considered the write and declined it — the two look\n * identical in state and are entirely different in cause.\n */\n rejected?: Rejection;\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 * Maximum causal depth of an event chain before the store refuses to extend it.\n *\n * @remarks\n * An event emitted while handling another is one deeper than its cause. Two reducers wired to\n * each other, or an effect that emits the event its own reducer answers, climb this without\n * bound — and the reduce queue drains synchronously, so in a browser that is a frozen tab with\n * no error and no stack, and on a server a pinned core.\n *\n * **On by default**, because the whole point is that the failure mode does not require\n * configuration to avoid. The default is far past any legitimate chain: an event caused by an\n * event caused by an event is normal, sixty-four deep is a bug. Raise it if an application\n * genuinely nests deeper, or set `Infinity` to opt out entirely and own the consequences.\n *\n * Breaching does not throw — see {@link StoreSpec.onCascade}.\n *\n * @default 64\n */\n maxReduceDepth?: number;\n\n /**\n * Maximum number of events one synchronous drain will process before refusing more.\n *\n * @remarks\n * A drain processes one root event plus every event emitted *while it runs* — so this counts a\n * single causal burst, not application traffic. A plain loop is unaffected: `emit` drains to\n * completion before it returns, so `for (const row of rows) store.emit(…)` is a thousand drains\n * of one event each, never one drain of a thousand.\n *\n * **Off by default** because a wide burst is not by itself a bug. One `sync` event whose\n * subscriber fans out to five hundred `upsert`s is a legitimate shape, and a default low enough\n * to catch a runaway would refuse it. Depth is what separates a cascade from a fan-out — a\n * fan-out is wide and shallow, a cascade is narrow and deep — which is why\n * {@link StoreSpec.maxReduceDepth} carries the default and this does not.\n *\n * Set it when a store's bursts are known to be bounded and an unexpectedly wide one is itself\n * the symptom worth catching.\n *\n * @default undefined (no limit)\n */\n maxTransitionsPerDrain?: number;\n\n /**\n * Called when a ceiling is breached, instead of throwing.\n *\n * @remarks\n * The offending emit is refused and the chain stops there; everything already committed\n * stands. It does not throw, because the throw would surface in whichever frame happened to be\n * emitting — a subscriber, an effect, a middleware — which is the same species of\n * hard-to-attribute failure the ceiling exists to prevent. A cascade is a wiring bug, and this\n * is where the wiring gets named.\n *\n * @param info - Which ceiling, the event that would have extended the chain, and its causal\n * chain of ids, newest last.\n */\n onCascade?: (info: CascadeInfo<EM>) => void;\n\n /**\n * Called when a reducer refuses a write by returning {@link Rejected}.\n *\n * @remarks\n * The caller learns of its own refusal from the `emit` result; this is for everyone else —\n * logging, metrics, alerting on a rate of rejected writes. Shaped as a callback rather than a\n * subscription for the same reason {@link StoreSpec.onReducerError} is: it is a rare global\n * signal, not something several independent parties register and unregister for.\n *\n * A refusal is a normal outcome, not an error. It means a reducer considered the write and\n * declined it — a stale compare-and-swap, an unmet precondition — and the event is rejected\n * whole, so no slice writes.\n *\n * @param rejection - The refusal and its reason.\n * @param event - The event that was refused.\n * @param slice - Name of the slice whose reducer refused.\n */\n onRejected?: (rejection: Rejection, event: EventUnion<EM>, slice: string) => void;\n};\n\n/**\n * What {@link StoreSpec.onCascade} receives when a ceiling is breached.\n *\n * @typeParam EM - Event map.\n *\n * @public\n */\nexport interface CascadeInfo<EM extends EventMapBase = EventMapBase> {\n /** Which ceiling was hit. */\n readonly limit: \"maxReduceDepth\" | \"maxTransitionsPerDrain\";\n /** The configured value that was exceeded. */\n readonly limitValue: number;\n /** The event that was refused — the one that would have extended the chain. */\n readonly event: EventUnion<EM>;\n /** Causal depth the refused event would have had. */\n readonly depth: number;\n /**\n * Ids from the root of the chain to the refused event's parent, newest last.\n *\n * @remarks\n * Bounded to the most recent entries: a cascade is long by definition, and the useful part is\n * the cycle at the end rather than the thousand identical hops before it.\n */\n readonly chain: readonly string[];\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(\n spec: { reducer: R; property: string },\n handler: (change: Change) => void,\n options?: ConnectOptions,\n ): Unsubscribe;\n\n /**\n * Sends a request and waits for the reply, correlating the two automatically.\n *\n * @remarks\n * Awaitable for the terminal reply, async-iterable for progress. See the implementation on\n * {@link Store.call} for the full contract: correlation, backpressure, timeouts, and why it\n * is a local primitive.\n */\n call<C extends keyof EM & string, T extends keyof EM[C] & string>(\n channel: C,\n type: T,\n payload: EM[C][T],\n opts: CallOptions<EM>,\n ): CallHandle<EventUnion<EM>, EventUnion<EM>>;\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\n * 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 | Rejection;\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\n * 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 * - `'written'`: Events that actually changed state\n * - `'all'`: Both committed and uncommitted events\n *\n * @remarks\n * `'committed'` means **not vetoed**, and always has. It fires for an event that passed\n * middleware whether or not any reducer wrote anything — including every event in a store with\n * no reducers at all, which is the shape a notification or analytics bus takes. Toasts,\n * animations and tracking depend on that, so it is not narrowed.\n *\n * `'written'` is the stricter fact, added rather than substituted: state changed. It fires\n * **after** the commit, so a subscriber reading `getState()` from it sees the new value — which\n * is what people tend to assume `'committed'` does.\n *\n * `'all'` deliberately stays `committed | uncommitted`. Folding `'written'` into it would hand\n * every existing `'all'` subscriber a second notification per written event and quietly double\n * their counts.\n *\n * @public\n */\nexport type EventPhase = \"committed\" | \"uncommitted\" | \"written\" | \"all\";\n\n/**\n * The phases a handler is actually *told about*.\n *\n * @remarks\n * `'all'` is a subscription selector, not an outcome — nothing is ever delivered \"in the all\n * phase\". Naming the difference keeps the two from being conflated in a handler signature, which\n * is where they were previously spelled out by hand and drifted: adding `'written'` to\n * {@link EventPhase} left three copies in `@yoltra/react` still claiming a handler could only\n * ever see two phases, and the build failed on the mismatch.\n *\n * @public\n */\nexport type NotifiedPhase = Exclude<EventPhase, \"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: NotifiedPhase,\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: NotifiedPhase,\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/**\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, { state: unknown }>>(\n reducers: R,\n hydration: Hydration,\n): R {\n if (!hydration.restored) return reducers;\n\n const next = {} as Record<string, { state: unknown }>;\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","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","REJECTED","Rejected","reason","isRejected","value","CallTimeoutError","idleMs","CallAbortedError","parseReply","reply","types","t","isReplyTo","requestId","correlationId","CallQueue","highWaterMark","item","taker","release","buffered","putter","next","DEFAULT_CALL_TIMEOUT_MS","DEFAULT_CALL_WATERMARK","performCall","deps","opts","replyChannel","isTerminal","queue","settle","fail","settled","terminal","resolve","reject","timer","unregister","finish","fn","graceful","onAbort","arm","onOk","onErr","onDone","getAtPath","parts","cur","seg","buildAncestorPaths","matchesWhen","when","getMiddlewareFunction","input","getMiddlewareWhen","normalizeEventKeys","spec","cloneInitialState","sliceName","freezeInDev","DEFAULT_DEDUP_KEY_WINDOW_MS","DEFAULT_MAX_REDUCE_DEPTH","CASCADE_CHAIN_LIMIT","NOT_COMMITTED","COMMITTED_UNWRITTEN","WRITTEN","now","Store","name","rSpec","effSpec","base","json","fp","windowMs","existing","effectiveWindow","cutoff","timestamp","limit","limitValue","depth","chain","emit","effectSet","effect","cause","parent","phase","phaseSet","allSet","rName","staged","prev","leafPaths","nextState","slice","toEmit","prop","reducers","effects","meta","middleware","mwInput","atomic","coarse","nextPlain","anyChanged","prevSlice","nextSlice","frozenNextSlice","oldValue","newValue","l","snapshot","events","evt","rejection","refused","anySliceChanged","scopedParent","dedupKey","contentWindow","id","done","r","parentId","instrumenting","prevState","sink","t0","mw","ok","rejectedBy","written","observer","changedPaths","reduceTimeMs","prevValues","nextValues","info","options","off","targetMap","unsubs","eventKeys","u","getState","typed","preserveState","currentKeys","nextEntries","nextKeys","k","partial","reducer","ch","tp","sourceEvent","_removed","rest","readAtPath","ancestorPaths","createStore","cfg","typedEvents","_","keys","warnedDottedIds","warnDottedId","sameOrder","current","createEntityAdapter","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","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","flush","schedule","stop","dehydrate","createWebStorageAdapter","storage","createMemoryAdapter","initial"],"mappings":"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"}