cross-tab-worker-databus 0.20.88 → 0.20.89

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,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/core/environment.ts", "../src/core/hash.ts", "../src/core/routing.ts", "../src/utils/metadata.ts", "../src/utils/validation.ts", "../src/core/storage-batch.ts", "../src/utils/storage-utils.ts", "../src/core/cluster.ts", "../src/core/trace.ts", "../src/core/replay-pruning.ts", "../src/core/replay-manager.ts", "../src/core/dedup-manager.ts", "../src/core/version.ts", "../src/core/data-bus.ts", "../src/worker-mode.ts", "../src/core/publication.ts"],
4
- "sourcesContent": ["/**\n * Browser environment adapters \u2014 storage, BroadcastChannel, timers, lifecycle.\n *\n * Separates platform-specific APIs from the core coordination logic so the\n * same Runtime can run in a browser, in a test, or in an embedded context\n * with custom adapters injected via `ClusterEnvironment`.\n */\nimport type { TabVisibilityState, WorkerClusterMessage } from './types';\nimport { CHANNEL_FALLBACK, STORAGE_CHANNEL_PREFIX, TAB_ID_STORAGE_KEY, TAB_VISIBILITY } from '../utils/constants';\nimport type { EVENT_TYPE } from '../utils/constants';\n\n/** Minimal storage interface compatible with both localStorage and MemoryStorage. */\nexport interface StorageLike {\n readonly length: number;\n clear(): void;\n getItem(key: string): string | null;\n key(index: number): string | null;\n removeItem(key: string): void;\n setItem(key: string, value: string): void;\n}\n\n/** Minimal BroadcastChannel interface. The cluster uses it for control messages and event fan-out. */\nexport interface ClusterChannel {\n addEventListener(type: typeof EVENT_TYPE.MESSAGE, listener: (event: MessageEvent<WorkerClusterMessage>) => void): void;\n removeEventListener(type: typeof EVENT_TYPE.MESSAGE, listener: (event: MessageEvent<WorkerClusterMessage>) => void): void;\n postMessage(message: WorkerClusterMessage): void;\n close(): void;\n}\n\n/**\n * Environment abstraction that lets the cluster operate in Node, SSR, or\n * test environments without touching browser globals directly.\n * Tests inject a fake environment to control timing, storage, and lifecycle.\n */\nexport interface ClusterEnvironment {\n /** localStorage (or null if unavailable). Wrapped by BatchingStorageWriter. */\n storage: StorageLike | null;\n /** sessionStorage (or null if unavailable). Used for stable tab IDs. */\n sessionStorage: StorageLike | null;\n /** Monotonic clock; injected so tests can control time. */\n now: () => number;\n /** Generates a random ID (UUID when crypto is available, else Math.random). */\n randomId: () => string;\n /** Creates a BroadcastChannel by name, or null if unsupported. */\n createChannel: (name: string) => ClusterChannel | null;\n /** Sets an interval; returns a handle for clearInterval. */\n setInterval: (callback: () => void, intervalMs: number) => unknown;\n /** Clears a handle from setInterval. */\n clearInterval: (handle: unknown) => void;\n /** Current tab visibility ('visible' or 'hidden'). */\n getVisibilityState: () => TabVisibilityState;\n /** Register a listener for visibilitychange events. */\n addVisibilityChangeListener: (listener: () => void) => void;\n /** Remove a previously-added visibilitychange listener. */\n removeVisibilityChangeListener: (listener: () => void) => void;\n /** Register a listener for pagehide (BFCache entry). */\n addPageHideListener: (listener: () => void) => void;\n /** Remove a previously-added pagehide listener. */\n removePageHideListener: (listener: () => void) => void;\n /** Register a listener for pageshow (BFCache exit / restore). */\n addPageShowListener: (listener: () => void) => void;\n /** Remove a previously-added pageshow listener. */\n removePageShowListener: (listener: () => void) => void;\n}\n\n/** Resolve a Web Storage interface by name, or null in non-browser contexts.\n * The `typeof window` guard short-circuits in SSR / Node (where `window` is\n * undefined) before touching it; the try/catch covers sandboxed or\n * storage-disabled browsers that throw on property access. */\nfunction getStorage(name: 'localStorage' | 'sessionStorage'): StorageLike | null {\n try {\n return typeof window === 'undefined' ? null : window[name];\n } catch {\n return null;\n }\n}\n\nfunction randomId(): string {\n try {\n return globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);\n } catch {\n return Math.random().toString(36).slice(2);\n }\n}\n\n/** Minimal window surface the storage-event channel needs (injectable for tests). */\nexport interface StorageEventWindow {\n addEventListener(type: 'storage', listener: (event: { key: string | null; newValue: string | null }) => void): void;\n removeEventListener(type: 'storage', listener: (event: { key: string | null; newValue: string | null }) => void): void;\n}\n\n/**\n * Create a {@link ClusterChannel} backed by localStorage `storage` events, as\n * a coordination fallback for environments where BroadcastChannel is\n * unavailable. Returns null when localStorage or a storage-event source is\n * missing.\n *\n * Semantics mirror BroadcastChannel: writes are not echoed to the sender\n * (per spec, the writing tab receives no `storage` event) and every message\n * is JSON-serializable. The payload is written under a dedicated key and\n * removed on close.\n *\n * Security note: unlike BroadcastChannel messages (memory only), these\n * payloads transit through localStorage and therefore persist \u2014 at least\n * transiently, and after a crash indefinitely. Coordination frames carry\n * plaintext topic names; callers who enable this fallback accept that\n * trade-off (see docs/configuration.md).\n */\nexport function createStorageEventChannel(options: {\n name: string;\n storage: StorageLike | null;\n win: StorageEventWindow | null;\n}): ClusterChannel | null {\n const { name, storage, win } = options;\n if (!storage || !win) return null;\n const key = `${STORAGE_CHANNEL_PREFIX}${name}`;\n const listeners = new Set<(event: MessageEvent<WorkerClusterMessage>) => void>();\n // A per-sender monotonically increasing sequence guarantees every write has\n // a distinct value, so a browser that suppresses same-value storage events\n // still delivers every message.\n let sequence = 0;\n\n const onStorage = (event: { key: string | null; newValue: string | null }) => {\n if (event.key !== key || event.newValue === null) return;\n let message: WorkerClusterMessage;\n try {\n const parsed = JSON.parse(event.newValue) as { seq?: unknown; message?: WorkerClusterMessage };\n if (!parsed || typeof parsed !== 'object' || typeof parsed.seq !== 'number' || !parsed.message) return;\n message = parsed.message;\n } catch {\n return;\n }\n // Cluster messages are read-only downstream; deliver a plain envelope.\n const event_ = { data: message } as MessageEvent<WorkerClusterMessage>;\n for (const listener of [...listeners]) listener(event_);\n };\n\n win.addEventListener('storage', onStorage);\n let closed = false;\n return {\n addEventListener(_type, listener) {\n listeners.add(listener);\n },\n removeEventListener(_type, listener) {\n listeners.delete(listener);\n },\n postMessage(message: WorkerClusterMessage): void {\n // A closed channel must not resurrect the payload in storage.\n if (closed) return;\n sequence += 1;\n storage.setItem(key, JSON.stringify({ seq: sequence, message }));\n },\n close(): void {\n closed = true;\n win.removeEventListener('storage', onStorage);\n listeners.clear();\n try {\n storage.removeItem(key);\n } catch {\n // Removal is best-effort; the key is namespaced and harmless.\n }\n }\n };\n}\n\n// A document can create multiple DataBus runtimes (for example market and\n// notice connections). Regenerate a copied opener id only on the first lookup\n// in that document; subsequent runtimes must continue sharing the same tabId.\nlet tabIdentityInitialized = false;\n\n/**\n * Default environment adapter for browser runtimes.\n * Probes for localStorage, BroadcastChannel, document, and window APIs\n * and gracefully returns null / no-ops when they are absent (SSR, Node).\n */\nexport function createBrowserEnvironment(options?: {\n /** When BroadcastChannel is unavailable, fall back to a localStorage\n * storage-event channel instead of degrading to local mode. Opt-in because\n * the fallback persists coordination payloads in localStorage. */\n channelFallback?: (typeof CHANNEL_FALLBACK)[keyof typeof CHANNEL_FALLBACK];\n}): ClusterEnvironment {\n const channelFallback = options?.channelFallback ?? CHANNEL_FALLBACK.NONE;\n return {\n storage: getStorage('localStorage'),\n sessionStorage: getStorage('sessionStorage'),\n now: Date.now,\n randomId,\n createChannel: name => {\n try {\n if (typeof BroadcastChannel !== 'undefined') return new BroadcastChannel(name);\n } catch {\n // fall through to the storage-event fallback.\n }\n return channelFallback === CHANNEL_FALLBACK.STORAGE_EVENT\n ? createStorageEventChannel({\n name,\n storage: getStorage('localStorage'),\n win: typeof window !== 'undefined' && typeof window.addEventListener === 'function' ? window : null\n })\n : null;\n },\n setInterval: (callback, intervalMs) => globalThis.setInterval(callback, intervalMs),\n clearInterval: handle => globalThis.clearInterval(handle as ReturnType<typeof setInterval>),\n getVisibilityState: () =>\n typeof document !== 'undefined' && document.visibilityState === TAB_VISIBILITY.HIDDEN\n ? TAB_VISIBILITY.HIDDEN\n : TAB_VISIBILITY.VISIBLE,\n addVisibilityChangeListener: listener => {\n if (typeof document !== 'undefined') document.addEventListener('visibilitychange', listener);\n },\n removeVisibilityChangeListener: listener => {\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', listener);\n },\n addPageHideListener: listener => {\n if (typeof window !== 'undefined') window.addEventListener('pagehide', listener);\n },\n removePageHideListener: listener => {\n if (typeof window !== 'undefined') window.removeEventListener('pagehide', listener);\n },\n addPageShowListener: listener => {\n if (typeof window !== 'undefined') window.addEventListener('pageshow', listener);\n },\n removePageShowListener: listener => {\n if (typeof window !== 'undefined') window.removeEventListener('pageshow', listener);\n }\n };\n}\n\n/**\n * Probe a storage instance with a write-read-delete round-trip.\n * Returns a type guard so the caller can narrow the type after a successful check.\n * Catches quota errors, disabled-storage (Safari private mode), or opaque\n * exceptions \u2014 any of which means the storage is not usable for coordination\n * and the Runtime must degrade to local mode.\n */\nexport function canUseStorage(storage: StorageLike | null, probeKey: string): storage is StorageLike {\n if (!storage) return false;\n try {\n storage.setItem(probeKey, '1');\n storage.removeItem(probeKey);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get-or-create a stable tab ID stored in sessionStorage.\n * sessionStorage is scoped to the tab and survives refresh, so the same tab\n * retains its identity across the page lifecycle without coordination overhead.\n * Falls back to a random ID when sessionStorage is unavailable.\n */\nexport function getOrCreateTabId(\n environment: ClusterEnvironment,\n key = TAB_ID_STORAGE_KEY\n): string {\n const storage = environment.sessionStorage;\n try {\n const existing = storage?.getItem(key);\n // `window.open()` may clone the opener's sessionStorage into the new tab.\n // A child page therefore must not blindly reuse the copied value: the\n // value identifies a page/tab instance, not an account or browser window.\n // `noopener` is still recommended by applications, but this guard keeps\n // the SDK safe when an opener is present.\n const hasOpener = typeof window !== 'undefined' && Boolean(window.opener);\n if (existing && (!hasOpener || tabIdentityInitialized)) {\n tabIdentityInitialized = true;\n return existing;\n }\n const created = `tab-${environment.randomId()}`;\n storage?.setItem(key, created);\n tabIdentityInitialized = true;\n return created;\n } catch {\n return `tab-${environment.randomId()}`;\n }\n}\n", "/**\n * Derives a stable 128-bit hex key from a string.\n *\n * This is a non-cryptographic four-way hash (inspired by MurmurHash-style\n * mixing). It exists so connection URLs and topic plaintext never touch\n * localStorage or BroadcastChannel namespaces \u2014 consumers only ever see the\n * opaque key. It trades collision resistance for speed and zero dependencies:\n * use `crypto.subtle.digest` if you need a cryptographic hash.\n */\nexport function createOpaqueKey(value: string): string {\n // Four independent lanes mix the input so a short value still diffuses\n // across all 128 bits rather than only exercising the low bits. Each lane\n // starts from a distinct 32-bit seed XORed with the length so that strings\n // of different lengths diverge from the first mix step.\n let h1 = SEED_H1 ^ value.length;\n let h2 = SEED_H2 ^ value.length;\n let h3 = SEED_H3 ^ value.length;\n let h4 = SEED_H4 ^ value.length;\n\n // Feed every UTF-16 code unit into all four lanes with distinct large primes.\n // Note: this operates on UTF-16 code units, so astral-plane characters (emoji,\n // rare CJK) are hashed as surrogate pairs \u2014 consistent within a process, but\n // not Unicode-normalized. Callers should normalize the topic string beforehand\n // if cross-normalization-form stability is required.\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n h1 = Math.imul(h1 ^ code, PRIME_H1);\n h2 = Math.imul(h2 ^ code, PRIME_H2);\n h3 = Math.imul(h3 ^ code, PRIME_H3);\n h4 = Math.imul(h4 ^ code, PRIME_H4);\n }\n\n // Final avalanche: cross-mix the lanes so nearby inputs produce distant keys,\n // avoiding the clustering a naive sum would exhibit in storage prefixes.\n h1 = avalancheMix(h1, h2);\n h2 = avalancheMix(h2, h3);\n h3 = avalancheMix(h3, h4);\n h4 = avalancheMix(h4, h1);\n\n return [h1, h2, h3, h4].map(hash => (hash >>> 0).toString(16).padStart(8, '0')).join('');\n}\n\n/** Distinct 32-bit seeds for the four hash lanes. */\nconst SEED_H1 = 0xdeadbeef;\nconst SEED_H2 = 0x41c6ce57;\nconst SEED_H3 = 0xc0decafe;\nconst SEED_H4 = 0x9e3779b9;\n\n/** Distinct large primes for the four per-character mix steps. */\nconst PRIME_H1 = 2_654_435_761;\nconst PRIME_H2 = 1_597_334_677;\nconst PRIME_H3 = 2_246_822_519;\nconst PRIME_H4 = 3_266_489_917;\n\n/** Final avalanche constant pair. Each lane is mixed with itself (shifted)\n * and XORed with a neighbor lane (shifted) to cross-diffuse the lanes. */\nconst AVALANCHE_PRIME = 2_246_822_507;\nconst AVALANCHE_CROSS = 3_266_489_909;\n\n/** One step of the final avalanche: mix `self` with a shift and prime, then\n * XOR with a cross-mix of `neighbor` (also shifted and primed) so a change\n * in any lane propagates to the others. The 16/13 shifts spread bits across\n * the 32-bit word before the prime multiply scrambles them further. */\nfunction avalancheMix(self: number, neighbor: number): number {\n return (\n Math.imul(self ^ (self >>> 16), AVALANCHE_PRIME) ^\n Math.imul(neighbor ^ (neighbor >>> 13), AVALANCHE_CROSS)\n );\n}\n", "/**\n * Routing primitives for topic-owner selection and rebalancing.\n *\n * Pure functions that select candidate Workers, compute the least-loaded\n * owner, and decide when to migrate a topic. All side-effect-free, making\n * them straightforward to test and reason about.\n */\nimport type { LoadWeightingOptions, WorkerRecord, WorkerRoute } from './types';\nimport { TAB_VISIBILITY, WORKER_STATUS } from '../utils/constants';\n\n/** Default cap on the number of Workers that can own topics concurrently.\n * Limits fan-out breadth: only N workers are eligible to be new-route\n * owners, so a cluster of 20 tabs still concentrates ownership on a few. */\nexport const DEFAULT_MAX_ACTIVE_WORKERS = 3;\n\n/**\n * Compute the effective load score used for owner selection.\n *\n * Legacy behavior: `worker.load` (owned-topic count) with no throughput\n * contribution. When a Worker publishes a traffic sample AND weights are set,\n * the normalized per-second rates are added on top, so a busy owner becomes\n * less attractive for NEW routes without ever migrating an existing one.\n * Returns the raw topic count when no sample or no weight is present.\n */\nexport function effectiveWorkerLoad(\n worker: WorkerRecord,\n options?: LoadWeightingOptions\n): number {\n // The base topic count must itself be finite before it is used as the\n // fallback. A corrupt `load` read back from a peer's stored record (JSON\n // `1e999` parses to Infinity; a malformed record can carry null/NaN) would\n // otherwise leak a non-finite score straight through the fallback branches,\n // re-introducing the order-dependent owner selection this function is meant\n // to be total against.\n const baseLoad = Number.isFinite(worker.load) ? worker.load : 0;\n const sample = worker.throughput;\n const messageRateWeight = options?.messageRateWeight ?? 0;\n const byteRateWeight = options?.byteRateWeight ?? 0;\n const scheduleLagWeight = options?.scheduleLagWeight ?? 0;\n // A missing sample, unset weights, or a non-positive window (no elapsed\n // time to derive a rate from) all fall back to the raw topic count.\n // `Number.isFinite` rather than a bare `<= 0`: `NaN <= 0` is false, so a\n // sample with a corrupt `windowMs` (read back from a peer's heartbeat record)\n // would otherwise divide through to a NaN score.\n if (\n !sample ||\n !Number.isFinite(sample.windowMs) ||\n sample.windowMs <= 0 ||\n (messageRateWeight === 0 && byteRateWeight === 0 && scheduleLagWeight === 0)\n ) {\n return baseLoad;\n }\n const windowSeconds = sample.windowMs / 1000;\n const messageRate = sample.messageCount / windowSeconds;\n const byteRate = sample.byteCount / windowSeconds;\n // Scheduling lag is the ratio of heartbeat overrun to wall time: a Worker\n // whose timers land late (starved event loop) contributes ~overrun/window.\n const scheduleLagRatio = sample.overrunMs / sample.windowMs;\n const weighted =\n baseLoad +\n messageRateWeight * messageRate +\n byteRateWeight * byteRate +\n scheduleLagWeight * scheduleLagRatio;\n // A non-finite score is never ordered: `selectLeastLoadedWorker` compares\n // `byLoad !== 0`, which is true for NaN, and `NaN < 0` is false \u2014 so a NaN\n // worker wins or loses purely by its index in the input array, making owner\n // selection depend on storage listing order rather than on load. A corrupt\n // sample field or a non-finite weight reaches this point, so fall back to\n // the raw topic count instead of leaking the NaN into routing.\n return Number.isFinite(weighted) ? weighted : baseLoad;\n}\n\n/**\n * Cheap, allocation-free estimate of a payload's wire size, used to populate\n * the byte side of an adaptive load sample. Only runs when adaptive routing is\n * enabled, so approximate sizes are fine \u2014 the goal is a stable cross-worker\n * comparison, not an exact byte count. Sizes: null/undefined 0, booleans 4,\n * numbers 8, strings their length, binary views their byteLength, arrays an\n * 8-byte header plus elements, plain objects the sum of their values.\n */\nexport function approximatePayloadBytes(payload: unknown): number {\n return estimatePayloadBytes(payload, 0);\n}\n\n/**\n * Depth cap for the recursive size estimate. Real payloads are shallow, and a\n * cap makes the function total against deeply nested or *cyclic* object graphs\n * \u2014 structured clone preserves cycles, so a cyclic publication can legitimately\n * reach the replay buffer (`getDiagnostics().replay.bytes`) and the adaptive\n * load sampler. Without the cap that recurred until the stack overflowed\n * (RangeError), taking the whole diagnostics/reconcile path down. Beyond the\n * cap the contribution is treated as 0 (it is an approximation either way).\n */\nconst MAX_PAYLOAD_DEPTH = 6;\n\nfunction estimatePayloadBytes(payload: unknown, depth: number): number {\n if (payload === null || payload === undefined) return 0;\n switch (typeof payload) {\n case 'boolean':\n return 4;\n case 'number':\n case 'bigint':\n return 8;\n case 'string':\n return payload.length;\n case 'symbol':\n case 'function':\n return 0;\n case 'object':\n break;\n }\n if (depth >= MAX_PAYLOAD_DEPTH) return 0;\n if (payload instanceof ArrayBuffer) return payload.byteLength;\n if (ArrayBuffer.isView(payload)) return payload.byteLength;\n if (Array.isArray(payload)) {\n let sum = 8;\n for (const item of payload) sum += estimatePayloadBytes(item, depth + 1);\n return sum;\n }\n let sum = 0;\n for (const value of Object.values(payload as Record<string, unknown>)) {\n sum += estimatePayloadBytes(value, depth + 1);\n }\n return sum;\n}\n\n/**\n * Pick the Worker with the fewest effective load, optionally preferring a\n * specific sticky owner when it is still in the candidate set.\n * Uses a single reduce pass instead of a full sort \u2014 O(n) \u2014 and breaks\n * load ties by workerId for deterministic routing across tabs (the\n * comparison is code-unit based, not locale-based, for cross-host stability).\n */\nexport function selectLeastLoadedWorker(\n workers: readonly WorkerRecord[],\n preferredWorkerId?: string,\n options?: LoadWeightingOptions\n): WorkerRecord | undefined {\n const preferred = workers.find(worker => worker.workerId === preferredWorkerId);\n if (preferred) return preferred;\n return workers.reduce<WorkerRecord | undefined>((least, worker) => {\n if (!least) return worker;\n const byLoad = effectiveWorkerLoad(worker, options) - effectiveWorkerLoad(least, options);\n if (byLoad !== 0) return byLoad < 0 ? worker : least;\n // Tie-break by workerId with a locale-independent comparison so routing\n // is deterministic regardless of the host's collation order.\n if (worker.workerId < least.workerId) return worker;\n return least;\n }, undefined);\n}\n\n/**\n * Select the (up to `maxActiveWorkers`) Workers eligible to own topics.\n *\n * Eligibility cascade:\n * 1. Only `connecting` / `connected` workers are candidates.\n * 2. If any candidate is visible, prefer visible tabs (hidden tabs yield as owner).\n * 3. Fall back to all available workers when none is visible, so the cluster\n * does not stall when every tab is in the background.\n * 4. Tie-break by registration time, then workerId, for determinism.\n */\nexport function selectActiveWorkers(\n workers: readonly WorkerRecord[],\n maxActiveWorkers = DEFAULT_MAX_ACTIVE_WORKERS\n): WorkerRecord[] {\n const healthyWorkers = workers.filter(\n worker => worker.status === WORKER_STATUS.CONNECTING || worker.status === WORKER_STATUS.CONNECTED\n );\n const availableWorkers = healthyWorkers.length > 0 ? healthyWorkers : [...workers];\n const visibleWorkers = availableWorkers.filter(worker => worker.visibilityState === TAB_VISIBILITY.VISIBLE);\n const candidates = visibleWorkers.length > 0 ? visibleWorkers : availableWorkers;\n return candidates\n .sort(\n (left, right) =>\n left.registeredAt - right.registeredAt ||\n (left.workerId < right.workerId ? -1 : left.workerId > right.workerId ? 1 : 0)\n )\n .slice(0, maxActiveWorkers);\n}\n\n/**\n * Decide whether `currentWorkerId` should hand one topic to a less-loaded peer.\n * Returns the target Worker only when its load gap is significant (more than\n * one topic lighter), so the cluster does not churn over a single-topic\n * imbalance. One topic is migrated per reconciliation round to avoid thrashing.\n *\n * This remains exported as a standalone routing utility for API compatibility.\n * WorkerClusterRuntime intentionally does not use it: established routes are\n * sticky and load balancing applies only when selecting a new owner.\n */\nexport function selectRebalanceTarget(\n workers: readonly WorkerRecord[],\n currentWorkerId: string\n): WorkerRecord | null {\n const currentWorker = workers.find(worker => worker.workerId === currentWorkerId);\n const leastLoadedWorker = selectLeastLoadedWorker(workers);\n if (\n !currentWorker ||\n !leastLoadedWorker ||\n currentWorker.workerId === leastLoadedWorker.workerId ||\n currentWorker.load <= leastLoadedWorker.load + 1\n ) {\n return null;\n }\n return leastLoadedWorker;\n}\n\n/** True when a route's owner is still a live, active Worker in the given set. */\nexport function hasActiveOwner(route: WorkerRoute | null, workers: readonly WorkerRecord[]): boolean {\n return Boolean(route && workers.some(worker => worker.workerId === route.workerId));\n}\n\n/** True when `pattern` is a wildcard topic: `*` (match everything) or a\n * `prefix.*` suffix wildcard (match any remainder, including multiple\n * segments). Any other string is an exact topic. */\nexport function isWildcardTopic(pattern: string): boolean {\n return pattern === '*' || pattern.endsWith('.*');\n}\n\n/** True when a publication on `topic` must be delivered to a subscription\n * made with `pattern`. Exact patterns match only themselves; wildcards use\n * prefix matching so `chat.*` matches `chat.room.1` and `*` matches anything.\n * The empty pattern never matches. */\nexport function topicMatchesPattern(pattern: string, topic: string): boolean {\n if (!pattern || !topic) return false;\n if (pattern === topic) return true;\n if (pattern === '*') return true;\n if (!pattern.endsWith('.*')) return false;\n return topic.startsWith(pattern.slice(0, -1));\n}\n", "/**\n * \u53D1\u5E03\u5143\u6570\u636E\u5DE5\u5177 \u2014\u2014 \u6784\u9020 { messageId?, timestamp? }\uFF0C\u4EC5\u5728\u5B57\u6BB5\u5DF2\u5B9A\u4E49\u65F6\u5199\u5165\uFF0C\n * \u907F\u514D\u5F80 wire \u5E27/\u63A7\u5236\u6D88\u606F\u91CC\u585E\u7A7A\u679A\u4E3E\u5B57\u6BB5\u3002\n *\n * \u6B64\u524D cluster.ts\u3001data-bus.ts\u3001centrifuge.ts\u3001centrifuge-session.ts \u5404\u81EA\u5185\u8054\n * \u5B9E\u73B0\u4E86\u4E00\u904D `...(x === undefined ? {} : { x })` \u5C55\u5F00\uFF0C\u884C\u4E3A\u5BB9\u6613\u6F02\u79FB\uFF1B\u7EDF\u4E00\u5230\u6B64\n * \u4E00\u5904\u540E\u6240\u6709\u8C03\u7528\u65B9\u5171\u4EAB\u540C\u4E00\u8BED\u4E49\u3002\n */\nimport type { DataBusPublicationMetadata } from '../core/types';\n\n/**\n * Copy defined publication metadata without adding empty enumerable fields.\n * Returns `undefined` when neither field is set, so callers can preserve a\n * legacy \"no metadata\" argument shape (e.g. `onControl(..., undefined)`).\n */\nexport function publicationMetadata(\n messageId?: string,\n timestamp?: number\n): DataBusPublicationMetadata | undefined {\n if (messageId === undefined && timestamp === undefined) return undefined;\n return {\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n };\n}\n", "/**\n * \u53C2\u6570\u6821\u9A8C\u5DE5\u5177 \u2014\u2014 CrossTabDataBus \u6784\u9020\u9009\u9879\u4E0E\u6301\u4E45\u5316\u914D\u7F6E\u7684\u5165\u53E3\u6821\u9A8C\u3002\n *\n * \u6240\u6709 `throw new TypeError(...)` \u6821\u9A8C\u96C6\u4E2D\u5728\u6B64\uFF0CDataBus \u6784\u9020\u5668\u4E0E\n * IndexedDbReplayPersistence \u5171\u7528\u540C\u4E00\u7EC4\u65AD\u8A00\uFF0C\u9519\u8BEF\u6D88\u606F\u4E0E\u539F\u6709\u8BED\u4E49\u4FDD\u6301\u4E00\u81F4\u3002\n *\n * \u8BED\u4E49\u7EA6\u5B9A\uFF1A\u53EF\u9009\u5B57\u6BB5\u53EA\u5728**\u663E\u5F0F\u63D0\u4F9B**\u65F6\u6821\u9A8C\uFF08undefined \u7531\u8C03\u7528\u65B9\u843D\u5230\u9ED8\u8BA4\u503C\uFF0C\n * \u9ED8\u8BA4\u503C\u59CB\u7EC8\u5408\u6CD5\uFF09\uFF1B\u5FC5\u586B\u5B57\u6BB5\u603B\u662F\u6821\u9A8C\u3002\n */\nimport type {\n DataBusDedupOptions,\n DataBusPersistenceRetryOptions,\n DataBusReplayOptions\n} from '../core/data-bus';\nimport type { LoadWeightingOptions } from '../core/types';\nimport { PRUNE_STRATEGY } from './constants';\n\n/** Assert `value` is a positive safe integer. Throws a TypeError otherwise. */\nexport function assertPositiveSafeInteger(value: unknown, name: string): void {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive safe integer, got ${String(value)}.`);\n }\n}\n\n/** Assert `value` is a positive finite number. Throws a TypeError otherwise. */\nexport function assertPositiveFiniteNumber(value: unknown, name: string): void {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive finite number.`);\n }\n}\n\n/** Assert `value` is a non-negative finite number. Throws a TypeError otherwise. */\nexport function assertNonNegativeFiniteNumber(value: unknown, name: string): void {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw new TypeError(`${name} must be a non-negative finite number.`);\n }\n}\n\n/** Assert `value` is a valid replay prune strategy ('count' | 'age' | 'both'). */\nexport function assertPruneStrategy(value: unknown): asserts value is 'count' | 'age' | 'both' {\n const allowed: readonly string[] = [PRUNE_STRATEGY.COUNT, PRUNE_STRATEGY.AGE, PRUNE_STRATEGY.BOTH];\n if (!allowed.includes(String(value))) {\n throw new TypeError('pruneStrategy must be count, age, or both.');\n }\n}\n\n/** Validate the replay options block. Optional fields are validated only when\n * provided; omitted fields fall through to their defaults. */\nexport function assertReplayOptions(replay: DataBusReplayOptions | undefined): void {\n if (!replay) return;\n if (replay.maxPerTopic !== undefined) assertPositiveSafeInteger(replay.maxPerTopic, 'replay.maxPerTopic');\n if (replay.pruneStrategy !== undefined) assertPruneStrategy(replay.pruneStrategy);\n if (replay.retentionMs !== undefined) assertPositiveFiniteNumber(replay.retentionMs, 'replay.retentionMs');\n if (replay.retentionSweepMs !== undefined) {\n assertPositiveFiniteNumber(replay.retentionSweepMs, 'replay.retentionSweepMs');\n }\n if (replay.persistenceRetry) assertPersistenceRetryOptions(replay.persistenceRetry);\n}\n\n/** Validate the replay persistence retry policy. */\nexport function assertPersistenceRetryOptions(retry: DataBusPersistenceRetryOptions): void {\n if (retry.maxAttempts !== undefined) {\n assertPositiveSafeInteger(retry.maxAttempts, 'replay.persistenceRetry.maxAttempts');\n }\n if (retry.backoffMs !== undefined) {\n assertNonNegativeFiniteNumber(retry.backoffMs, 'replay.persistenceRetry.backoffMs');\n }\n}\n\n/** Validate the dedup options block. Optional fields are validated only when\n * provided; omitted fields fall through to their defaults. */\nexport function assertDedupOptions(dedup: DataBusDedupOptions | undefined): void {\n if (!dedup) return;\n if (dedup.maxEntries !== undefined) assertPositiveSafeInteger(dedup.maxEntries, 'dedup.maxEntries');\n if (dedup.ttlMs !== undefined) assertPositiveFiniteNumber(dedup.ttlMs, 'dedup.ttlMs');\n if (dedup.sweepMs !== undefined) assertPositiveFiniteNumber(dedup.sweepMs, 'dedup.sweepMs');\n const bounds = dedup.adaptiveTtl;\n if (bounds !== undefined) {\n // Both bounds must be finite positive numbers with `minMs <= maxMs`.\n // A `NaN`/non-number slips past a plain `<=` comparison (`NaN <= 0` and\n // `maxMs < NaN` are both false), which would leave `currentTtl()` returning\n // `NaN` and silently disable expiry instead of failing loudly.\n const finite = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n if (!finite(bounds.minMs) || !finite(bounds.maxMs) || bounds.minMs <= 0 || bounds.maxMs < bounds.minMs) {\n throw new TypeError('dedup.adaptiveTtl bounds are invalid.');\n }\n }\n}\n\n/** Validate the transport recovery pacing options. Optional fields are validated\n * only when provided; omitted fields fall through to their defaults. */\nexport function assertRecoveryOptions(recovery: {\n cooldownMs?: number;\n maxAttempts?: number;\n} | undefined): void {\n if (!recovery) return;\n if (recovery.cooldownMs !== undefined) {\n assertPositiveFiniteNumber(recovery.cooldownMs, 'recovery.cooldownMs');\n }\n const maxAttempts = recovery.maxAttempts;\n if (\n maxAttempts !== undefined &&\n !(maxAttempts === Number.POSITIVE_INFINITY ||\n (typeof maxAttempts === 'number' && Number.isSafeInteger(maxAttempts) && maxAttempts > 0))\n ) {\n throw new TypeError('recovery.maxAttempts must be a positive safe integer.');\n }\n}\n\n/** Validate the adaptive owner-weighting weights.\n *\n * Each weight is a non-negative finite number of \"topic-equivalents\" added per\n * unit of the sampled signal; `0` (the default) disables that signal. A\n * negative weight would invert the documented policy \u2014 biasing NEW routes\n * toward the *busiest* Worker instead of the quietest \u2014 and a non-finite one\n * would poison the score, so both are rejected here rather than silently\n * steering traffic. */\nexport function assertLoadWeightingOptions(loadWeighting: LoadWeightingOptions | undefined): void {\n if (!loadWeighting) return;\n const weights: Record<string, number | undefined> = {\n messageRateWeight: loadWeighting.messageRateWeight,\n byteRateWeight: loadWeighting.byteRateWeight,\n scheduleLagWeight: loadWeighting.scheduleLagWeight\n };\n for (const [name, value] of Object.entries(weights)) {\n if (value !== undefined) assertNonNegativeFiniteNumber(value, `loadWeighting.${name}`);\n }\n}\n\n/** Validate the cluster coordination options shared by `WorkerClusterRuntime`\n * and `CrossTabDataBus`.\n *\n * These were previously unvalidated, so a `heartbeatIntervalMs` of `0` or `NaN`\n * silently turned the heartbeat `setInterval` into a 0ms busy loop (the same\n * failure the Centrifuge PING guard exists to prevent), a non-positive\n * `workerTtlMs` pruned every peer on the first reconcile, and a non-positive\n * `maxActiveWorkers`/`routeOwnerCacheMax` disabled ownership or caching\n * outright. `Infinity` is rejected for the heartbeat here: unlike the Centrifuge\n * PING it cannot mean \"disable\", because a Worker that never refreshes its\n * heartbeat is pruned by its own TTL. */\nexport function assertClusterOptions(options: {\n maxActiveWorkers?: number;\n heartbeatIntervalMs?: number;\n workerTtlMs?: number;\n routeOwnerCacheMax?: number;\n loadWeighting?: LoadWeightingOptions;\n}): void {\n if (options.maxActiveWorkers !== undefined) {\n assertPositiveSafeInteger(options.maxActiveWorkers, 'maxActiveWorkers');\n }\n if (options.heartbeatIntervalMs !== undefined) {\n assertPositiveFiniteNumber(options.heartbeatIntervalMs, 'heartbeatIntervalMs');\n }\n if (options.workerTtlMs !== undefined) {\n assertPositiveFiniteNumber(options.workerTtlMs, 'workerTtlMs');\n }\n if (options.routeOwnerCacheMax !== undefined) {\n assertPositiveSafeInteger(options.routeOwnerCacheMax, 'routeOwnerCacheMax');\n }\n assertLoadWeightingOptions(options.loadWeighting);\n}\n\n/** Validate the SharedWorker PING heartbeat interval. A value of `0`, a negative\n * number, or `NaN` would otherwise make `setInterval` degenerate into a 0ms busy\n * loop, driving the reaper and the main-thread PING out of control. `Infinity`\n * is allowed and disables heartbeats entirely (for environments where the\n * SharedWorker reaper is not needed, e.g. a single-tab deployment).\n * @throws {TypeError} when `value` is not a positive finite number or Infinity. */\nexport function assertHeartbeatInterval(value: number): void {\n if (value === Infinity) return;\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) return;\n throw new TypeError(\n `Centrifuge heartbeatIntervalMs must be a positive number or Infinity, got ${String(value)}.`\n );\n}\n\n/** Validate that `value` is structured-cloneable. Throws early so config errors\n * surface on the main thread rather than silently failing inside the Worker\n * (where a DataCloneError would be reported as a generic Worker error with no\n * actionable message). Skips validation when `structuredClone` is unavailable\n * (older browsers without the API) \u2014 the Worker will still throw on its own.\n * @throws {TypeError} when `value` contains non-cloneable members (functions,\n * Symbols, DOM nodes, etc.). */\nexport function assertStructuredCloneable(value: unknown): void {\n if (typeof structuredClone !== 'function') return;\n try {\n structuredClone(value);\n } catch (error) {\n throw new TypeError(\n 'Centrifuge Worker configuration and published data must be structured-cloneable.',\n { cause: error }\n );\n }\n}\n", "/**\n * BatchingStorageWriter \u2014 coalesced, resilient localStorage writes.\n *\n * Decorates a StorageLike with write coalescing: mutations within the same task\n * are merged by key and flushed once via a microtask, with exponential backoff\n * on quota/failure. Keeps the coordination metadata writes off the hot path.\n */\nimport type { StorageLike } from './environment';\nimport { DEFAULT_STORAGE_PREFIX } from '../utils/constants';\n\n/** Initial retry delay for a failed storage write (ms). */\nconst INITIAL_RETRY_DELAY_MS = 50;\n/** Maximum retry delay after exponential backoff (ms). Caps at 1.6 s so a\n * persistently failing key retries roughly every 1-2 s, not every minute. */\nconst MAX_RETRY_DELAY_MS = 1_600;\n// Max retry attempts per key before giving up and dropping the write, so a\n// structurally failing key (e.g. a payload the underlying storage rejects)\n// cannot stall coordination forever. The local transport remains usable.\nconst MAX_RETRY_ATTEMPTS = 5;\n\n/**\n * Coalesces synchronous storage writes and applies them in one pass, with\n * exponential backoff when the underlying storage rejects a write.\n *\n * Wraps a {@link StorageLike} so callers (WorkerClusterRuntime) see a normal\n * storage interface; reads transparently see pending writes before they flush.\n * The coalescing window is one microtask, so a burst of heartbeat + route +\n * subscriber writes in the same task becomes a single localStorage flush.\n */\nexport class BatchingStorageWriter implements StorageLike {\n /** Coalesced write set. A `null` value represents a pending delete. */\n private readonly pending = new Map<string, string | null>();\n /** Per-key retry counter, reset on a successful write. */\n private readonly retryCount = new Map<string, number>();\n private flushScheduled = false;\n private retryHandle: ReturnType<typeof setTimeout> | null = null;\n private retryDelayMs = INITIAL_RETRY_DELAY_MS;\n\n constructor(private readonly storage: StorageLike) {}\n\n /** Number of writes queued in memory but not yet flushed to storage.\n * Used by tests to assert the coalescing window and by flush() to detect\n * the all-drained state. */\n get pendingSize(): number {\n return this.pending.size;\n }\n\n get length(): number {\n return this.keys().length;\n }\n\n clear(): void {\n this.pending.clear();\n this.flushScheduled = false;\n this.storage.clear();\n this.cancelRetry();\n this.retryCount.clear();\n // Reset backoff so a burst of clear()/flush() cycles does not leave the\n // writer stuck at an elevated retry delay.\n this.retryDelayMs = INITIAL_RETRY_DELAY_MS;\n }\n\n // Reads always see the pending value first (task-local consistency), then\n // fall back to the underlying storage.\n getItem(key: string): string | null {\n if (this.pending.has(key)) return this.pending.get(key) ?? null;\n return this.storage.getItem(key);\n }\n\n key(index: number): string | null {\n return this.keys()[index] ?? null;\n }\n\n removeItem(key: string): void {\n this.pending.set(key, null);\n this.scheduleFlush();\n }\n\n setItem(key: string, value: string): void {\n this.pending.set(key, value);\n this.scheduleFlush();\n }\n\n flush(): void {\n this.flushScheduled = false;\n this.cancelRetry();\n // Apply writes from a snapshot so a concurrent scheduleFlush during the\n // loop cannot re-enter or corrupt the pending map mid-iteration.\n // Array.from is preferred over [...this.pending] here: it avoids the\n // spread's intermediate iterator allocation on a hot path that heartbeats\n // and route writes hit every few seconds.\n for (const [key, value] of Array.from(this.pending)) {\n try {\n if (value === null) this.storage.removeItem(key);\n else this.storage.setItem(key, value);\n this.pending.delete(key);\n this.retryCount.delete(key);\n } catch {\n const attempts = (this.retryCount.get(key) ?? 0) + 1;\n // A persistently failing key (e.g. a payload the storage rejects)\n // is dropped after MAX_RETRY_ATTEMPTS so coordination is not stuck\n // forever. Best-effort: the local transport stays usable without it.\n if (attempts >= MAX_RETRY_ATTEMPTS) {\n this.pending.delete(key);\n this.retryCount.delete(key);\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(`[${DEFAULT_STORAGE_PREFIX}] storage write gave up after retries, dropping key:`, key);\n }\n continue;\n }\n this.retryCount.set(key, attempts);\n // Remaining pending writes stay queued for the next attempt, which\n // retries with backoff. Coalescing is preserved by setting the gate\n // so a concurrent scheduleFlush cannot start a second overlapping pass.\n // `break` stops the flush at the first failure so the retry loop can\n // re-attempt this key (and the remaining pending entries) together,\n // rather than continuing to apply later keys while an earlier one is\n // still in a failed-and-retrying state.\n this.scheduleRetry();\n break;\n }\n }\n if (this.pending.size === 0) {\n this.retryDelayMs = INITIAL_RETRY_DELAY_MS;\n this.retryCount.clear();\n }\n }\n\n /** Union of persisted keys and pending writes, minus pending deletes. */\n private keys(): string[] {\n const keys = new Set<string>();\n for (let index = 0; index < this.storage.length; index += 1) {\n const key = this.storage.key(index);\n if (key !== null) keys.add(key);\n }\n for (const [key, value] of this.pending) {\n if (value === null) keys.delete(key);\n else keys.add(key);\n }\n return Array.from(keys);\n }\n\n // Coalesce all synchronous writes within one task into a single microtask\n // flush, avoiding a localStorage write per heartbeat/route/subscriber update.\n // The queueMicrotask fallback to setTimeout handles older runtimes and\n // non-browser environments where queueMicrotask is absent.\n private scheduleFlush(): void {\n if (this.flushScheduled) return;\n this.flushScheduled = true;\n const flush = () => {\n this.flushScheduled = false;\n this.flush();\n };\n if (typeof queueMicrotask === 'function') queueMicrotask(flush);\n else setTimeout(flush, 0);\n }\n\n // Schedule a single retry timer. The guard ensures only one retry is in\n // flight at a time; subsequent scheduleRetry calls during the wait are\n // no-ops because the first retry will re-flush all pending keys together.\n private scheduleRetry(): void {\n if (this.retryHandle !== null) return;\n this.retryHandle = setTimeout(() => {\n this.retryHandle = null;\n this.flush();\n }, this.retryDelayMs);\n // Exponential backoff: 50ms \u2192 100ms \u2192 \u2026 \u2192 capped at 1600ms.\n this.retryDelayMs = Math.min(MAX_RETRY_DELAY_MS, this.retryDelayMs * 2);\n }\n\n private cancelRetry(): void {\n if (this.retryHandle !== null) {\n clearTimeout(this.retryHandle);\n this.retryHandle = null;\n }\n }\n}\n", "/**\n * localStorage \u8BFB\u5199\u5DE5\u5177 \u2014\u2014 \u5BB9\u9519\u7684 JSON \u8BFB\u3001\u9759\u9ED8\u5199\u3001\u6309\u524D\u7F00\u679A\u4E3E\u3002\n *\n * \u4ECE WorkerClusterRuntime \u62C6\u51FA\uFF1Acluster.ts \u9876\u90E8\u539F\u6709\u7684 readJson/writeJson/\n * listKeys/readAllByPrefix \u56DB\u5904\u642C\u5230\u6B64\u6587\u4EF6\uFF0C\u534F\u8C03\u5C42\u4E0E\u5FEB\u7167\u903B\u8F91\u5171\u7528\u540C\u4E00\u5957\n * \u5BB9\u9519\u8BED\u4E49\uFF08\u635F\u574F JSON \u89C6\u4E3A\u4E0D\u5B58\u5728\u3001\u5199\u5931\u8D25\u4E0D\u629B\u51FA\uFF09\u3002\n */\nimport type { StorageLike } from '../core/environment';\n\n/** Parse a JSON value from storage, returning null on malformed or missing data.\n * Never throws \u2014 a corrupt record is treated as absent so the reconcile cycle\n * can recreate it. */\nexport function readJson<T>(storage: StorageLike, key: string): T | null {\n try {\n const value = storage.getItem(key);\n return value ? (JSON.parse(value) as T) : null;\n } catch {\n return null;\n }\n}\n\n/** Write a JSON value to storage, swallowing storage errors (coordination is\n * best-effort; a failed write does not break the local transport). The actual\n * write may be coalesced by BatchingStorageWriter \u2014 this just calls setItem. */\nexport function writeJson(storage: StorageLike, key: string, value: unknown): void {\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // Coordination is best-effort. The local transport remains usable.\n }\n}\n\n/** List all storage keys that start with `prefix`. */\nexport function listKeys(storage: StorageLike, prefix: string): string[] {\n try {\n return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter(\n (key): key is string => Boolean(key?.startsWith(prefix))\n );\n } catch {\n return [];\n }\n}\n\n/** Read and parse every JSON record whose key starts with `prefix`. */\nexport function readAllByPrefix<T>(storage: StorageLike, prefix: string): Array<{ key: string; value: T }> {\n return listKeys(storage, prefix)\n .map(key => ({ key, value: readJson<T>(storage, key) }))\n .filter((entry): entry is { key: string; value: T } => entry.value !== null);\n}\n", "/**\n * WorkerClusterRuntime \u2014 cross-tab cluster coordination layer.\n *\n * Manages Worker registration, heartbeat, sticky topic-owner routing,\n * page-lifecycle handoff/resume, and BroadcastChannel-based\n * control messaging. Each DataBus instance owns one Runtime which drives the\n * transport and coordinates with other tabs via localStorage + BroadcastChannel.\n */\nimport { canUseStorage, createBrowserEnvironment, getOrCreateTabId } from './environment';\nimport type { ClusterChannel, ClusterEnvironment, StorageLike } from './environment';\nimport { createOpaqueKey } from './hash';\nimport {\n DEFAULT_MAX_ACTIVE_WORKERS,\n approximatePayloadBytes,\n selectActiveWorkers,\n selectLeastLoadedWorker,\n topicMatchesPattern\n} from './routing';\nimport type {\n DataBusPublicationMetadata,\n LoadWeightingOptions,\n TopicSubscriberRecord,\n WorkerClusterMessage,\n WorkerControlAction,\n WorkerRecord,\n WorkerRole,\n WorkerRoute,\n WorkerStatus,\n WorkerThroughputSample\n} from './types';\nimport { BatchingStorageWriter } from './storage-batch';\nimport {\n CLUSTER_MESSAGE_TYPE,\n CONTROL_ACTION,\n DEFAULT_STORAGE_PREFIX,\n RELIABILITY_OPERATION,\n WORKER_ROLE,\n WORKER_STATUS\n} from '../utils/constants';\nimport { publicationMetadata } from '../utils/metadata';\nimport { readAllByPrefix, readJson, writeJson } from '../utils/storage-utils';\nimport { assertClusterOptions } from '../utils/validation';\n\n/** Callbacks the cluster invokes to drive the transport and lifecycle. */\nexport interface WorkerClusterHandlers {\n /** A SUBSCRIBE/UNSUBSCRIBE/PUBLISH control action was received for this worker. */\n onControl: (\n action: WorkerControlAction,\n topic: string,\n data?: unknown,\n messageId?: string,\n timestamp?: number\n ) => void;\n /** Optional batched variant of the PUBLISH action: invoked once when a\n * CONTROL frame carries multiple publication items. When absent, the\n * cluster falls back to per-item `onControl('PUBLISH', \u2026)` calls. */\n onPublishBatch?: (\n topic: string,\n items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>\n ) => void;\n /** A fan-out publication event was received from another Worker.\n * `originTabId` is the tab that produced the original publication when the\n * cluster forwards one; it survives the BroadcastChannel hop so listeners\n * can tell a local dispatch from a cross-tab relay. */\n /** Optional hook for forward-compatible messages from newer runtimes. */\n onUnknownMessage?: (message: unknown) => void;\n onEvent: (\n eventType: string,\n payload: unknown,\n sourceWorkerId: string,\n originTabId?: string\n ) => void;\n /** The cluster suspended (tab hidden / pagehide). */\n onSuspend?: () => void;\n /** The cluster resumed (tab visible / pageshow). */\n onResume?: () => void;\n /** Bounded diagnostics for route confirmation, graceful migration, and stranded-handoff recovery. */\n onDiagnostic?: (event: {\n operation: (typeof RELIABILITY_OPERATION.ROUTE_ACK | typeof RELIABILITY_OPERATION.ROUTE_MIGRATION | typeof RELIABILITY_OPERATION.ROUTE_MIGRATION_RECOVERY);\n topic: string;\n }) => void;\n}\n\nexport interface WorkerClusterOptions {\n /** Namespace for the cluster's storage keys and BroadcastChannel.\n * Two DataBus instances with different clusterKeys operate in isolation. */\n clusterKey: string;\n /** Callbacks the cluster invokes to drive the transport and lifecycle. */\n handlers: WorkerClusterHandlers;\n /** Inject a custom environment (for tests or SSR). Defaults to browser. */\n environment?: ClusterEnvironment;\n /** Override the storage key prefix (default 'cross-tab-worker-databus'). */\n storagePrefix?: string;\n /** Inject a stable tab ID (for tests). Defaults to sessionStorage-derived. */\n tabId?: string;\n /** Inject a worker ID (for tests). Defaults to 'worker-<tabId>-<random>'. */\n workerId?: string;\n /** Cap on concurrently active owners (default 3). See DEFAULT_MAX_ACTIVE_WORKERS. */\n maxActiveWorkers?: number;\n /** Heartbeat + reconcile interval in ms (default 3000). */\n heartbeatIntervalMs?: number;\n /** TTL after which a silent worker is pruned (default 10000). */\n workerTtlMs?: number;\n /** Maximum entries kept in the publish route-owner cache (default 256).\n * When the cap is reached, the oldest (FIFO) entry is evicted. */\n routeOwnerCacheMax?: number;\n /** Optional adaptive owner weighting. When set, this worker samples its own\n * fan-out traffic and publishes it with every heartbeat so peers can steer\n * NEW routes toward quieter workers; the weights are forwarded to the\n * least-loaded selection. Absent (default) keeps pure topic-count routing. */\n loadWeighting?: LoadWeightingOptions;\n}\n\n/** Read-only snapshot of the cluster state for diagnostics and tracing. */\nexport interface WorkerClusterSnapshot {\n protocolVersion: number;\n /** Protocol versions advertised by each currently visible peer; null means legacy peer. */\n peerProtocolVersions: Record<string, number | null>;\n coordinated: boolean;\n suspended: boolean;\n currentWorker: WorkerRecord;\n workers: WorkerRecord[];\n /** Routes with the plaintext topic injected from the in-memory knownTopics cache. */\n routes: Array<WorkerRoute & { topic: string | null }>;\n subscribedTopics: string[];\n assignedTopics: string[];\n /** Opaque key \u2192 plaintext topic mapping for debugging. */\n knownTopics: Array<{ topicKey: string; topic: string }>;\n routeOwnerCache?: { size: number; max: number; hits: number; misses: number };\n}\n\nconst CLUSTER_PROTOCOL_VERSION = 1;\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 3_000;\nconst DEFAULT_WORKER_TTL_MS = 10_000;\n// Upper bound on the topicKey \u2192 topic reverse cache. Control messages from\n// other workers can reference arbitrary topics, so cap growth to avoid an\n// unbounded memory leak from a misbehaving or malicious peer.\nconst MAX_KNOWN_TOPICS = 500;\n\n/**\n * Cross-tab worker coordination runtime.\n *\n * Manages a cluster of Workers (one per tab) that share topics via localStorage\n * and BroadcastChannel. Each Worker publishes its own record, subscribes to\n * topics, and routes publications through the owning Worker to avoid duplicates.\n *\n * Key responsibilities:\n * - Heartbeat-based failure detection (stale workers pruned after `workerTtlMs`)\n * - Topic-to-Worker routing with load-based rebalancing\n * - Page lifecycle integration (suspend on hide, resume on show)\n * - Storage-backed coordination with BatchingStorageWriter for write coalescing\n */\nexport class WorkerClusterRuntime {\n readonly tabId: string;\n readonly workerId: string;\n\n private readonly environment: ClusterEnvironment;\n private readonly handlers: WorkerClusterHandlers;\n private storage: StorageLike | null;\n private readonly maxActiveWorkers: number;\n private readonly heartbeatIntervalMs: number;\n private readonly workerTtlMs: number;\n private readonly workerPrefix: string;\n private readonly routePrefix: string;\n private readonly subscriberPrefix: string;\n private readonly channelName: string;\n /** Adaptive load weighting options; undefined keeps legacy topic-count routing. */\n private readonly loadWeighting: LoadWeightingOptions | undefined;\n /** Rolling traffic accumulator folded into the worker record on writeRecord. */\n private throughputWindow: { startedAt: number; messageCount: number; byteCount: number } = {\n startedAt: 0,\n messageCount: 0,\n byteCount: 0\n };\n // Topics this tab has subscribed to (local interest, plaintext).\n private readonly subscribedTopics = new Set<string>();\n // Topics assigned to this Worker as owner (topicKey \u2192 topic). Authoritative:\n // membership drives isAssigned() and load. Grows only via CONTROL/SUBSCRIBE\n // (or local self-subscribe), never via the reverse cache.\n private readonly assignedTopics = new Map<string, string>();\n private readonly routeOwnerCache = new Map<string, { workerId: string; generation: number }>();\n private readonly routeOwnerCacheMax: number;\n private routeOwnerCacheHits = 0;\n private routeOwnerCacheMisses = 0;\n private unknownMessageCount = 0;\n private lastUnknownMessageType: string | null = null;\n private touchRouteOwnerCache(topicKey: string, value: { workerId: string; generation: number }): void {\n if (this.routeOwnerCache.has(topicKey)) this.routeOwnerCache.delete(topicKey);\n this.routeOwnerCache.set(topicKey, value);\n while (this.routeOwnerCache.size > this.routeOwnerCacheMax) {\n const oldest = this.routeOwnerCache.keys().next().value;\n if (oldest === undefined) break;\n this.routeOwnerCache.delete(oldest);\n }\n }\n private readonly wildcardPublishCache = new Map<string, string | null>();\n // Reverse mapping: opaque topicKey \u2192 plaintext topic. A bounded cache with\n // FIFO eviction \u2014 NOT authoritative. It can hold a topicKey that is also in\n // assignedTopics (the owned guard prevents evicting those), because it is\n // the only source of plaintext when storage is unavailable. See the\n // rememberTopic() doc for the eviction contract.\n private readonly knownTopics = new Map<string, string>();\n private channel: ClusterChannel | null = null;\n private heartbeatHandle: unknown = null;\n private started = false;\n private suspended = false;\n private lifecycleListening = false;\n private currentRecord: WorkerRecord;\n\n constructor(options: WorkerClusterOptions) {\n assertClusterOptions(options);\n this.environment = options.environment ?? createBrowserEnvironment();\n this.handlers = options.handlers;\n this.maxActiveWorkers = options.maxActiveWorkers ?? DEFAULT_MAX_ACTIVE_WORKERS;\n this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n this.routeOwnerCacheMax = options.routeOwnerCacheMax ?? 256;\n this.workerTtlMs = options.workerTtlMs ?? DEFAULT_WORKER_TTL_MS;\n this.loadWeighting = options.loadWeighting;\n // Derive storage keys from a hash of the cluster key so that the plaintext\n // cluster identifier never appears in localStorage.\n const clusterHash = createOpaqueKey(options.clusterKey || '__default__');\n const prefix = options.storagePrefix ?? DEFAULT_STORAGE_PREFIX;\n const baseKey = `${prefix}:${clusterHash}`;\n this.workerPrefix = `${baseKey}:worker:`;\n this.routePrefix = `${baseKey}:route:`;\n this.subscriberPrefix = `${baseKey}:subscriber:`;\n this.channelName = `${prefix}:bus:${clusterHash}`;\n // Wrap localStorage in a BatchingStorageWriter to coalesce writes.\n this.storage = canUseStorage(this.environment.storage, `${baseKey}:probe`)\n ? new BatchingStorageWriter(this.environment.storage)\n : null;\n this.tabId = options.tabId ?? getOrCreateTabId(this.environment, `${prefix}:tab-id`);\n this.workerId = options.workerId ?? `worker-${this.tabId}-${this.environment.randomId()}`;\n const now = this.environment.now();\n this.currentRecord = {\n protocolVersion: CLUSTER_PROTOCOL_VERSION,\n workerId: this.workerId,\n tabId: this.tabId,\n load: 0,\n role: WORKER_ROLE.STANDBY,\n status: WORKER_STATUS.CONNECTING,\n visibilityState: this.environment.getVisibilityState(),\n heartbeatAt: now,\n registeredAt: now\n };\n }\n\n /** Start the cluster: register, listen for lifecycle events, and begin heartbeats. */\n start(): void {\n if (this.started) return;\n this.suspended = false;\n this.addLifecycleListeners();\n this.activate();\n }\n\n /**\n * Stop the cluster: pause heartbeats, hand off assigned topics, remove\n * the worker record, and clean up lifecycle listeners. Idempotent.\n * The .clear() calls after pause() are safe no-ops when pause already\n * cleared the maps (the handoff path), but ensure a full teardown in the\n * stop() path where callers expect every Set/Map to be empty afterwards.\n */\n stop(): void {\n if (!this.started && !this.suspended) return;\n this.pause();\n this.flushStorage();\n this.removeLifecycleListeners();\n this.subscribedTopics.clear();\n this.assignedTopics.clear();\n this.routeOwnerCache.clear();\n this.wildcardPublishCache.clear();\n this.knownTopics.clear();\n this.suspended = false;\n }\n\n /**\n * Activate the cluster: open the BroadcastChannel, register the worker record,\n * subscribe to topics, and start the heartbeat interval.\n */\n private activate(): void {\n if (this.started) return;\n this.started = true;\n // Create the BroadcastChannel for cross-tab messaging. If storage is\n // unavailable, we cannot coordinate \u2014 skip the channel. If the channel\n // itself fails to construct (sandboxed iframe, permissions policy),\n // null out storage too: without a channel the storage writes have no\n // peer to observe them, so the BatchingStorageWriter would write for\n // nothing and the degraded code paths must take over.\n this.channel = this.storage ? this.environment.createChannel(this.channelName) : null;\n if (!this.channel) this.storage = null;\n this.channel?.addEventListener('message', this.handleMessage);\n const now = this.environment.now();\n this.currentRecord = {\n ...this.currentRecord,\n heartbeatAt: now,\n registeredAt: now,\n visibilityState: this.environment.getVisibilityState()\n };\n this.refreshRole(this.readWorkers());\n this.writeRecord(true);\n // Re-subscribe any topics that were subscribed before the cluster started.\n // rememberTopic is called once per topic regardless of branch so the reverse\n // cache is populated before either the control message or the subscriber write.\n for (const topic of this.subscribedTopics) {\n const topicKey = this.rememberTopic(topic);\n if (!this.storage) this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n else this.writeSubscriber(topicKey);\n }\n this.reconcile();\n // Periodic heartbeat + reconciliation.\n this.heartbeatHandle = this.environment.setInterval(() => {\n this.writeRecord(false);\n this.reconcile();\n }, this.heartbeatIntervalMs);\n }\n\n /**\n * Pause the cluster on pagehide: stop heartbeats, hand off assigned topics\n * to other workers, remove our worker record, and close the channel.\n */\n private pause(): void {\n if (!this.started) return;\n this.started = false;\n this.suspended = true;\n if (this.heartbeatHandle !== null) this.environment.clearInterval(this.heartbeatHandle);\n this.heartbeatHandle = null;\n this.channel?.removeEventListener('message', this.handleMessage);\n // Order matters: release local subscriptions BEFORE handing off assigned\n // topics, then clear the assignment map. Releasing first removes the\n // subscriber records so handoff sees the correct remaining subscribers;\n // clearing after handoff ensures no topic is both handed off and left\n // dangling. Do not reorder without addressing the handoff semantics.\n for (const topic of this.subscribedTopics) this.releaseSubscription(topic, false);\n this.handoffAssignedTopics();\n this.assignedTopics.clear();\n this.routeOwnerCache.clear();\n this.wildcardPublishCache.clear();\n this.removeStorage(this.workerStorageKey(this.workerId));\n // Persist the final routes and worker removal before asking peers to\n // reconcile. A pagehide CONTROL message may be lost; REGISTRY must still\n // let peers observe the completed handoff immediately.\n this.flushStorage();\n this.notifyRegistry();\n const channel = this.channel;\n this.channel = null;\n this.handlers.onSuspend?.();\n // Defer the physical close by one task: BroadcastChannel.close() discards\n // messages still queued for delivery \u2014 including the handoff's\n // ROUTE_RELEASED \u2014 which can strand the handoff target with an\n // unconfirmed route on a loaded runner. Letting the queued frames flush\n // first keeps the strict handoff live; on a frozen BFCache page the task\n // simply never runs and the channel object is garbage-collected with it.\n if (typeof globalThis.setTimeout === 'function') {\n globalThis.setTimeout(() => channel?.close(), 0);\n } else {\n channel?.close();\n }\n }\n\n /** Update the worker's connection status and persist the change. */\n setStatus(status: WorkerStatus): void {\n if (this.currentRecord.status === status) return;\n this.currentRecord = { ...this.currentRecord, status };\n if (this.started) this.writeRecord(true);\n }\n\n /**\n * Subscribe to a topic. Returns true if this worker becomes the assigned owner.\n * The topic is recorded locally and the cluster is notified via storage or\n * direct control message.\n */\n subscribe(topic: string): boolean {\n const topicKey = this.rememberTopic(topic);\n this.subscribedTopics.add(topic);\n if (!this.started) return false;\n if (!this.storage) {\n this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n return true;\n }\n this.writeSubscriber(topicKey);\n const workers = this.readWorkers();\n const existingRoute = this.readRoute(topicKey);\n if (this.routeOwnerIsLive(existingRoute, workers)) {\n return existingRoute?.workerId === this.workerId;\n }\n\n const activeWorkers = selectActiveWorkers(workers, this.maxActiveWorkers);\n const owner = selectLeastLoadedWorker(activeWorkers, undefined, this.loadWeighting) ?? this.currentRecord;\n // A missing live owner cannot participate in a strict handoff. Assign and\n // subscribe immediately; pagehide uses handoffAssignedTopics() while the\n // old owner is still present when release ordering is required.\n this.writeRoute(topicKey, owner, undefined, (existingRoute?.generation ?? 0) + 1);\n this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n this.notifyRegistry();\n return owner.workerId === this.workerId;\n }\n\n /**\n * Remove the local subscription. Cleans up the subscriber record and, if no\n * subscribers remain, deletes the route so the owning Worker can unsubscribe.\n */\n unsubscribe(topic: string): void {\n this.subscribedTopics.delete(topic);\n const topicKey = this.releaseSubscription(topic);\n // Keep the topic in knownTopics if we remain the owner (we may still fan out).\n if (topicKey && !this.assignedTopics.has(topicKey)) this.knownTopics.delete(topicKey);\n }\n\n /** Remove this tab's subscriber record and, when it was the last one, delete\n * the route. Returns the topicKey (so callers like `unsubscribe` can reuse\n * it instead of re-hashing the topic to evict the reverse cache). */\n private releaseSubscription(topic: string, notifyOwner = true): string {\n const topicKey = this.rememberTopic(topic);\n this.removeStorage(this.subscriberStorageKey(topicKey, this.tabId));\n const route = this.readRoute(topicKey);\n if (!route) return topicKey;\n const subscribers = this.readSubscriberTabIds(topicKey, this.readWorkers());\n if (subscribers.length === 0) {\n this.removeStorage(this.routeStorageKey(topicKey));\n if (notifyOwner) this.sendControl(route.workerId, CONTROL_ACTION.UNSUBSCRIBE, topic, topicKey);\n }\n return topicKey;\n }\n\n /** Transfer assigned topics to other active workers so subscribers are not orphaned during pause. */\n private handoffAssignedTopics(): void {\n if (!this.storage || this.assignedTopics.size === 0) return;\n const remainingWorkers = this.readWorkers().filter(worker => worker.workerId !== this.workerId);\n const activeWorkers = selectActiveWorkers(remainingWorkers, this.maxActiveWorkers);\n // `WorkerRecord.load` is a snapshot from before this handoff. Keep a\n // projected load locally so a batch of topics is distributed across the\n // remaining workers instead of every route choosing the same initial\n // minimum.\n const projectedLoads = new Map(activeWorkers.map(worker => [worker.workerId, worker.load]));\n\n for (const [topicKey, topic] of this.assignedTopics) {\n const previous = this.readRoute(topicKey);\n if (previous?.workerId !== this.workerId) continue;\n const subscribers = this.readSubscriberTabIds(topicKey, remainingWorkers);\n if (subscribers.length === 0) {\n this.removeStorage(this.routeStorageKey(topicKey));\n continue;\n }\n const owner = selectLeastLoadedWorker(\n activeWorkers.map(worker => ({ ...worker, load: projectedLoads.get(worker.workerId) ?? worker.load })),\n undefined,\n this.loadWeighting\n );\n if (!owner) continue;\n projectedLoads.set(owner.workerId, (projectedLoads.get(owner.workerId) ?? owner.load) + 1);\n const generation = (previous?.generation ?? 0) + 1;\n this.writeRoute(topicKey, owner, previous?.workerId, generation);\n this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_MIGRATION, topic });\n // Make the new route visible before the target confirms it. This also\n // leaves a durable unconfirmed assignment when unload drops CONTROL.\n this.flushStorage();\n // Release the old server subscription before authorizing the new owner.\n // The ACK is sent after the transport operation has been requested.\n this.handlers.onControl(CONTROL_ACTION.UNSUBSCRIBE, topic);\n this.sendRouteReleased(owner.workerId, topic, topicKey, generation);\n }\n }\n\n /**\n * Publish a message to `topic`, routing through the owning Worker (or self if\n * no owner is found). Returns false when the control message could not be\n * posted to a remote owner, so the caller can surface the failure instead of\n * silently dropping the publication.\n */\n publish(topic: string, data: unknown, messageId?: string): boolean;\n publish(topic: string, data: unknown, metadata?: DataBusPublicationMetadata): boolean;\n publish(\n topic: string,\n data: unknown,\n metadataOrMessageId?: DataBusPublicationMetadata | string\n ): boolean {\n const metadata = typeof metadataOrMessageId === 'string'\n ? { messageId: metadataOrMessageId }\n : metadataOrMessageId;\n const topicKey = this.rememberTopic(topic);\n // The owning Worker already has a synchronous assignment map. Reuse it\n // for the hot local-publish path instead of scanning worker and route\n // records on every message. Wildcard assignments also own matching\n // concrete topics, so they can use the same fast path.\n if (this.assignedTopics.has(topicKey)) {\n return this.sendControl(this.workerId, CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);\n }\n // A local wildcard owns matching concrete topics. The scan result is\n // memoised per concrete topic: `undefined` means \"not scanned yet\", a\n // pattern string means \"this local wildcard matched\", and `null` means\n // \"scanned, no local wildcard matched\". The cached positive value is a\n // scan-skip marker only \u2014 it is deliberately NOT re-checked against\n // `assignedTopics`, because that map is keyed by the opaque topic key, so\n // a plaintext pattern could never match a key (the check was unreachable).\n // Only the first (scanning) call may dispatch locally; later calls route\n // through `resolvePublishTarget`, which honours a concrete remote owner.\n const cachedPattern = this.wildcardPublishCache.get(topic);\n if (cachedPattern === undefined) {\n for (const pattern of this.assignedTopics.values()) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) {\n this.wildcardPublishCache.set(topic, pattern);\n return this.sendControl(this.workerId, CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);\n }\n }\n this.wildcardPublishCache.set(topic, null);\n }\n return this.sendControl(this.resolvePublishTarget(topic, topicKey), CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);\n }\n\n /**\n * Burst-friendly variant of `publish()`: packs up to N items into a single\n * BroadcastChannel postMessage so the receiving owner dispatches them all in\n * one tick. Per-item dedup / replay / dispatch ordering is preserved; items\n * may carry their own messageId/timestamp. Empty batch is a no-op,\n * single-item batch delegates to `publish()`.\n */\n publishBatch(\n topic: string,\n items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>\n ): boolean {\n if (items.length === 0) return true;\n if (items.length === 1) {\n const single = items[0]!;\n const metadata = single.messageId !== undefined || single.timestamp !== undefined\n ? {\n ...(single.messageId !== undefined ? { messageId: single.messageId } : {}),\n ...(single.timestamp !== undefined ? { timestamp: single.timestamp } : {})\n }\n : undefined;\n return this.publish(topic, single.data, metadata);\n }\n const topicKey = this.rememberTopic(topic);\n if (this.assignedTopics.has(topicKey)) {\n this.dispatchLocalPublishBatch(topic, topicKey, items);\n return true;\n }\n const cachedPattern = this.wildcardPublishCache.get(topic);\n if (cachedPattern === undefined) {\n for (const pattern of this.assignedTopics.values()) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) {\n this.wildcardPublishCache.set(topic, pattern);\n this.dispatchLocalPublishBatch(topic, topicKey, items);\n return true;\n }\n }\n this.wildcardPublishCache.set(topic, null);\n }\n const target = this.resolvePublishTarget(topic, topicKey);\n if (target === this.workerId) {\n this.dispatchLocalPublishBatch(topic, topicKey, items);\n return true;\n }\n return this.send({\n type: CLUSTER_MESSAGE_TYPE.CONTROL,\n sourceWorkerId: this.workerId,\n targetWorkerId: target,\n action: CONTROL_ACTION.PUBLISH,\n topic,\n topicKey,\n items: items.map(item => ({\n data: item.data,\n ...(item.messageId !== undefined ? { messageId: item.messageId } : {}),\n ...(item.timestamp !== undefined ? { timestamp: item.timestamp } : {})\n }))\n });\n }\n\n /** Resolve which worker should receive a PUBLISH for `topic`. Centralises the\n * route-owner cache lookup so `publish()` and `publishBatch()` share one path. */\n private resolvePublishTarget(topic: string, topicKey: string): string {\n const workers = this.readWorkers();\n const route = this.readRoute(topicKey);\n const cached = this.routeOwnerCache.get(topicKey);\n const cachedLive = cached && route && route.generation === cached.generation && route.workerId === cached.workerId && workers.some(worker => worker.workerId === cached.workerId);\n if (cachedLive) this.routeOwnerCacheHits += 1; else this.routeOwnerCacheMisses += 1;\n const target = cachedLive\n ? cached.workerId\n : this.routeOwnerIsLive(route, workers)\n ? route?.workerId ?? this.workerId\n : this.workerId;\n if (route && target === route.workerId) {\n this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });\n } else {\n this.routeOwnerCache.delete(topicKey);\n }\n return target;\n }\n\n /** Fan out a single batched item to the local onControl path. */\n private dispatchLocalPublish(\n topic: string,\n topicKey: string,\n data: unknown,\n messageId?: string,\n timestamp?: number\n ): void {\n void topicKey;\n const meta = publicationMetadata(messageId, timestamp);\n if (meta) this.handlers.onControl(CONTROL_ACTION.PUBLISH, topic, data, meta.messageId, meta.timestamp);\n else this.handlers.onControl(CONTROL_ACTION.PUBLISH, topic, data);\n }\n\n /** Fan out a publication batch to the local onControl path: one\n * onPublishBatch call when the owner supports it, per-item otherwise. */\n private dispatchLocalPublishBatch(\n topic: string,\n topicKey: string,\n items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>\n ): void {\n if (this.handlers.onPublishBatch) {\n this.handlers.onPublishBatch(topic, items);\n return;\n }\n for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);\n }\n\n /** True when `route` exists and its owner worker is among `workers`.\n * Shared by subscribe (skip re-assignment) and publish (route to owner).\n * Intentionally returns a plain boolean (not a type guard) so the caller\n * can still access `route?.generation` in the false branch. */\n private routeOwnerIsLive(route: WorkerRoute | null, workers: readonly WorkerRecord[]): boolean {\n return Boolean(route && workers.some(worker => worker.workerId === route.workerId));\n }\n\n /** Broadcast an event to every tab \u2014 used to fan out transport publications.\n * `originTabId` (when set) is propagated across the BroadcastChannel hop so\n * listeners can attribute the event to its source tab even after fan-out. */\n broadcastEvent(eventType: string, payload: unknown, originTabId?: string): void {\n // Default to the producing tab so listeners can attribute the event to\n // its source tab across the BroadcastChannel hop without callers having\n // to thread the tabId through every call site.\n const effectiveOriginTabId = originTabId ?? this.tabId;\n this.recordTraffic(payload);\n this.send({ type: CLUSTER_MESSAGE_TYPE.EVENT, sourceWorkerId: this.workerId, eventType, payload, originTabId: effectiveOriginTabId });\n }\n\n /** Count one fan-out unit toward the adaptive load sample. No-op unless\n * adaptive weighting is configured. */\n private recordTraffic(payload: unknown): void {\n if (this.loadWeighting === undefined) return;\n this.throughputWindow.messageCount += 1;\n this.throughputWindow.byteCount += approximatePayloadBytes(payload);\n }\n\n /** Convert the accumulated window into a publishable throughput sample and\n * reset the accumulator. Returns undefined until a full window has elapsed\n * so the first write does not emit a zero-width sample. */\n private sampleThroughput(now: number): WorkerThroughputSample | undefined {\n if (this.throughputWindow.startedAt === 0) {\n this.throughputWindow.startedAt = now;\n return undefined;\n }\n const windowMs = now - this.throughputWindow.startedAt;\n if (windowMs <= 0) return undefined;\n const sample: WorkerThroughputSample = {\n windowMs,\n messageCount: this.throughputWindow.messageCount,\n byteCount: this.throughputWindow.byteCount,\n // How much later the heartbeat landed than its nominal interval. This\n // window is anchored at the previous writeRecord (one heartbeat tick),\n // so a starved event loop stretches windowMs past the interval and the\n // positive excess is the scheduling-overrun signal.\n overrunMs: Math.max(0, windowMs - this.heartbeatIntervalMs),\n sampledAt: now\n };\n this.throughputWindow = { startedAt: now, messageCount: 0, byteCount: 0 };\n return sample;\n }\n\n isAssigned(topic: string): boolean {\n // Deliberately recompute the key via createOpaqueKey rather than\n // rememberTopic(): this is a read-only query, not a state change, so it\n // must not populate the knownTopics reverse-cache. Hashing is cheap enough\n // that re-deriving here is preferable to evicting a cached entry that the\n // storage-less readRoute path may need (see rememberTopic eviction guard).\n const topicKey = createOpaqueKey(topic);\n // Prefer the in-memory assignment map: it is updated synchronously on\n // SUBSCRIBE/UNSUBSCRIBE, whereas readRoute() may observe a route that has\n // not yet been flushed through the BatchingStorageWriter, causing a message\n // destined for this worker to be dropped during the write window.\n if (this.assignedTopics.has(topicKey)) return true;\n // Wildcard assignments: this worker owns the transport subscription for a\n // pattern (e.g. \"chat.*\"), so publications arriving under a matching\n // concrete topic (e.g. \"chat.room.1\", as delivered by pattern-aware\n // servers) belong to the same route and must fan out from here too.\n for (const pattern of this.assignedTopics.values()) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) return true;\n }\n return this.readRoute(topicKey)?.workerId === this.workerId;\n }\n\n /** True if this worker is among the active set (eligible to own topics). */\n isActiveWorker(): boolean {\n return this.isActiveAmong(this.readWorkers());\n }\n\n /** True when this workerId is in the active subset of `workers`. Shared by\n * isActiveWorker() and refreshRole() so both compute role identically. */\n private isActiveAmong(workers: readonly WorkerRecord[]): boolean {\n return selectActiveWorkers(workers, this.maxActiveWorkers).some(\n worker => worker.workerId === this.workerId\n );\n }\n\n /** True when this tab has a local subscriber registered for `topic` \u2014\n * exactly, or via a wildcard subscription that matches it. */\n hasLocalSubscriber(topic: string): boolean {\n if (this.subscribedTopics.has(topic)) return true;\n for (const pattern of this.subscribedTopics) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) return true;\n }\n return false;\n }\n\n /** Count and last type of unknown protocol messages observed. */\n getUnknownMessageStats(): { count: number; lastType: string | null } { return { count: this.unknownMessageCount, lastType: this.lastUnknownMessageType }; }\n\n /** Read-only snapshot of the cluster state (workers, routes, assignments). */\n getSnapshot(): WorkerClusterSnapshot {\n const workers = this.storage ? this.readWorkers() : [{ ...this.currentRecord }];\n const routes = this.storage\n ? readAllByPrefix<WorkerRoute>(this.storage, this.routePrefix).map(({ value }) => ({\n ...value,\n topic: this.knownTopics.get(value.topicKey) ?? null\n }))\n : [];\n return {\n protocolVersion: CLUSTER_PROTOCOL_VERSION,\n peerProtocolVersions: Object.fromEntries(workers.map(worker => [worker.workerId, worker.protocolVersion ?? null])),\n coordinated: Boolean(this.storage && this.channel),\n suspended: this.suspended,\n currentWorker: { ...this.currentRecord },\n workers: workers.map(worker => ({ ...worker })),\n routes,\n subscribedTopics: Array.from(this.subscribedTopics),\n assignedTopics: Array.from(this.assignedTopics.values()),\n knownTopics: Array.from(this.knownTopics.entries(), ([topicKey, topic]) => ({ topicKey, topic })),\n routeOwnerCache: { size: this.routeOwnerCache.size, max: this.routeOwnerCacheMax, hits: this.routeOwnerCacheHits, misses: this.routeOwnerCacheMisses }\n };\n }\n\n private readonly handlePageHide = () => this.pause();\n\n private readonly handlePageShow = () => {\n if (!this.suspended) return;\n this.suspended = false;\n this.handlers.onResume?.();\n this.activate();\n };\n\n private readonly handleVisibilityChange = () => {\n const visibilityState = this.environment.getVisibilityState();\n if (visibilityState === this.currentRecord.visibilityState) return;\n this.currentRecord = { ...this.currentRecord, visibilityState };\n if (this.started) {\n this.writeRecord(true);\n this.reconcile();\n }\n };\n\n private addLifecycleListeners(): void {\n if (this.lifecycleListening) return;\n this.lifecycleListening = true;\n this.environment.addPageHideListener(this.handlePageHide);\n this.environment.addPageShowListener(this.handlePageShow);\n this.environment.addVisibilityChangeListener(this.handleVisibilityChange);\n }\n\n private removeLifecycleListeners(): void {\n if (!this.lifecycleListening) return;\n this.lifecycleListening = false;\n this.environment.removePageHideListener(this.handlePageHide);\n this.environment.removePageShowListener(this.handlePageShow);\n this.environment.removeVisibilityChangeListener(this.handleVisibilityChange);\n }\n\n /** Handle an incoming cluster message: dispatch by type to the per-type handlers. */\n private readonly handleMessage = (event: MessageEvent<WorkerClusterMessage>) => {\n const message = event.data;\n if (!message || message.sourceWorkerId === this.workerId) return;\n switch (message.type) {\n case CLUSTER_MESSAGE_TYPE.CONTROL:\n return this.handleControlMessage(message);\n case CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED:\n return this.handleRouteReleasedMessage(message);\n case CLUSTER_MESSAGE_TYPE.EVENT:\n this.handlers.onEvent(message.eventType, message.payload, message.sourceWorkerId, message.originTabId);\n return;\n case CLUSTER_MESSAGE_TYPE.REGISTRY:\n this.reconcile();\n return;\n default: {\n this.unknownMessageCount += 1;\n const unknown = message as unknown as { type?: unknown };\n this.lastUnknownMessageType = typeof unknown.type === 'string' ? unknown.type : null;\n this.handlers.onUnknownMessage?.(message);\n return;\n }\n }\n };\n\n /** Handle a point-to-point CONTROL message (SUBSCRIBE / UNSUBSCRIBE / PUBLISH). */\n private handleControlMessage(\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.CONTROL }>\n ): void {\n if (message.targetWorkerId !== this.workerId) return;\n this.rememberTopic(message.topic);\n switch (message.action) {\n case CONTROL_ACTION.SUBSCRIBE:\n this.assignedTopics.set(message.topicKey, message.topic);\n this.confirmRoute(message.topicKey);\n break;\n case CONTROL_ACTION.UNSUBSCRIBE:\n // A graceful handoff release short-circuits the generic dispatch.\n if (this.releaseHandoffOnUnsubscribe(message)) return;\n break;\n case CONTROL_ACTION.PUBLISH:\n if (message.items && message.items.length > 0) {\n // A batched CONTROL: hand the whole batch to the transport at once\n // when the owner supports it, preserving per-item metadata.\n if (this.handlers.onPublishBatch) {\n this.handlers.onPublishBatch(message.topic, message.items);\n return;\n }\n for (const item of message.items) {\n const itemMeta = publicationMetadata(item.messageId, item.timestamp);\n if (itemMeta) this.handlers.onControl(CONTROL_ACTION.PUBLISH, message.topic, item.data, itemMeta.messageId, itemMeta.timestamp);\n else this.handlers.onControl(CONTROL_ACTION.PUBLISH, message.topic, item.data);\n }\n return;\n }\n break;\n default:\n break;\n }\n const metadata = publicationMetadata(message.messageId, message.timestamp);\n if (metadata) this.handlers.onControl(\n message.action,\n message.topic,\n message.data,\n metadata.messageId,\n metadata.timestamp\n );\n else this.handlers.onControl(message.action, message.topic, message.data);\n if (message.action !== CONTROL_ACTION.PUBLISH) this.updateLoad();\n }\n\n /**\n * When this worker is the previous owner in a graceful handoff and the new\n * owner asks us to unsubscribe, release the old transport subscription and\n * ACK the handoff with ROUTE_RELEASED. Returns true when the message was a\n * handoff release (the generic CONTROL dispatch must not run as well).\n */\n private releaseHandoffOnUnsubscribe(\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.CONTROL }>\n ): boolean {\n this.assignedTopics.delete(message.topicKey);\n const route = this.readRoute(message.topicKey);\n if (route?.handoffFromWorkerId !== this.workerId) return false;\n this.handlers.onControl(CONTROL_ACTION.UNSUBSCRIBE, message.topic, undefined);\n this.sendRouteReleased(route.workerId, message.topic, message.topicKey, route.generation);\n this.updateLoad();\n return true;\n }\n\n /** Post a ROUTE_RELEASED ACK to the new owner, carrying the current route\n * generation so only the matching new owner may act on it. */\n private sendRouteReleased(\n targetWorkerId: string,\n topic: string,\n topicKey: string,\n generation: number\n ): void {\n this.send({\n type: CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED,\n sourceWorkerId: this.workerId,\n targetWorkerId,\n topic,\n topicKey,\n generation\n });\n }\n\n /**\n * Accept a graceful handoff only when the route still points to this worker,\n * the release comes from the recorded previous owner, and the generation is\n * at least as new as ours. Any other ROUTE_RELEASED is stale and dropped.\n */\n private handleRouteReleasedMessage(\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED }>\n ): void {\n if (message.targetWorkerId !== this.workerId) return;\n const route = this.readRoute(message.topicKey);\n if (!route || this.isStaleRouteRelease(route, message)) return;\n this.assignedTopics.set(message.topicKey, message.topic);\n this.confirmRoute(message.topicKey);\n this.handlers.onControl(CONTROL_ACTION.SUBSCRIBE, message.topic, undefined);\n this.updateLoad();\n }\n\n /** A ROUTE_RELEASED is stale (and must be dropped) unless the route still\n * points to us, the release comes from the recorded previous owner, and\n * the release generation is at least as new as ours. A replayed ACK from an\n * earlier handoff round (e.g. an a\u2194b ping-pong) carries an older generation\n * and must not confirm the current round. */\n private isStaleRouteRelease(\n route: WorkerRoute,\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED }>\n ): boolean {\n return (\n route.workerId !== this.workerId ||\n route.handoffFromWorkerId !== message.sourceWorkerId ||\n message.generation < route.generation\n );\n }\n\n /** True when an unconfirmed handoff route has been stuck longer than a\n * worker TTL. The ACK for a live handoff is posted synchronously with the\n * route write, so anything older than the TTL with a dead previous owner\n * will never complete \u2014 while a fresh unconfirmed route may simply be\n * waiting out its confirmation flush and must be left alone. */\n private isStaleHandoff(route: WorkerRoute): boolean {\n return this.environment.now() - route.updatedAt > this.workerTtlMs;\n }\n\n /** Full reconciliation cycle: workers, subscriptions, and assigned topics. */\n private reconcile(): void {\n if (!this.started) return;\n const workers = this.reconcileWorkers();\n const activeWorkers = selectActiveWorkers(workers, this.maxActiveWorkers);\n this.reconcileSubscriptions(workers, activeWorkers);\n this.reconcileAssignedTopics();\n this.updateLoad();\n }\n\n /** Prune stale workers/subscribers/routes and refresh role. Returns the live worker list.\n * Subscribers are cleaned before routes so cleanupOrphanedRoutes sees the\n * updated subscriber set when deciding whether a route is truly orphaned. */\n private reconcileWorkers(): WorkerRecord[] {\n const workers = this.readWorkers();\n this.cleanupOrphanedSubscribers(workers);\n this.cleanupOrphanedRoutes(workers);\n const roleChanged = this.refreshRole(workers);\n if (roleChanged) this.writeRecord(false);\n return workers;\n }\n\n /**\n * Ensure every local subscription has a route and write subscriber records.\n *\n * Existing routes are deliberately sticky while their owner Worker is alive.\n * Load and visibility only influence placement of a new route; they must not\n * move an already-subscribed Topic merely because another Tab joins or becomes\n * visible. Ownership changes only after the owner leaves or its heartbeat\n * expires, which avoids unnecessary transport subscribe/unsubscribe churn.\n */\n private reconcileSubscriptions(\n workers: readonly WorkerRecord[],\n activeWorkers: readonly WorkerRecord[]\n ): void {\n const liveWorkerIds = new Set(workers.map(worker => worker.workerId));\n // Projected loads for stranded-handoff re-elections within this pass,\n // mirroring handoffAssignedTopics(): without it, every stranded topic\n // would pile onto the same least-loaded worker from the pass-start\n // snapshot \u2014 and routes are sticky, so the imbalance would persist.\n const recoveryProjectedLoads = new Map<string, number>();\n\n for (const topic of this.subscribedTopics) {\n const topicKey = this.rememberTopic(topic);\n this.writeSubscriber(topicKey);\n const route = this.readRoute(topicKey);\n if (!route || !liveWorkerIds.has(route.workerId)) {\n const owner = selectLeastLoadedWorker(activeWorkers, undefined, this.loadWeighting) ?? this.currentRecord;\n // A route invalidated by owner departure or heartbeat expiry is\n // recovered immediately. Graceful pagehide uses the strict ACK path in\n // handoffAssignedTopics(), where the departing owner is still known.\n this.writeRoute(topicKey, owner, undefined, (route?.generation ?? 0) + 1);\n this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n this.notifyRegistry();\n continue;\n }\n if (route.confirmedAt === undefined) {\n // During a handoff, the new owner waits for ROUTE_RELEASED from the\n // previous owner. Retrying SUBSCRIBE here would recreate overlap.\n if (!route.handoffFromWorkerId) {\n this.sendControl(route.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n } else if (!liveWorkerIds.has(route.handoffFromWorkerId) && this.isStaleHandoff(route)) {\n // The previous owner is gone, its ROUTE_RELEASED never arrived\n // (dropped channel message under load, or a crash between the route\n // write and the ACK), AND the handoff has been stuck longer than a\n // worker TTL. Waiting longer cannot help \u2014 nobody remains who could\n // send the ACK \u2014 and the route would strand unconfirmed forever:\n // the new owner keeps waiting while peers treat the live new owner\n // as authoritative and stay out. Re-elect a live owner and clear\n // the handoff marker so the normal confirmation path can complete.\n // The age gate matters: a fresh handoff route may simply not have\n // its confirmation flushed through the batching writer yet, and a\n // peer reconciling in that window must not mistake it for a\n // stranded one. While the previous owner is still alive this branch\n // is unreachable, so the strict handoff keeps its no-overlap\n // guarantee.\n const owner = selectLeastLoadedWorker(\n activeWorkers.map(worker => ({ ...worker, load: recoveryProjectedLoads.get(worker.workerId) ?? worker.load })),\n undefined,\n this.loadWeighting\n ) ?? this.currentRecord;\n recoveryProjectedLoads.set(owner.workerId, (recoveryProjectedLoads.get(owner.workerId) ?? owner.load) + 1);\n // Single-writer rule: only the elected owner performs the\n // re-election. Peers that compute a different owner stand down and\n // wait for its write. Concurrent writes from divergent views would\n // ping-pong generations and drop confirmations \u2014 every fresh write\n // is unconfirmed by construction, so two writers rewriting the same\n // route keep invalidating each other's confirmations and re-send\n // SUBSCRIBEs every pass. Standing down is always safe: the elected\n // owner reconciles on its own heartbeat, and if views disagree this\n // round they converge on the next flush (bounded by one heartbeat),\n // after which every peer computes the same owner.\n // Exception: when the elected owner has no local subscription it\n // will never reconcile this topic, so standing down would stall\n // forever. Fall back to writing the route and notifying it\n // directly (assigning without a local subscription is exactly what\n // the graceful handoff and the crash path already do).\n if (owner.workerId !== this.workerId) {\n const subscriberTabIds = new Set(this.readSubscriberTabIds(topicKey, workers));\n const ownerSubscribed = workers.some(\n worker => worker.workerId === owner.workerId && subscriberTabIds.has(worker.tabId)\n );\n if (ownerSubscribed) continue;\n }\n this.writeRoute(topicKey, owner, undefined, route.generation + 1);\n this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_MIGRATION_RECOVERY, topic });\n this.notifyRegistry();\n }\n }\n }\n }\n\n /** Drop assignments where the route no longer points to this worker. */\n private reconcileAssignedTopics(): void {\n for (const [topicKey, topic] of [...this.assignedTopics]) {\n const route = this.readRoute(topicKey);\n if (route?.workerId === this.workerId) continue;\n this.assignedTopics.delete(topicKey);\n this.handlers.onControl(CONTROL_ACTION.UNSUBSCRIBE, topic, undefined);\n if (route?.handoffFromWorkerId === this.workerId) {\n this.sendRouteReleased(route.workerId, topic, topicKey, route.generation);\n }\n if (!this.subscribedTopics.has(topic)) this.knownTopics.delete(topicKey);\n }\n }\n\n /**\n * Send a control message to `targetWorkerId`, or execute locally when targeting self.\n * Local execution updates the assignment map and route synchronously, bypassing\n * the BroadcastChannel latency.\n */\n private sendControl(\n targetWorkerId: string,\n action: WorkerControlAction,\n topic: string,\n topicKey: string,\n data?: unknown,\n metadata?: DataBusPublicationMetadata\n ): boolean {\n if (targetWorkerId === this.workerId) {\n switch (action) {\n case CONTROL_ACTION.SUBSCRIBE:\n this.assignedTopics.set(topicKey, topic);\n this.confirmRoute(topicKey);\n break;\n case CONTROL_ACTION.UNSUBSCRIBE:\n this.assignedTopics.delete(topicKey);\n break;\n case CONTROL_ACTION.PUBLISH:\n default:\n break;\n }\n if (metadata) this.handlers.onControl(\n action,\n topic,\n data,\n metadata.messageId,\n metadata.timestamp\n );\n else this.handlers.onControl(action, topic, data);\n if (action !== CONTROL_ACTION.PUBLISH) this.updateLoad();\n return true;\n }\n return this.send({\n type: CLUSTER_MESSAGE_TYPE.CONTROL,\n sourceWorkerId: this.workerId,\n targetWorkerId,\n action,\n topic,\n topicKey,\n ...(data === undefined ? {} : { data }),\n ...(metadata?.messageId === undefined ? {} : { messageId: metadata.messageId }),\n ...(metadata?.timestamp === undefined ? {} : { timestamp: metadata.timestamp })\n });\n }\n\n /** Post a message on the BroadcastChannel. Returns false on postMessage failure. */\n private send(message: WorkerClusterMessage): boolean {\n if (!this.channel) return false;\n try {\n this.channel.postMessage({ ...message, protocolVersion: CLUSTER_PROTOCOL_VERSION });\n return true;\n } catch {\n return false;\n }\n }\n\n /** Read all live worker records from storage, pruning stale entries past the TTL. */\n private readWorkers(): WorkerRecord[] {\n if (!this.storage) return [this.currentRecord];\n const now = this.environment.now();\n const workers: WorkerRecord[] = [];\n for (const { key, value: worker } of readAllByPrefix<WorkerRecord>(this.storage, this.workerPrefix)) {\n if (worker.workerId !== this.workerId && now - worker.heartbeatAt > this.workerTtlMs) {\n this.removeStorage(key);\n continue;\n }\n workers.push(worker);\n }\n if (this.started && !workers.some(worker => worker.workerId === this.workerId)) workers.push(this.currentRecord);\n return workers;\n }\n\n /** Enumerate all tab IDs that have a subscriber record for `topicKey`. */\n private readSubscriberTabIds(topicKey: string, workers: readonly WorkerRecord[]): string[] {\n if (!this.storage) {\n // Degraded mode: only this tab can be a subscriber. Recover the plaintext\n // topic to check local interest \u2014 without it we cannot know if we care.\n const topic = this.knownTopics.get(topicKey);\n return topic && this.subscribedTopics.has(topic) ? [this.tabId] : [];\n }\n const activeTabIds = new Set(workers.map(worker => worker.tabId));\n const subscribers = new Set<string>();\n for (const { key, value: record } of readAllByPrefix<TopicSubscriberRecord>(\n this.storage,\n `${this.subscriberPrefix}${topicKey}:`\n )) {\n if (!activeTabIds.has(record.tabId)) {\n this.removeStorage(key);\n continue;\n }\n subscribers.add(record.tabId);\n }\n return Array.from(subscribers);\n }\n\n /** Read the current route for `topicKey`, returning null when no storage layer exists. */\n private readRoute(topicKey: string): WorkerRoute | null {\n if (!this.storage) return this.buildLocalRoute(topicKey);\n return readJson<WorkerRoute>(this.storage, this.routeStorageKey(topicKey));\n }\n\n /** Synthesize a self-owned route when storage is unavailable (degraded mode).\n * The plaintext topic must be recoverable from the knownTopics cache; a\n * missing entry means we never subscribed to or were assigned the topic,\n * so there is no route to report. */\n private buildLocalRoute(topicKey: string): WorkerRoute | null {\n const topic = this.knownTopics.get(topicKey);\n if (!topic) return null;\n if (!this.subscribedTopics.has(topic) && !this.assignedTopics.has(topicKey)) return null;\n return {\n topicKey,\n workerId: this.workerId,\n tabId: this.tabId,\n updatedAt: this.environment.now(),\n generation: 1\n };\n }\n\n /** Persist a route assignment, mapping `topicKey` to the owning Worker. */\n private writeRoute(\n topicKey: string,\n owner: WorkerRecord,\n handoffFromWorkerId?: string,\n generation = 1\n ): void {\n if (!this.storage) return;\n writeJson(this.storage, this.routeStorageKey(topicKey), this.buildRouteRecord(topicKey, owner, handoffFromWorkerId, generation));\n }\n\n /** Construct a WorkerRoute record from the owner + handoff fields. Extracted\n * so writeRoute and confirmRoute share the same shape; confirmedAt is added\n * by confirmRoute via spread. */\n private buildRouteRecord(\n topicKey: string,\n owner: WorkerRecord,\n handoffFromWorkerId: string | undefined,\n generation: number\n ): WorkerRoute {\n return {\n topicKey,\n workerId: owner.workerId,\n tabId: owner.tabId,\n updatedAt: this.environment.now(),\n generation,\n ...(handoffFromWorkerId ? { handoffFromWorkerId } : {})\n };\n }\n\n /** Stamp a route as confirmed once the owning Worker has acknowledged the assignment. */\n private confirmRoute(topicKey: string): void {\n if (!this.storage) return;\n const route = this.readRoute(topicKey);\n if (!route || route.workerId !== this.workerId || route.confirmedAt !== undefined) return;\n writeJson(this.storage, this.routeStorageKey(topicKey), {\n ...route,\n confirmedAt: this.environment.now()\n } satisfies WorkerRoute);\n const topic = this.knownTopics.get(topicKey);\n if (topic) this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_ACK, topic });\n }\n\n /** Remove routes whose topic has no subscribers and whose TTL has expired. */\n private cleanupOrphanedRoutes(workers: readonly WorkerRecord[]): void {\n if (!this.storage) return;\n const now = this.environment.now();\n for (const { key, value: route } of readAllByPrefix<WorkerRoute>(this.storage, this.routePrefix)) {\n if (now - route.updatedAt <= this.workerTtlMs) continue;\n if (this.readSubscriberTabIds(route.topicKey, workers).length > 0) continue;\n this.removeStorage(key);\n }\n }\n\n /** Remove subscriber records for tabs that are no longer active. */\n private cleanupOrphanedSubscribers(workers: readonly WorkerRecord[]): void {\n if (!this.storage) return;\n const activeTabIds = new Set(workers.map(worker => worker.tabId));\n for (const { key, value: record } of readAllByPrefix<TopicSubscriberRecord>(this.storage, this.subscriberPrefix)) {\n if (!activeTabIds.has(record.tabId)) this.removeStorage(key);\n }\n }\n\n /** Persist a subscriber record for this tab on `topicKey`. */\n private writeSubscriber(topicKey: string): void {\n if (!this.storage) return;\n writeJson(this.storage, this.subscriberStorageKey(topicKey, this.tabId), {\n tabId: this.tabId,\n updatedAt: this.environment.now()\n } satisfies TopicSubscriberRecord);\n }\n\n /** Persist the current worker record with an updated heartbeat timestamp.\n * @param notify \u2014 when true, broadcast a REGISTRY nudge so peers reconcile\n * immediately instead of waiting for the next heartbeat. False on the\n * periodic heartbeat tick (peers will notice on their own heartbeat) to\n * avoid a REGISTRY storm every 3 s; true on status/role changes that\n * peers should observe promptly. */\n private writeRecord(notify: boolean): void {\n const now = this.environment.now();\n const sample = this.loadWeighting !== undefined ? this.sampleThroughput(now) : undefined;\n this.currentRecord = {\n ...this.currentRecord,\n heartbeatAt: now,\n ...(sample ? { throughput: sample } : {})\n };\n if (this.storage) writeJson(this.storage, this.workerStorageKey(this.workerId), this.currentRecord);\n if (notify) this.notifyRegistry();\n }\n\n /** Broadcast a REGISTRY message to trigger reconciliation on other tabs. */\n private notifyRegistry(): void {\n this.send({ type: CLUSTER_MESSAGE_TYPE.REGISTRY, sourceWorkerId: this.workerId });\n }\n\n /** Recompute whether this worker is active (eligible to own topics) or standby. Returns true when changed. */\n private refreshRole(workers: readonly WorkerRecord[]): boolean {\n const role: WorkerRole = this.isActiveAmong(workers) ? WORKER_ROLE.ACTIVE : WORKER_ROLE.STANDBY;\n if (role === this.currentRecord.role) return false;\n this.currentRecord = { ...this.currentRecord, role };\n return true;\n }\n\n /** Persist the current topic load count (number of assigned topics) for load-balanced routing. */\n private updateLoad(): void {\n const load = this.assignedTopics.size;\n if (load === this.currentRecord.load) return;\n this.currentRecord = { ...this.currentRecord, load };\n if (this.started) this.writeRecord(true);\n }\n\n /**\n * Hash `topic` into its opaque key and populate the reverse-lookup cache.\n *\n * Despite the name, this is NOT a cache lookup \u2014 it unconditionally writes\n * the `topicKey \u2192 topic` pair. Hashing is cheap enough that a caller needing\n * the key should always call this rather than check `knownTopics` first;\n * the cache's FIFO eviction below keeps it bounded. Only `isAssigned`\n * deliberately bypasses this (it must not pollute the cache on a read-only\n * query), so if you add a new call site, prefer `rememberTopic` unless you\n * have the same \"read-only query\" reason.\n */\n private rememberTopic(topic: string): string {\n const topicKey = createOpaqueKey(topic);\n this.knownTopics.set(topicKey, topic);\n // Evict the oldest entry when the cache exceeds its cap. Plain Map iteration\n // order is insertion order, so deleting the first key is FIFO eviction (not\n // true LRU \u2014 reads do not promote recency). Hashing is cheap, so a missed\n // reverse-lookup merely recomputes the key, but the storage-less fallback\n // path (readSubscriberTabIds/readRoute) relies on this cache to recover the\n // plaintext topic. Never evict a key this worker still owns, or those reads\n // would silently return null for an assigned topic.\n if (this.knownTopics.size > MAX_KNOWN_TOPICS) {\n // Evict the oldest non-owned entry (FIFO). Scan from the front so the\n // cap holds as long as at least one tracked topic is not owned. Only\n // when every entry is owned (degenerate) do we let the cap slip \u2014 owned\n // topics must stay resolvable for the storage-less read path.\n // Scan from the front (oldest insertion) for the first non-owned entry.\n // `break` after one eviction: we only need to get back under the cap, and\n // evicting more would unnecessarily drop resolvable topics. If every\n // entry is owned (degenerate), the loop completes without evicting \u2014\n // owned topics must stay resolvable for the storage-less read path.\n for (const candidate of this.knownTopics.keys()) {\n if (candidate === topicKey || this.assignedTopics.has(candidate)) continue;\n this.knownTopics.delete(candidate);\n break;\n }\n }\n return topicKey;\n }\n\n private workerStorageKey(workerId: string): string {\n return `${this.workerPrefix}${workerId}`;\n }\n\n private routeStorageKey(topicKey: string): string {\n return `${this.routePrefix}${topicKey}`;\n }\n\n private subscriberStorageKey(topicKey: string, tabId: string): string {\n return `${this.subscriberPrefix}${topicKey}:${tabId}`;\n }\n\n private removeStorage(key: string): void {\n try {\n this.storage?.removeItem(key);\n } catch {\n // Ignore unavailable storage.\n }\n }\n\n /** Force-flush any pending batched writes (used during shutdown/teardown). */\n private flushStorage(): void {\n if (this.storage instanceof BatchingStorageWriter) this.storage.flush();\n }\n}\n", "/**\n * DataBusTraceReporter \u2014 optional diagnostics, metrics, and delivery latency.\n *\n * Aggregates message throughput and dispatch latency over configurable windows,\n * emitting structured events (lifecycle, status, subscription, coordination,\n * error) and periodic metrics summaries. The sink is decoupled from the hot\n * message path \u2014 errors in the sink are isolated to console.warn.\n */\nimport type { WorkerStatus } from './types';\nimport type {\n PERSISTENCE_OPERATION,\n RECOVERY_OUTCOME,\n RELIABILITY_OPERATION,\n SUBSCRIPTION_ACTION,\n TRACE_ERROR_SOURCE,\n TRACE_LIFECYCLE_ACTION\n} from '../utils/constants';\nimport {\n DEFAULT_STORAGE_PREFIX,\n TRACE_EVENT_TYPE,\n TRACE_MODE\n} from '../utils/constants';\n\n/** Trace reporting mode: record only events, only metrics, or both. */\n/** Selects which trace categories the reporter emits.\n * - `events` \u2014 lifecycle/status/subscription/coordination/error events only.\n * - `metrics` \u2014 periodic `message_metrics` snapshots only.\n * - `all` \u2014 both event streams and metrics snapshots. */\nexport type DataBusTraceMode = (typeof TRACE_MODE)[keyof typeof TRACE_MODE];\n\n/** Emitted when the DataBus starts, stops, suspends, or resumes. */\nexport interface DataBusLifecycleTraceEvent {\n type: typeof TRACE_EVENT_TYPE.LIFECYCLE;\n action: (typeof TRACE_LIFECYCLE_ACTION)[keyof typeof TRACE_LIFECYCLE_ACTION];\n timestamp: number;\n}\n\n/** Emitted when the transport connection status changes. */\nexport interface DataBusStatusTraceEvent {\n type: typeof TRACE_EVENT_TYPE.STATUS;\n status: WorkerStatus;\n timestamp: number;\n}\n\n/** Emitted when a topic subscription is added or removed. */\nexport interface DataBusSubscriptionTraceEvent {\n type: typeof TRACE_EVENT_TYPE.SUBSCRIPTION;\n action: (typeof SUBSCRIPTION_ACTION)[keyof typeof SUBSCRIPTION_ACTION];\n topic: string;\n activeTopics: number;\n timestamp: number;\n}\n\n/** Emitted after each transport open (initial start and recovery) to record\n * the coordinated cluster state, including the settled route list. */\nexport interface DataBusCoordinationTraceEvent {\n type: typeof TRACE_EVENT_TYPE.COORDINATION;\n coordinated: boolean;\n activeWorkers: number;\n workers: string[];\n routes: string[];\n timestamp: number;\n}\n\n/** Emitted when a transport or operation error occurs. */\nexport interface DataBusErrorTraceEvent {\n type: typeof TRACE_EVENT_TYPE.ERROR;\n source: (typeof TRACE_ERROR_SOURCE)[keyof typeof TRACE_ERROR_SOURCE];\n timestamp: number;\n}\n\n/** Bounded reliability diagnostics for recovery, acknowledgments, and migrations. */\nexport interface DataBusReliabilityTraceEvent {\n type: typeof TRACE_EVENT_TYPE.RELIABILITY;\n operation: (typeof RELIABILITY_OPERATION)[keyof typeof RELIABILITY_OPERATION];\n topic?: string;\n persistenceOperation?: (typeof PERSISTENCE_OPERATION)[keyof typeof PERSISTENCE_OPERATION];\n attempt?: number;\n /** Outcome for a transport recovery attempt. */\n outcome?: (typeof RECOVERY_OUTCOME)[keyof typeof RECOVERY_OUTCOME];\n durationMs?: number;\n timestamp: number;\n}\n\n/**\n * Periodic metrics snapshot: message throughput, dispatch latency percentiles,\n * and active topic count. Aggregated over the interval and emitted every\n * `metricsIntervalMs`.\n */\nexport interface DataBusMetricsTraceEvent {\n type: typeof TRACE_EVENT_TYPE.MESSAGE_METRICS;\n durationMs: number;\n received: number;\n dispatched: number;\n topics: number;\n dispatchSamples: number;\n dispatchAvgMs: number;\n dispatchP50Ms: number;\n dispatchP95Ms: number;\n dispatchMaxMs: number;\n dedupAccepted: number;\n dedupSuppressed: number;\n timestamp: number;\n}\n\n/**\n * Synchronous metrics snapshot: same derived counters as a periodic\n * `message_metrics` event, but queryable on demand (e.g. from\n * `getDiagnostics()`) without a sink or an interval flush, and without\n * resetting the aggregation window.\n */\nexport interface DataBusMetricsSnapshot {\n /** Milliseconds elapsed in the current aggregation window. */\n durationMs: number;\n /** Messages received in the window. */\n received: number;\n /** Messages dispatched locally in the window. */\n dispatched: number;\n /** Distinct topics touched in the window. */\n topics: number;\n /** Latency samples collected in the window. */\n dispatchSamples: number;\n dispatchAvgMs: number;\n dispatchP50Ms: number;\n dispatchP95Ms: number;\n dispatchMaxMs: number;\n dedupAccepted: number;\n dedupSuppressed: number;\n timestamp: number;\n}\n\nexport type DataBusTraceEvent =\n | DataBusLifecycleTraceEvent\n | DataBusStatusTraceEvent\n | DataBusSubscriptionTraceEvent\n | DataBusCoordinationTraceEvent\n | DataBusErrorTraceEvent\n | DataBusReliabilityTraceEvent\n | DataBusMetricsTraceEvent;\n\n/** Distributive-conditional type: given a trace event union, derive the same shape minus `timestamp`. */\ntype DataBusTraceEventInput = DataBusTraceEvent extends infer TEvent\n ? TEvent extends DataBusTraceEvent\n ? Omit<TEvent, 'timestamp'>\n : never\n : never;\n\n/** Configuration for {@link DataBusTraceReporter}. `sink` receives every\n * emitted event (filtered by `mode`); all other fields are optional. */\nexport interface DataBusTraceOptions {\n /** When `false`, the reporter is inert (no events emitted). Default `true`. */\n enabled?: boolean;\n /** Which event categories to emit. Default `all`. */\n mode?: DataBusTraceMode;\n /** Aggregation window for `message_metrics` events. Default 5 s. */\n metricsIntervalMs?: number;\n /** Injectable epoch clock for deterministic metrics and lifecycle tests. */\n now?: () => number;\n /** Callback invoked for each emitted trace event. */\n sink: (event: DataBusTraceEvent) => void;\n /** Queue sink delivery onto a microtask to keep hot paths non-blocking. */\n asyncSink?: boolean;\n}\n\n// Default bounds for the metrics aggregation window.\n/** Default metrics aggregation window: 5 s between snapshots. */\nconst DEFAULT_METRICS_INTERVAL_MS = 5_000;\nconst MAX_PENDING_TOPICS = 1_000;\nconst MAX_PENDING_MESSAGES_PER_TOPIC = 256;\n// Latency histogram: 20 buckets, each 50ms wide \u2192 covers 0\u20131000ms.\nconst LATENCY_BUCKET_COUNT = 20;\nconst LATENCY_BUCKET_SIZE_MS = 50;\n\n/**\n * Aggregates DataBus diagnostics \u2014 lifecycle events, status changes, and\n * periodic latency histograms \u2014 and forwards them to a user-supplied sink.\n *\n * Latency is measured in a bucketed histogram (20 buckets \u00D7 50ms) rather than\n * storing every sample, keeping memory bounded even under high throughput.\n */\nexport class DataBusTraceReporter {\n private readonly enabled: boolean;\n private readonly mode: DataBusTraceMode;\n private readonly metricsIntervalMs: number;\n private readonly sink: (event: DataBusTraceEvent) => void;\n private readonly now: () => number;\n private readonly asyncSink: boolean;\n private pendingEvents: DataBusTraceEvent[] = [];\n private sinkFlushScheduled = false;\n private intervalHandle: ReturnType<typeof setInterval> | null = null;\n private intervalStartedAt = 0;\n // A reporter may be flushed explicitly before start(), but once stop() is\n // called it must remain inert until a new start() begins another session.\n private stopped = false;\n private received = 0;\n private dispatched = 0;\n private latencySamples = 0;\n private readonly topics = new Set<string>();\n // Per-topic FIFO of received timestamps, used to compute dispatch latency.\n private readonly receivedAt = new Map<string, number[]>();\n // Bucketed histogram: bucket index = floor(delayMs / 50), capped at 19.\n private readonly latencyBuckets = new Array<number>(LATENCY_BUCKET_COUNT).fill(0);\n private latencySumMs = 0;\n private dedupAccepted = 0;\n private dedupSuppressed = 0;\n\n constructor(options?: DataBusTraceOptions, now: () => number = options?.now ?? Date.now) {\n this.enabled = options?.enabled ?? false;\n this.mode = options?.mode ?? 'all';\n this.metricsIntervalMs = normalizeInterval(options?.metricsIntervalMs);\n this.sink = options?.sink ?? (() => undefined);\n this.asyncSink = options?.asyncSink ?? false;\n this.now = now;\n }\n\n /** Start the periodic metrics flush interval. No-op when mode is 'events'\n * (no metrics to emit), when disabled, or when already running. */\n start(): void {\n if (!this.enabled || this.intervalHandle || this.mode === TRACE_MODE.EVENTS) return;\n this.stopped = false;\n this.intervalStartedAt = this.now();\n this.intervalHandle = setInterval(() => this.flush(), this.metricsIntervalMs);\n }\n\n /** Pause the metrics interval and reset accumulated counters. */\n pause(): void {\n if (this.intervalHandle) clearInterval(this.intervalHandle);\n this.intervalHandle = null;\n this.intervalStartedAt = 0;\n this.resetMetrics();\n }\n\n stop(): void {\n this.stopped = true;\n this.pause();\n }\n\n /** Synchronous sink state for diagnostics: whether delivery is async and how\n * many events are queued behind the microtask flush. A growing queue under\n * `asyncSink: true` is the first sign of sink back-pressure. */\n getSinkState(): { asyncSink: boolean; pendingEvents: number } {\n return { asyncSink: this.asyncSink, pendingEvents: this.pendingEvents.length };\n }\n\n /** Record an instantaneous trace event (lifecycle, status, error, etc.). */\n event(event: DataBusTraceEventInput): void {\n if (!this.enabled || this.mode === TRACE_MODE.METRICS) return;\n this.emit({ ...event, timestamp: this.now() } as DataBusTraceEvent);\n }\n\n /** Synchronous snapshot of the current metrics window without resetting it.\n * Returns the same derived counters as a periodic `message_metrics` event,\n * or null when metrics recording is inactive (disabled or events-only mode).\n * The window keeps accumulating until the next interval flush. */\n getMetrics(): DataBusMetricsSnapshot | null {\n if (!this.metricsActive || this.stopped) return null;\n const timestamp = this.now();\n const samples = this.latencySamples;\n return {\n durationMs: Math.max(0, timestamp - this.intervalStartedAt),\n received: this.received,\n dispatched: this.dispatched,\n topics: this.topics.size,\n dispatchSamples: samples,\n dispatchAvgMs: roundMs(samples === 0 ? 0 : this.latencySumMs / samples),\n dispatchP50Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.5)),\n dispatchP95Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.95)),\n dispatchMaxMs: roundMs(percentileMs(this.latencyBuckets, samples, 1)),\n dedupAccepted: this.dedupAccepted,\n dedupSuppressed: this.dedupSuppressed,\n timestamp\n };\n }\n\n /** Record that a message was received on `topic`; stores its timestamp for latency tracking. */\n recordReceived(topic: string): void {\n if (!this.metricsActive) return;\n this.received += 1;\n this.topics.add(topic);\n const queue = this.receivedAt.get(topic);\n // First receive for this topic creates a new FIFO queue (subject to the\n // topic cap); subsequent receives append to the existing queue (subject to\n // the per-topic cap). Both caps prevent a single misbehaving topic from\n // exhausting memory.\n if (!queue) {\n if (this.receivedAt.size >= MAX_PENDING_TOPICS) return;\n this.receivedAt.set(topic, [this.now()]);\n return;\n }\n if (queue.length >= MAX_PENDING_MESSAGES_PER_TOPIC) return;\n queue.push(this.now());\n }\n\n /**\n * Record that a received message will never be dispatched locally. Pops the\n * matching FIFO slot so a later dispatch on the same topic does not pair\n * with a stale receive timestamp.\n */\n recordDiscarded(topic: string): void {\n if (!this.metricsActive) return;\n const queue = this.receivedAt.get(topic);\n if (!queue) return;\n queue.shift();\n if (queue.length === 0) this.receivedAt.delete(topic);\n }\n\n /**\n * Record that a message was dispatched on `topic`. Pops the oldest receive\n * timestamp (FIFO) and increments the latency histogram. Dispatches without\n * a matching receive (e.g. broadcast fan-out from another tab) still count\n * as dispatched but do not produce a latency sample.\n */\n recordDispatched(topic: string): void {\n if (!this.metricsActive) return;\n this.dispatched += 1;\n this.topics.add(topic);\n const queue = this.receivedAt.get(topic);\n const receivedTimestamp = queue?.shift();\n if (queue && queue.length === 0) this.receivedAt.delete(topic);\n if (receivedTimestamp === undefined) return;\n this.latencySamples += 1;\n const delayMs = Math.max(0, this.now() - receivedTimestamp);\n // Map the raw delay to a 50ms-wide bucket, capped at the last bucket.\n const bucketIndex = Math.min(LATENCY_BUCKET_COUNT - 1, Math.floor(delayMs / LATENCY_BUCKET_SIZE_MS));\n this.latencyBuckets[bucketIndex] = (this.latencyBuckets[bucketIndex] ?? 0) + 1;\n this.latencySumMs += delayMs;\n }\n\n /** Record deduplication outcomes for the next metrics window. */\n recordDedupAccepted(): void {\n if (this.metricsActive) this.dedupAccepted += 1;\n }\n\n recordDedupSuppressed(): void {\n if (this.metricsActive) this.dedupSuppressed += 1;\n }\n\n /** True when metrics recording is active: enabled and mode includes metrics.\n * Extracted so the four record / flush methods share one guard expression\n * instead of repeating `!this.enabled || this.mode === 'events'` at each. */\n private get metricsActive(): boolean {\n return this.enabled && this.mode !== TRACE_MODE.EVENTS;\n }\n\n /** Emit the accumulated metrics snapshot if the interval is active. */\n flush(): void {\n if (!this.metricsActive || this.stopped) return;\n this.flushNow();\n }\n\n private flushNow(): void {\n const timestamp = this.now();\n // Only emit when there was activity in this window \u2014 an all-zero metrics\n // snapshot adds noise without information. The interval still advances\n // intervalStartedAt so the next window's duration is measured correctly.\n if (this.received > 0 || this.dispatched > 0 || this.dedupAccepted > 0 || this.dedupSuppressed > 0) {\n const samples = this.latencySamples;\n this.emit({\n type: TRACE_EVENT_TYPE.MESSAGE_METRICS,\n durationMs: Math.max(0, timestamp - this.intervalStartedAt),\n received: this.received,\n dispatched: this.dispatched,\n topics: this.topics.size,\n dispatchSamples: samples,\n dispatchAvgMs: roundMs(samples === 0 ? 0 : this.latencySumMs / samples),\n // Percentiles are derived from the histogram, not sorted samples.\n dispatchP50Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.5)),\n dispatchP95Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.95)),\n dispatchMaxMs: roundMs(percentileMs(this.latencyBuckets, samples, 1)),\n dedupAccepted: this.dedupAccepted,\n dedupSuppressed: this.dedupSuppressed,\n timestamp\n });\n this.resetMetrics();\n }\n this.intervalStartedAt = timestamp;\n }\n\n private resetMetrics(): void {\n this.received = 0;\n this.dispatched = 0;\n this.latencySamples = 0;\n this.topics.clear();\n this.receivedAt.clear();\n this.latencyBuckets.fill(0);\n this.latencySumMs = 0;\n this.dedupAccepted = 0;\n this.dedupSuppressed = 0;\n }\n\n private emit(event: DataBusTraceEvent): void {\n if (this.asyncSink) {\n this.pendingEvents.push(event);\n if (!this.sinkFlushScheduled) {\n this.sinkFlushScheduled = true;\n queueMicrotask(() => {\n this.sinkFlushScheduled = false;\n const events = this.pendingEvents;\n this.pendingEvents = [];\n for (const queued of events) this.emitSync(queued);\n });\n }\n return;\n }\n this.emitSync(event);\n }\n\n private emitSync(event: DataBusTraceEvent): void {\n try {\n this.sink(event);\n } catch (error) {\n // Diagnostics must never affect data delivery, but surface a broken sink\n // so instrumentation bugs are not silently hidden.\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(`[${DEFAULT_STORAGE_PREFIX}] trace sink threw:`, error);\n }\n }\n }\n}\n\n/** Validate the metrics interval, falling back to the default when omitted.\n * @throws {RangeError} when `value` is not a positive finite number. */\nfunction normalizeInterval(value: number | undefined): number {\n if (value === undefined) return DEFAULT_METRICS_INTERVAL_MS;\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError('trace.metricsIntervalMs must be a positive finite number.');\n }\n return value;\n}\n\n/**\n * Approximate a percentile from the bucketed histogram. Walks buckets in\n * order, accumulating counts until the cumulative total reaches the rank\n * (`percentile * sampleCount`), and returns the bucket's midpoint as the\n * estimate. Returns the histogram ceiling when the rank exceeds all counts.\n */\nfunction percentileMs(buckets: readonly number[], sampleCount: number, percentile: number): number {\n if (sampleCount <= 0) return 0;\n const rank = Math.max(1, Math.ceil(percentile * sampleCount));\n let seen = 0;\n for (let index = 0; index < buckets.length; index += 1) {\n seen += buckets[index] ?? 0;\n if (seen >= rank) return (index + 0.5) * LATENCY_BUCKET_SIZE_MS;\n }\n return buckets.length * LATENCY_BUCKET_SIZE_MS;\n}\n\n/** Round to one decimal place for stable, readable metrics output.\n * Avoids floating-point noise like 12.300000000001 in the trace sink. */\nfunction roundMs(value: number): number {\n return Math.round(value * 10) / 10;\n}\n", "import type { DataBusMessage } from './types';\nimport { PRUNE_STRATEGY } from '../utils/constants';\n\n/** Inputs shared by the in-memory ring and durable replay adapters. */\nexport interface ReplayPruningOptions {\n maxPerTopic: number;\n pruneStrategy: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n retentionMs: number | undefined;\n now: number;\n}\n\n/**\n * Apply the public replay pruning policy to an insertion-ordered history.\n *\n * `count` keeps the newest `maxPerTopic` entries. `both` applies the retention\n * cutoff first and then the count cap. `age` intentionally leaves timestamped\n * entries uncapped so the retention window is the only bound for them, while\n * timestamp-less legacy entries are still capped by `maxPerTopic` because they\n * have no timestamp by which they can ever expire. The returned array is the\n * same instance when no entries need to be removed.\n */\nexport function pruneReplayHistory<TData>(\n messages: DataBusMessage<TData>[],\n options: ReplayPruningOptions\n): DataBusMessage<TData>[] {\n const { maxPerTopic, pruneStrategy, retentionMs, now } = options;\n const ageEnabled = pruneStrategy !== PRUNE_STRATEGY.COUNT && retentionMs !== undefined;\n if (!ageEnabled) {\n return messages.length > maxPerTopic ? messages.slice(-maxPerTopic) : messages;\n }\n\n const cutoff = now - retentionMs;\n let hasExpired = false;\n let timestamplessCount = 0;\n for (const message of messages) {\n if (message.timestamp === undefined) timestamplessCount += 1;\n else if (message.timestamp < cutoff) hasExpired = true;\n }\n\n let pruned = hasExpired\n ? messages.filter(message => message.timestamp === undefined || message.timestamp >= cutoff)\n : messages;\n\n if (pruneStrategy === PRUNE_STRATEGY.BOTH) {\n return pruned.length > maxPerTopic ? pruned.slice(-maxPerTopic) : pruned;\n }\n\n if (timestamplessCount <= maxPerTopic) return pruned;\n let timestamplessToDrop = timestamplessCount - maxPerTopic;\n pruned = pruned.filter(message => {\n if (message.timestamp === undefined && timestamplessToDrop > 0) {\n timestamplessToDrop -= 1;\n return false;\n }\n return true;\n });\n return pruned;\n}\n", "/**\n * ReplayManager \u2014 bounded per-topic replay history with optional durable\n * persistence.\n *\n * Extracted from CrossTabDataBus so the ring buffers, IndexedDB append/load\n * lifecycle, retention cleanup, and retry policy live in one self-contained\n * unit. The DataBus keeps a thin delegation: `record()` on dispatch,\n * `deliverReplay()` on late-joining handlers, `start`/`stop`/`suspend()` on\n * lifecycle transitions, and `clear*()` on the public replay API.\n *\n * Replay is opt-in: an instance is created with `enabled: false` when the\n * DataBus has no `replay` options, making the zero-overhead default (no ring,\n * no timer, no persistence calls) explicit.\n *\n * Persistence failures are reported through the injected `onPersistenceError`\n * sink (the DataBus routes these to its persistence failure ledger and health\n * summary). Transient failures are retried with exponential backoff; a\n * `PersistenceRetryCancelledError` is thrown when a lifecycle transition\n * (suspend/stop) supersedes the in-flight operation, and is swallowed by the\n * DataBus's persistence error sink so teardown never surfaces noise.\n */\nimport { isWildcardTopic, topicMatchesPattern } from './routing';\nimport { pruneReplayHistory } from './replay-pruning';\nimport type { DataBusReplayPersistence } from './replay-persistence';\nimport type { DataBusTraceReporter } from './trace';\nimport type { DataBusMessage, DataBusMessageHandler } from './types';\nimport { approximatePayloadBytes } from './routing';\nimport { PERSISTENCE_OPERATION, RELIABILITY_OPERATION, TRACE_EVENT_TYPE } from '../utils/constants';\nimport type { PRUNE_STRATEGY } from '../utils/constants';\n\n/** Thrown when a lifecycle transition cancels an in-flight persistence retry. */\nexport class PersistenceRetryCancelledError extends Error {\n constructor() {\n super('Persistence retry cancelled by lifecycle transition.');\n this.name = 'PersistenceRetryCancelledError';\n }\n}\n\n/** Resolved constructor options after the DataBus applies defaults. */\nexport interface ReplayManagerDeps<TData = unknown> {\n /** Whether replay buffering is enabled at all (false \u2192 no-op instance). */\n enabled: boolean;\n /** Per-topic count cap. AGE bounds timestamped entries by retention and\n * still applies this cap to timestamp-less legacy entries. */\n maxPerTopic: number;\n /** Optional durable history backend; null \u2192 in-memory only. */\n persistence?: DataBusReplayPersistence<TData> | null;\n /** Optional producer-timestamp retention window in milliseconds. */\n retentionMs?: number | undefined;\n /** History trimming policy. */\n pruneStrategy: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n /** Optional periodic sweep interval for durable retention cleanup. */\n retentionSweepMs?: number | undefined;\n /** Total persistence attempts including the initial operation. */\n persistenceRetryMaxAttempts: number;\n /** Initial delay between persistence retry attempts. */\n persistenceRetryBackoffMs: number;\n /** Injectable epoch clock; used for retention cutoffs. */\n now: () => number;\n /** Trace sink for persistence retry/cleanup diagnostics. */\n trace: DataBusTraceReporter;\n /** Sink for persistence failures (routes to the DataBus failure ledger). */\n onPersistenceError: (error: unknown) => void;\n /** Sink for a throwing replay-delivery handler (dispatch error source). */\n onDispatchError: (error: unknown) => void;\n}\n\n/** Cap on the exponential backoff delay (ms) for persistence retries. */\nconst MAX_RETRY_DELAY_MS = 1_600;\n\nexport class ReplayManager<TData = unknown> {\n private readonly buffers: Map<string, DataBusMessage<TData>[]> | null;\n private readonly maxPerTopic: number;\n private readonly persistence: DataBusReplayPersistence<TData> | null;\n private readonly retentionMs: number | undefined;\n private readonly pruneStrategy: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n private readonly retentionSweepMs: number | undefined;\n private readonly persistenceRetryMaxAttempts: number;\n private readonly persistenceRetryBackoffMs: number;\n private readonly now: () => number;\n private readonly trace: DataBusTraceReporter;\n private readonly onPersistenceError: (error: unknown) => void;\n private readonly onDispatchError: (error: unknown) => void;\n /** Bumped on suspend/stop so in-flight persistence retries are cancelled. */\n private retryGeneration = 0;\n private pendingReplayPersistence: DataBusMessage<TData>[] = [];\n private persistenceFlushScheduled = false;\n private readonly hydration: Promise<void>;\n /** Coalesced retention cleanup: the newest cutoff wins while one is running. */\n private retentionCleanup: Promise<void> | null = null;\n private retentionCutoff: number | null = null;\n private retentionTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(private readonly deps: ReplayManagerDeps<TData>) {\n this.buffers = deps.enabled ? new Map() : null;\n this.maxPerTopic = deps.maxPerTopic;\n this.persistence = deps.persistence ?? null;\n this.retentionMs = deps.retentionMs;\n this.pruneStrategy = deps.pruneStrategy;\n this.retentionSweepMs = deps.retentionSweepMs;\n this.persistenceRetryMaxAttempts = deps.persistenceRetryMaxAttempts;\n this.persistenceRetryBackoffMs = deps.persistenceRetryBackoffMs;\n this.now = deps.now;\n this.trace = deps.trace;\n this.onPersistenceError = deps.onPersistenceError;\n this.onDispatchError = deps.onDispatchError;\n this.hydration = this.hydrate();\n }\n\n /** True when replay buffering is enabled. */\n get enabled(): boolean {\n return this.buffers !== null;\n }\n\n /** Append a dispatched publication to the topic's replay ring buffer.\n * No-op when replay is disabled. */\n record(message: DataBusMessage<TData>): void {\n if (!this.buffers) return;\n let buffer = this.buffers.get(message.topic);\n if (!buffer) {\n buffer = [];\n this.buffers.set(message.topic, buffer);\n }\n // Preserve the public message shape for legacy adapters. Retention pruning\n // applies to messages that carry an explicit producer timestamp.\n buffer.push(message);\n const pruned = pruneReplayHistory(buffer, {\n maxPerTopic: this.maxPerTopic,\n pruneStrategy: this.pruneStrategy,\n retentionMs: this.retentionMs,\n now: this.now()\n });\n if (pruned !== buffer) {\n buffer = pruned;\n this.buffers.set(message.topic, buffer);\n }\n if (!this.persistence) return;\n if (this.persistence.appendBatch) {\n this.pendingReplayPersistence.push(message);\n this.schedulePersistenceFlush();\n } else {\n void this.withPersistenceRetry(PERSISTENCE_OPERATION.APPEND, () => this.persistence!.append(message))\n .catch(error => this.onPersistenceError(error));\n }\n if (this.retentionMs !== undefined && this.persistence.clearBefore) {\n this.scheduleRetentionCleanup(this.now() - this.retentionMs);\n }\n }\n\n /** Deliver buffered history to a newly-registered handler. For an exact\n * topic this is that topic's ring; for a wildcard subscription every\n * buffered topic matching the pattern contributes (in buffer insertion\n * order). Replay deliveries are marked `replayed: true` and are not counted\n * into trace metrics.\n *\n * When a durable persistence backend is present, delivery waits for the\n * hydration load to settle first; `isHandlerActive` is then consulted so a\n * handler that unsubscribed during the async load does not receive history.\n */\n deliverReplay(\n topic: string,\n replayOption: boolean | number,\n handler: DataBusMessageHandler<TData>,\n isHandlerActive?: () => boolean\n ): void {\n if (!this.buffers) return;\n const limit = typeof replayOption === 'number'\n ? Math.min(Math.floor(replayOption), this.maxPerTopic)\n : this.maxPerTopic;\n if (this.persistence) {\n void this.hydration.then(() => {\n if (isHandlerActive?.() ?? true) this.deliver(topic, limit, handler);\n });\n return;\n }\n this.deliver(topic, limit, handler);\n }\n\n /** Clean up a topic that lost its last local handler: drop the ring buffer,\n * filter queued batch flushes (so an in-flight append cannot undo the\n * clearTopic), and prune durable history. */\n onTopicUnsubscribed(topic: string): void {\n if (!this.buffers) return;\n this.buffers.delete(topic);\n // A batched persistence flush may still be queued behind this task; drop\n // the topic's pending entries so clearTopic is not undone by the append.\n this.pendingReplayPersistence = this.pendingReplayPersistence.filter(message => message.topic !== topic);\n if (this.persistence?.clearTopic) {\n void this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence!.clearTopic!(topic))\n .catch(error => this.onPersistenceError(error));\n }\n }\n\n /** Clear all in-memory replay buffers and, when supported, durable history.\n * Reports persistence failures and rethrows, mirroring the public API\n * contract that callers can observe a failed clear. */\n async clearAll(): Promise<void> {\n if (!this.buffers) return;\n this.buffers.clear();\n // Cancel any queued batch flush so cleared history is not re-appended.\n this.pendingReplayPersistence = [];\n if (this.persistence?.clear) {\n try {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR, () => this.persistence!.clear!());\n } catch (error) {\n this.onPersistenceError(error);\n throw error;\n }\n }\n }\n\n /** Clear replay history for one exact topic, including durable storage. */\n async clearTopic(topic: string): Promise<void> {\n if (!this.buffers) return;\n this.buffers.delete(topic);\n this.pendingReplayPersistence = this.pendingReplayPersistence.filter(message => message.topic !== topic);\n if (this.persistence?.clearTopic) {\n try {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence!.clearTopic!(topic));\n } catch (error) {\n this.onPersistenceError(error);\n throw error;\n }\n }\n }\n\n /** Remove replay entries older than an epoch-millisecond cutoff. */\n async clearBefore(timestamp: number): Promise<void> {\n if (!Number.isFinite(timestamp)) throw new TypeError('timestamp must be finite.');\n if (this.buffers) {\n for (const [topic, messages] of this.buffers) {\n const kept = messages.filter(message => message.timestamp === undefined || message.timestamp >= timestamp);\n if (kept.length) this.buffers.set(topic, kept);\n else this.buffers.delete(topic);\n }\n }\n // A queued batch flush must not resurrect pruned entries.\n this.pendingReplayPersistence = this.pendingReplayPersistence.filter(\n message => message.timestamp === undefined || message.timestamp >= timestamp\n );\n if (this.persistence?.clearBefore) {\n try {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_BEFORE, () => this.persistence!.clearBefore!(timestamp));\n } catch (error) {\n this.onPersistenceError(error);\n throw error;\n }\n }\n }\n\n /** Start the periodic retention sweep. No-op when no durable retention\n * config makes it necessary. */\n start(): void {\n if (this.retentionTimer || !this.retentionMs || !this.retentionSweepMs || !this.persistence?.clearBefore) return;\n this.retentionTimer = setInterval(() => {\n this.scheduleRetentionCleanup(this.now() - this.retentionMs!);\n }, this.retentionSweepMs);\n }\n\n /** Stop the periodic retention sweep. */\n stop(): void {\n if (this.retentionTimer) clearInterval(this.retentionTimer);\n this.retentionTimer = null;\n }\n\n /** Suspend the manager: cancel in-flight persistence retries (so a hidden tab\n * or stopped bus does not keep hammering the store) and stop the sweep. */\n suspend(): void {\n this.retryGeneration += 1;\n this.stop();\n }\n\n /** Drop all in-memory buffers (used on full teardown). */\n resetBuffers(): void {\n this.buffers?.clear();\n }\n\n /** Buffer occupancy for diagnostics. `bytes` is an approximate in-memory\n * payload footprint (same heuristic as adaptive load weighting), computed on\n * demand so the hot append path never pays for it. */\n getStats(): { enabled: boolean; topics: number; messages: number; bytes: number } {\n let messages = 0;\n let bytes = 0;\n if (this.buffers) {\n for (const buffer of this.buffers.values()) {\n messages += buffer.length;\n for (const message of buffer) bytes += approximatePayloadBytes(message.data);\n }\n }\n return { enabled: this.enabled, topics: this.buffers?.size ?? 0, messages, bytes };\n }\n\n /** Deliver history from one topic's ring to a handler, isolating a throwing\n * handler so the remaining buffers are still delivered. */\n private deliver(topic: string, limit: number, handler: DataBusMessageHandler<TData>): void {\n if (!this.buffers || limit <= 0) return;\n const deliverBuffer = (buffer: DataBusMessage<TData>[]) => {\n for (const message of buffer.slice(-limit)) {\n try {\n handler({ ...message, replayed: true });\n } catch (error) {\n this.onDispatchError(error);\n }\n }\n };\n if (isWildcardTopic(topic)) {\n for (const [bufferedTopic, buffer] of this.buffers) {\n if (topicMatchesPattern(topic, bufferedTopic)) deliverBuffer(buffer);\n }\n return;\n }\n const buffer = this.buffers.get(topic);\n if (buffer) deliverBuffer(buffer);\n }\n\n /** Coalesce queued persistence appends into a single microtask batch so a\n * burst of publications does not issue one IndexedDB transaction each.\n * Only reachable when the backend advertises `appendBatch` (the sole queuer,\n * `record()`, guards on it), so the batched path is unconditional here. */\n private schedulePersistenceFlush(): void {\n if (this.persistenceFlushScheduled) return;\n this.persistenceFlushScheduled = true;\n queueMicrotask(() => {\n this.persistenceFlushScheduled = false;\n const batch = this.pendingReplayPersistence.splice(0);\n if (batch.length === 0 || !this.persistence) return;\n void this.withPersistenceRetry(PERSISTENCE_OPERATION.APPEND, () => this.persistence!.appendBatch!(batch))\n .catch(error => this.onPersistenceError(error));\n });\n }\n\n /** Load durable history into the in-memory rings once at startup, pruning\n * entries past the retention window first. Failures are reported but do not\n * block startup \u2014 the bus runs with whatever survived. */\n private async hydrate(): Promise<void> {\n if (!this.buffers || !this.persistence) {\n return;\n }\n try {\n if (this.retentionMs !== undefined && this.persistence.clearBefore) {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_BEFORE, () => this.persistence!.clearBefore!(this.now() - this.retentionMs!));\n }\n const loaded = await this.withPersistenceRetry(PERSISTENCE_OPERATION.LOAD, () => this.persistence!.load());\n for (const message of loaded) {\n let buffer = this.buffers.get(message.topic);\n if (!buffer) {\n buffer = [];\n this.buffers.set(message.topic, buffer);\n }\n buffer.push(message);\n }\n const hydrationNow = this.now();\n for (const [topic, buffer] of this.buffers) {\n const pruned = pruneReplayHistory(buffer, {\n maxPerTopic: this.maxPerTopic,\n pruneStrategy: this.pruneStrategy,\n retentionMs: this.retentionMs,\n now: hydrationNow\n });\n if (pruned !== buffer) this.buffers.set(topic, pruned);\n }\n } catch (error) {\n this.onPersistenceError(error);\n }\n }\n\n /** Coalesce retention cleanup: the newest cutoff wins while one pass runs,\n * so a burst of publications issues at most one clearBefore transaction. */\n private scheduleRetentionCleanup(cutoff: number): void {\n if (!this.persistence?.clearBefore) return;\n if (this.retentionCutoff === null || cutoff > this.retentionCutoff) {\n this.retentionCutoff = cutoff;\n }\n if (this.retentionCleanup) return;\n this.retentionCleanup = (async () => {\n while (this.retentionCutoff !== null) {\n const nextCutoff = this.retentionCutoff;\n this.retentionCutoff = null;\n try {\n await this.persistence!.clearBefore!(nextCutoff);\n } catch (error) {\n this.onPersistenceError(error);\n }\n }\n })().finally(() => {\n this.retentionCleanup = null;\n if (this.retentionCutoff !== null) {\n this.scheduleRetentionCleanup(this.retentionCutoff);\n }\n });\n }\n\n /** Run a persistence operation with exponential backoff on transient failure.\n * Bumped `retryGeneration` (suspend/stop) cancels the loop early; a\n * structurally failing operation throws after `persistenceRetryMaxAttempts`,\n * leaving the ring buffer intact so the bus keeps working. */\n private async withPersistenceRetry<T>(\n persistenceOperation: (typeof PERSISTENCE_OPERATION)[keyof typeof PERSISTENCE_OPERATION],\n operation: () => Promise<T>\n ): Promise<T> {\n const generation = this.retryGeneration;\n let attempt = 0;\n let delay = this.persistenceRetryBackoffMs;\n while (true) {\n attempt += 1;\n try {\n if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();\n return await operation();\n } catch (error) {\n if (error instanceof PersistenceRetryCancelledError || generation !== this.retryGeneration) {\n throw new PersistenceRetryCancelledError();\n }\n if (attempt >= this.persistenceRetryMaxAttempts) throw error;\n this.trace.event({\n type: TRACE_EVENT_TYPE.RELIABILITY,\n operation: RELIABILITY_OPERATION.PERSISTENCE_RETRY,\n persistenceOperation,\n attempt,\n });\n if (delay > 0) await new Promise<void>(resolve => setTimeout(resolve, delay));\n if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();\n delay = Math.min(delay * 2, MAX_RETRY_DELAY_MS);\n }\n }\n }\n}\n", "/**\n * DedupManager \u2014 bounded duplicate suppression for publications carrying\n * `messageId`.\n *\n * Extracted from CrossTabDataBus so the seen-ID map, adaptive TTL, and sweep\n * timer live in one self-contained unit with their own lifecycle. The DataBus\n * keeps a thin delegation: `isDuplicate()` on the inbound path, `start`/`stop`\n * on the lifecycle transitions, and `getStats()`/`reset()` on the diagnostics\n * surface.\n *\n * Deduplication is opt-in: an instance is only created when the DataBus was\n * configured with `dedup` options, so the zero-overhead default (no map, no\n * timer) is preserved.\n */\nimport type { DataBusTraceReporter } from './trace';\nimport { RELIABILITY_OPERATION, TRACE_EVENT_TYPE } from '../utils/constants';\n\n/** Opt-in bounded duplicate suppression for publications carrying `messageId`. */\nexport interface DataBusDedupOptions {\n /** Max remembered message IDs before the oldest (FIFO) entry is evicted.\n * Default 1_000. */\n maxEntries?: number;\n /** Time-to-live for a remembered ID. Default 60_000 ms. */\n ttlMs?: number;\n /** Optional periodic sweep interval for quiet-topic expiry. */\n sweepMs?: number;\n /** Injectable epoch clock for deterministic tests and non-wall-clock hosts. */\n now?: () => number;\n /** Optional adaptive TTL bounds; enabled only when both are provided. */\n adaptiveTtl?: { minMs: number; maxMs: number };\n}\n\n/** Bounded deduplication counters for diagnostics and health checks. */\nexport interface DataBusDedupStats {\n enabled: boolean;\n /** Currently remembered message IDs. */\n tracked: number;\n /** Publications suppressed as duplicates since the last reset. */\n suppressed: number;\n /** Publications accepted (tracked) since the last reset. */\n accepted: number;\n /** Effective TTL when adaptive bounds are configured. */\n ttlMs?: number;\n}\n\n/** Resolved constructor options after defaults are applied. */\nexport interface DedupManagerOptions {\n /** Whether deduplication is enabled at all (false \u2192 no-op instance). */\n enabled: boolean;\n maxEntries: number;\n ttlMs: number;\n adaptiveBounds?: { minMs: number; maxMs: number } | undefined;\n /** Optional periodic sweep interval for quiet-topic expiry. */\n sweepMs?: number | undefined;\n /** Injectable epoch clock; also used as the message-arrival clock. */\n now: () => number;\n /** Trace sink for suppression events and metrics counters. */\n trace: DataBusTraceReporter;\n}\n\n/** Fixed observation window for adaptive TTL rate computation (5 s). */\nconst ADAPTIVE_WINDOW_MS = 5_000;\n/** Message rate per ms treated as \"quiet\" \u2014 at or below this the adaptive TTL\n * relaxes toward `maxMs`. */\nconst QUIET_RATE_PER_MS = 0.01;\n\nexport class DedupManager {\n private readonly enabled: boolean;\n private readonly maxEntries: number;\n private readonly ttlMs: number;\n private readonly adaptiveBounds: { minMs: number; maxMs: number } | undefined;\n private readonly sweepMs: number | undefined;\n private readonly now: () => number;\n private readonly trace: DataBusTraceReporter;\n private readonly seenMessageIds = new Map<string, number>();\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n private windowStartedAt = 0;\n private windowAccepted = 0;\n private suppressed = 0;\n private accepted = 0;\n\n constructor(options: DedupManagerOptions) {\n this.enabled = options.enabled;\n this.maxEntries = options.maxEntries;\n this.ttlMs = options.ttlMs;\n this.adaptiveBounds = options.adaptiveBounds;\n this.sweepMs = options.sweepMs;\n this.now = options.now;\n this.trace = options.trace;\n this.windowStartedAt = this.now();\n }\n\n /** True when a publication carrying `messageId` was already seen. Records the\n * ID and updates counters/trace on acceptance. Disabled or ID-less messages\n * always pass through. */\n isDuplicate(messageId: string, topic: string): boolean {\n if (!this.enabled || !messageId) return false;\n const now = this.now();\n // Opportunistic expiry on the hot path keeps the map bounded between sweeps.\n // This must use the effective (adaptive) TTL, not the fixed one: otherwise\n // a burst that shrinks the window toward minMs would still retain IDs for\n // the full fixed ttlMs here while the sweep prunes them early.\n const ttlMs = this.currentTtl();\n for (const [id, timestamp] of this.seenMessageIds) {\n if (now - timestamp > ttlMs) this.seenMessageIds.delete(id);\n }\n if (this.seenMessageIds.has(messageId)) {\n this.suppressed += 1;\n this.trace.event({\n type: TRACE_EVENT_TYPE.RELIABILITY,\n operation: RELIABILITY_OPERATION.DEDUP_SUPPRESSED,\n topic\n });\n this.trace.recordDedupSuppressed();\n return true;\n }\n this.seenMessageIds.set(messageId, now);\n this.accepted += 1;\n this.windowAccepted += 1;\n this.trace.recordDedupAccepted();\n // Cap growth: evict oldest (FIFO) entries when the map exceeds the cap, so a\n // high-cardinality burst of distinct IDs cannot exhaust memory.\n while (this.seenMessageIds.size > this.maxEntries) {\n const oldest = this.seenMessageIds.keys().next().value;\n if (oldest === undefined) break;\n this.seenMessageIds.delete(oldest);\n }\n return false;\n }\n\n /** Start the periodic expiry sweep. No-op when disabled or no sweepMs was\n * configured (the hot-path opportunistic expiry still bounds the map). */\n start(): void {\n if (this.sweepTimer || !this.enabled || !this.sweepMs) return;\n this.sweepTimer = setInterval(() => this.pruneExpired(), this.sweepMs);\n }\n\n /** Stop the periodic expiry sweep. */\n stop(): void {\n if (this.sweepTimer) clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n\n /** Return bounded deduplication counters for diagnostics and health checks. */\n getStats(): DataBusDedupStats {\n return {\n enabled: this.enabled,\n tracked: this.seenMessageIds.size,\n suppressed: this.suppressed,\n accepted: this.accepted,\n ...(this.adaptiveBounds ? { ttlMs: this.currentTtl() } : {})\n };\n }\n\n /** Drop all remembered IDs and reset dedup counters. */\n reset(): void {\n this.seenMessageIds.clear();\n this.suppressed = 0;\n this.accepted = 0;\n this.windowStartedAt = this.now();\n this.windowAccepted = 0;\n }\n\n /** Remove IDs whose timestamp predates the effective TTL cutoff. */\n private pruneExpired(): void {\n const cutoff = this.now() - this.currentTtl();\n for (const [id, timestamp] of this.seenMessageIds) {\n if (timestamp < cutoff) this.seenMessageIds.delete(id);\n }\n }\n\n /** Effective TTL: the fixed `ttlMs` unless adaptive bounds are configured, in\n * which case a higher recent message rate shortens the window (dedup only\n * needs to live long enough to bridge duplicate bursts). The window resets\n * every ADAPTIVE_WINDOW_MS. */\n private currentTtl(): number {\n if (!this.adaptiveBounds) return this.ttlMs;\n const now = this.now();\n const elapsed = now - this.windowStartedAt;\n if (elapsed >= ADAPTIVE_WINDOW_MS) {\n this.windowStartedAt = now;\n this.windowAccepted = 0;\n return this.adaptiveBounds.maxMs;\n }\n const rate = this.windowAccepted / Math.max(1, elapsed);\n const factor = Math.min(1, rate / QUIET_RATE_PER_MS);\n return this.adaptiveBounds.maxMs - (this.adaptiveBounds.maxMs - this.adaptiveBounds.minMs) * factor;\n }\n}\n", "/**\n * SDK version reported in diagnostics and health summaries.\n *\n * The value is injected at bundle time (esbuild `define`) from package.json so\n * it can never drift from the release. Source-level consumers (typecheck,\n * vitest) get the same value via the vitest `define`; the declare keeps tsc\n * happy when no define is present.\n */\ndeclare const __SDK_VERSION__: string | undefined;\n\nexport const SDK_VERSION: string =\n typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '';\n", "/**\n * CrossTabDataBus \u2014 the primary public API for cross-tab data distribution.\n *\n * Wraps WorkerClusterRuntime for cluster coordination and a DataBusTransport\n * for the real connection. Handles local handler reference counting, subscription\n * queuing, message dispatch, and clean lifecycle management (start/stop/BFCache).\n */\nimport { WorkerClusterRuntime } from './cluster';\nimport type { WorkerClusterOptions, WorkerClusterSnapshot } from './cluster';\nimport { topicMatchesPattern } from './routing';\nimport type {\n DataBusErrorHandler,\n DataBusMessage,\n DataBusMessageHandler,\n DataBusPublishOptions,\n DataBusStatusHandler,\n DataBusTransport,\n WorkerStatus\n} from './types';\nimport { DataBusTraceReporter } from './trace';\nimport type { DataBusMetricsSnapshot, DataBusTraceOptions } from './trace';\nimport type { DataBusReplayPersistence } from './replay-persistence';\nimport { PersistenceRetryCancelledError, ReplayManager } from './replay-manager';\nimport { DedupManager } from './dedup-manager';\nimport type { DataBusDedupOptions, DataBusDedupStats } from './dedup-manager';\nimport { SDK_VERSION } from './version';\nimport {\n CONTROL_ACTION,\n DEFAULT_STORAGE_PREFIX,\n FAILURE_SOURCE,\n HEALTH_STATE,\n INVOKE_LABEL,\n PRUNE_STRATEGY,\n PUBLICATION_EVENT,\n RECOVERY_OUTCOME,\n RELIABILITY_OPERATION,\n SUBSCRIPTION_ACTION,\n TRACE_EVENT_TYPE,\n TRACE_ERROR_SOURCE,\n TRACE_LIFECYCLE_ACTION,\n WORKER_ROLE,\n WORKER_STATUS\n} from '../utils/constants';\nimport { publicationMetadata } from '../utils/metadata';\nimport { assertDedupOptions, assertReplayOptions, assertRecoveryOptions } from '../utils/validation';\n\n/** Default ring size per topic when replay is enabled without a limit. */\nconst DEFAULT_REPLAY_MAX_PER_TOPIC = 100;\n\n/** Constructor options for {@link CrossTabDataBus}. Extends WorkerClusterOptions\n * (cluster coordination config) with the transport, initial connection config,\n * and trace options. */\n/** Replay (bounded local history) configuration. When present, the DataBus\n * keeps a bounded ring buffer of the most recent dispatched publications per\n * topic, and `subscribe()` can deliver that history to late-joining handlers.\n * Buffers live in memory by default; an optional persistence backend can make\n * them durable. */\nexport interface DataBusReplayOptions<TData = unknown> {\n /** Maximum buffered publications per topic under 'count'/'both'. With 'age',\n * timestamped history is bounded by `retentionMs` and timestamp-less legacy\n * entries are capped by this value. Oldest entries are evicted first.\n * Default 100. */\n maxPerTopic?: number;\n /** Optional durable history backend. Defaults to in-memory only. */\n persistence?: DataBusReplayPersistence<TData>;\n /** Optional producer-timestamp retention window in milliseconds. */\n retentionMs?: number;\n /** History trimming policy: 'count' (default) caps each topic at\n * `maxPerTopic`, 'age' prunes by `retentionMs`, and 'both' applies both.\n * With 'age', timestamped entries are bounded by the retention window and\n * timestamp-less legacy entries are capped by `maxPerTopic`. An 'age'\n * strategy without `retentionMs` falls back to the count cap. */\n pruneStrategy?: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n /** Optional periodic sweep interval for durable retention cleanup. */\n retentionSweepMs?: number;\n /** Optional bounded retry policy for transient persistence failures. */\n persistenceRetry?: DataBusPersistenceRetryOptions;\n}\n\nexport interface DataBusPersistenceRetryOptions {\n /** Total attempts including the initial operation. Default 1. */\n maxAttempts?: number;\n /** Initial delay between attempts. Default 50ms. */\n backoffMs?: number;\n}\n\nexport type { DataBusDedupOptions, DataBusDedupStats };\n\nexport interface DataBusDiagnostics {\n sdkVersion: string;\n status: WorkerStatus;\n started: boolean;\n transportReady: boolean;\n recovery: { attempt: number; exhausted: boolean; maxAttempts: number; hasError: boolean; errorMessage: string | null; errorAt: number | null; generation: number; lastSuccessAt: number | null };\n dedup: DataBusDedupStats;\n replay: { enabled: boolean; topics: number; messages: number; bytes: number };\n persistence: DataBusPersistenceHealth;\n protocol: { version: number; unknownMessages: number; lastUnknownMessageType: string | null; peers: Record<string, number | null> };\n transport: { name: string; backend: string | null; status: WorkerStatus; suspended: boolean };\n cluster: WorkerClusterSnapshot;\n /** Current trace metrics window counters, or null when metrics are inactive. */\n metrics: DataBusMetricsSnapshot | null;\n /** Trace sink delivery mode and queued-event depth (asyncSink back-pressure). */\n trace: { asyncSink: boolean; pendingEvents: number };\n}\n\n/** Where a retained failure originated, as surfaced by {@link DataBusHealthSummary}. */\nexport type DataBusFailureSource = (typeof FAILURE_SOURCE)[keyof typeof FAILURE_SOURCE];\n\nexport interface DataBusLastFailure {\n source: DataBusFailureSource;\n message: string;\n at: number;\n}\n\n/** Bounded failure counters for the optional replay persistence backend. */\nexport interface DataBusPersistenceHealth {\n /** Total persistence failures reported since the last explicit start(). */\n failures: number;\n lastFailureAt: number | null;\n lastErrorMessage: string | null;\n}\n\n/** Compact single-object health verdict for dashboards and readiness probes.\n * Unlike {@link DataBusDiagnostics} this answers one question first \u2014 is the\n * bus usable right now \u2014 then attaches the failure and recovery context that\n * explains the verdict. */\nexport interface DataBusHealthSummary {\n /** True only while the bus is started, not suspended, and the transport is ready. */\n healthy: boolean;\n /** Lifecycle-derived verdict: 'stopped' | 'starting' | 'healthy' | 'recovering' | 'suspended' | 'degraded'.\n * 'degraded' means automatic recovery is exhausted and the transport is still down \u2014 a manual\n * start() (or resume) is required. */\n state: (typeof HEALTH_STATE)[keyof typeof HEALTH_STATE];\n status: WorkerStatus;\n sdkVersion: string;\n started: boolean;\n suspended: boolean;\n transport: { name: string; backend: string | null; ready: boolean; status: WorkerStatus };\n recovery: ReturnType<CrossTabDataBus['getRecoveryStats']>;\n /** Most recent failure of any source since the last explicit start(). */\n lastFailure: DataBusLastFailure | null;\n persistence: DataBusPersistenceHealth;\n /** Current trace metrics window, or null when trace metrics are inactive. */\n metrics: DataBusMetricsSnapshot | null;\n /** Trace sink delivery mode and queued-event depth (asyncSink back-pressure). */\n trace: { asyncSink: boolean; pendingEvents: number };\n}\n\nexport interface CrossTabDataBusOptions<TConfig, TData>\n extends Omit<WorkerClusterOptions, 'handlers'> {\n transport: DataBusTransport<TConfig, TData>;\n initialConfig?: TConfig;\n autoStart?: boolean;\n trace?: DataBusTraceOptions;\n /** Opt-in bounded per-topic history. Absent \u2192 no buffering, zero overhead. */\n replay?: DataBusReplayOptions<TData>;\n /** Optional duplicate suppression; absent means every publication is delivered. */\n dedup?: DataBusDedupOptions;\n /** Automatic transport recovery pacing. */\n recovery?: { cooldownMs?: number; maxAttempts?: number };\n}\n\n/**\n * High-level cross-tab pub/sub client.\n *\n * Orchestrates a transport (e.g. Centrifuge WebSocket inside a Worker) and a\n * WorkerClusterRuntime for cross-tab coordination. Messages arriving from the\n * transport are fanned out to all tabs in the cluster and dispatched locally\n * to registered handlers.\n */\nexport class CrossTabDataBus<TConfig = unknown, TData = unknown> {\n private readonly transport: DataBusTransport<TConfig, TData>;\n private readonly cluster: WorkerClusterRuntime;\n // Map of topic \u2192 set of local subscribers.\n private readonly topicHandlers = new Map<string, Set<DataBusMessageHandler<TData>>>();\n // Topics for which the transport has been asked to subscribe (used to avoid\n // duplicate subscribe calls during reconnection).\n private readonly transportSubscribedTopics = new Set<string>();\n private readonly statusHandlers = new Set<DataBusStatusHandler>();\n private readonly errorHandlers = new Set<DataBusErrorHandler>();\n private readonly replayManager: ReplayManager<TData>;\n private readonly initialConfig: TConfig | undefined;\n private readonly hasInitialConfig: boolean;\n private readonly trace: DataBusTraceReporter;\n private readonly dedupManager: DedupManager;\n private readonly now: () => number;\n private activeConfig: TConfig | undefined;\n private status: WorkerStatus = WORKER_STATUS.DISCONNECTED;\n private started = false;\n private stopping = false;\n private transportReady = false;\n // Whether the installed transport has reported `connected` at least once\n // since the current open began. A clean `disconnected` after this point is\n // a lost working connection, not the pre-connect window of a worker-style\n // backend whose start() resolves before it reports the connection.\n private transportHasConnected = false;\n // Last transport failure, retained so ready() can surface it to callers who\n // never awaited start() directly. Cleared on the next successful start.\n private lastError: unknown = null;\n private lastErrorAt: number | null = null;\n // Unified failure ledger for the health summary: the most recent failure of\n // any source (transport, persistence, dispatch) since the last explicit start.\n private lastFailure: DataBusLastFailure | null = null;\n private persistenceFailureCount = 0;\n private persistenceLastFailureAt: number | null = null;\n private persistenceLastErrorMessage: string | null = null;\n // Gate that serialises start/stop/suspend/resume \u2014 only one lifecycle\n // transition at a time. Resets to null once the operation settles.\n private startPromise: Promise<void> | null = null;\n // Gate for an explicit stop(). Concurrent stop() calls share it, and a\n // start() received while stopping chains a fresh start after it.\n private stopPromise: Promise<void> | null = null;\n // A start() requested while an explicit stop() is still settling. Kept\n // separate from startPromise because stop()'s finally block clears the\n // ordinary lifecycle gate before the queued start is allowed to run.\n private queuedStart: Promise<void> | null = null;\n // Lazy readiness view of queuedStart. start() keeps its documented\n // resolve-on-cancellation contract, while ready() must reject when the\n // queued intent was superseded by a later stop().\n private queuedStartReady: Promise<void> | null = null;\n private queuedStartReadyToken = 0;\n // The queued continuation is chained to the stop promise and cannot be\n // un-scheduled once scheduled. A later stop() therefore invalidates the\n // current intent by recording its token; a subsequent start() issues a\n // higher token so the latest lifecycle request still wins.\n private queuedStartToken = 0;\n private canceledQueuedStartToken = 0;\n // Timestamp of the last automatic transport recovery attempt.\n // Used to avoid a tight retry loop when the transport fails repeatedly.\n private lastRecoveryAt = 0;\n // Monotonic attempt number within one runtime recovery sequence; reset once\n // a transport reopen succeeds so traces can correlate repeated failures.\n private recoveryAttempt = 0;\n private recoveryExhausted = false;\n // Gate that holds transport operations issued after a runtime `error` until\n // the scheduled recovery attempt has actually run. Without it, a dead\n // transport still has `transportReady === true` during the cooldown, so\n // publishes/subscribes would be written to the failed connection and lost.\n private recoveryGate: Promise<void> | null = null;\n private recoveryGateRelease: (() => void) | null = null;\n private recoveryTimer: ReturnType<typeof setTimeout> | null = null;\n private recoveryTimerToken = 0;\n // Once an automatic attempt fails, an explicit transport operation may\n // recover immediately instead of waiting for the next paced attempt. The\n // gate still stays closed so the operation cannot reach the failed\n // transport; it is released by the successful on-demand reopen.\n private recoveryDemandAllowed = false;\n /** Monotonic generation incremented on every successful transport open.\n * Stays in lockstep with `lastSuccessAt` so callers can detect that the\n * transport has been reopened even if the timestamp window is short. */\n private recoveryGeneration = 0;\n /** Timestamp of the most recent successful transport open. Null until the\n * transport has reached the `ready` state at least once. */\n private lastSuccessAt: number | null = null;\n // True while the tab is hidden so an in-flight transport start does not mark\n // the transport ready after suspendTransport() has stopped it.\n private suspended = false;\n // Single gate for async transport.stop() cleanup, shared by failed opens and\n // page-hide suspension. Kept separate from startPromise so ready() still\n // surfaces a failure while later opens and automatic recovery wait for the\n // stop to settle.\n private pendingStop: Promise<void> | null = null;\n // Ownership token for asynchronous transport opens. Every lifecycle\n // transition invalidates callbacks and failure cleanup from older opens.\n private lifecycleEpoch = 0;\n // Minimum interval in ms between automatic recovery attempts.\n private readonly recoveryCooldownMs: number;\n private readonly recoveryMaxAttempts: number;\n\n constructor(options: CrossTabDataBusOptions<TConfig, TData>) {\n const replay = options.replay;\n assertReplayOptions(replay);\n const { autoStart, initialConfig, trace, transport, dedup, recovery, ...clusterOptions } = options;\n assertRecoveryOptions(recovery);\n this.recoveryCooldownMs = recovery?.cooldownMs ?? 1000;\n this.recoveryMaxAttempts = recovery?.maxAttempts ?? Number.POSITIVE_INFINITY;\n this.now = dedup?.now ?? Date.now;\n this.transport = transport;\n this.initialConfig = initialConfig;\n this.hasInitialConfig = 'initialConfig' in options;\n this.trace = new DataBusTraceReporter(trace);\n this.replayManager = new ReplayManager<TData>({\n enabled: replay !== undefined,\n maxPerTopic: replay?.maxPerTopic ?? DEFAULT_REPLAY_MAX_PER_TOPIC,\n persistence: (replay?.persistence as DataBusReplayPersistence<TData> | undefined) ?? null,\n retentionMs: replay?.retentionMs,\n pruneStrategy: replay?.pruneStrategy ?? PRUNE_STRATEGY.COUNT,\n retentionSweepMs: replay?.retentionSweepMs,\n persistenceRetryMaxAttempts: replay?.persistenceRetry?.maxAttempts ?? 1,\n persistenceRetryBackoffMs: replay?.persistenceRetry?.backoffMs ?? 50,\n now: this.now,\n trace: this.trace,\n onPersistenceError: error => this.reportPersistenceError(error),\n onDispatchError: error => this.reportError(error, FAILURE_SOURCE.DISPATCH)\n });\n assertDedupOptions(dedup);\n this.dedupManager = new DedupManager({\n enabled: dedup !== undefined,\n maxEntries: dedup?.maxEntries ?? 1_000,\n ttlMs: dedup?.ttlMs ?? 60_000,\n adaptiveBounds: dedup?.adaptiveTtl,\n sweepMs: dedup?.sweepMs,\n now: this.now,\n trace: this.trace\n });\n this.cluster = new WorkerClusterRuntime({\n ...clusterOptions,\n handlers: {\n // The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH\n // control message \u2014 meaning the owning Worker has delegated the action to us.\n onControl: (action, topic, data, messageId, timestamp) => {\n switch (action) {\n case CONTROL_ACTION.SUBSCRIBE:\n if (this.subscribeTransport(topic)) this.traceSubscription(SUBSCRIPTION_ACTION.SUBSCRIBE, topic);\n break;\n case CONTROL_ACTION.UNSUBSCRIBE:\n if (this.unsubscribeTransport(topic)) this.traceSubscription(SUBSCRIPTION_ACTION.UNSUBSCRIBE, topic);\n break;\n case CONTROL_ACTION.PUBLISH:\n this.runTransport(() => this.transport.publish(topic, data, publicationMetadata(messageId, timestamp)));\n break;\n default:\n break;\n }\n },\n // Batched variant of the PUBLISH action (CONTROL frames carrying\n // multiple items, and the local publishBatch fast path). Uses the\n // transport's one-frame publishBatch when available, preserving\n // per-item metadata; otherwise falls back to per-item publishes.\n onPublishBatch: (topic, items) => {\n if (typeof this.transport.publishBatch === 'function') {\n this.runTransport(() => this.transport.publishBatch!(topic, items));\n return;\n }\n for (const item of items) {\n this.runTransport(() => this.transport.publish(\n topic,\n item.data,\n publicationMetadata(item.messageId, item.timestamp)\n ));\n }\n },\n // The cluster calls `onEvent` when a publication broadcast arrives from\n // another tab. Dispatch locally if we have subscribers. The payload is\n // typed `unknown` at the cluster boundary (the cluster is transport-\n // agnostic); here we narrow it to DataBusMessage \u2014 the sender is our\n // own broadcastEvent call, which always posts a DataBusMessage.\n onEvent: (eventType, payload, _sourceWorkerId, originTabId) => {\n if (eventType !== PUBLICATION_EVENT) return;\n const incoming = payload as DataBusMessage<TData>;\n // Prefer the originTabId the sender stamped; only fall back to the\n // broadcast cluster tabId when the older cluster version is in use.\n const message: DataBusMessage<TData> = incoming.originTabId !== undefined\n ? incoming\n : originTabId !== undefined\n ? { ...incoming, originTabId }\n : incoming;\n if (this.cluster.hasLocalSubscriber(message.topic)) this.dispatch(message);\n },\n onSuspend: () => {\n // Suppress the suspend trace event during an explicit stop() so\n // the trace log ends on 'stop' rather than 'suspend'\u2192'stop'.\n if (!this.stopping) this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.SUSPEND });\n this.trace.pause();\n this.replayManager.suspend();\n this.stopDedupSweep();\n this.suspendTransport();\n },\n onResume: () => {\n this.resumeSuspendedResources();\n this.resumeTransport();\n },\n onDiagnostic: event => {\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, ...event });\n }\n }\n });\n // Auto-start when initialConfig is provided, or when autoStart is explicitly true.\n if (autoStart ?? this.hasInitialConfig) this.ensureStarted();\n }\n\n /**\n * Start the DataBus with the given transport config.\n *\n * The first call starts the cluster and opens the transport. Concurrent calls\n * during an in-flight open return the same promise. A call received while an\n * explicit stop() is settling queues one fresh start after cleanup; a later\n * stop() before that queued start runs cancels it, so the latest lifecycle\n * intent wins. Once an operation settles (success or failure) its promise\n * gate is cleared so a subsequent start() or resumeTransport() can open a\n * fresh lifecycle.\n */\n start(config: TConfig): Promise<void> {\n if (this.queuedStart) return this.queuedStart;\n if (this.stopping) return this.queueStartAfterStop(config);\n // A suspended transport uses the same promise for startPromise and\n // pendingStop. Treat it as a stop gate here so an explicit start() queues a\n // real reopen instead of returning a promise that only waits for cleanup.\n if (this.startPromise && this.startPromise !== this.pendingStop) return this.startPromise;\n if (this.started) {\n const transportDown =\n !this.transportReady ||\n this.status === WORKER_STATUS.ERROR ||\n this.status === WORKER_STATUS.DISCONNECTED;\n if (!transportDown) return Promise.resolve();\n // An explicit start() is a manual recovery path after the automatic\n // recovery budget is exhausted. Keep the cluster and subscriptions\n // intact, but begin a fresh failure/recovery ledger before reopening.\n this.activeConfig = config;\n this.resetFailureState();\n // An explicit start() is also a documented resume path out of BFCache\n // suspension: it clears `suspended` and reopens the transport. The\n // cluster keeps its own paused flag and is normally resumed by the\n // pageshow listener, so resume it here too. Otherwise the bus reports a\n // healthy transport while cross-tab coordination stays dormant (closed\n // channel, no heartbeat, cleared assignments) and incoming publications\n // are discarded by isAssigned() until the next pageshow. reopenTransport()\n // clears `suspended` and installs the opening first so the cluster's\n // re-subscription traffic parks behind it instead of hitting the stopped\n // transport; cluster.start() is idempotent and a no-op when not paused.\n const resumingFromSuspend = this.suspended;\n if (resumingFromSuspend) this.resumeSuspendedResources();\n const opening = this.reopenTransport();\n if (resumingFromSuspend) this.cluster.start();\n return opening;\n }\n this.started = true;\n this.stopping = false;\n this.suspended = false;\n this.activeConfig = config;\n // A fresh start begins a new failure ledger so health consumers correlate\n // failures with the current session, not the previous one.\n this.resetFailureState();\n this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });\n this.trace.start();\n this.startDedupSweep();\n this.replayManager.start();\n this.updateStatus(WORKER_STATUS.CONNECTING);\n this.cluster.start();\n // Establish the opening before replaying topicHandlers: cluster.subscribe()\n // can synchronously invoke onControl for self-owned topics, and those\n // callbacks would otherwise see startPromise=null and open a second transport.\n const lifecycleEpoch = ++this.lifecycleEpoch;\n const opening = this.openTransport(\n config,\n this.pendingStop ?? Promise.resolve(),\n true,\n lifecycleEpoch\n );\n this.startPromise = opening;\n // Replay subscriptions that were registered before start() or that were lost\n // during a previous failure recovery. The cluster.stop() call in the failure\n // path clears subscribedTopics, but topicHandlers retains the intent.\n // Iterating topicHandlers (not transportSubscribedTopics) because the\n // transport hasn't subscribed to anything yet on a fresh start.\n for (const topic of this.topicHandlers.keys()) {\n this.cluster.subscribe(topic);\n }\n // Once startup settles (success or failure), clear the pending gate so a\n // later start()/resumeTransport() can open a fresh operation. Guard against\n // clobbering a promise that suspend/resume may have already swapped in.\n void opening.then(\n () => {\n if (this.startPromise !== opening) return;\n // Emit the coordination snapshot only after the transport has opened\n // and the just-issued subscriptions have flushed, so the routes list\n // (and the role/assignment picture) is populated rather than always\n // empty \u2014 the synchronous pre-open snapshot would see no routes.\n this.emitCoordinationTrace();\n this.startPromise = null;\n },\n () => {\n if (this.startPromise === opening) this.startPromise = null;\n }\n );\n return opening;\n }\n\n /** Return a cancellation-aware readiness view of the current queued start. */\n private getQueuedStartReady(): Promise<void> {\n const queued = this.queuedStart;\n if (!queued) {\n return Promise.reject(new Error('No queued start is in flight.'));\n }\n const token = this.queuedStartToken;\n if (this.queuedStartReady && this.queuedStartReadyToken === token) {\n return this.queuedStartReady;\n }\n this.queuedStartReadyToken = token;\n this.queuedStartReady = queued.then(() => {\n if (token <= this.canceledQueuedStartToken) {\n throw new Error(\n 'CrossTabDataBus start was canceled by a later stop(); ready() cannot report readiness. ' +\n 'Call start() again after stop() resolves.'\n );\n }\n if (!this.started || !this.transportReady) {\n throw new Error('CrossTabDataBus restart completed without a ready transport.');\n }\n });\n return this.queuedStartReady;\n }\n\n /** Queue exactly one fresh start after an in-flight explicit stop settles. */\n private queueStartAfterStop(config: TConfig): Promise<void> {\n if (this.queuedStart) return this.queuedStart;\n const stop = this.stopPromise ?? Promise.resolve();\n const token = ++this.queuedStartToken;\n const queued = stop\n .catch(() => undefined)\n .then(() => {\n // Clear before invoking start(), which installs its own startPromise.\n if (this.queuedStart === queued) this.queuedStart = null;\n // stop() may have arrived after this restart was queued. The queued\n // continuation still runs (it is already chained), but it must not\n // reopen the transport: the latest lifecycle intent was a stop.\n if (token <= this.canceledQueuedStartToken) return;\n return this.start(config);\n });\n this.queuedStart = queued;\n return queued;\n }\n\n /** Release every operation waiting on the scheduled recovery attempt. */\n private releaseRecoveryGate(): void {\n const release = this.recoveryGateRelease;\n this.recoveryGate = null;\n this.recoveryGateRelease = null;\n this.recoveryDemandAllowed = false;\n release?.();\n }\n\n /** Cancel a pending automatic retry when an explicit lifecycle transition\n * supersedes it. The released gate re-enters runTransport(), which then\n * follows the newest start/stop/suspend intent. */\n private cancelScheduledRecovery(): void {\n this.recoveryTimerToken += 1;\n if (this.recoveryTimer !== null) {\n clearTimeout(this.recoveryTimer);\n this.recoveryTimer = null;\n }\n this.releaseRecoveryGate();\n }\n\n /** Keep the recovery gate closed after a failed attempt while allowing the\n * next explicit transport operation to start an immediate on-demand reopen.\n * If no gate/successor retry remains, release any waiters. */\n private allowDemandRecovery(): void {\n if (\n this.recoveryGate !== null &&\n this.started &&\n !this.stopping &&\n !this.suspended &&\n this.status === WORKER_STATUS.ERROR\n ) {\n this.recoveryDemandAllowed = true;\n return;\n }\n this.releaseRecoveryGate();\n }\n\n /** Reset failure and recovery diagnostics for a new explicit start session. */\n private resetFailureState(): void {\n this.cancelScheduledRecovery();\n this.lastError = null;\n this.lastErrorAt = null;\n this.lastFailure = null;\n this.persistenceFailureCount = 0;\n this.persistenceLastFailureAt = null;\n this.persistenceLastErrorMessage = null;\n this.recoveryAttempt = 0;\n this.recoveryExhausted = false;\n this.lastRecoveryAt = 0;\n }\n\n /**\n * Open the transport, chained after `before` to ensure lifecycle ordering.\n * When `stopClusterOnFailure` is true (initial start), a transport failure\n * tears down the cluster as well.\n */\n private openTransport(\n config: TConfig,\n before: Promise<unknown>,\n stopClusterOnFailure: boolean,\n lifecycleEpoch: number\n ): Promise<void> {\n this.transportReady = false;\n const chainedPendingStop = this.pendingStop;\n const isCurrentLifecycle = () => lifecycleEpoch === this.lifecycleEpoch;\n // A transport can report `error` synchronously before start() settles.\n // Suppress the user-facing status notification until openTransport's catch\n // has created the stop gate and cleared startPromise; otherwise an onStatus\n // retry runs while the failed opening still owns the gate.\n let startupInProgress = true;\n return before\n .catch(() => undefined)\n .then(() => {\n // stop(), suspendTransport(), or a newer reopen may have arrived while\n // this opening was queued behind a pending stop. Abandon the open and\n // keep the settled stop gate visible so stop() does not issue a second\n // transport.stop().\n if (!isCurrentLifecycle() || this.stopping || this.suspended) return;\n // A stop we actually chained after has settled; this opening now owns\n // the lifecycle. A stop created concurrently (e.g. by suspendTransport)\n // is a different promise and must stay visible to later catch/resume\n // paths so cleanup is not duplicated.\n if (this.pendingStop === chainedPendingStop) this.pendingStop = null;\n // A fresh transport instance starts from scratch: until it reports\n // `connected` again its status is \"not connected yet\".\n this.transportHasConnected = false;\n return Promise.resolve(\n this.transport.start(config, {\n onMessage: message => {\n if (isCurrentLifecycle()) this.handleTransportMessage(message);\n },\n onStatus: status => {\n if (isCurrentLifecycle()) {\n this.updateStatus(status, status !== WORKER_STATUS.ERROR || !startupInProgress);\n }\n },\n onError: error => {\n if (isCurrentLifecycle()) this.reportError(error);\n }\n })\n ).then(() => {\n startupInProgress = false;\n if (!isCurrentLifecycle()) return;\n // A transport may report 'error' synchronously during start() (e.g. a\n // Worker that fails to boot) while still returning normally. Treat that\n // as a startup failure instead of marking the transport ready, so a\n // later subscribe/unsubscribe triggers a reopen rather than being\n // silently dropped on a dead transport.\n if (this.status === WORKER_STATUS.ERROR) {\n throw new Error('Transport failed during startup.');\n }\n if (!this.suspended && !this.stopping) {\n this.recoveryGeneration += 1;\n this.lastSuccessAt = this.now();\n this.transportReady = true;\n // Release operations held during an automatic or on-demand reopen\n // only after the ready flag is visible. Releasing inside the\n // CONNECTED callback would make those operations bounce off the\n // still-clearing startPromise and can let ready() win the race.\n this.releaseRecoveryGate();\n }\n });\n })\n .catch(error => {\n // A newer suspend/resume/stop owns the lifecycle now. Do not let this\n // superseded open tear down the newer operation or clear its gate.\n if (!isCurrentLifecycle()) throw error;\n startupInProgress = false;\n // Reset started before reporting so an initial-start failure does not\n // schedule automatic recovery; only the caller can retry a first start.\n if (stopClusterOnFailure) this.started = false;\n // Keep the stop cleanup in a single gate so a subsequent\n // start()/reopenTransport() cannot overlap an asynchronous\n // transport.stop(). If suspendTransport() already chained a stop for\n // the tab hiding mid-open, reuse it instead of stopping twice.\n if (!this.pendingStop) {\n this.pendingStop = this.createStopPromise();\n }\n this.transportReady = false;\n if (stopClusterOnFailure) {\n this.stopping = true;\n this.cluster.stop();\n this.stopping = false;\n }\n // Record the failure before any user callback can retry. The status\n // notification below runs re-entrantly and may legitimately call\n // start(); that explicit lifecycle must be able to reset this ledger.\n this.recordError(error);\n // Make the failed opening observable as settled before notifying any\n // status or error handler. Leaving the old rejecting promise in\n // startPromise would make a synchronous retry return the failure it is\n // reacting to instead of opening a new lifecycle. Settlement handlers\n // only clear this field when they still own the gate, so a reentrant\n // retry and any later error notification remain safe.\n this.startPromise = null;\n this.updateStatus(WORKER_STATUS.ERROR);\n this.notifyError(error);\n throw error;\n });\n }\n\n /**\n * Await the DataBus to be fully started (lazy init when using initialConfig).\n * Returns a rejected promise when the transport has failed and no start is in\n * flight \u2014 the caller can retry by calling start() or ready() again. While an\n * explicit stop() is settling, this rejects unless a restart is queued behind\n * it; false readiness during teardown is never reported. While the tab is\n * BFCache-suspended (pagehide without a following pageshow), this also\n * rejects: the suspended start promise is the transport-stop gate, not a\n * readiness signal.\n */\n ready(): Promise<void> {\n // A start() queued behind an in-flight stop is the newest lifecycle intent;\n // ready() remains its shared completion gate. Without that queued intent,\n // reporting readiness while teardown is in progress would be false.\n if (this.queuedStart) return this.getQueuedStartReady();\n if (this.stopping) {\n return Promise.reject(new Error(\n 'CrossTabDataBus is stopping; ready() cannot report readiness until stop() resolves. ' +\n 'Wait for stop() to resolve, then call start() before awaiting ready().'\n ));\n }\n // A page-hide suspension reuses startPromise as the async transport-stop\n // gate. That promise proves cleanup completed, not that the transport is\n // ready, so never let ready() resolve while the tab is intentionally\n // suspended. An explicit start()/pageshow clears the flag and installs a\n // real reopen promise before this check runs.\n if (this.suspended) {\n return Promise.reject(new Error(\n 'CrossTabDataBus is suspended; ready() cannot report readiness until pageshow resumes the transport.'\n ));\n }\n // An explicit start(config) does not become an implicit initialConfig.\n // When that attempted start failed, ready() must still surface its real\n // transport error instead of masking it with \"requires initialConfig\".\n if (!this.started && !this.hasInitialConfig && this.lastError !== null) {\n return Promise.reject(this.lastError);\n }\n try {\n this.ensureStarted();\n } catch (error) {\n return Promise.reject(error);\n }\n if (this.startPromise) return this.startPromise;\n if (this.transportReady) return Promise.resolve();\n // Surface the last failure so callers can distinguish a transient retry\n // from a dead transport. The promise is rejected, not thrown, so the\n // caller can retry by calling ready() or start() again.\n if (this.lastError !== null) return Promise.reject(this.lastError);\n return Promise.reject(\n new Error('Transport is not ready and no start operation is in flight')\n );\n }\n\n /**\n * Register a handler for `topic`. The handler fires on every publication\n * delivered to this tab, regardless of which tab published it. Returns an\n * unsubscribe function for convenience. During an explicit stop() the\n * registration is rejected through onError and a no-op cleanup is returned,\n * so a late subscriber cannot leak into a future restart.\n */\n subscribe(\n topic: string,\n handler: DataBusMessageHandler<TData>,\n options?: { replay?: boolean | number }\n ): () => void {\n // A subscription requested during teardown would either be erased by\n // topicHandlers.clear() or leak into the next start while its handler was\n // already dropped. Reject it explicitly, consistent with publish(), and\n // return a safe cleanup function so callers can keep uniform teardown code.\n if (this.stopping) {\n this.reportError(new Error(\n 'CrossTabDataBus is stopping; subscribe() was not registered. ' +\n 'Wait for stop() to resolve, then call start() before subscribing again.'\n ));\n return () => {};\n }\n this.ensureStarted();\n const handlers = this.topicHandlers.get(topic) ?? new Set<DataBusMessageHandler<TData>>();\n const wasUnused = handlers.size === 0;\n handlers.add(handler);\n this.topicHandlers.set(topic, handlers);\n // This 0\u21921 transition is the SINGLE entry point into the cluster\n // subscription. The cluster's subscribedTopics is a Set, so repeated\n // installs of the same topic after a drop cannot double-subscribe:\n // wasUnused is the only gate, and cluster.subscribe() is idempotent by\n // construction. The matching n\u21920 gate is in the unsubscribe path below.\n if (wasUnused) this.cluster.subscribe(topic);\n if (options?.replay) {\n this.replayManager.deliverReplay(topic, options.replay, handler, () =>\n Boolean(this.topicHandlers.get(topic)?.has(handler))\n );\n }\n return () => this.unsubscribe(topic, handler);\n }\n\n /** Remove a specific handler, or all handlers for `topic`.\n * When `handler` is omitted, clears every handler for the topic \u2014 the\n * caller used the `unsubscribe(topic)` form expecting a full teardown.\n * The cluster is only notified on the n\u21920 transition (handlers.size === 0). */\n unsubscribe(topic: string, handler?: DataBusMessageHandler<TData>): void {\n const handlers = this.topicHandlers.get(topic);\n if (!handlers) return;\n if (handler) handlers.delete(handler);\n else handlers.clear();\n if (handlers.size > 0) return;\n this.topicHandlers.delete(topic);\n this.replayManager.onTopicUnsubscribed(topic);\n this.cluster.unsubscribe(topic);\n }\n\n /** Clear all in-memory replay buffers and, when supported, durable history. */\n async clearReplay(): Promise<void> {\n await this.replayManager.clearAll();\n }\n\n /** Clear replay history for one exact topic, including durable storage. */\n async clearReplayTopic(topic: string): Promise<void> {\n await this.replayManager.clearTopic(topic);\n }\n\n /** Remove replay entries older than an epoch-millisecond cutoff. */\n async clearReplayBefore(timestamp: number): Promise<void> {\n await this.replayManager.clearBefore(timestamp);\n }\n\n /** Return bounded deduplication counters for diagnostics and health checks. */\n getDedupStats(): DataBusDedupStats {\n return this.dedupManager.getStats();\n }\n\n /** Drop all remembered IDs and reset dedup counters. */\n resetDedup(): void {\n this.dedupManager.reset();\n }\n\n /** Publish a message to `topic`. The owning Worker delivers it to the transport. */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): void {\n this.ensureStarted();\n if (this.rejectPublishDuringStop('publish')) return;\n if (!this.cluster.publish(topic, data, options)) {\n this.reportError(\n new Error('Failed to send the publish control message to the owning worker.')\n );\n }\n }\n\n /**\n * Burst-friendly variant of `publish()`: delivers `items` in a single\n * BroadcastChannel postMessage so the receiving owner can dispatch them all\n * in one tick. Per-item dedup / replay / ordering is preserved; each item\n * may carry its own `messageId` / `timestamp` via `options`. Empty array is\n * a no-op; single-item array delegates to `publish()`.\n */\n publishBatch(\n topic: string,\n items: ReadonlyArray<{ data: unknown; options?: DataBusPublishOptions }>\n ): void {\n this.ensureStarted();\n if (items.length === 0) return;\n if (this.rejectPublishDuringStop('publishBatch')) return;\n if (items.length === 1) {\n const first = items[0]!;\n this.publish(topic, first.data, first.options);\n return;\n }\n const mapped = items.map(item => ({\n data: item.data,\n ...publicationMetadata(item.options?.messageId, item.options?.timestamp)\n }));\n if (!this.cluster.publishBatch(topic, mapped)) {\n this.reportError(\n new Error('Failed to send the batched publish control message to the owning worker.')\n );\n }\n }\n\n /** Register a handler that fires on every transport status change. Immediately invoked with the current status. */\n onStatus(handler: DataBusStatusHandler): () => void {\n this.statusHandlers.add(handler);\n try {\n handler(this.status);\n } catch (error) {\n this.reportError(error);\n }\n return () => this.statusHandlers.delete(handler);\n }\n\n /** Register a handler for transport errors. */\n onError(handler: DataBusErrorHandler): () => void {\n this.errorHandlers.add(handler);\n return () => this.errorHandlers.delete(handler);\n }\n\n /** Current transport connection status. */\n getStatus(): WorkerStatus {\n return this.status;\n }\n\n /** Return the current automatic transport recovery state plus diagnostics.\n * `hasError`/`errorMessage`/`errorAt` describe the most recent retained\n * *transport* failure \u2014 from a transport open or a runtime `onError`. They\n * share the lifetime of the unified `lastFailure` ledger: a successful\n * recovery keeps the last failure visible, and only an explicit `start()`\n * clears it. `generation` increments on every successful transport open\n * (initial start and every recovery); `lastSuccessAt` is the timestamp of\n * the most recent successful open, or `null` until the transport reaches\n * `ready`. */\n getRecoveryStats(): {\n attempt: number;\n exhausted: boolean;\n maxAttempts: number;\n hasError: boolean;\n errorMessage: string | null;\n errorAt: number | null;\n generation: number;\n lastSuccessAt: number | null;\n } {\n const errorMessage = this.lastError instanceof Error ? this.lastError.message : this.lastError === null ? null : String(this.lastError);\n return {\n attempt: this.recoveryAttempt,\n exhausted: this.recoveryExhausted,\n maxAttempts: this.recoveryMaxAttempts,\n hasError: this.lastError !== null,\n errorMessage,\n errorAt: this.lastErrorAt,\n generation: this.recoveryGeneration,\n lastSuccessAt: this.lastSuccessAt\n };\n }\n\n /** Bounded failure counters for the replay persistence backend. */\n getPersistenceStats(): DataBusPersistenceHealth {\n return {\n failures: this.persistenceFailureCount,\n lastFailureAt: this.persistenceLastFailureAt,\n lastErrorMessage: this.persistenceLastErrorMessage\n };\n }\n\n /** Compact health verdict for dashboards, readiness probes, and support\n * bundles. Answers \"is the bus usable right now\" first, then attaches the\n * unified failure ledger and recovery context that explains the verdict. */\n getHealthSummary(): DataBusHealthSummary {\n const transport = this.transport;\n // The live transport status is the source of truth for serviceability. A\n // transport that reports 'connected' is healthy even during the short\n // window before start() settles and the DataBus sets transportReady:\n // operations are queued behind that in-flight start promise rather than\n // dropped. `transportReady` stays in the snapshot as a diagnostic.\n const transportDown = this.status !== WORKER_STATUS.CONNECTED;\n const state: DataBusHealthSummary['state'] =\n !this.started\n ? HEALTH_STATE.STOPPED\n : this.suspended\n ? HEALTH_STATE.SUSPENDED\n : transportDown\n ? this.recoveryExhausted\n ? HEALTH_STATE.DEGRADED\n : this.status === WORKER_STATUS.CONNECTING && this.recoveryAttempt === 0\n ? HEALTH_STATE.STARTING\n : HEALTH_STATE.RECOVERING\n : HEALTH_STATE.HEALTHY;\n return {\n healthy: state === HEALTH_STATE.HEALTHY,\n state,\n status: this.status,\n sdkVersion: SDK_VERSION,\n started: this.started,\n suspended: this.suspended,\n transport: {\n name: transport.diagnosticsName ?? transport.constructor.name,\n backend: transport.diagnosticsBackend ?? null,\n ready: this.transportReady,\n status: this.status\n },\n recovery: this.getRecoveryStats(),\n lastFailure: this.lastFailure,\n persistence: this.getPersistenceStats(),\n metrics: this.trace.getMetrics(),\n trace: this.trace.getSinkState()\n };\n }\n\n /** Snapshot of the cluster state (workers, routes, assignments).\n * For diagnostics only \u2014 the returned object is a shallow copy but\n * nested arrays are snapshots at call time. */\n getClusterSnapshot() {\n return this.cluster.getSnapshot();\n }\n\n /** Return a single health snapshot combining lifecycle, recovery, dedup, replay, and cluster state. */\n getDiagnostics(): DataBusDiagnostics {\n const replay = this.replayManager.getStats();\n const cluster = this.cluster.getSnapshot();\n const unknownMessages = this.cluster.getUnknownMessageStats();\n const transport = this.transport;\n return {\n status: this.status,\n sdkVersion: SDK_VERSION,\n started: this.started,\n transportReady: this.transportReady,\n recovery: this.getRecoveryStats(),\n dedup: this.getDedupStats(),\n replay: { enabled: replay.enabled, topics: replay.topics, messages: replay.messages, bytes: replay.bytes },\n persistence: this.getPersistenceStats(),\n protocol: { version: cluster.protocolVersion, unknownMessages: unknownMessages.count, lastUnknownMessageType: unknownMessages.lastType, peers: cluster.peerProtocolVersions },\n transport: {\n name: transport.diagnosticsName ?? transport.constructor.name,\n backend: transport.diagnosticsBackend ?? null,\n status: this.status,\n suspended: this.suspended\n },\n cluster,\n metrics: this.trace.getMetrics(),\n trace: this.trace.getSinkState()\n };\n }\n\n /** Synchronous snapshot of the current trace metrics window (throughput,\n * dispatch latency, dedup outcomes), without flushing or resetting it.\n * Returns null when trace metrics are inactive (disabled or events-only). */\n getMetrics(): DataBusMetricsSnapshot | null {\n return this.trace.getMetrics();\n }\n\n /**\n * Gracefully stop the DataBus: unsubscribe all topics, stop the cluster,\n * and close the transport. Concurrent and repeated calls share the in-flight\n * stop promise. A start() received while stopping runs after this completes,\n * unless another stop() arrives first and cancels that queued restart.\n */\n stop(): Promise<void> {\n // A restart queued behind an in-flight stop is stale as soon as another\n // stop() is requested. Invalidate it and release the single queue slot so\n // a later start() can still queue a fresh restart with a higher token.\n if (this.queuedStart) {\n this.canceledQueuedStartToken = this.queuedStartToken;\n this.queuedStart = null;\n }\n if (this.stopPromise) return this.stopPromise;\n if (!this.started && !this.startPromise && !this.pendingStop && !this.transportReady) {\n return Promise.resolve();\n }\n const stopPromise = this.performStop();\n this.stopPromise = stopPromise;\n void stopPromise.then(\n () => {\n if (this.stopPromise === stopPromise) this.stopPromise = null;\n },\n () => {\n if (this.stopPromise === stopPromise) this.stopPromise = null;\n }\n );\n return stopPromise;\n }\n\n private async performStop(): Promise<void> {\n this.lifecycleEpoch += 1;\n this.stopping = true;\n this.cancelScheduledRecovery();\n this.replayManager.suspend();\n this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.STOP });\n this.trace.stop();\n this.stopDedupSweep();\n this.topicHandlers.clear();\n this.replayManager.resetBuffers();\n this.cluster.stop();\n try {\n await this.startPromise?.catch(() => undefined);\n // A failed-open or suspend cleanup already stopped the transport;\n // awaiting it is enough, so stop() is not called a second time.\n const pendingStop = this.pendingStop;\n if (pendingStop) await pendingStop.catch(() => undefined);\n else await this.transport.stop();\n } catch (error) {\n // A transport whose stop() rejects must not reject stop() itself: the\n // finally below completes the teardown either way, concurrent/repeated\n // callers share this one promise, and the React/Vue adapters legitimately\n // fire-and-forget `void bus.stop()`, where a rejection would surface as\n // an unhandled rejection. Route the failure through the same\n // ledger/onError channel suspendTransport() and createStopPromise() use.\n this.reportError(error);\n } finally {\n this.transportSubscribedTopics.clear();\n this.resetDedup();\n this.started = false;\n this.stopping = false;\n this.suspended = false;\n this.transportReady = false;\n this.startPromise = null;\n this.pendingStop = null;\n this.lastError = null;\n this.lastErrorAt = null;\n this.activeConfig = undefined;\n this.recoveryAttempt = 0;\n this.recoveryExhausted = false;\n this.updateStatus(WORKER_STATUS.DISCONNECTED);\n }\n }\n\n /**\n * Incoming message from the transport.\n * Records metrics, checks ownership via the cluster, broadcasts to other tabs,\n * and dispatches locally.\n */\n private handleTransportMessage(message: DataBusMessage<TData>): void {\n if (this.dedupManager.isDuplicate(message.messageId ?? '', message.topic)) return;\n this.trace.recordReceived(message.topic);\n // Drop messages for topics we do not own \u2014 the owning Worker fans out.\n if (!this.cluster.isAssigned(message.topic)) {\n this.trace.recordDiscarded(message.topic);\n return;\n }\n // Stamp the originating tab BEFORE broadcasting so neighbors replaying\n // history can attribute each entry to the tab that produced it. Locally\n // we publish first and dispatch second to keep the contract: a handler\n // called before the broadcast settled would still observe originTabId.\n const stamped: DataBusMessage<TData> = message.originTabId === undefined\n ? { ...message, originTabId: this.cluster.tabId }\n : message;\n this.cluster.broadcastEvent(PUBLICATION_EVENT, stamped, stamped.originTabId);\n if (this.cluster.hasLocalSubscriber(message.topic)) {\n this.dispatch(stamped);\n return;\n }\n this.trace.recordDiscarded(message.topic);\n }\n\n /** Start enqueuing the dedup expiry sweep (delegated to {@link DedupManager}). */\n private startDedupSweep(): void {\n this.dedupManager.start();\n }\n\n /** Stop enqueuing the dedup expiry sweep. */\n private stopDedupSweep(): void {\n this.dedupManager.stop();\n }\n\n /** Deliver a message to every local handler registered for its topic,\n * plus every handler registered with a wildcard subscription that matches\n * (e.g. a handler subscribed to \"chat.*\" receives \"chat.room.1\"). */\n private dispatch(message: DataBusMessage<TData>): void {\n this.trace.recordDispatched(message.topic);\n this.invokeHandlers(this.topicHandlers.get(message.topic) ?? [], handler => handler(message));\n for (const [pattern, handlers] of this.topicHandlers) {\n if (pattern !== message.topic && topicMatchesPattern(pattern, message.topic)) {\n this.invokeHandlers(handlers, handler => handler(message));\n }\n }\n this.replayManager.record(message);\n }\n\n /**\n * Propagate a status change to the cluster, trace, and all registered\n * status handlers. On reconnect, re-subscribe any topics assigned to us.\n */\n private updateStatus(status: WorkerStatus, notifyHandlers = true): void {\n const previousStatus = this.status;\n this.status = status;\n if (status === WORKER_STATUS.CONNECTED) this.transportHasConnected = true;\n if (previousStatus !== status) this.trace.event({ type: TRACE_EVENT_TYPE.STATUS, status });\n this.cluster.setStatus(status);\n // Clear transport subscriptions on disconnect; the transport is gone.\n if (status === WORKER_STATUS.DISCONNECTED || status === WORKER_STATUS.ERROR) this.transportSubscribedTopics.clear();\n // Re-subscribe assigned topics when the transport reconnects.\n if (status === WORKER_STATUS.CONNECTED && previousStatus !== WORKER_STATUS.CONNECTED) {\n // A transport may recover itself without a DataBus reopen (for example a\n // protocol-level reconnect). In that case the installed transport is\n // already ready and can drain operations held during recovery. During a\n // DataBus reopen transportReady is false until openTransport succeeds;\n // that success path releases the gate after publishing the ready state.\n if (this.transportReady) this.releaseRecoveryGate();\n for (const topic of this.cluster.getSnapshot().assignedTopics) this.subscribeTransport(topic);\n }\n // Auto-recover from a runtime transport failure (e.g. a crashed Worker)\n // while the bus is still meant to be started. Guarded by a cooldown to\n // avoid a tight retry loop when the transport fails immediately.\n // Uses setTimeout so the recovery does not run re-entrantly inside the\n // callback that produced this status (e.g. openTransport's catch).\n if (status === WORKER_STATUS.ERROR && this.started && !this.stopping) {\n const now = this.now();\n if (now - this.lastRecoveryAt >= this.recoveryCooldownMs) {\n this.lastRecoveryAt = now;\n const attempt = ++this.recoveryAttempt;\n if (attempt > this.recoveryMaxAttempts) {\n if (!this.recoveryExhausted) {\n this.recoveryExhausted = true;\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: this.recoveryMaxAttempts, outcome: RECOVERY_OUTCOME.EXHAUSTED });\n }\n // No automatic attempt is left. Release demand-driven operations so\n // subscribe/publish can still start an explicit manual recovery.\n this.releaseRecoveryGate();\n return;\n }\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt, outcome: RECOVERY_OUTCOME.SCHEDULED });\n // Arm the gate before the timer so operations arriving in the\n // cooldown window cannot slip past onto the failed connection.\n if (this.recoveryGate === null) {\n let release!: () => void;\n this.recoveryGate = new Promise<void>(resolve => {\n release = resolve;\n });\n this.recoveryGateRelease = release;\n }\n this.recoveryDemandAllowed = false;\n const timerToken = ++this.recoveryTimerToken;\n this.recoveryTimer = setTimeout(() => {\n if (timerToken !== this.recoveryTimerToken) return;\n this.recoveryTimer = null;\n if (this.stopping || !this.started || this.suspended || this.status !== WORKER_STATUS.ERROR) {\n this.releaseRecoveryGate();\n return;\n }\n this.recoveryDemandAllowed = false;\n const opening = this.reopenTransport(attempt);\n void opening.then(\n () => this.releaseRecoveryGate(),\n () => this.allowDemandRecovery()\n );\n }, this.recoveryCooldownMs);\n }\n } else if (status === WORKER_STATUS.ERROR) {\n // An error outside an active recovery sequence (for example after an\n // initial start failure) must not leave demand-driven operations gated.\n this.releaseRecoveryGate();\n }\n if (notifyHandlers) this.invokeHandlers(this.statusHandlers, handler => handler(status));\n }\n\n private recordError(error: unknown, source: DataBusFailureSource = FAILURE_SOURCE.TRANSPORT): void {\n const at = this.now();\n // Transport failures must land in *both* ledgers. lastFailure is the\n // unified record exposed by getHealthSummary(); lastError/lastErrorAt are\n // the transport-failure ledger behind getRecoveryStats().hasError and\n // ready()'s \"surface the last failure\" path. Recording only lastFailure\n // let a single health snapshot report a retained transport failure while\n // recovery claimed hasError: false / errorMessage: null.\n if (source === FAILURE_SOURCE.TRANSPORT) {\n this.lastError = error;\n this.lastErrorAt = at;\n }\n this.lastFailure = {\n source,\n message: error instanceof Error ? error.message : String(error),\n at\n };\n if (source === FAILURE_SOURCE.PERSISTENCE) {\n this.persistenceFailureCount += 1;\n this.persistenceLastFailureAt = this.lastFailure.at;\n this.persistenceLastErrorMessage = this.lastFailure.message;\n }\n this.trace.event({\n type: TRACE_EVENT_TYPE.ERROR,\n source: source === FAILURE_SOURCE.TRANSPORT ? TRACE_ERROR_SOURCE.TRANSPORT : TRACE_ERROR_SOURCE.OPERATION\n });\n }\n\n private notifyError(error: unknown): void {\n this.invokeHandlers(this.errorHandlers, handler => handler(error), INVOKE_LABEL.ERROR_HANDLER);\n }\n\n private reportError(error: unknown, source: DataBusFailureSource = FAILURE_SOURCE.TRANSPORT): void {\n this.recordError(error, source);\n this.notifyError(error);\n }\n\n /** Report a persistence failure to the trace and the unified failure ledger,\n * unless it is a {@link PersistenceRetryCancelledError} cancellation from a\n * lifecycle transition (teardown should stay quiet). */\n private reportPersistenceError(error: unknown): void {\n if (error instanceof PersistenceRetryCancelledError) return;\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.PERSISTENCE_CLEANUP });\n this.reportError(error, FAILURE_SOURCE.PERSISTENCE);\n }\n\n private traceSubscription(action: (typeof SUBSCRIPTION_ACTION)[keyof typeof SUBSCRIPTION_ACTION], topic: string): void {\n this.trace.event({\n type: TRACE_EVENT_TYPE.SUBSCRIPTION,\n action,\n topic,\n activeTopics: this.transportSubscribedTopics.size\n });\n }\n\n /** Emit the coordination trace snapshot from the current cluster state.\n * Called after a transport opens (start and recovery), when the role and\n * route picture has settled \u2014 the synchronous pre-open snapshot would see no\n * routes because their writes are still coalesced in the batch writer. */\n private emitCoordinationTrace(): void {\n const snapshot = this.cluster.getSnapshot();\n this.trace.event({\n type: TRACE_EVENT_TYPE.COORDINATION,\n coordinated: snapshot.coordinated,\n activeWorkers: snapshot.workers.filter(worker => worker.role === WORKER_ROLE.ACTIVE).length,\n workers: snapshot.workers.map(formatWorkerTrace),\n routes: snapshot.routes.map(formatRouteTrace)\n });\n }\n\n /** Ask the transport to subscribe to a topic (idempotent). */\n private subscribeTransport(topic: string): boolean {\n if (this.transportSubscribedTopics.has(topic)) return false;\n this.transportSubscribedTopics.add(topic);\n this.runTransport(() => this.transport.subscribe(topic));\n return true;\n }\n\n private unsubscribeTransport(topic: string): boolean {\n if (!this.transportSubscribedTopics.delete(topic)) return false;\n this.runTransport(() => this.transport.unsubscribe(topic));\n return true;\n }\n\n /** Invoke `callback` for each item in `handlers`, isolating a throwing\n * callback so the remaining ones still run. Dispatch/status handler failures\n * are routed to `reportError` (which surfaces them to error subscribers);\n * error-handler failures are logged to the console to avoid infinite\n * recursion through reportError itself. */\n private invokeHandlers<T>(\n handlers: Iterable<T>,\n callback: (handler: T) => void,\n label: (typeof INVOKE_LABEL)[keyof typeof INVOKE_LABEL] = INVOKE_LABEL.DISPATCH\n ): void {\n for (const handler of handlers) {\n try {\n callback(handler);\n } catch (error) {\n if (label === INVOKE_LABEL.ERROR_HANDLER) {\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(`[${DEFAULT_STORAGE_PREFIX}] error handler threw:`, error);\n }\n } else {\n this.reportError(error, FAILURE_SOURCE.DISPATCH);\n }\n }\n }\n }\n\n /** Resume the resources paused by a pagehide suspension. Both the native\n * pageshow path and explicit start() must run this so an explicit resume\n * cannot leave trace metrics and periodic cleanup timers permanently off. */\n private resumeSuspendedResources(): void {\n this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.RESUME });\n this.trace.start();\n this.startDedupSweep();\n this.replayManager.start();\n }\n\n /**\n * Suspend the transport when the tab goes hidden. Stops the transport and\n * clears subscription state so it will be re-established on resume.\n */\n private suspendTransport(): void {\n if (this.stopping) return;\n this.lifecycleEpoch += 1;\n this.suspended = true;\n this.cancelScheduledRecovery();\n this.transportReady = false;\n this.transportSubscribedTopics.clear();\n this.updateStatus(WORKER_STATUS.DISCONNECTED);\n // Repeated hide/show rounds can leave a resume opening queued behind an\n // older stop gate. If this suspend is already represented by that gate,\n // reuse it. Otherwise the current startPromise is a newer opening (which\n // may already have called transport.start), so chain a fresh idempotent\n // stop after it. This restores the invariant that startPromise and\n // pendingStop are the same promise while suspended; without it a later\n // pageShow reuses the now-superseded opening and the bus stays hidden.\n if (this.pendingStop && (this.startPromise === null || this.startPromise === this.pendingStop)) {\n // A failed open may already own the stop cleanup; reuse it instead of\n // issuing a redundant idempotent stop. Restore the suspended invariant\n // so a later pageshow/start chains its reopen behind this same gate.\n this.startPromise = this.pendingStop;\n return;\n }\n // Chain the stop after any in-flight start so an async open settles first.\n const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();\n const stopping = pending\n .catch(() => undefined)\n .then(() => this.transport.stop())\n .catch(error => this.reportError(error));\n this.startPromise = stopping;\n this.pendingStop = stopping;\n }\n\n /** Create an immediate stop promise (no prior chain). Used by openTransport's\n * failure path where there is no in-flight start to wait for. */\n private createStopPromise(): Promise<void> {\n return Promise.resolve()\n .then(() => this.transport.stop())\n .catch(stopError => this.reportError(stopError));\n }\n\n /**\n * Resume the transport when the tab becomes visible again, or recover from a\n * runtime transport failure. Re-opens the transport with the stored active\n * config, chained after any pending operation so an async transport stop\n * completes before the new start. Returns the opening promise.\n */\n private resumeTransport(): void {\n void this.reopenTransport();\n }\n\n /**\n * Re-open the transport with the previously stored active config. Chains\n * after any in-flight lifecycle operation (e.g. a suspend stop), swallowing\n * its rejection so the reopen is not blocked. Returns the opening promise so\n * callers can queue operations behind it.\n */\n private reopenTransport(recoveryAttempt?: number): Promise<void> {\n if (this.stopping || this.activeConfig === undefined) return Promise.resolve();\n // A resume/recovery already has an opening in flight. Reuse it so a stale\n // recovery timer or a second caller cannot open a second transport. A\n // page-hide stop gate (startPromise === pendingStop) must not be reused\n // as an opening \u2014 that would make resume return a promise that resolves\n // on stop completion, not on a ready transport. Instead, fall through and\n // chain the new open after that pending stop.\n if (this.startPromise && this.startPromise !== this.pendingStop) return this.startPromise;\n const config = this.activeConfig;\n const traceAttempt = recoveryAttempt ?? (this.recoveryAttempt > 0 ? this.recoveryAttempt : undefined);\n // A resume/recovery means the bus is meant to keep running, even after an\n // initial start failed and then recovered while hidden. Without this, a\n // later stop() would be a no-op and leave the reopened transport running.\n this.started = true;\n this.suspended = false;\n this.updateStatus(WORKER_STATUS.CONNECTING);\n const lifecycleEpoch = ++this.lifecycleEpoch;\n const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();\n const opening = pending\n .catch(() => undefined)\n .then(() => this.openTransport(config, Promise.resolve(), false, lifecycleEpoch));\n this.startPromise = opening;\n // Reset the gate on success too, so a later runtime failure can schedule a\n // fresh reopen instead of reusing this settled promise.\n void opening.then(\n () => {\n if (this.startPromise === opening) this.startPromise = null;\n if (lifecycleEpoch !== this.lifecycleEpoch) return;\n if (traceAttempt !== undefined) {\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: traceAttempt, outcome: RECOVERY_OUTCOME.SUCCEEDED });\n this.recoveryAttempt = 0;\n this.recoveryExhausted = false;\n }\n },\n () => {\n if (this.startPromise === opening) this.startPromise = null;\n if (lifecycleEpoch !== this.lifecycleEpoch) return;\n if (traceAttempt !== undefined) {\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: traceAttempt, outcome: RECOVERY_OUTCOME.FAILED });\n }\n }\n );\n void opening.catch(() => undefined);\n return opening;\n }\n\n /**\n * Run a transport operation now if the transport is ready, otherwise queue\n * it behind the start promise. This ensures subscribe/unsubscribe calls made\n * during startup are not lost.\n */\n private runTransport(operation: () => void | Promise<void>): void {\n // A hidden tab's transport is intentionally stopped; subscriptions are\n // re-established by the cluster on resume, and publications must not be\n // sent to a stopped transport.\n if (this.suspended) return;\n // Automatic recovery is scheduled but has not run yet. Hold the operation\n // until that attempt settles instead of writing it to the connection that\n // just reported `error`.\n if (this.recoveryGate && !this.stopping) {\n // A failed automatic attempt leaves the gate closed but enables explicit\n // demand recovery. The first transport operation starts that reopen once;\n // every waiter remains queued behind the gate and runs after success.\n if (this.recoveryDemandAllowed && this.status === WORKER_STATUS.ERROR && !this.suspended) {\n this.recoveryDemandAllowed = false;\n const opening = this.reopenTransport();\n void opening.then(\n () => this.releaseRecoveryGate(),\n () => this.allowDemandRecovery()\n );\n }\n const gate = this.recoveryGate;\n void gate.then(() => {\n if (this.stopping || this.suspended) return;\n this.runTransport(operation);\n });\n return;\n }\n // `transportReady` is intentionally retained through a runtime error so\n // ready() keeps tracking the installed transport. Operations, however,\n // must not be written to a connection that is gone. `error` always falls\n // through to recovery, and a clean `disconnected` *after* the transport\n // actually reached `connected` means the working connection dropped (a\n // WebSocket `close`); both fall through to the demand-driven reopen below\n // so the operation is flushed against the replacement instead of being\n // handed to a closed socket that can only report a dropped frame. A\n // transport that resolved start() before reporting its first `connected`\n // (worker-style backends report the connection asynchronously) keeps the\n // previous behaviour: its `disconnected` status is \"not connected yet\".\n const droppedAfterConnect =\n this.transportHasConnected && this.status === WORKER_STATUS.DISCONNECTED;\n if (this.transportReady && this.status !== WORKER_STATUS.ERROR && !droppedAfterConnect && !this.stopping) {\n try {\n void Promise.resolve(operation()).catch(error => this.reportError(error));\n } catch (error) {\n this.reportError(error);\n }\n return;\n }\n // Transport is down but we are still meant to be started \u2014 reopen so the\n // operation is not silently dropped. This covers the case where a resume\n // or recovery attempt failed, leaving transportReady=false, startPromise=null.\n let ready = this.startPromise;\n if (!ready && this.started && !this.stopping && this.activeConfig !== undefined) {\n ready = this.reopenTransport();\n }\n if (!ready || this.stopping) return;\n void ready\n .then(\n () => {\n if (!this.started || this.stopping || this.suspended) return;\n return operation();\n },\n // The opening promise reports its own lifecycle failure through\n // openTransport(). Swallowing it here prevents a stale startup\n // rejection from being recorded again after an onStatus/onError\n // callback has already started and reset the ledger for a retry.\n () => undefined\n )\n .catch(error => this.reportError(error));\n }\n\n /**\n * Publications started after teardown begins cannot reach any transport.\n * Surface that as a normal asynchronous API failure instead of letting\n * runTransport() return silently. Empty publishBatch() calls remain a no-op\n * and are filtered by the caller before this check.\n */\n private rejectPublishDuringStop(operation: 'publish' | 'publishBatch'): boolean {\n if (!this.stopping) return false;\n this.reportError(new Error(\n `CrossTabDataBus is stopping; ${operation}() was not sent. ` +\n 'Wait for stop() to resolve, then call start() before publishing again.'\n ));\n return true;\n }\n\n /**\n * Ensure the DataBus is started, throwing if no initialConfig was provided.\n * Called automatically by subscribe/publish/ready when autoStart is true.\n */\n private ensureStarted(): void {\n if (this.started) return;\n if (!this.hasInitialConfig) {\n throw new Error(\n 'CrossTabDataBus requires initialConfig for automatic startup, or an explicit start(config) call.'\n );\n }\n const starting = this.start(this.initialConfig as TConfig);\n void starting.catch(() => undefined);\n }\n}\n\n/** Format a WorkerRecord for the coordination trace event. */\nfunction formatWorkerTrace(worker: { workerId: string; status: string; load: number; tabId: string }): string {\n return `${worker.workerId}|${worker.status}|load=${worker.load}|tab=${worker.tabId}`;\n}\n\n/** Format a route for the coordination trace event. */\nfunction formatRouteTrace(route: { topicKey: string; workerId: string; confirmedAt?: number }): string {\n return `${route.topicKey}@${route.workerId}|confirmed=${route.confirmedAt !== undefined}`;\n}\n", "/**\n * Worker-mode types and backend selection.\n *\n * Defines the WorkerMode preference flags and the selectWorkerBackend function\n * that resolves the preference against browser capability to pick the actual\n * backend (dedicated, shared, or local fallback).\n *\n * The literal values are derived from `utils/constants.ts` so the preference and\n * resolved-backend strings referenced across the transport stay in one place.\n */\nimport { WORKER_BACKEND, WORKER_MODE } from './utils/constants';\n\n/** Preferred Worker mode.\n * - `dedicated` \u2192 try Dedicated Worker first (one WebSocket per tab).\n * - `shared` \u2192 try SharedWorker first (one process, per-port connections).\n * - `auto` \u2192 same as `shared` (alias for forward compatibility). */\nexport type WorkerMode = (typeof WORKER_MODE)[keyof typeof WORKER_MODE];\n\n/** Resolved backend that was actually created. `local` means the session\n * runs on the main thread (fallback when no Worker API is available). */\nexport type WorkerBackend = (typeof WORKER_BACKEND)[keyof typeof WORKER_BACKEND];\n\n/** Override Worker availability for testing or environments where feature\n * detection is unreliable (e.g. sandboxed iframes). When a field is omitted,\n * the global `typeof Worker` / `typeof SharedWorker` check is used. */\nexport interface WorkerAvailability {\n /** When provided, overrides `typeof Worker !== 'undefined'`. */\n worker?: boolean;\n /** When provided, overrides `typeof SharedWorker !== 'undefined'`. */\n sharedWorker?: boolean;\n}\n\n/**\n * Resolves the worker backend by feature detection, without touching globals\n * that may be missing in SSR or embedded environments.\n *\n * - `dedicated` prefers Dedicated Worker, then SharedWorker, then local mode.\n * - `shared` and `auto` prefer SharedWorker, then Dedicated Worker, then local.\n * Returns `'local'` when neither Worker API is available (main-thread fallback).\n */\nexport function selectWorkerBackend(\n mode: WorkerMode,\n availability: WorkerAvailability = {}\n): WorkerBackend {\n const hasDedicated = availability.worker ?? typeof Worker !== 'undefined';\n const hasShared = availability.sharedWorker ?? typeof SharedWorker !== 'undefined';\n if (mode === WORKER_MODE.SHARED || mode === WORKER_MODE.AUTO) {\n return hasShared ? WORKER_BACKEND.SHARED : hasDedicated ? WORKER_BACKEND.DEDICATED : WORKER_BACKEND.LOCAL;\n }\n return hasDedicated ? WORKER_BACKEND.DEDICATED : hasShared ? WORKER_BACKEND.SHARED : WORKER_BACKEND.LOCAL;\n}", "import type { DataBusPublication } from './types';\n\n/**\n * Normalize legacy flat publications, metadata payload envelopes, and the\n * canonical `{ op: 'publication', publication: ... }` shape.\n *\n * `fallbackTopic` is used by transports such as Centrifuge where the channel\n * is supplied out-of-band by the client library rather than inside the data.\n */\nexport function parseDataBusPublication<TData = unknown>(\n value: unknown,\n fallbackTopic?: string\n): DataBusPublication<TData> | null {\n if (!value || typeof value !== 'object') {\n return fallbackTopic ? { topic: fallbackTopic, data: value as TData } : null;\n }\n const frame = value as Record<string, unknown>;\n const nested = frame.publication && typeof frame.publication === 'object'\n ? frame.publication as Record<string, unknown>\n : null;\n const publication = nested ?? frame;\n const topic = typeof publication.topic === 'string' ? publication.topic : fallbackTopic;\n if (!topic) return null;\n\n // Centrifuge's legacy metadata envelope carries `{ data, messageId }`\n // without a topic because the channel is provided by PublicationContext.\n const hasMetadataEnvelope = fallbackTopic !== undefined\n && Object.prototype.hasOwnProperty.call(publication, 'data')\n && (typeof publication.messageId === 'string' || typeof publication.timestamp === 'number');\n const data = nested || fallbackTopic === undefined || hasMetadataEnvelope\n ? publication.data\n : value;\n const messageId = typeof publication.messageId === 'string' && publication.messageId.length > 0\n ? publication.messageId\n : undefined;\n const timestamp = typeof publication.timestamp === 'number' && Number.isFinite(publication.timestamp)\n ? publication.timestamp\n : undefined;\n return {\n topic,\n data: data as TData,\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Browser environment adapters \u2014 storage, BroadcastChannel, timers, lifecycle.\n *\n * Separates platform-specific APIs from the core coordination logic so the\n * same Runtime can run in a browser, in a test, or in an embedded context\n * with custom adapters injected via `ClusterEnvironment`.\n */\nimport type { TabVisibilityState, WorkerClusterMessage } from './types';\nimport { CHANNEL_FALLBACK, STORAGE_CHANNEL_PREFIX, TAB_ID_STORAGE_KEY, TAB_VISIBILITY } from '../utils/constants';\nimport type { EVENT_TYPE } from '../utils/constants';\n\n/** Minimal storage interface compatible with both localStorage and MemoryStorage. */\nexport interface StorageLike {\n readonly length: number;\n clear(): void;\n getItem(key: string): string | null;\n key(index: number): string | null;\n removeItem(key: string): void;\n setItem(key: string, value: string): void;\n}\n\n/** Minimal BroadcastChannel interface. The cluster uses it for control messages and event fan-out. */\nexport interface ClusterChannel {\n addEventListener(type: typeof EVENT_TYPE.MESSAGE, listener: (event: MessageEvent<WorkerClusterMessage>) => void): void;\n removeEventListener(type: typeof EVENT_TYPE.MESSAGE, listener: (event: MessageEvent<WorkerClusterMessage>) => void): void;\n postMessage(message: WorkerClusterMessage): void;\n close(): void;\n}\n\n/**\n * Environment abstraction that lets the cluster operate in Node, SSR, or\n * test environments without touching browser globals directly.\n * Tests inject a fake environment to control timing, storage, and lifecycle.\n */\nexport interface ClusterEnvironment {\n /** localStorage (or null if unavailable). Wrapped by BatchingStorageWriter. */\n storage: StorageLike | null;\n /** sessionStorage (or null if unavailable). Used for stable tab IDs. */\n sessionStorage: StorageLike | null;\n /** Monotonic clock; injected so tests can control time. */\n now: () => number;\n /** Generates a random ID (UUID when crypto is available, else Math.random). */\n randomId: () => string;\n /** Creates a BroadcastChannel by name, or null if unsupported. */\n createChannel: (name: string) => ClusterChannel | null;\n /** Sets an interval; returns a handle for clearInterval. */\n setInterval: (callback: () => void, intervalMs: number) => unknown;\n /** Clears a handle from setInterval. */\n clearInterval: (handle: unknown) => void;\n /** Current tab visibility ('visible' or 'hidden'). */\n getVisibilityState: () => TabVisibilityState;\n /** Register a listener for visibilitychange events. */\n addVisibilityChangeListener: (listener: () => void) => void;\n /** Remove a previously-added visibilitychange listener. */\n removeVisibilityChangeListener: (listener: () => void) => void;\n /** Register a listener for pagehide (BFCache entry). */\n addPageHideListener: (listener: () => void) => void;\n /** Remove a previously-added pagehide listener. */\n removePageHideListener: (listener: () => void) => void;\n /** Register a listener for pageshow (BFCache exit / restore). */\n addPageShowListener: (listener: () => void) => void;\n /** Remove a previously-added pageshow listener. */\n removePageShowListener: (listener: () => void) => void;\n}\n\n/** Resolve a Web Storage interface by name, or null in non-browser contexts.\n * The `typeof window` guard short-circuits in SSR / Node (where `window` is\n * undefined) before touching it; the try/catch covers sandboxed or\n * storage-disabled browsers that throw on property access. */\nfunction getStorage(name: 'localStorage' | 'sessionStorage'): StorageLike | null {\n try {\n return typeof window === 'undefined' ? null : window[name];\n } catch {\n return null;\n }\n}\n\nfunction randomId(): string {\n try {\n return globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);\n } catch {\n return Math.random().toString(36).slice(2);\n }\n}\n\n/** Minimal window surface the storage-event channel needs (injectable for tests). */\nexport interface StorageEventWindow {\n addEventListener(type: 'storage', listener: (event: { key: string | null; newValue: string | null }) => void): void;\n removeEventListener(type: 'storage', listener: (event: { key: string | null; newValue: string | null }) => void): void;\n}\n\n/**\n * Create a {@link ClusterChannel} backed by localStorage `storage` events, as\n * a coordination fallback for environments where BroadcastChannel is\n * unavailable. Returns null when localStorage or a storage-event source is\n * missing.\n *\n * Semantics mirror BroadcastChannel: writes are not echoed to the sender\n * (per spec, the writing tab receives no `storage` event) and every message\n * is JSON-serializable. The payload is written under a dedicated key and\n * removed on close.\n *\n * Security note: unlike BroadcastChannel messages (memory only), these\n * payloads transit through localStorage and therefore persist \u2014 at least\n * transiently, and after a crash indefinitely. Coordination frames carry\n * plaintext topic names; callers who enable this fallback accept that\n * trade-off (see docs/configuration.md).\n */\nexport function createStorageEventChannel(options: {\n name: string;\n storage: StorageLike | null;\n win: StorageEventWindow | null;\n}): ClusterChannel | null {\n const { name, storage, win } = options;\n if (!storage || !win) return null;\n const key = `${STORAGE_CHANNEL_PREFIX}${name}`;\n const listeners = new Set<(event: MessageEvent<WorkerClusterMessage>) => void>();\n // A per-sender monotonically increasing sequence guarantees every write has\n // a distinct value, so a browser that suppresses same-value storage events\n // still delivers every message.\n let sequence = 0;\n\n const onStorage = (event: { key: string | null; newValue: string | null }) => {\n if (event.key !== key || event.newValue === null) return;\n let message: WorkerClusterMessage;\n try {\n const parsed = JSON.parse(event.newValue) as { seq?: unknown; message?: WorkerClusterMessage };\n if (!parsed || typeof parsed !== 'object' || typeof parsed.seq !== 'number' || !parsed.message) return;\n message = parsed.message;\n } catch {\n return;\n }\n // Cluster messages are read-only downstream; deliver a plain envelope.\n const event_ = { data: message } as MessageEvent<WorkerClusterMessage>;\n for (const listener of [...listeners]) listener(event_);\n };\n\n win.addEventListener('storage', onStorage);\n let closed = false;\n return {\n addEventListener(_type, listener) {\n listeners.add(listener);\n },\n removeEventListener(_type, listener) {\n listeners.delete(listener);\n },\n postMessage(message: WorkerClusterMessage): void {\n // A closed channel must not resurrect the payload in storage.\n if (closed) return;\n sequence += 1;\n storage.setItem(key, JSON.stringify({ seq: sequence, message }));\n },\n close(): void {\n closed = true;\n win.removeEventListener('storage', onStorage);\n listeners.clear();\n try {\n storage.removeItem(key);\n } catch {\n // Removal is best-effort; the key is namespaced and harmless.\n }\n }\n };\n}\n\n// A document can create multiple DataBus runtimes (for example market and\n// notice connections). Regenerate a copied opener id only on the first lookup\n// in that document; subsequent runtimes must continue sharing the same tabId.\nlet tabIdentityInitialized = false;\n\n/**\n * Default environment adapter for browser runtimes.\n * Probes for localStorage, BroadcastChannel, document, and window APIs\n * and gracefully returns null / no-ops when they are absent (SSR, Node).\n */\nexport function createBrowserEnvironment(options?: {\n /** When BroadcastChannel is unavailable, fall back to a localStorage\n * storage-event channel instead of degrading to local mode. Opt-in because\n * the fallback persists coordination payloads in localStorage. */\n channelFallback?: (typeof CHANNEL_FALLBACK)[keyof typeof CHANNEL_FALLBACK];\n}): ClusterEnvironment {\n const channelFallback = options?.channelFallback ?? CHANNEL_FALLBACK.NONE;\n return {\n storage: getStorage('localStorage'),\n sessionStorage: getStorage('sessionStorage'),\n now: Date.now,\n randomId,\n createChannel: name => {\n try {\n if (typeof BroadcastChannel !== 'undefined') return new BroadcastChannel(name);\n } catch {\n // fall through to the storage-event fallback.\n }\n return channelFallback === CHANNEL_FALLBACK.STORAGE_EVENT\n ? createStorageEventChannel({\n name,\n storage: getStorage('localStorage'),\n win: typeof window !== 'undefined' && typeof window.addEventListener === 'function' ? window : null\n })\n : null;\n },\n setInterval: (callback, intervalMs) => globalThis.setInterval(callback, intervalMs),\n clearInterval: handle => globalThis.clearInterval(handle as ReturnType<typeof setInterval>),\n getVisibilityState: () =>\n typeof document !== 'undefined' && document.visibilityState === TAB_VISIBILITY.HIDDEN\n ? TAB_VISIBILITY.HIDDEN\n : TAB_VISIBILITY.VISIBLE,\n addVisibilityChangeListener: listener => {\n if (typeof document !== 'undefined') document.addEventListener('visibilitychange', listener);\n },\n removeVisibilityChangeListener: listener => {\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', listener);\n },\n addPageHideListener: listener => {\n if (typeof window !== 'undefined') window.addEventListener('pagehide', listener);\n },\n removePageHideListener: listener => {\n if (typeof window !== 'undefined') window.removeEventListener('pagehide', listener);\n },\n addPageShowListener: listener => {\n if (typeof window !== 'undefined') window.addEventListener('pageshow', listener);\n },\n removePageShowListener: listener => {\n if (typeof window !== 'undefined') window.removeEventListener('pageshow', listener);\n }\n };\n}\n\n/**\n * Probe a storage instance with a write-read-delete round-trip.\n * Returns a type guard so the caller can narrow the type after a successful check.\n * Catches quota errors, disabled-storage (Safari private mode), or opaque\n * exceptions \u2014 any of which means the storage is not usable for coordination\n * and the Runtime must degrade to local mode.\n */\nexport function canUseStorage(storage: StorageLike | null, probeKey: string): storage is StorageLike {\n if (!storage) return false;\n try {\n storage.setItem(probeKey, '1');\n storage.removeItem(probeKey);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get-or-create a stable tab ID stored in sessionStorage.\n * sessionStorage is scoped to the tab and survives refresh, so the same tab\n * retains its identity across the page lifecycle without coordination overhead.\n * Falls back to a random ID when sessionStorage is unavailable.\n */\nexport function getOrCreateTabId(\n environment: ClusterEnvironment,\n key = TAB_ID_STORAGE_KEY\n): string {\n const storage = environment.sessionStorage;\n try {\n const existing = storage?.getItem(key);\n // `window.open()` may clone the opener's sessionStorage into the new tab.\n // A child page therefore must not blindly reuse the copied value: the\n // value identifies a page/tab instance, not an account or browser window.\n // `noopener` is still recommended by applications, but this guard keeps\n // the SDK safe when an opener is present.\n const hasOpener = typeof window !== 'undefined' && Boolean(window.opener);\n if (existing && (!hasOpener || tabIdentityInitialized)) {\n tabIdentityInitialized = true;\n return existing;\n }\n const created = `tab-${environment.randomId()}`;\n storage?.setItem(key, created);\n tabIdentityInitialized = true;\n return created;\n } catch {\n return `tab-${environment.randomId()}`;\n }\n}\n", "/**\n * Derives a stable 128-bit hex key from a string.\n *\n * This is a non-cryptographic four-way hash (inspired by MurmurHash-style\n * mixing). It exists so connection URLs and topic plaintext never touch\n * localStorage or BroadcastChannel namespaces \u2014 consumers only ever see the\n * opaque key. It trades collision resistance for speed and zero dependencies:\n * use `crypto.subtle.digest` if you need a cryptographic hash.\n */\nexport function createOpaqueKey(value: string): string {\n // Four independent lanes mix the input so a short value still diffuses\n // across all 128 bits rather than only exercising the low bits. Each lane\n // starts from a distinct 32-bit seed XORed with the length so that strings\n // of different lengths diverge from the first mix step.\n let h1 = SEED_H1 ^ value.length;\n let h2 = SEED_H2 ^ value.length;\n let h3 = SEED_H3 ^ value.length;\n let h4 = SEED_H4 ^ value.length;\n\n // Feed every UTF-16 code unit into all four lanes with distinct large primes.\n // Note: this operates on UTF-16 code units, so astral-plane characters (emoji,\n // rare CJK) are hashed as surrogate pairs \u2014 consistent within a process, but\n // not Unicode-normalized. Callers should normalize the topic string beforehand\n // if cross-normalization-form stability is required.\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n h1 = Math.imul(h1 ^ code, PRIME_H1);\n h2 = Math.imul(h2 ^ code, PRIME_H2);\n h3 = Math.imul(h3 ^ code, PRIME_H3);\n h4 = Math.imul(h4 ^ code, PRIME_H4);\n }\n\n // Final avalanche: cross-mix the lanes so nearby inputs produce distant keys,\n // avoiding the clustering a naive sum would exhibit in storage prefixes.\n h1 = avalancheMix(h1, h2);\n h2 = avalancheMix(h2, h3);\n h3 = avalancheMix(h3, h4);\n h4 = avalancheMix(h4, h1);\n\n return [h1, h2, h3, h4].map(hash => (hash >>> 0).toString(16).padStart(8, '0')).join('');\n}\n\n/** Distinct 32-bit seeds for the four hash lanes. */\nconst SEED_H1 = 0xdeadbeef;\nconst SEED_H2 = 0x41c6ce57;\nconst SEED_H3 = 0xc0decafe;\nconst SEED_H4 = 0x9e3779b9;\n\n/** Distinct large primes for the four per-character mix steps. */\nconst PRIME_H1 = 2_654_435_761;\nconst PRIME_H2 = 1_597_334_677;\nconst PRIME_H3 = 2_246_822_519;\nconst PRIME_H4 = 3_266_489_917;\n\n/** Final avalanche constant pair. Each lane is mixed with itself (shifted)\n * and XORed with a neighbor lane (shifted) to cross-diffuse the lanes. */\nconst AVALANCHE_PRIME = 2_246_822_507;\nconst AVALANCHE_CROSS = 3_266_489_909;\n\n/** One step of the final avalanche: mix `self` with a shift and prime, then\n * XOR with a cross-mix of `neighbor` (also shifted and primed) so a change\n * in any lane propagates to the others. The 16/13 shifts spread bits across\n * the 32-bit word before the prime multiply scrambles them further. */\nfunction avalancheMix(self: number, neighbor: number): number {\n return (\n Math.imul(self ^ (self >>> 16), AVALANCHE_PRIME) ^\n Math.imul(neighbor ^ (neighbor >>> 13), AVALANCHE_CROSS)\n );\n}\n", "/**\n * Routing primitives for topic-owner selection and rebalancing.\n *\n * Pure functions that select candidate Workers, compute the least-loaded\n * owner, and decide when to migrate a topic. All side-effect-free, making\n * them straightforward to test and reason about.\n */\nimport type { LoadWeightingOptions, WorkerRecord, WorkerRoute } from './types';\nimport { TAB_VISIBILITY, WORKER_STATUS } from '../utils/constants';\n\n/** Default cap on the number of Workers that can own topics concurrently.\n * Limits fan-out breadth: only N workers are eligible to be new-route\n * owners, so a cluster of 20 tabs still concentrates ownership on a few. */\nexport const DEFAULT_MAX_ACTIVE_WORKERS = 3;\n\n/**\n * Compute the effective load score used for owner selection.\n *\n * Legacy behavior: `worker.load` (owned-topic count) with no throughput\n * contribution. When a Worker publishes a traffic sample AND weights are set,\n * the normalized per-second rates are added on top, so a busy owner becomes\n * less attractive for NEW routes without ever migrating an existing one.\n * Returns the raw topic count when no sample or no weight is present.\n */\nexport function effectiveWorkerLoad(\n worker: WorkerRecord,\n options?: LoadWeightingOptions\n): number {\n // The base topic count must itself be finite before it is used as the\n // fallback. A corrupt `load` read back from a peer's stored record (JSON\n // `1e999` parses to Infinity; a malformed record can carry null/NaN) would\n // otherwise leak a non-finite score straight through the fallback branches,\n // re-introducing the order-dependent owner selection this function is meant\n // to be total against.\n const baseLoad = Number.isFinite(worker.load) ? worker.load : 0;\n const sample = worker.throughput;\n const messageRateWeight = options?.messageRateWeight ?? 0;\n const byteRateWeight = options?.byteRateWeight ?? 0;\n const scheduleLagWeight = options?.scheduleLagWeight ?? 0;\n // A missing sample, unset weights, or a non-positive window (no elapsed\n // time to derive a rate from) all fall back to the raw topic count.\n // `Number.isFinite` rather than a bare `<= 0`: `NaN <= 0` is false, so a\n // sample with a corrupt `windowMs` (read back from a peer's heartbeat record)\n // would otherwise divide through to a NaN score.\n if (\n !sample ||\n !Number.isFinite(sample.windowMs) ||\n sample.windowMs <= 0 ||\n (messageRateWeight === 0 && byteRateWeight === 0 && scheduleLagWeight === 0)\n ) {\n return baseLoad;\n }\n const windowSeconds = sample.windowMs / 1000;\n const messageRate = sample.messageCount / windowSeconds;\n const byteRate = sample.byteCount / windowSeconds;\n // Scheduling lag is the ratio of heartbeat overrun to wall time: a Worker\n // whose timers land late (starved event loop) contributes ~overrun/window.\n const scheduleLagRatio = sample.overrunMs / sample.windowMs;\n const weighted =\n baseLoad +\n messageRateWeight * messageRate +\n byteRateWeight * byteRate +\n scheduleLagWeight * scheduleLagRatio;\n // A non-finite score is never ordered: `selectLeastLoadedWorker` compares\n // `byLoad !== 0`, which is true for NaN, and `NaN < 0` is false \u2014 so a NaN\n // worker wins or loses purely by its index in the input array, making owner\n // selection depend on storage listing order rather than on load. A corrupt\n // sample field or a non-finite weight reaches this point, so fall back to\n // the raw topic count instead of leaking the NaN into routing.\n return Number.isFinite(weighted) ? weighted : baseLoad;\n}\n\n/**\n * Cheap, allocation-free estimate of a payload's wire size, used to populate\n * the byte side of an adaptive load sample. Only runs when adaptive routing is\n * enabled, so approximate sizes are fine \u2014 the goal is a stable cross-worker\n * comparison, not an exact byte count. Sizes: null/undefined 0, booleans 4,\n * numbers 8, strings their length, binary views their byteLength, arrays an\n * 8-byte header plus elements, plain objects the sum of their values.\n */\nexport function approximatePayloadBytes(payload: unknown): number {\n return estimatePayloadBytes(payload, 0);\n}\n\n/**\n * Depth cap for the recursive size estimate. Real payloads are shallow, and a\n * cap makes the function total against deeply nested or *cyclic* object graphs\n * \u2014 structured clone preserves cycles, so a cyclic publication can legitimately\n * reach the replay buffer (`getDiagnostics().replay.bytes`) and the adaptive\n * load sampler. Without the cap that recurred until the stack overflowed\n * (RangeError), taking the whole diagnostics/reconcile path down. Beyond the\n * cap the contribution is treated as 0 (it is an approximation either way).\n */\nconst MAX_PAYLOAD_DEPTH = 6;\n\nfunction estimatePayloadBytes(payload: unknown, depth: number): number {\n if (payload === null || payload === undefined) return 0;\n switch (typeof payload) {\n case 'boolean':\n return 4;\n case 'number':\n case 'bigint':\n return 8;\n case 'string':\n return payload.length;\n case 'symbol':\n case 'function':\n return 0;\n case 'object':\n break;\n }\n if (depth >= MAX_PAYLOAD_DEPTH) return 0;\n if (payload instanceof ArrayBuffer) return payload.byteLength;\n if (ArrayBuffer.isView(payload)) return payload.byteLength;\n if (Array.isArray(payload)) {\n let sum = 8;\n for (const item of payload) sum += estimatePayloadBytes(item, depth + 1);\n return sum;\n }\n let sum = 0;\n for (const value of Object.values(payload as Record<string, unknown>)) {\n sum += estimatePayloadBytes(value, depth + 1);\n }\n return sum;\n}\n\n/**\n * Pick the Worker with the fewest effective load, optionally preferring a\n * specific sticky owner when it is still in the candidate set.\n * Uses a single reduce pass instead of a full sort \u2014 O(n) \u2014 and breaks\n * load ties by workerId for deterministic routing across tabs (the\n * comparison is code-unit based, not locale-based, for cross-host stability).\n */\nexport function selectLeastLoadedWorker(\n workers: readonly WorkerRecord[],\n preferredWorkerId?: string,\n options?: LoadWeightingOptions\n): WorkerRecord | undefined {\n const preferred = workers.find(worker => worker.workerId === preferredWorkerId);\n if (preferred) return preferred;\n return workers.reduce<WorkerRecord | undefined>((least, worker) => {\n if (!least) return worker;\n const byLoad = effectiveWorkerLoad(worker, options) - effectiveWorkerLoad(least, options);\n if (byLoad !== 0) return byLoad < 0 ? worker : least;\n // Tie-break by workerId with a locale-independent comparison so routing\n // is deterministic regardless of the host's collation order.\n if (worker.workerId < least.workerId) return worker;\n return least;\n }, undefined);\n}\n\n/**\n * Select the (up to `maxActiveWorkers`) Workers eligible to own topics.\n *\n * Eligibility cascade:\n * 1. Only `connecting` / `connected` workers are candidates.\n * 2. If any candidate is visible, prefer visible tabs (hidden tabs yield as owner).\n * 3. Fall back to all available workers when none is visible, so the cluster\n * does not stall when every tab is in the background.\n * 4. Tie-break by registration time, then workerId, for determinism.\n */\nexport function selectActiveWorkers(\n workers: readonly WorkerRecord[],\n maxActiveWorkers = DEFAULT_MAX_ACTIVE_WORKERS\n): WorkerRecord[] {\n const healthyWorkers = workers.filter(\n worker => worker.status === WORKER_STATUS.CONNECTING || worker.status === WORKER_STATUS.CONNECTED\n );\n const availableWorkers = healthyWorkers.length > 0 ? healthyWorkers : [...workers];\n const visibleWorkers = availableWorkers.filter(worker => worker.visibilityState === TAB_VISIBILITY.VISIBLE);\n const candidates = visibleWorkers.length > 0 ? visibleWorkers : availableWorkers;\n return candidates\n .sort(\n (left, right) =>\n left.registeredAt - right.registeredAt ||\n (left.workerId < right.workerId ? -1 : left.workerId > right.workerId ? 1 : 0)\n )\n .slice(0, maxActiveWorkers);\n}\n\n/**\n * Decide whether `currentWorkerId` should hand one topic to a less-loaded peer.\n * Returns the target Worker only when its load gap is significant (more than\n * one topic lighter), so the cluster does not churn over a single-topic\n * imbalance. One topic is migrated per reconciliation round to avoid thrashing.\n *\n * This remains exported as a standalone routing utility for API compatibility.\n * WorkerClusterRuntime intentionally does not use it: established routes are\n * sticky and load balancing applies only when selecting a new owner.\n */\nexport function selectRebalanceTarget(\n workers: readonly WorkerRecord[],\n currentWorkerId: string\n): WorkerRecord | null {\n const currentWorker = workers.find(worker => worker.workerId === currentWorkerId);\n const leastLoadedWorker = selectLeastLoadedWorker(workers);\n if (\n !currentWorker ||\n !leastLoadedWorker ||\n currentWorker.workerId === leastLoadedWorker.workerId ||\n currentWorker.load <= leastLoadedWorker.load + 1\n ) {\n return null;\n }\n return leastLoadedWorker;\n}\n\n/** True when a route's owner is still a live, active Worker in the given set. */\nexport function hasActiveOwner(route: WorkerRoute | null, workers: readonly WorkerRecord[]): boolean {\n return Boolean(route && workers.some(worker => worker.workerId === route.workerId));\n}\n\n/** True when `pattern` is a wildcard topic: `*` (match everything) or a\n * `prefix.*` suffix wildcard (match any remainder, including multiple\n * segments). Any other string is an exact topic. */\nexport function isWildcardTopic(pattern: string): boolean {\n return pattern === '*' || pattern.endsWith('.*');\n}\n\n/** True when a publication on `topic` must be delivered to a subscription\n * made with `pattern`. Exact patterns match only themselves; wildcards use\n * prefix matching so `chat.*` matches `chat.room.1` and `*` matches anything.\n * The empty pattern never matches. */\nexport function topicMatchesPattern(pattern: string, topic: string): boolean {\n if (!pattern || !topic) return false;\n if (pattern === topic) return true;\n if (pattern === '*') return true;\n if (!pattern.endsWith('.*')) return false;\n return topic.startsWith(pattern.slice(0, -1));\n}\n", "/**\n * \u53D1\u5E03\u5143\u6570\u636E\u5DE5\u5177 \u2014\u2014 \u6784\u9020 { messageId?, timestamp? }\uFF0C\u4EC5\u5728\u5B57\u6BB5\u5DF2\u5B9A\u4E49\u65F6\u5199\u5165\uFF0C\n * \u907F\u514D\u5F80 wire \u5E27/\u63A7\u5236\u6D88\u606F\u91CC\u585E\u7A7A\u679A\u4E3E\u5B57\u6BB5\u3002\n *\n * \u6B64\u524D cluster.ts\u3001data-bus.ts\u3001centrifuge.ts\u3001centrifuge-session.ts \u5404\u81EA\u5185\u8054\n * \u5B9E\u73B0\u4E86\u4E00\u904D `...(x === undefined ? {} : { x })` \u5C55\u5F00\uFF0C\u884C\u4E3A\u5BB9\u6613\u6F02\u79FB\uFF1B\u7EDF\u4E00\u5230\u6B64\n * \u4E00\u5904\u540E\u6240\u6709\u8C03\u7528\u65B9\u5171\u4EAB\u540C\u4E00\u8BED\u4E49\u3002\n */\nimport type { DataBusPublicationMetadata } from '../core/types';\n\n/**\n * Copy defined publication metadata without adding empty enumerable fields.\n * Returns `undefined` when neither field is set, so callers can preserve a\n * legacy \"no metadata\" argument shape (e.g. `onControl(..., undefined)`).\n */\nexport function publicationMetadata(\n messageId?: string,\n timestamp?: number\n): DataBusPublicationMetadata | undefined {\n if (messageId === undefined && timestamp === undefined) return undefined;\n return {\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n };\n}\n", "/**\n * \u53C2\u6570\u6821\u9A8C\u5DE5\u5177 \u2014\u2014 CrossTabDataBus \u6784\u9020\u9009\u9879\u4E0E\u6301\u4E45\u5316\u914D\u7F6E\u7684\u5165\u53E3\u6821\u9A8C\u3002\n *\n * \u6240\u6709 `throw new TypeError(...)` \u6821\u9A8C\u96C6\u4E2D\u5728\u6B64\uFF0CDataBus \u6784\u9020\u5668\u4E0E\n * IndexedDbReplayPersistence \u5171\u7528\u540C\u4E00\u7EC4\u65AD\u8A00\uFF0C\u9519\u8BEF\u6D88\u606F\u4E0E\u539F\u6709\u8BED\u4E49\u4FDD\u6301\u4E00\u81F4\u3002\n *\n * \u8BED\u4E49\u7EA6\u5B9A\uFF1A\u53EF\u9009\u5B57\u6BB5\u53EA\u5728**\u663E\u5F0F\u63D0\u4F9B**\u65F6\u6821\u9A8C\uFF08undefined \u7531\u8C03\u7528\u65B9\u843D\u5230\u9ED8\u8BA4\u503C\uFF0C\n * \u9ED8\u8BA4\u503C\u59CB\u7EC8\u5408\u6CD5\uFF09\uFF1B\u5FC5\u586B\u5B57\u6BB5\u603B\u662F\u6821\u9A8C\u3002\n */\nimport type {\n DataBusDedupOptions,\n DataBusPersistenceRetryOptions,\n DataBusReplayOptions\n} from '../core/data-bus';\nimport type { LoadWeightingOptions } from '../core/types';\nimport { PRUNE_STRATEGY } from './constants';\n\n/** Assert `value` is a positive safe integer. Throws a TypeError otherwise. */\nexport function assertPositiveSafeInteger(value: unknown, name: string): void {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive safe integer, got ${String(value)}.`);\n }\n}\n\n/** Assert `value` is a positive finite number. Throws a TypeError otherwise. */\nexport function assertPositiveFiniteNumber(value: unknown, name: string): void {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive finite number.`);\n }\n}\n\n/** Assert `value` is a non-negative finite number. Throws a TypeError otherwise. */\nexport function assertNonNegativeFiniteNumber(value: unknown, name: string): void {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw new TypeError(`${name} must be a non-negative finite number.`);\n }\n}\n\n/** Assert `value` is a valid replay prune strategy ('count' | 'age' | 'both'). */\nexport function assertPruneStrategy(value: unknown): asserts value is 'count' | 'age' | 'both' {\n const allowed: readonly string[] = [PRUNE_STRATEGY.COUNT, PRUNE_STRATEGY.AGE, PRUNE_STRATEGY.BOTH];\n if (!allowed.includes(String(value))) {\n throw new TypeError('pruneStrategy must be count, age, or both.');\n }\n}\n\n/** Validate the replay options block. Optional fields are validated only when\n * provided; omitted fields fall through to their defaults. */\nexport function assertReplayOptions(replay: DataBusReplayOptions | undefined): void {\n if (!replay) return;\n if (replay.maxPerTopic !== undefined) assertPositiveSafeInteger(replay.maxPerTopic, 'replay.maxPerTopic');\n if (replay.pruneStrategy !== undefined) assertPruneStrategy(replay.pruneStrategy);\n if (replay.retentionMs !== undefined) assertPositiveFiniteNumber(replay.retentionMs, 'replay.retentionMs');\n if (replay.retentionSweepMs !== undefined) {\n assertPositiveFiniteNumber(replay.retentionSweepMs, 'replay.retentionSweepMs');\n }\n if (replay.persistenceRetry) assertPersistenceRetryOptions(replay.persistenceRetry);\n}\n\n/** Validate the replay persistence retry policy. */\nexport function assertPersistenceRetryOptions(retry: DataBusPersistenceRetryOptions): void {\n if (retry.maxAttempts !== undefined) {\n assertPositiveSafeInteger(retry.maxAttempts, 'replay.persistenceRetry.maxAttempts');\n }\n if (retry.backoffMs !== undefined) {\n assertNonNegativeFiniteNumber(retry.backoffMs, 'replay.persistenceRetry.backoffMs');\n }\n}\n\n/** Validate the dedup options block. Optional fields are validated only when\n * provided; omitted fields fall through to their defaults. */\nexport function assertDedupOptions(dedup: DataBusDedupOptions | undefined): void {\n if (!dedup) return;\n if (dedup.maxEntries !== undefined) assertPositiveSafeInteger(dedup.maxEntries, 'dedup.maxEntries');\n if (dedup.ttlMs !== undefined) assertPositiveFiniteNumber(dedup.ttlMs, 'dedup.ttlMs');\n if (dedup.sweepMs !== undefined) assertPositiveFiniteNumber(dedup.sweepMs, 'dedup.sweepMs');\n const bounds = dedup.adaptiveTtl;\n if (bounds !== undefined) {\n // Both bounds must be finite positive numbers with `minMs <= maxMs`.\n // A `NaN`/non-number slips past a plain `<=` comparison (`NaN <= 0` and\n // `maxMs < NaN` are both false), which would leave `currentTtl()` returning\n // `NaN` and silently disable expiry instead of failing loudly.\n const finite = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n if (!finite(bounds.minMs) || !finite(bounds.maxMs) || bounds.minMs <= 0 || bounds.maxMs < bounds.minMs) {\n throw new TypeError('dedup.adaptiveTtl bounds are invalid.');\n }\n }\n}\n\n/** Validate the transport recovery pacing options. Optional fields are validated\n * only when provided; omitted fields fall through to their defaults. */\nexport function assertRecoveryOptions(recovery: {\n cooldownMs?: number;\n maxAttempts?: number;\n} | undefined): void {\n if (!recovery) return;\n if (recovery.cooldownMs !== undefined) {\n assertPositiveFiniteNumber(recovery.cooldownMs, 'recovery.cooldownMs');\n }\n const maxAttempts = recovery.maxAttempts;\n if (\n maxAttempts !== undefined &&\n !(maxAttempts === Number.POSITIVE_INFINITY ||\n (typeof maxAttempts === 'number' && Number.isSafeInteger(maxAttempts) && maxAttempts > 0))\n ) {\n throw new TypeError('recovery.maxAttempts must be a positive safe integer.');\n }\n}\n\n/** Validate the adaptive owner-weighting weights.\n *\n * Each weight is a non-negative finite number of \"topic-equivalents\" added per\n * unit of the sampled signal; `0` (the default) disables that signal. A\n * negative weight would invert the documented policy \u2014 biasing NEW routes\n * toward the *busiest* Worker instead of the quietest \u2014 and a non-finite one\n * would poison the score, so both are rejected here rather than silently\n * steering traffic. */\nexport function assertLoadWeightingOptions(loadWeighting: LoadWeightingOptions | undefined): void {\n if (!loadWeighting) return;\n const weights: Record<string, number | undefined> = {\n messageRateWeight: loadWeighting.messageRateWeight,\n byteRateWeight: loadWeighting.byteRateWeight,\n scheduleLagWeight: loadWeighting.scheduleLagWeight\n };\n for (const [name, value] of Object.entries(weights)) {\n if (value !== undefined) assertNonNegativeFiniteNumber(value, `loadWeighting.${name}`);\n }\n}\n\n/** Validate the cluster coordination options shared by `WorkerClusterRuntime`\n * and `CrossTabDataBus`.\n *\n * These were previously unvalidated, so a `heartbeatIntervalMs` of `0` or `NaN`\n * silently turned the heartbeat `setInterval` into a 0ms busy loop (the same\n * failure the Centrifuge PING guard exists to prevent), a non-positive\n * `workerTtlMs` pruned every peer on the first reconcile, and a non-positive\n * `maxActiveWorkers`/`routeOwnerCacheMax` disabled ownership or caching\n * outright. `Infinity` is rejected for the heartbeat here: unlike the Centrifuge\n * PING it cannot mean \"disable\", because a Worker that never refreshes its\n * heartbeat is pruned by its own TTL. */\nexport function assertClusterOptions(options: {\n maxActiveWorkers?: number;\n heartbeatIntervalMs?: number;\n workerTtlMs?: number;\n routeOwnerCacheMax?: number;\n loadWeighting?: LoadWeightingOptions;\n}): void {\n if (options.maxActiveWorkers !== undefined) {\n assertPositiveSafeInteger(options.maxActiveWorkers, 'maxActiveWorkers');\n }\n if (options.heartbeatIntervalMs !== undefined) {\n assertPositiveFiniteNumber(options.heartbeatIntervalMs, 'heartbeatIntervalMs');\n }\n if (options.workerTtlMs !== undefined) {\n assertPositiveFiniteNumber(options.workerTtlMs, 'workerTtlMs');\n }\n if (options.routeOwnerCacheMax !== undefined) {\n assertPositiveSafeInteger(options.routeOwnerCacheMax, 'routeOwnerCacheMax');\n }\n assertLoadWeightingOptions(options.loadWeighting);\n}\n\n/** Validate the SharedWorker PING heartbeat interval. A value of `0`, a negative\n * number, or `NaN` would otherwise make `setInterval` degenerate into a 0ms busy\n * loop, driving the reaper and the main-thread PING out of control. `Infinity`\n * is allowed and disables heartbeats entirely (for environments where the\n * SharedWorker reaper is not needed, e.g. a single-tab deployment).\n * @throws {TypeError} when `value` is not a positive finite number or Infinity. */\nexport function assertHeartbeatInterval(value: number): void {\n if (value === Infinity) return;\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) return;\n throw new TypeError(\n `Centrifuge heartbeatIntervalMs must be a positive number or Infinity, got ${String(value)}.`\n );\n}\n\n/** Validate that `value` is structured-cloneable. Throws early so config errors\n * surface on the main thread rather than silently failing inside the Worker\n * (where a DataCloneError would be reported as a generic Worker error with no\n * actionable message). Skips validation when `structuredClone` is unavailable\n * (older browsers without the API) \u2014 the Worker will still throw on its own.\n * @throws {TypeError} when `value` contains non-cloneable members (functions,\n * Symbols, DOM nodes, etc.). */\nexport function assertStructuredCloneable(value: unknown): void {\n if (typeof structuredClone !== 'function') return;\n try {\n structuredClone(value);\n } catch (error) {\n throw new TypeError(\n 'Centrifuge Worker configuration and published data must be structured-cloneable.',\n { cause: error }\n );\n }\n}\n", "/**\n * BatchingStorageWriter \u2014 coalesced, resilient localStorage writes.\n *\n * Decorates a StorageLike with write coalescing: mutations within the same task\n * are merged by key and flushed once via a microtask, with exponential backoff\n * on quota/failure. Keeps the coordination metadata writes off the hot path.\n */\nimport type { StorageLike } from './environment';\nimport { DEFAULT_STORAGE_PREFIX } from '../utils/constants';\n\n/** Initial retry delay for a failed storage write (ms). */\nconst INITIAL_RETRY_DELAY_MS = 50;\n/** Maximum retry delay after exponential backoff (ms). Caps at 1.6 s so a\n * persistently failing key retries roughly every 1-2 s, not every minute. */\nconst MAX_RETRY_DELAY_MS = 1_600;\n// Max retry attempts per key before giving up and dropping the write, so a\n// structurally failing key (e.g. a payload the underlying storage rejects)\n// cannot stall coordination forever. The local transport remains usable.\nconst MAX_RETRY_ATTEMPTS = 5;\n\n/**\n * Coalesces synchronous storage writes and applies them in one pass, with\n * exponential backoff when the underlying storage rejects a write.\n *\n * Wraps a {@link StorageLike} so callers (WorkerClusterRuntime) see a normal\n * storage interface; reads transparently see pending writes before they flush.\n * The coalescing window is one microtask, so a burst of heartbeat + route +\n * subscriber writes in the same task becomes a single localStorage flush.\n */\nexport class BatchingStorageWriter implements StorageLike {\n /** Coalesced write set. A `null` value represents a pending delete. */\n private readonly pending = new Map<string, string | null>();\n /** Per-key retry counter, reset on a successful write. */\n private readonly retryCount = new Map<string, number>();\n private flushScheduled = false;\n private retryHandle: ReturnType<typeof setTimeout> | null = null;\n private retryDelayMs = INITIAL_RETRY_DELAY_MS;\n\n constructor(private readonly storage: StorageLike) {}\n\n /** Number of writes queued in memory but not yet flushed to storage.\n * Used by tests to assert the coalescing window and by flush() to detect\n * the all-drained state. */\n get pendingSize(): number {\n return this.pending.size;\n }\n\n get length(): number {\n return this.keys().length;\n }\n\n clear(): void {\n this.pending.clear();\n this.flushScheduled = false;\n this.storage.clear();\n this.cancelRetry();\n this.retryCount.clear();\n // Reset backoff so a burst of clear()/flush() cycles does not leave the\n // writer stuck at an elevated retry delay.\n this.retryDelayMs = INITIAL_RETRY_DELAY_MS;\n }\n\n // Reads always see the pending value first (task-local consistency), then\n // fall back to the underlying storage.\n getItem(key: string): string | null {\n if (this.pending.has(key)) return this.pending.get(key) ?? null;\n return this.storage.getItem(key);\n }\n\n key(index: number): string | null {\n return this.keys()[index] ?? null;\n }\n\n removeItem(key: string): void {\n this.pending.set(key, null);\n this.scheduleFlush();\n }\n\n setItem(key: string, value: string): void {\n this.pending.set(key, value);\n this.scheduleFlush();\n }\n\n flush(): void {\n this.flushScheduled = false;\n this.cancelRetry();\n // Apply writes from a snapshot so a concurrent scheduleFlush during the\n // loop cannot re-enter or corrupt the pending map mid-iteration.\n // Array.from is preferred over [...this.pending] here: it avoids the\n // spread's intermediate iterator allocation on a hot path that heartbeats\n // and route writes hit every few seconds.\n for (const [key, value] of Array.from(this.pending)) {\n try {\n if (value === null) this.storage.removeItem(key);\n else this.storage.setItem(key, value);\n this.pending.delete(key);\n this.retryCount.delete(key);\n } catch {\n const attempts = (this.retryCount.get(key) ?? 0) + 1;\n // A persistently failing key (e.g. a payload the storage rejects)\n // is dropped after MAX_RETRY_ATTEMPTS so coordination is not stuck\n // forever. Best-effort: the local transport stays usable without it.\n if (attempts >= MAX_RETRY_ATTEMPTS) {\n this.pending.delete(key);\n this.retryCount.delete(key);\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(`[${DEFAULT_STORAGE_PREFIX}] storage write gave up after retries, dropping key:`, key);\n }\n continue;\n }\n this.retryCount.set(key, attempts);\n // Remaining pending writes stay queued for the next attempt, which\n // retries with backoff. Coalescing is preserved by setting the gate\n // so a concurrent scheduleFlush cannot start a second overlapping pass.\n // `break` stops the flush at the first failure so the retry loop can\n // re-attempt this key (and the remaining pending entries) together,\n // rather than continuing to apply later keys while an earlier one is\n // still in a failed-and-retrying state.\n this.scheduleRetry();\n break;\n }\n }\n if (this.pending.size === 0) {\n this.retryDelayMs = INITIAL_RETRY_DELAY_MS;\n this.retryCount.clear();\n }\n }\n\n /** Union of persisted keys and pending writes, minus pending deletes. */\n private keys(): string[] {\n const keys = new Set<string>();\n for (let index = 0; index < this.storage.length; index += 1) {\n const key = this.storage.key(index);\n if (key !== null) keys.add(key);\n }\n for (const [key, value] of this.pending) {\n if (value === null) keys.delete(key);\n else keys.add(key);\n }\n return Array.from(keys);\n }\n\n // Coalesce all synchronous writes within one task into a single microtask\n // flush, avoiding a localStorage write per heartbeat/route/subscriber update.\n // The queueMicrotask fallback to setTimeout handles older runtimes and\n // non-browser environments where queueMicrotask is absent.\n private scheduleFlush(): void {\n if (this.flushScheduled) return;\n this.flushScheduled = true;\n const flush = () => {\n this.flushScheduled = false;\n this.flush();\n };\n if (typeof queueMicrotask === 'function') queueMicrotask(flush);\n else setTimeout(flush, 0);\n }\n\n // Schedule a single retry timer. The guard ensures only one retry is in\n // flight at a time; subsequent scheduleRetry calls during the wait are\n // no-ops because the first retry will re-flush all pending keys together.\n private scheduleRetry(): void {\n if (this.retryHandle !== null) return;\n this.retryHandle = setTimeout(() => {\n this.retryHandle = null;\n this.flush();\n }, this.retryDelayMs);\n // Exponential backoff: 50ms \u2192 100ms \u2192 \u2026 \u2192 capped at 1600ms.\n this.retryDelayMs = Math.min(MAX_RETRY_DELAY_MS, this.retryDelayMs * 2);\n }\n\n private cancelRetry(): void {\n if (this.retryHandle !== null) {\n clearTimeout(this.retryHandle);\n this.retryHandle = null;\n }\n }\n}\n", "/**\n * localStorage \u8BFB\u5199\u5DE5\u5177 \u2014\u2014 \u5BB9\u9519\u7684 JSON \u8BFB\u3001\u9759\u9ED8\u5199\u3001\u6309\u524D\u7F00\u679A\u4E3E\u3002\n *\n * \u4ECE WorkerClusterRuntime \u62C6\u51FA\uFF1Acluster.ts \u9876\u90E8\u539F\u6709\u7684 readJson/writeJson/\n * listKeys/readAllByPrefix \u56DB\u5904\u642C\u5230\u6B64\u6587\u4EF6\uFF0C\u534F\u8C03\u5C42\u4E0E\u5FEB\u7167\u903B\u8F91\u5171\u7528\u540C\u4E00\u5957\n * \u5BB9\u9519\u8BED\u4E49\uFF08\u635F\u574F JSON \u89C6\u4E3A\u4E0D\u5B58\u5728\u3001\u5199\u5931\u8D25\u4E0D\u629B\u51FA\uFF09\u3002\n */\nimport type { StorageLike } from '../core/environment';\n\n/** Parse a JSON value from storage, returning null on malformed or missing data.\n * Never throws \u2014 a corrupt record is treated as absent so the reconcile cycle\n * can recreate it. */\nexport function readJson<T>(storage: StorageLike, key: string): T | null {\n try {\n const value = storage.getItem(key);\n return value ? (JSON.parse(value) as T) : null;\n } catch {\n return null;\n }\n}\n\n/** Write a JSON value to storage, swallowing storage errors (coordination is\n * best-effort; a failed write does not break the local transport). The actual\n * write may be coalesced by BatchingStorageWriter \u2014 this just calls setItem. */\nexport function writeJson(storage: StorageLike, key: string, value: unknown): void {\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // Coordination is best-effort. The local transport remains usable.\n }\n}\n\n/** List all storage keys that start with `prefix`. */\nexport function listKeys(storage: StorageLike, prefix: string): string[] {\n try {\n return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter(\n (key): key is string => Boolean(key?.startsWith(prefix))\n );\n } catch {\n return [];\n }\n}\n\n/** Read and parse every JSON record whose key starts with `prefix`. */\nexport function readAllByPrefix<T>(storage: StorageLike, prefix: string): Array<{ key: string; value: T }> {\n return listKeys(storage, prefix)\n .map(key => ({ key, value: readJson<T>(storage, key) }))\n .filter((entry): entry is { key: string; value: T } => entry.value !== null);\n}\n", "/**\n * WorkerClusterRuntime \u2014 cross-tab cluster coordination layer.\n *\n * Manages Worker registration, heartbeat, sticky topic-owner routing,\n * page-lifecycle handoff/resume, and BroadcastChannel-based\n * control messaging. Each DataBus instance owns one Runtime which drives the\n * transport and coordinates with other tabs via localStorage + BroadcastChannel.\n */\nimport { canUseStorage, createBrowserEnvironment, getOrCreateTabId } from './environment';\nimport type { ClusterChannel, ClusterEnvironment, StorageLike } from './environment';\nimport { createOpaqueKey } from './hash';\nimport {\n DEFAULT_MAX_ACTIVE_WORKERS,\n approximatePayloadBytes,\n selectActiveWorkers,\n selectLeastLoadedWorker,\n topicMatchesPattern\n} from './routing';\nimport type {\n DataBusPublicationMetadata,\n LoadWeightingOptions,\n TopicSubscriberRecord,\n WorkerClusterMessage,\n WorkerControlAction,\n WorkerRecord,\n WorkerRole,\n WorkerRoute,\n WorkerStatus,\n WorkerThroughputSample\n} from './types';\nimport { BatchingStorageWriter } from './storage-batch';\nimport {\n CLUSTER_MESSAGE_TYPE,\n CONTROL_ACTION,\n DEFAULT_STORAGE_PREFIX,\n RELIABILITY_OPERATION,\n WORKER_ROLE,\n WORKER_STATUS\n} from '../utils/constants';\nimport { publicationMetadata } from '../utils/metadata';\nimport { readAllByPrefix, readJson, writeJson } from '../utils/storage-utils';\nimport { assertClusterOptions } from '../utils/validation';\n\n/** Callbacks the cluster invokes to drive the transport and lifecycle. */\nexport interface WorkerClusterHandlers {\n /** A SUBSCRIBE/UNSUBSCRIBE/PUBLISH control action was received for this worker. */\n onControl: (\n action: WorkerControlAction,\n topic: string,\n data?: unknown,\n messageId?: string,\n timestamp?: number\n ) => void;\n /** Optional batched variant of the PUBLISH action: invoked once when a\n * CONTROL frame carries multiple publication items. When absent, the\n * cluster falls back to per-item `onControl('PUBLISH', \u2026)` calls. */\n onPublishBatch?: (\n topic: string,\n items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>\n ) => void;\n /** A fan-out publication event was received from another Worker.\n * `originTabId` is the tab that produced the original publication when the\n * cluster forwards one; it survives the BroadcastChannel hop so listeners\n * can tell a local dispatch from a cross-tab relay. */\n /** Optional hook for forward-compatible messages from newer runtimes. */\n onUnknownMessage?: (message: unknown) => void;\n onEvent: (\n eventType: string,\n payload: unknown,\n sourceWorkerId: string,\n originTabId?: string\n ) => void;\n /** The cluster suspended (tab hidden / pagehide). */\n onSuspend?: () => void;\n /** The cluster resumed (tab visible / pageshow). */\n onResume?: () => void;\n /** Bounded diagnostics for route confirmation, graceful migration, and stranded-handoff recovery. */\n onDiagnostic?: (event: {\n operation: (typeof RELIABILITY_OPERATION.ROUTE_ACK | typeof RELIABILITY_OPERATION.ROUTE_MIGRATION | typeof RELIABILITY_OPERATION.ROUTE_MIGRATION_RECOVERY);\n topic: string;\n }) => void;\n}\n\nexport interface WorkerClusterOptions {\n /** Namespace for the cluster's storage keys and BroadcastChannel.\n * Two DataBus instances with different clusterKeys operate in isolation. */\n clusterKey: string;\n /** Callbacks the cluster invokes to drive the transport and lifecycle. */\n handlers: WorkerClusterHandlers;\n /** Inject a custom environment (for tests or SSR). Defaults to browser. */\n environment?: ClusterEnvironment;\n /** Override the storage key prefix (default 'cross-tab-worker-databus'). */\n storagePrefix?: string;\n /** Inject a stable tab ID (for tests). Defaults to sessionStorage-derived. */\n tabId?: string;\n /** Inject a worker ID (for tests). Defaults to 'worker-<tabId>-<random>'. */\n workerId?: string;\n /** Cap on concurrently active owners (default 3). See DEFAULT_MAX_ACTIVE_WORKERS. */\n maxActiveWorkers?: number;\n /** Heartbeat + reconcile interval in ms (default 3000). */\n heartbeatIntervalMs?: number;\n /** TTL after which a silent worker is pruned (default 10000). */\n workerTtlMs?: number;\n /** Maximum entries kept in the publish route-owner cache (default 256).\n * When the cap is reached, the oldest (FIFO) entry is evicted. */\n routeOwnerCacheMax?: number;\n /** Optional adaptive owner weighting. When set, this worker samples its own\n * fan-out traffic and publishes it with every heartbeat so peers can steer\n * NEW routes toward quieter workers; the weights are forwarded to the\n * least-loaded selection. Absent (default) keeps pure topic-count routing. */\n loadWeighting?: LoadWeightingOptions;\n}\n\n/** Read-only snapshot of the cluster state for diagnostics and tracing. */\nexport interface WorkerClusterSnapshot {\n protocolVersion: number;\n /** Protocol versions advertised by each currently visible peer; null means legacy peer. */\n peerProtocolVersions: Record<string, number | null>;\n coordinated: boolean;\n suspended: boolean;\n currentWorker: WorkerRecord;\n workers: WorkerRecord[];\n /** Routes with the plaintext topic injected from the in-memory knownTopics cache. */\n routes: Array<WorkerRoute & { topic: string | null }>;\n subscribedTopics: string[];\n assignedTopics: string[];\n /** Opaque key \u2192 plaintext topic mapping for debugging. */\n knownTopics: Array<{ topicKey: string; topic: string }>;\n routeOwnerCache?: { size: number; max: number; hits: number; misses: number };\n}\n\nconst CLUSTER_PROTOCOL_VERSION = 1;\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 3_000;\nconst DEFAULT_WORKER_TTL_MS = 10_000;\n// Upper bound on the topicKey \u2192 topic reverse cache. Control messages from\n// other workers can reference arbitrary topics, so cap growth to avoid an\n// unbounded memory leak from a misbehaving or malicious peer.\nconst MAX_KNOWN_TOPICS = 500;\n\n/**\n * Cross-tab worker coordination runtime.\n *\n * Manages a cluster of Workers (one per tab) that share topics via localStorage\n * and BroadcastChannel. Each Worker publishes its own record, subscribes to\n * topics, and routes publications through the owning Worker to avoid duplicates.\n *\n * Key responsibilities:\n * - Heartbeat-based failure detection (stale workers pruned after `workerTtlMs`)\n * - Topic-to-Worker routing with load-based rebalancing\n * - Page lifecycle integration (suspend on hide, resume on show)\n * - Storage-backed coordination with BatchingStorageWriter for write coalescing\n */\nexport class WorkerClusterRuntime {\n readonly tabId: string;\n readonly workerId: string;\n\n private readonly environment: ClusterEnvironment;\n private readonly handlers: WorkerClusterHandlers;\n private storage: StorageLike | null;\n private readonly maxActiveWorkers: number;\n private readonly heartbeatIntervalMs: number;\n private readonly workerTtlMs: number;\n private readonly workerPrefix: string;\n private readonly routePrefix: string;\n private readonly subscriberPrefix: string;\n private readonly channelName: string;\n /** Adaptive load weighting options; undefined keeps legacy topic-count routing. */\n private readonly loadWeighting: LoadWeightingOptions | undefined;\n /** Rolling traffic accumulator folded into the worker record on writeRecord. */\n private throughputWindow: { startedAt: number; messageCount: number; byteCount: number } = {\n startedAt: 0,\n messageCount: 0,\n byteCount: 0\n };\n // Topics this tab has subscribed to (local interest, plaintext).\n private readonly subscribedTopics = new Set<string>();\n // Topics assigned to this Worker as owner (topicKey \u2192 topic). Authoritative:\n // membership drives isAssigned() and load. Grows only via CONTROL/SUBSCRIBE\n // (or local self-subscribe), never via the reverse cache.\n private readonly assignedTopics = new Map<string, string>();\n private readonly routeOwnerCache = new Map<string, { workerId: string; generation: number }>();\n private readonly routeOwnerCacheMax: number;\n private routeOwnerCacheHits = 0;\n private routeOwnerCacheMisses = 0;\n private unknownMessageCount = 0;\n private lastUnknownMessageType: string | null = null;\n private touchRouteOwnerCache(topicKey: string, value: { workerId: string; generation: number }): void {\n if (this.routeOwnerCache.has(topicKey)) this.routeOwnerCache.delete(topicKey);\n this.routeOwnerCache.set(topicKey, value);\n while (this.routeOwnerCache.size > this.routeOwnerCacheMax) {\n const oldest = this.routeOwnerCache.keys().next().value;\n if (oldest === undefined) break;\n this.routeOwnerCache.delete(oldest);\n }\n }\n private readonly wildcardPublishCache = new Map<string, string | null>();\n // Reverse mapping: opaque topicKey \u2192 plaintext topic. A bounded cache with\n // FIFO eviction \u2014 NOT authoritative. It can hold a topicKey that is also in\n // assignedTopics (the owned guard prevents evicting those), because it is\n // the only source of plaintext when storage is unavailable. See the\n // rememberTopic() doc for the eviction contract.\n private readonly knownTopics = new Map<string, string>();\n private channel: ClusterChannel | null = null;\n private heartbeatHandle: unknown = null;\n private started = false;\n private suspended = false;\n private lifecycleListening = false;\n private currentRecord: WorkerRecord;\n\n constructor(options: WorkerClusterOptions) {\n assertClusterOptions(options);\n this.environment = options.environment ?? createBrowserEnvironment();\n this.handlers = options.handlers;\n this.maxActiveWorkers = options.maxActiveWorkers ?? DEFAULT_MAX_ACTIVE_WORKERS;\n this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n this.routeOwnerCacheMax = options.routeOwnerCacheMax ?? 256;\n this.workerTtlMs = options.workerTtlMs ?? DEFAULT_WORKER_TTL_MS;\n this.loadWeighting = options.loadWeighting;\n // Derive storage keys from a hash of the cluster key so that the plaintext\n // cluster identifier never appears in localStorage.\n const clusterHash = createOpaqueKey(options.clusterKey || '__default__');\n const prefix = options.storagePrefix ?? DEFAULT_STORAGE_PREFIX;\n const baseKey = `${prefix}:${clusterHash}`;\n this.workerPrefix = `${baseKey}:worker:`;\n this.routePrefix = `${baseKey}:route:`;\n this.subscriberPrefix = `${baseKey}:subscriber:`;\n this.channelName = `${prefix}:bus:${clusterHash}`;\n // Wrap localStorage in a BatchingStorageWriter to coalesce writes.\n this.storage = canUseStorage(this.environment.storage, `${baseKey}:probe`)\n ? new BatchingStorageWriter(this.environment.storage)\n : null;\n this.tabId = options.tabId ?? getOrCreateTabId(this.environment, `${prefix}:tab-id`);\n this.workerId = options.workerId ?? `worker-${this.tabId}-${this.environment.randomId()}`;\n const now = this.environment.now();\n this.currentRecord = {\n protocolVersion: CLUSTER_PROTOCOL_VERSION,\n workerId: this.workerId,\n tabId: this.tabId,\n load: 0,\n role: WORKER_ROLE.STANDBY,\n status: WORKER_STATUS.CONNECTING,\n visibilityState: this.environment.getVisibilityState(),\n heartbeatAt: now,\n registeredAt: now\n };\n }\n\n /** Start the cluster: register, listen for lifecycle events, and begin heartbeats. */\n start(): void {\n if (this.started) return;\n this.suspended = false;\n this.addLifecycleListeners();\n this.activate();\n }\n\n /**\n * Stop the cluster: pause heartbeats, hand off assigned topics, remove\n * the worker record, and clean up lifecycle listeners. Idempotent.\n * The .clear() calls after pause() are safe no-ops when pause already\n * cleared the maps (the handoff path), but ensure a full teardown in the\n * stop() path where callers expect every Set/Map to be empty afterwards.\n */\n stop(): void {\n if (!this.started && !this.suspended) return;\n this.pause();\n this.flushStorage();\n this.removeLifecycleListeners();\n this.subscribedTopics.clear();\n this.assignedTopics.clear();\n this.routeOwnerCache.clear();\n this.wildcardPublishCache.clear();\n this.knownTopics.clear();\n this.suspended = false;\n }\n\n /**\n * Activate the cluster: open the BroadcastChannel, register the worker record,\n * subscribe to topics, and start the heartbeat interval.\n */\n private activate(): void {\n if (this.started) return;\n this.started = true;\n // Create the BroadcastChannel for cross-tab messaging. If storage is\n // unavailable, we cannot coordinate \u2014 skip the channel. If the channel\n // itself fails to construct (sandboxed iframe, permissions policy),\n // null out storage too: without a channel the storage writes have no\n // peer to observe them, so the BatchingStorageWriter would write for\n // nothing and the degraded code paths must take over.\n this.channel = this.storage ? this.environment.createChannel(this.channelName) : null;\n if (!this.channel) this.storage = null;\n this.channel?.addEventListener('message', this.handleMessage);\n const now = this.environment.now();\n this.currentRecord = {\n ...this.currentRecord,\n heartbeatAt: now,\n registeredAt: now,\n visibilityState: this.environment.getVisibilityState()\n };\n this.refreshRole(this.readWorkers());\n this.writeRecord(true);\n // Re-subscribe any topics that were subscribed before the cluster started.\n // rememberTopic is called once per topic regardless of branch so the reverse\n // cache is populated before either the control message or the subscriber write.\n for (const topic of this.subscribedTopics) {\n const topicKey = this.rememberTopic(topic);\n if (!this.storage) this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n else this.writeSubscriber(topicKey);\n }\n this.reconcile();\n // Periodic heartbeat + reconciliation.\n this.heartbeatHandle = this.environment.setInterval(() => {\n this.writeRecord(false);\n this.reconcile();\n }, this.heartbeatIntervalMs);\n }\n\n /**\n * Pause the cluster on pagehide: stop heartbeats, hand off assigned topics\n * to other workers, remove our worker record, and close the channel.\n */\n private pause(): void {\n if (!this.started) return;\n this.started = false;\n this.suspended = true;\n if (this.heartbeatHandle !== null) this.environment.clearInterval(this.heartbeatHandle);\n this.heartbeatHandle = null;\n this.channel?.removeEventListener('message', this.handleMessage);\n // Order matters: release local subscriptions BEFORE handing off assigned\n // topics, then clear the assignment map. Releasing first removes the\n // subscriber records so handoff sees the correct remaining subscribers;\n // clearing after handoff ensures no topic is both handed off and left\n // dangling. Do not reorder without addressing the handoff semantics.\n for (const topic of this.subscribedTopics) this.releaseSubscription(topic, false);\n this.handoffAssignedTopics();\n this.assignedTopics.clear();\n this.routeOwnerCache.clear();\n this.wildcardPublishCache.clear();\n this.removeStorage(this.workerStorageKey(this.workerId));\n // Persist the final routes and worker removal before asking peers to\n // reconcile. A pagehide CONTROL message may be lost; REGISTRY must still\n // let peers observe the completed handoff immediately.\n this.flushStorage();\n this.notifyRegistry();\n const channel = this.channel;\n this.channel = null;\n this.handlers.onSuspend?.();\n // Defer the physical close by one task: BroadcastChannel.close() discards\n // messages still queued for delivery \u2014 including the handoff's\n // ROUTE_RELEASED \u2014 which can strand the handoff target with an\n // unconfirmed route on a loaded runner. Letting the queued frames flush\n // first keeps the strict handoff live; on a frozen BFCache page the task\n // simply never runs and the channel object is garbage-collected with it.\n if (typeof globalThis.setTimeout === 'function') {\n globalThis.setTimeout(() => channel?.close(), 0);\n } else {\n channel?.close();\n }\n }\n\n /** Update the worker's connection status and persist the change. */\n setStatus(status: WorkerStatus): void {\n if (this.currentRecord.status === status) return;\n this.currentRecord = { ...this.currentRecord, status };\n if (this.started) this.writeRecord(true);\n }\n\n /**\n * Subscribe to a topic. Returns true if this worker becomes the assigned owner.\n * The topic is recorded locally and the cluster is notified via storage or\n * direct control message.\n */\n subscribe(topic: string): boolean {\n const topicKey = this.rememberTopic(topic);\n this.subscribedTopics.add(topic);\n if (!this.started) return false;\n if (!this.storage) {\n this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n return true;\n }\n this.writeSubscriber(topicKey);\n const workers = this.readWorkers();\n const existingRoute = this.readRoute(topicKey);\n if (this.routeOwnerIsLive(existingRoute, workers)) {\n return existingRoute?.workerId === this.workerId;\n }\n\n const activeWorkers = selectActiveWorkers(workers, this.maxActiveWorkers);\n const owner = selectLeastLoadedWorker(activeWorkers, undefined, this.loadWeighting) ?? this.currentRecord;\n // A missing live owner cannot participate in a strict handoff. Assign and\n // subscribe immediately; pagehide uses handoffAssignedTopics() while the\n // old owner is still present when release ordering is required.\n this.writeRoute(topicKey, owner, undefined, (existingRoute?.generation ?? 0) + 1);\n this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n this.notifyRegistry();\n return owner.workerId === this.workerId;\n }\n\n /**\n * Remove the local subscription. Cleans up the subscriber record and, if no\n * subscribers remain, deletes the route so the owning Worker can unsubscribe.\n */\n unsubscribe(topic: string): void {\n this.subscribedTopics.delete(topic);\n const topicKey = this.releaseSubscription(topic);\n // Keep the topic in knownTopics if we remain the owner (we may still fan out).\n if (topicKey && !this.assignedTopics.has(topicKey)) this.knownTopics.delete(topicKey);\n }\n\n /** Remove this tab's subscriber record and, when it was the last one, delete\n * the route. Returns the topicKey (so callers like `unsubscribe` can reuse\n * it instead of re-hashing the topic to evict the reverse cache). */\n private releaseSubscription(topic: string, notifyOwner = true): string {\n const topicKey = this.rememberTopic(topic);\n this.removeStorage(this.subscriberStorageKey(topicKey, this.tabId));\n const route = this.readRoute(topicKey);\n if (!route) return topicKey;\n const subscribers = this.readSubscriberTabIds(topicKey, this.readWorkers());\n if (subscribers.length === 0) {\n this.removeStorage(this.routeStorageKey(topicKey));\n if (notifyOwner) this.sendControl(route.workerId, CONTROL_ACTION.UNSUBSCRIBE, topic, topicKey);\n }\n return topicKey;\n }\n\n /** Transfer assigned topics to other active workers so subscribers are not orphaned during pause. */\n private handoffAssignedTopics(): void {\n if (!this.storage || this.assignedTopics.size === 0) return;\n const remainingWorkers = this.readWorkers().filter(worker => worker.workerId !== this.workerId);\n const activeWorkers = selectActiveWorkers(remainingWorkers, this.maxActiveWorkers);\n // `WorkerRecord.load` is a snapshot from before this handoff. Keep a\n // projected load locally so a batch of topics is distributed across the\n // remaining workers instead of every route choosing the same initial\n // minimum.\n const projectedLoads = new Map(activeWorkers.map(worker => [worker.workerId, worker.load]));\n\n for (const [topicKey, topic] of this.assignedTopics) {\n const previous = this.readRoute(topicKey);\n if (previous?.workerId !== this.workerId) continue;\n const subscribers = this.readSubscriberTabIds(topicKey, remainingWorkers);\n if (subscribers.length === 0) {\n this.removeStorage(this.routeStorageKey(topicKey));\n continue;\n }\n const owner = selectLeastLoadedWorker(\n activeWorkers.map(worker => ({ ...worker, load: projectedLoads.get(worker.workerId) ?? worker.load })),\n undefined,\n this.loadWeighting\n );\n if (!owner) continue;\n projectedLoads.set(owner.workerId, (projectedLoads.get(owner.workerId) ?? owner.load) + 1);\n const generation = (previous?.generation ?? 0) + 1;\n this.writeRoute(topicKey, owner, previous?.workerId, generation);\n this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_MIGRATION, topic });\n // Make the new route visible before the target confirms it. This also\n // leaves a durable unconfirmed assignment when unload drops CONTROL.\n this.flushStorage();\n // Release the old server subscription before authorizing the new owner.\n // The ACK is sent after the transport operation has been requested.\n this.handlers.onControl(CONTROL_ACTION.UNSUBSCRIBE, topic);\n this.sendRouteReleased(owner.workerId, topic, topicKey, generation);\n }\n }\n\n /**\n * Publish a message to `topic`, routing through the owning Worker (or self if\n * no owner is found). Returns false when the control message could not be\n * posted to a remote owner, so the caller can surface the failure instead of\n * silently dropping the publication.\n */\n publish(topic: string, data: unknown, messageId?: string): boolean;\n publish(topic: string, data: unknown, metadata?: DataBusPublicationMetadata): boolean;\n publish(\n topic: string,\n data: unknown,\n metadataOrMessageId?: DataBusPublicationMetadata | string\n ): boolean {\n const metadata = typeof metadataOrMessageId === 'string'\n ? { messageId: metadataOrMessageId }\n : metadataOrMessageId;\n const topicKey = this.rememberTopic(topic);\n // The owning Worker already has a synchronous assignment map. Reuse it\n // for the hot local-publish path instead of scanning worker and route\n // records on every message. Wildcard assignments also own matching\n // concrete topics, so they can use the same fast path.\n if (this.assignedTopics.has(topicKey)) {\n return this.sendControl(this.workerId, CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);\n }\n // A local wildcard owns matching concrete topics. The scan result is\n // memoised per concrete topic: `undefined` means \"not scanned yet\", a\n // pattern string means \"this local wildcard matched\", and `null` means\n // \"scanned, no local wildcard matched\". The cached positive value is a\n // scan-skip marker only \u2014 it is deliberately NOT re-checked against\n // `assignedTopics`, because that map is keyed by the opaque topic key, so\n // a plaintext pattern could never match a key (the check was unreachable).\n // Only the first (scanning) call may dispatch locally; later calls route\n // through `resolvePublishTarget`, which honours a concrete remote owner.\n const cachedPattern = this.wildcardPublishCache.get(topic);\n if (cachedPattern === undefined) {\n for (const pattern of this.assignedTopics.values()) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) {\n this.wildcardPublishCache.set(topic, pattern);\n return this.sendControl(this.workerId, CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);\n }\n }\n this.wildcardPublishCache.set(topic, null);\n }\n return this.sendControl(this.resolvePublishTarget(topic, topicKey), CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);\n }\n\n /**\n * Burst-friendly variant of `publish()`: packs up to N items into a single\n * BroadcastChannel postMessage so the receiving owner dispatches them all in\n * one tick. Per-item dedup / replay / dispatch ordering is preserved; items\n * may carry their own messageId/timestamp. Empty batch is a no-op,\n * single-item batch delegates to `publish()`.\n */\n publishBatch(\n topic: string,\n items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>\n ): boolean {\n if (items.length === 0) return true;\n if (items.length === 1) {\n const single = items[0]!;\n const metadata = single.messageId !== undefined || single.timestamp !== undefined\n ? {\n ...(single.messageId !== undefined ? { messageId: single.messageId } : {}),\n ...(single.timestamp !== undefined ? { timestamp: single.timestamp } : {})\n }\n : undefined;\n return this.publish(topic, single.data, metadata);\n }\n const topicKey = this.rememberTopic(topic);\n if (this.assignedTopics.has(topicKey)) {\n this.dispatchLocalPublishBatch(topic, topicKey, items);\n return true;\n }\n const cachedPattern = this.wildcardPublishCache.get(topic);\n if (cachedPattern === undefined) {\n for (const pattern of this.assignedTopics.values()) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) {\n this.wildcardPublishCache.set(topic, pattern);\n this.dispatchLocalPublishBatch(topic, topicKey, items);\n return true;\n }\n }\n this.wildcardPublishCache.set(topic, null);\n }\n const target = this.resolvePublishTarget(topic, topicKey);\n if (target === this.workerId) {\n this.dispatchLocalPublishBatch(topic, topicKey, items);\n return true;\n }\n return this.send({\n type: CLUSTER_MESSAGE_TYPE.CONTROL,\n sourceWorkerId: this.workerId,\n targetWorkerId: target,\n action: CONTROL_ACTION.PUBLISH,\n topic,\n topicKey,\n items: items.map(item => ({\n data: item.data,\n ...(item.messageId !== undefined ? { messageId: item.messageId } : {}),\n ...(item.timestamp !== undefined ? { timestamp: item.timestamp } : {})\n }))\n });\n }\n\n /** Resolve which worker should receive a PUBLISH for `topic`. Centralises the\n * route-owner cache lookup so `publish()` and `publishBatch()` share one path. */\n private resolvePublishTarget(topic: string, topicKey: string): string {\n const workers = this.readWorkers();\n const route = this.readRoute(topicKey);\n const cached = this.routeOwnerCache.get(topicKey);\n const cachedLive = cached && route && route.generation === cached.generation && route.workerId === cached.workerId && workers.some(worker => worker.workerId === cached.workerId);\n if (cachedLive) this.routeOwnerCacheHits += 1; else this.routeOwnerCacheMisses += 1;\n const target = cachedLive\n ? cached.workerId\n : this.routeOwnerIsLive(route, workers)\n ? route?.workerId ?? this.workerId\n : this.workerId;\n if (route && target === route.workerId) {\n this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });\n } else {\n this.routeOwnerCache.delete(topicKey);\n }\n return target;\n }\n\n /** Fan out a single batched item to the local onControl path. */\n private dispatchLocalPublish(\n topic: string,\n topicKey: string,\n data: unknown,\n messageId?: string,\n timestamp?: number\n ): void {\n void topicKey;\n const meta = publicationMetadata(messageId, timestamp);\n if (meta) this.handlers.onControl(CONTROL_ACTION.PUBLISH, topic, data, meta.messageId, meta.timestamp);\n else this.handlers.onControl(CONTROL_ACTION.PUBLISH, topic, data);\n }\n\n /** Fan out a publication batch to the local onControl path: one\n * onPublishBatch call when the owner supports it, per-item otherwise. */\n private dispatchLocalPublishBatch(\n topic: string,\n topicKey: string,\n items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>\n ): void {\n if (this.handlers.onPublishBatch) {\n this.handlers.onPublishBatch(topic, items);\n return;\n }\n for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);\n }\n\n /** True when `route` exists and its owner worker is among `workers`.\n * Shared by subscribe (skip re-assignment) and publish (route to owner).\n * Intentionally returns a plain boolean (not a type guard) so the caller\n * can still access `route?.generation` in the false branch. */\n private routeOwnerIsLive(route: WorkerRoute | null, workers: readonly WorkerRecord[]): boolean {\n return Boolean(route && workers.some(worker => worker.workerId === route.workerId));\n }\n\n /** Broadcast an event to every tab \u2014 used to fan out transport publications.\n * `originTabId` (when set) is propagated across the BroadcastChannel hop so\n * listeners can attribute the event to its source tab even after fan-out. */\n broadcastEvent(eventType: string, payload: unknown, originTabId?: string): void {\n // Default to the producing tab so listeners can attribute the event to\n // its source tab across the BroadcastChannel hop without callers having\n // to thread the tabId through every call site.\n const effectiveOriginTabId = originTabId ?? this.tabId;\n this.recordTraffic(payload);\n this.send({ type: CLUSTER_MESSAGE_TYPE.EVENT, sourceWorkerId: this.workerId, eventType, payload, originTabId: effectiveOriginTabId });\n }\n\n /** Count one fan-out unit toward the adaptive load sample. No-op unless\n * adaptive weighting is configured. */\n private recordTraffic(payload: unknown): void {\n if (this.loadWeighting === undefined) return;\n this.throughputWindow.messageCount += 1;\n this.throughputWindow.byteCount += approximatePayloadBytes(payload);\n }\n\n /** Convert the accumulated window into a publishable throughput sample and\n * reset the accumulator. Returns undefined until a full window has elapsed\n * so the first write does not emit a zero-width sample. */\n private sampleThroughput(now: number): WorkerThroughputSample | undefined {\n if (this.throughputWindow.startedAt === 0) {\n this.throughputWindow.startedAt = now;\n return undefined;\n }\n const windowMs = now - this.throughputWindow.startedAt;\n if (windowMs <= 0) return undefined;\n const sample: WorkerThroughputSample = {\n windowMs,\n messageCount: this.throughputWindow.messageCount,\n byteCount: this.throughputWindow.byteCount,\n // How much later the heartbeat landed than its nominal interval. This\n // window is anchored at the previous writeRecord (one heartbeat tick),\n // so a starved event loop stretches windowMs past the interval and the\n // positive excess is the scheduling-overrun signal.\n overrunMs: Math.max(0, windowMs - this.heartbeatIntervalMs),\n sampledAt: now\n };\n this.throughputWindow = { startedAt: now, messageCount: 0, byteCount: 0 };\n return sample;\n }\n\n isAssigned(topic: string): boolean {\n // Deliberately recompute the key via createOpaqueKey rather than\n // rememberTopic(): this is a read-only query, not a state change, so it\n // must not populate the knownTopics reverse-cache. Hashing is cheap enough\n // that re-deriving here is preferable to evicting a cached entry that the\n // storage-less readRoute path may need (see rememberTopic eviction guard).\n const topicKey = createOpaqueKey(topic);\n // Prefer the in-memory assignment map: it is updated synchronously on\n // SUBSCRIBE/UNSUBSCRIBE, whereas readRoute() may observe a route that has\n // not yet been flushed through the BatchingStorageWriter, causing a message\n // destined for this worker to be dropped during the write window.\n if (this.assignedTopics.has(topicKey)) return true;\n // Wildcard assignments: this worker owns the transport subscription for a\n // pattern (e.g. \"chat.*\"), so publications arriving under a matching\n // concrete topic (e.g. \"chat.room.1\", as delivered by pattern-aware\n // servers) belong to the same route and must fan out from here too.\n for (const pattern of this.assignedTopics.values()) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) return true;\n }\n return this.readRoute(topicKey)?.workerId === this.workerId;\n }\n\n /** True if this worker is among the active set (eligible to own topics). */\n isActiveWorker(): boolean {\n return this.isActiveAmong(this.readWorkers());\n }\n\n /** True when this workerId is in the active subset of `workers`. Shared by\n * isActiveWorker() and refreshRole() so both compute role identically. */\n private isActiveAmong(workers: readonly WorkerRecord[]): boolean {\n return selectActiveWorkers(workers, this.maxActiveWorkers).some(\n worker => worker.workerId === this.workerId\n );\n }\n\n /** True when this tab has a local subscriber registered for `topic` \u2014\n * exactly, or via a wildcard subscription that matches it. */\n hasLocalSubscriber(topic: string): boolean {\n if (this.subscribedTopics.has(topic)) return true;\n for (const pattern of this.subscribedTopics) {\n if (pattern !== topic && topicMatchesPattern(pattern, topic)) return true;\n }\n return false;\n }\n\n /** Count and last type of unknown protocol messages observed. */\n getUnknownMessageStats(): { count: number; lastType: string | null } { return { count: this.unknownMessageCount, lastType: this.lastUnknownMessageType }; }\n\n /** Read-only snapshot of the cluster state (workers, routes, assignments). */\n getSnapshot(): WorkerClusterSnapshot {\n const workers = this.storage ? this.readWorkers() : [{ ...this.currentRecord }];\n const routes = this.storage\n ? readAllByPrefix<WorkerRoute>(this.storage, this.routePrefix).map(({ value }) => ({\n ...value,\n topic: this.knownTopics.get(value.topicKey) ?? null\n }))\n : [];\n return {\n protocolVersion: CLUSTER_PROTOCOL_VERSION,\n peerProtocolVersions: Object.fromEntries(workers.map(worker => [worker.workerId, worker.protocolVersion ?? null])),\n coordinated: Boolean(this.storage && this.channel),\n suspended: this.suspended,\n currentWorker: { ...this.currentRecord },\n workers: workers.map(worker => ({ ...worker })),\n routes,\n subscribedTopics: Array.from(this.subscribedTopics),\n assignedTopics: Array.from(this.assignedTopics.values()),\n knownTopics: Array.from(this.knownTopics.entries(), ([topicKey, topic]) => ({ topicKey, topic })),\n routeOwnerCache: { size: this.routeOwnerCache.size, max: this.routeOwnerCacheMax, hits: this.routeOwnerCacheHits, misses: this.routeOwnerCacheMisses }\n };\n }\n\n private readonly handlePageHide = () => this.pause();\n\n private readonly handlePageShow = () => {\n if (!this.suspended) return;\n this.suspended = false;\n this.handlers.onResume?.();\n this.activate();\n };\n\n private readonly handleVisibilityChange = () => {\n const visibilityState = this.environment.getVisibilityState();\n if (visibilityState === this.currentRecord.visibilityState) return;\n this.currentRecord = { ...this.currentRecord, visibilityState };\n if (this.started) {\n this.writeRecord(true);\n this.reconcile();\n }\n };\n\n private addLifecycleListeners(): void {\n if (this.lifecycleListening) return;\n this.lifecycleListening = true;\n this.environment.addPageHideListener(this.handlePageHide);\n this.environment.addPageShowListener(this.handlePageShow);\n this.environment.addVisibilityChangeListener(this.handleVisibilityChange);\n }\n\n private removeLifecycleListeners(): void {\n if (!this.lifecycleListening) return;\n this.lifecycleListening = false;\n this.environment.removePageHideListener(this.handlePageHide);\n this.environment.removePageShowListener(this.handlePageShow);\n this.environment.removeVisibilityChangeListener(this.handleVisibilityChange);\n }\n\n /** Handle an incoming cluster message: dispatch by type to the per-type handlers. */\n private readonly handleMessage = (event: MessageEvent<WorkerClusterMessage>) => {\n const message = event.data;\n if (!message || message.sourceWorkerId === this.workerId) return;\n switch (message.type) {\n case CLUSTER_MESSAGE_TYPE.CONTROL:\n return this.handleControlMessage(message);\n case CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED:\n return this.handleRouteReleasedMessage(message);\n case CLUSTER_MESSAGE_TYPE.EVENT:\n this.handlers.onEvent(message.eventType, message.payload, message.sourceWorkerId, message.originTabId);\n return;\n case CLUSTER_MESSAGE_TYPE.REGISTRY:\n this.reconcile();\n return;\n default: {\n this.unknownMessageCount += 1;\n const unknown = message as unknown as { type?: unknown };\n this.lastUnknownMessageType = typeof unknown.type === 'string' ? unknown.type : null;\n this.handlers.onUnknownMessage?.(message);\n return;\n }\n }\n };\n\n /** Handle a point-to-point CONTROL message (SUBSCRIBE / UNSUBSCRIBE / PUBLISH). */\n private handleControlMessage(\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.CONTROL }>\n ): void {\n if (message.targetWorkerId !== this.workerId) return;\n this.rememberTopic(message.topic);\n switch (message.action) {\n case CONTROL_ACTION.SUBSCRIBE:\n this.assignedTopics.set(message.topicKey, message.topic);\n this.confirmRoute(message.topicKey);\n break;\n case CONTROL_ACTION.UNSUBSCRIBE:\n // A graceful handoff release short-circuits the generic dispatch.\n if (this.releaseHandoffOnUnsubscribe(message)) return;\n break;\n case CONTROL_ACTION.PUBLISH:\n if (message.items && message.items.length > 0) {\n // A batched CONTROL: hand the whole batch to the transport at once\n // when the owner supports it, preserving per-item metadata.\n if (this.handlers.onPublishBatch) {\n this.handlers.onPublishBatch(message.topic, message.items);\n return;\n }\n for (const item of message.items) {\n const itemMeta = publicationMetadata(item.messageId, item.timestamp);\n if (itemMeta) this.handlers.onControl(CONTROL_ACTION.PUBLISH, message.topic, item.data, itemMeta.messageId, itemMeta.timestamp);\n else this.handlers.onControl(CONTROL_ACTION.PUBLISH, message.topic, item.data);\n }\n return;\n }\n break;\n default:\n break;\n }\n const metadata = publicationMetadata(message.messageId, message.timestamp);\n if (metadata) this.handlers.onControl(\n message.action,\n message.topic,\n message.data,\n metadata.messageId,\n metadata.timestamp\n );\n else this.handlers.onControl(message.action, message.topic, message.data);\n if (message.action !== CONTROL_ACTION.PUBLISH) this.updateLoad();\n }\n\n /**\n * When this worker is the previous owner in a graceful handoff and the new\n * owner asks us to unsubscribe, release the old transport subscription and\n * ACK the handoff with ROUTE_RELEASED. Returns true when the message was a\n * handoff release (the generic CONTROL dispatch must not run as well).\n */\n private releaseHandoffOnUnsubscribe(\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.CONTROL }>\n ): boolean {\n this.assignedTopics.delete(message.topicKey);\n const route = this.readRoute(message.topicKey);\n if (route?.handoffFromWorkerId !== this.workerId) return false;\n this.handlers.onControl(CONTROL_ACTION.UNSUBSCRIBE, message.topic, undefined);\n this.sendRouteReleased(route.workerId, message.topic, message.topicKey, route.generation);\n this.updateLoad();\n return true;\n }\n\n /** Post a ROUTE_RELEASED ACK to the new owner, carrying the current route\n * generation so only the matching new owner may act on it. */\n private sendRouteReleased(\n targetWorkerId: string,\n topic: string,\n topicKey: string,\n generation: number\n ): void {\n this.send({\n type: CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED,\n sourceWorkerId: this.workerId,\n targetWorkerId,\n topic,\n topicKey,\n generation\n });\n }\n\n /**\n * Accept a graceful handoff only when the route still points to this worker,\n * the release comes from the recorded previous owner, and the generation is\n * at least as new as ours. Any other ROUTE_RELEASED is stale and dropped.\n */\n private handleRouteReleasedMessage(\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED }>\n ): void {\n if (message.targetWorkerId !== this.workerId) return;\n const route = this.readRoute(message.topicKey);\n if (!route || this.isStaleRouteRelease(route, message)) return;\n this.assignedTopics.set(message.topicKey, message.topic);\n this.confirmRoute(message.topicKey);\n this.handlers.onControl(CONTROL_ACTION.SUBSCRIBE, message.topic, undefined);\n this.updateLoad();\n }\n\n /** A ROUTE_RELEASED is stale (and must be dropped) unless the route still\n * points to us, the release comes from the recorded previous owner, and\n * the release generation is at least as new as ours. A replayed ACK from an\n * earlier handoff round (e.g. an a\u2194b ping-pong) carries an older generation\n * and must not confirm the current round. */\n private isStaleRouteRelease(\n route: WorkerRoute,\n message: Extract<WorkerClusterMessage, { type: typeof CLUSTER_MESSAGE_TYPE.ROUTE_RELEASED }>\n ): boolean {\n return (\n route.workerId !== this.workerId ||\n route.handoffFromWorkerId !== message.sourceWorkerId ||\n message.generation < route.generation\n );\n }\n\n /** True when an unconfirmed handoff route has been stuck longer than a\n * worker TTL. The ACK for a live handoff is posted synchronously with the\n * route write, so anything older than the TTL with a dead previous owner\n * will never complete \u2014 while a fresh unconfirmed route may simply be\n * waiting out its confirmation flush and must be left alone. */\n private isStaleHandoff(route: WorkerRoute): boolean {\n return this.environment.now() - route.updatedAt > this.workerTtlMs;\n }\n\n /** Full reconciliation cycle: workers, subscriptions, and assigned topics. */\n private reconcile(): void {\n if (!this.started) return;\n const workers = this.reconcileWorkers();\n const activeWorkers = selectActiveWorkers(workers, this.maxActiveWorkers);\n this.reconcileSubscriptions(workers, activeWorkers);\n this.reconcileAssignedTopics();\n this.updateLoad();\n }\n\n /** Prune stale workers/subscribers/routes and refresh role. Returns the live worker list.\n * Subscribers are cleaned before routes so cleanupOrphanedRoutes sees the\n * updated subscriber set when deciding whether a route is truly orphaned. */\n private reconcileWorkers(): WorkerRecord[] {\n const workers = this.readWorkers();\n this.cleanupOrphanedSubscribers(workers);\n this.cleanupOrphanedRoutes(workers);\n const roleChanged = this.refreshRole(workers);\n if (roleChanged) this.writeRecord(false);\n return workers;\n }\n\n /**\n * Ensure every local subscription has a route and write subscriber records.\n *\n * Existing routes are deliberately sticky while their owner Worker is alive.\n * Load and visibility only influence placement of a new route; they must not\n * move an already-subscribed Topic merely because another Tab joins or becomes\n * visible. Ownership changes only after the owner leaves or its heartbeat\n * expires, which avoids unnecessary transport subscribe/unsubscribe churn.\n */\n private reconcileSubscriptions(\n workers: readonly WorkerRecord[],\n activeWorkers: readonly WorkerRecord[]\n ): void {\n const liveWorkerIds = new Set(workers.map(worker => worker.workerId));\n // Projected loads for stranded-handoff re-elections within this pass,\n // mirroring handoffAssignedTopics(): without it, every stranded topic\n // would pile onto the same least-loaded worker from the pass-start\n // snapshot \u2014 and routes are sticky, so the imbalance would persist.\n const recoveryProjectedLoads = new Map<string, number>();\n\n for (const topic of this.subscribedTopics) {\n const topicKey = this.rememberTopic(topic);\n this.writeSubscriber(topicKey);\n const route = this.readRoute(topicKey);\n if (!route || !liveWorkerIds.has(route.workerId)) {\n const owner = selectLeastLoadedWorker(activeWorkers, undefined, this.loadWeighting) ?? this.currentRecord;\n // A route invalidated by owner departure or heartbeat expiry is\n // recovered immediately. Graceful pagehide uses the strict ACK path in\n // handoffAssignedTopics(), where the departing owner is still known.\n this.writeRoute(topicKey, owner, undefined, (route?.generation ?? 0) + 1);\n this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n this.notifyRegistry();\n continue;\n }\n if (route.confirmedAt === undefined) {\n // During a handoff, the new owner waits for ROUTE_RELEASED from the\n // previous owner. Retrying SUBSCRIBE here would recreate overlap.\n if (!route.handoffFromWorkerId) {\n this.sendControl(route.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n } else if (!liveWorkerIds.has(route.handoffFromWorkerId) && this.isStaleHandoff(route)) {\n // The previous owner is gone, its ROUTE_RELEASED never arrived\n // (dropped channel message under load, or a crash between the route\n // write and the ACK), AND the handoff has been stuck longer than a\n // worker TTL. Waiting longer cannot help \u2014 nobody remains who could\n // send the ACK \u2014 and the route would strand unconfirmed forever:\n // the new owner keeps waiting while peers treat the live new owner\n // as authoritative and stay out. Re-elect a live owner and clear\n // the handoff marker so the normal confirmation path can complete.\n // The age gate matters: a fresh handoff route may simply not have\n // its confirmation flushed through the batching writer yet, and a\n // peer reconciling in that window must not mistake it for a\n // stranded one. While the previous owner is still alive this branch\n // is unreachable, so the strict handoff keeps its no-overlap\n // guarantee.\n const owner = selectLeastLoadedWorker(\n activeWorkers.map(worker => ({ ...worker, load: recoveryProjectedLoads.get(worker.workerId) ?? worker.load })),\n undefined,\n this.loadWeighting\n ) ?? this.currentRecord;\n recoveryProjectedLoads.set(owner.workerId, (recoveryProjectedLoads.get(owner.workerId) ?? owner.load) + 1);\n // Single-writer rule: only the elected owner performs the\n // re-election. Peers that compute a different owner stand down and\n // wait for its write. Concurrent writes from divergent views would\n // ping-pong generations and drop confirmations \u2014 every fresh write\n // is unconfirmed by construction, so two writers rewriting the same\n // route keep invalidating each other's confirmations and re-send\n // SUBSCRIBEs every pass. Standing down is always safe: the elected\n // owner reconciles on its own heartbeat, and if views disagree this\n // round they converge on the next flush (bounded by one heartbeat),\n // after which every peer computes the same owner.\n // Exception: when the elected owner has no local subscription it\n // will never reconcile this topic, so standing down would stall\n // forever. Fall back to writing the route and notifying it\n // directly (assigning without a local subscription is exactly what\n // the graceful handoff and the crash path already do).\n if (owner.workerId !== this.workerId) {\n const subscriberTabIds = new Set(this.readSubscriberTabIds(topicKey, workers));\n const ownerSubscribed = workers.some(\n worker => worker.workerId === owner.workerId && subscriberTabIds.has(worker.tabId)\n );\n if (ownerSubscribed) continue;\n }\n this.writeRoute(topicKey, owner, undefined, route.generation + 1);\n this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);\n this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_MIGRATION_RECOVERY, topic });\n this.notifyRegistry();\n }\n }\n }\n }\n\n /** Drop assignments where the route no longer points to this worker. */\n private reconcileAssignedTopics(): void {\n for (const [topicKey, topic] of [...this.assignedTopics]) {\n const route = this.readRoute(topicKey);\n if (route?.workerId === this.workerId) continue;\n this.assignedTopics.delete(topicKey);\n this.handlers.onControl(CONTROL_ACTION.UNSUBSCRIBE, topic, undefined);\n if (route?.handoffFromWorkerId === this.workerId) {\n this.sendRouteReleased(route.workerId, topic, topicKey, route.generation);\n }\n if (!this.subscribedTopics.has(topic)) this.knownTopics.delete(topicKey);\n }\n }\n\n /**\n * Send a control message to `targetWorkerId`, or execute locally when targeting self.\n * Local execution updates the assignment map and route synchronously, bypassing\n * the BroadcastChannel latency.\n */\n private sendControl(\n targetWorkerId: string,\n action: WorkerControlAction,\n topic: string,\n topicKey: string,\n data?: unknown,\n metadata?: DataBusPublicationMetadata\n ): boolean {\n if (targetWorkerId === this.workerId) {\n switch (action) {\n case CONTROL_ACTION.SUBSCRIBE:\n this.assignedTopics.set(topicKey, topic);\n this.confirmRoute(topicKey);\n break;\n case CONTROL_ACTION.UNSUBSCRIBE:\n this.assignedTopics.delete(topicKey);\n break;\n case CONTROL_ACTION.PUBLISH:\n default:\n break;\n }\n if (metadata) this.handlers.onControl(\n action,\n topic,\n data,\n metadata.messageId,\n metadata.timestamp\n );\n else this.handlers.onControl(action, topic, data);\n if (action !== CONTROL_ACTION.PUBLISH) this.updateLoad();\n return true;\n }\n return this.send({\n type: CLUSTER_MESSAGE_TYPE.CONTROL,\n sourceWorkerId: this.workerId,\n targetWorkerId,\n action,\n topic,\n topicKey,\n ...(data === undefined ? {} : { data }),\n ...(metadata?.messageId === undefined ? {} : { messageId: metadata.messageId }),\n ...(metadata?.timestamp === undefined ? {} : { timestamp: metadata.timestamp })\n });\n }\n\n /** Post a message on the BroadcastChannel. Returns false on postMessage failure. */\n private send(message: WorkerClusterMessage): boolean {\n if (!this.channel) return false;\n try {\n this.channel.postMessage({ ...message, protocolVersion: CLUSTER_PROTOCOL_VERSION });\n return true;\n } catch {\n return false;\n }\n }\n\n /** Read all live worker records from storage, pruning stale entries past the TTL. */\n private readWorkers(): WorkerRecord[] {\n if (!this.storage) return [this.currentRecord];\n const now = this.environment.now();\n const workers: WorkerRecord[] = [];\n for (const { key, value: worker } of readAllByPrefix<WorkerRecord>(this.storage, this.workerPrefix)) {\n if (worker.workerId !== this.workerId && now - worker.heartbeatAt > this.workerTtlMs) {\n this.removeStorage(key);\n continue;\n }\n workers.push(worker);\n }\n if (this.started && !workers.some(worker => worker.workerId === this.workerId)) workers.push(this.currentRecord);\n return workers;\n }\n\n /** Enumerate all tab IDs that have a subscriber record for `topicKey`. */\n private readSubscriberTabIds(topicKey: string, workers: readonly WorkerRecord[]): string[] {\n if (!this.storage) {\n // Degraded mode: only this tab can be a subscriber. Recover the plaintext\n // topic to check local interest \u2014 without it we cannot know if we care.\n const topic = this.knownTopics.get(topicKey);\n return topic && this.subscribedTopics.has(topic) ? [this.tabId] : [];\n }\n const activeTabIds = new Set(workers.map(worker => worker.tabId));\n const subscribers = new Set<string>();\n for (const { key, value: record } of readAllByPrefix<TopicSubscriberRecord>(\n this.storage,\n `${this.subscriberPrefix}${topicKey}:`\n )) {\n if (!activeTabIds.has(record.tabId)) {\n this.removeStorage(key);\n continue;\n }\n subscribers.add(record.tabId);\n }\n return Array.from(subscribers);\n }\n\n /** Read the current route for `topicKey`, returning null when no storage layer exists. */\n private readRoute(topicKey: string): WorkerRoute | null {\n if (!this.storage) return this.buildLocalRoute(topicKey);\n return readJson<WorkerRoute>(this.storage, this.routeStorageKey(topicKey));\n }\n\n /** Synthesize a self-owned route when storage is unavailable (degraded mode).\n * The plaintext topic must be recoverable from the knownTopics cache; a\n * missing entry means we never subscribed to or were assigned the topic,\n * so there is no route to report. */\n private buildLocalRoute(topicKey: string): WorkerRoute | null {\n const topic = this.knownTopics.get(topicKey);\n if (!topic) return null;\n if (!this.subscribedTopics.has(topic) && !this.assignedTopics.has(topicKey)) return null;\n return {\n topicKey,\n workerId: this.workerId,\n tabId: this.tabId,\n updatedAt: this.environment.now(),\n generation: 1\n };\n }\n\n /** Persist a route assignment, mapping `topicKey` to the owning Worker. */\n private writeRoute(\n topicKey: string,\n owner: WorkerRecord,\n handoffFromWorkerId?: string,\n generation = 1\n ): void {\n if (!this.storage) return;\n writeJson(this.storage, this.routeStorageKey(topicKey), this.buildRouteRecord(topicKey, owner, handoffFromWorkerId, generation));\n }\n\n /** Construct a WorkerRoute record from the owner + handoff fields. Extracted\n * so writeRoute and confirmRoute share the same shape; confirmedAt is added\n * by confirmRoute via spread. */\n private buildRouteRecord(\n topicKey: string,\n owner: WorkerRecord,\n handoffFromWorkerId: string | undefined,\n generation: number\n ): WorkerRoute {\n return {\n topicKey,\n workerId: owner.workerId,\n tabId: owner.tabId,\n updatedAt: this.environment.now(),\n generation,\n ...(handoffFromWorkerId ? { handoffFromWorkerId } : {})\n };\n }\n\n /** Stamp a route as confirmed once the owning Worker has acknowledged the assignment. */\n private confirmRoute(topicKey: string): void {\n if (!this.storage) return;\n const route = this.readRoute(topicKey);\n if (!route || route.workerId !== this.workerId || route.confirmedAt !== undefined) return;\n writeJson(this.storage, this.routeStorageKey(topicKey), {\n ...route,\n confirmedAt: this.environment.now()\n } satisfies WorkerRoute);\n const topic = this.knownTopics.get(topicKey);\n if (topic) this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_ACK, topic });\n }\n\n /** Remove routes whose topic has no subscribers and whose TTL has expired. */\n private cleanupOrphanedRoutes(workers: readonly WorkerRecord[]): void {\n if (!this.storage) return;\n const now = this.environment.now();\n for (const { key, value: route } of readAllByPrefix<WorkerRoute>(this.storage, this.routePrefix)) {\n if (now - route.updatedAt <= this.workerTtlMs) continue;\n if (this.readSubscriberTabIds(route.topicKey, workers).length > 0) continue;\n this.removeStorage(key);\n }\n }\n\n /** Remove subscriber records for tabs that are no longer active. */\n private cleanupOrphanedSubscribers(workers: readonly WorkerRecord[]): void {\n if (!this.storage) return;\n const activeTabIds = new Set(workers.map(worker => worker.tabId));\n for (const { key, value: record } of readAllByPrefix<TopicSubscriberRecord>(this.storage, this.subscriberPrefix)) {\n if (!activeTabIds.has(record.tabId)) this.removeStorage(key);\n }\n }\n\n /** Persist a subscriber record for this tab on `topicKey`. */\n private writeSubscriber(topicKey: string): void {\n if (!this.storage) return;\n writeJson(this.storage, this.subscriberStorageKey(topicKey, this.tabId), {\n tabId: this.tabId,\n updatedAt: this.environment.now()\n } satisfies TopicSubscriberRecord);\n }\n\n /** Persist the current worker record with an updated heartbeat timestamp.\n * @param notify \u2014 when true, broadcast a REGISTRY nudge so peers reconcile\n * immediately instead of waiting for the next heartbeat. False on the\n * periodic heartbeat tick (peers will notice on their own heartbeat) to\n * avoid a REGISTRY storm every 3 s; true on status/role changes that\n * peers should observe promptly. */\n private writeRecord(notify: boolean): void {\n const now = this.environment.now();\n const sample = this.loadWeighting !== undefined ? this.sampleThroughput(now) : undefined;\n this.currentRecord = {\n ...this.currentRecord,\n heartbeatAt: now,\n ...(sample ? { throughput: sample } : {})\n };\n if (this.storage) writeJson(this.storage, this.workerStorageKey(this.workerId), this.currentRecord);\n if (notify) this.notifyRegistry();\n }\n\n /** Broadcast a REGISTRY message to trigger reconciliation on other tabs. */\n private notifyRegistry(): void {\n this.send({ type: CLUSTER_MESSAGE_TYPE.REGISTRY, sourceWorkerId: this.workerId });\n }\n\n /** Recompute whether this worker is active (eligible to own topics) or standby. Returns true when changed. */\n private refreshRole(workers: readonly WorkerRecord[]): boolean {\n const role: WorkerRole = this.isActiveAmong(workers) ? WORKER_ROLE.ACTIVE : WORKER_ROLE.STANDBY;\n if (role === this.currentRecord.role) return false;\n this.currentRecord = { ...this.currentRecord, role };\n return true;\n }\n\n /** Persist the current topic load count (number of assigned topics) for load-balanced routing. */\n private updateLoad(): void {\n const load = this.assignedTopics.size;\n if (load === this.currentRecord.load) return;\n this.currentRecord = { ...this.currentRecord, load };\n if (this.started) this.writeRecord(true);\n }\n\n /**\n * Hash `topic` into its opaque key and populate the reverse-lookup cache.\n *\n * Despite the name, this is NOT a cache lookup \u2014 it unconditionally writes\n * the `topicKey \u2192 topic` pair. Hashing is cheap enough that a caller needing\n * the key should always call this rather than check `knownTopics` first;\n * the cache's FIFO eviction below keeps it bounded. Only `isAssigned`\n * deliberately bypasses this (it must not pollute the cache on a read-only\n * query), so if you add a new call site, prefer `rememberTopic` unless you\n * have the same \"read-only query\" reason.\n */\n private rememberTopic(topic: string): string {\n const topicKey = createOpaqueKey(topic);\n this.knownTopics.set(topicKey, topic);\n // Evict the oldest entry when the cache exceeds its cap. Plain Map iteration\n // order is insertion order, so deleting the first key is FIFO eviction (not\n // true LRU \u2014 reads do not promote recency). Hashing is cheap, so a missed\n // reverse-lookup merely recomputes the key, but the storage-less fallback\n // path (readSubscriberTabIds/readRoute) relies on this cache to recover the\n // plaintext topic. Never evict a key this worker still owns, or those reads\n // would silently return null for an assigned topic.\n if (this.knownTopics.size > MAX_KNOWN_TOPICS) {\n // Evict the oldest non-owned entry (FIFO). Scan from the front so the\n // cap holds as long as at least one tracked topic is not owned. Only\n // when every entry is owned (degenerate) do we let the cap slip \u2014 owned\n // topics must stay resolvable for the storage-less read path.\n // Scan from the front (oldest insertion) for the first non-owned entry.\n // `break` after one eviction: we only need to get back under the cap, and\n // evicting more would unnecessarily drop resolvable topics. If every\n // entry is owned (degenerate), the loop completes without evicting \u2014\n // owned topics must stay resolvable for the storage-less read path.\n for (const candidate of this.knownTopics.keys()) {\n if (candidate === topicKey || this.assignedTopics.has(candidate)) continue;\n this.knownTopics.delete(candidate);\n break;\n }\n }\n return topicKey;\n }\n\n private workerStorageKey(workerId: string): string {\n return `${this.workerPrefix}${workerId}`;\n }\n\n private routeStorageKey(topicKey: string): string {\n return `${this.routePrefix}${topicKey}`;\n }\n\n private subscriberStorageKey(topicKey: string, tabId: string): string {\n return `${this.subscriberPrefix}${topicKey}:${tabId}`;\n }\n\n private removeStorage(key: string): void {\n try {\n this.storage?.removeItem(key);\n } catch {\n // Ignore unavailable storage.\n }\n }\n\n /** Force-flush any pending batched writes (used during shutdown/teardown). */\n private flushStorage(): void {\n if (this.storage instanceof BatchingStorageWriter) this.storage.flush();\n }\n}\n", "/**\n * DataBusTraceReporter \u2014 optional diagnostics, metrics, and delivery latency.\n *\n * Aggregates message throughput and dispatch latency over configurable windows,\n * emitting structured events (lifecycle, status, subscription, coordination,\n * error) and periodic metrics summaries. The sink is decoupled from the hot\n * message path \u2014 errors in the sink are isolated to console.warn.\n */\nimport type { WorkerStatus } from './types';\nimport type {\n PERSISTENCE_OPERATION,\n RECOVERY_OUTCOME,\n RELIABILITY_OPERATION,\n SUBSCRIPTION_ACTION,\n TRACE_ERROR_SOURCE,\n TRACE_LIFECYCLE_ACTION\n} from '../utils/constants';\nimport {\n DEFAULT_STORAGE_PREFIX,\n TRACE_EVENT_TYPE,\n TRACE_MODE\n} from '../utils/constants';\n\n/** Trace reporting mode: record only events, only metrics, or both. */\n/** Selects which trace categories the reporter emits.\n * - `events` \u2014 lifecycle/status/subscription/coordination/error events only.\n * - `metrics` \u2014 periodic `message_metrics` snapshots only.\n * - `all` \u2014 both event streams and metrics snapshots. */\nexport type DataBusTraceMode = (typeof TRACE_MODE)[keyof typeof TRACE_MODE];\n\n/** Emitted when the DataBus starts, stops, suspends, or resumes. */\nexport interface DataBusLifecycleTraceEvent {\n type: typeof TRACE_EVENT_TYPE.LIFECYCLE;\n action: (typeof TRACE_LIFECYCLE_ACTION)[keyof typeof TRACE_LIFECYCLE_ACTION];\n timestamp: number;\n}\n\n/** Emitted when the transport connection status changes. */\nexport interface DataBusStatusTraceEvent {\n type: typeof TRACE_EVENT_TYPE.STATUS;\n status: WorkerStatus;\n timestamp: number;\n}\n\n/** Emitted when a topic subscription is added or removed. */\nexport interface DataBusSubscriptionTraceEvent {\n type: typeof TRACE_EVENT_TYPE.SUBSCRIPTION;\n action: (typeof SUBSCRIPTION_ACTION)[keyof typeof SUBSCRIPTION_ACTION];\n topic: string;\n activeTopics: number;\n timestamp: number;\n}\n\n/** Emitted after each transport open (initial start and recovery) to record\n * the coordinated cluster state, including the settled route list. */\nexport interface DataBusCoordinationTraceEvent {\n type: typeof TRACE_EVENT_TYPE.COORDINATION;\n coordinated: boolean;\n activeWorkers: number;\n workers: string[];\n routes: string[];\n timestamp: number;\n}\n\n/** Emitted when a transport or operation error occurs. */\nexport interface DataBusErrorTraceEvent {\n type: typeof TRACE_EVENT_TYPE.ERROR;\n source: (typeof TRACE_ERROR_SOURCE)[keyof typeof TRACE_ERROR_SOURCE];\n timestamp: number;\n}\n\n/** Bounded reliability diagnostics for recovery, acknowledgments, and migrations. */\nexport interface DataBusReliabilityTraceEvent {\n type: typeof TRACE_EVENT_TYPE.RELIABILITY;\n operation: (typeof RELIABILITY_OPERATION)[keyof typeof RELIABILITY_OPERATION];\n topic?: string;\n persistenceOperation?: (typeof PERSISTENCE_OPERATION)[keyof typeof PERSISTENCE_OPERATION];\n attempt?: number;\n /** Outcome for a transport recovery attempt. */\n outcome?: (typeof RECOVERY_OUTCOME)[keyof typeof RECOVERY_OUTCOME];\n durationMs?: number;\n timestamp: number;\n}\n\n/**\n * Periodic metrics snapshot: message throughput, dispatch latency percentiles,\n * and active topic count. Aggregated over the interval and emitted every\n * `metricsIntervalMs`.\n */\nexport interface DataBusMetricsTraceEvent {\n type: typeof TRACE_EVENT_TYPE.MESSAGE_METRICS;\n durationMs: number;\n received: number;\n dispatched: number;\n topics: number;\n dispatchSamples: number;\n dispatchAvgMs: number;\n dispatchP50Ms: number;\n dispatchP95Ms: number;\n dispatchMaxMs: number;\n dedupAccepted: number;\n dedupSuppressed: number;\n timestamp: number;\n}\n\n/**\n * Synchronous metrics snapshot: same derived counters as a periodic\n * `message_metrics` event, but queryable on demand (e.g. from\n * `getDiagnostics()`) without a sink or an interval flush, and without\n * resetting the aggregation window.\n */\nexport interface DataBusMetricsSnapshot {\n /** Milliseconds elapsed in the current aggregation window. */\n durationMs: number;\n /** Messages received in the window. */\n received: number;\n /** Messages dispatched locally in the window. */\n dispatched: number;\n /** Distinct topics touched in the window. */\n topics: number;\n /** Latency samples collected in the window. */\n dispatchSamples: number;\n dispatchAvgMs: number;\n dispatchP50Ms: number;\n dispatchP95Ms: number;\n dispatchMaxMs: number;\n dedupAccepted: number;\n dedupSuppressed: number;\n timestamp: number;\n}\n\nexport type DataBusTraceEvent =\n | DataBusLifecycleTraceEvent\n | DataBusStatusTraceEvent\n | DataBusSubscriptionTraceEvent\n | DataBusCoordinationTraceEvent\n | DataBusErrorTraceEvent\n | DataBusReliabilityTraceEvent\n | DataBusMetricsTraceEvent;\n\n/** Distributive-conditional type: given a trace event union, derive the same shape minus `timestamp`. */\ntype DataBusTraceEventInput = DataBusTraceEvent extends infer TEvent\n ? TEvent extends DataBusTraceEvent\n ? Omit<TEvent, 'timestamp'>\n : never\n : never;\n\n/** Configuration for {@link DataBusTraceReporter}. `sink` receives every\n * emitted event (filtered by `mode`); all other fields are optional. */\nexport interface DataBusTraceOptions {\n /** When `false`, the reporter is inert (no events emitted). Default `true`. */\n enabled?: boolean;\n /** Which event categories to emit. Default `all`. */\n mode?: DataBusTraceMode;\n /** Aggregation window for `message_metrics` events. Default 5 s. */\n metricsIntervalMs?: number;\n /** Injectable epoch clock for deterministic metrics and lifecycle tests. */\n now?: () => number;\n /** Callback invoked for each emitted trace event. */\n sink: (event: DataBusTraceEvent) => void;\n /** Queue sink delivery onto a microtask to keep hot paths non-blocking. */\n asyncSink?: boolean;\n}\n\n// Default bounds for the metrics aggregation window.\n/** Default metrics aggregation window: 5 s between snapshots. */\nconst DEFAULT_METRICS_INTERVAL_MS = 5_000;\nconst MAX_PENDING_TOPICS = 1_000;\nconst MAX_PENDING_MESSAGES_PER_TOPIC = 256;\n// Latency histogram: 20 buckets, each 50ms wide \u2192 covers 0\u20131000ms.\nconst LATENCY_BUCKET_COUNT = 20;\nconst LATENCY_BUCKET_SIZE_MS = 50;\n\n/**\n * Aggregates DataBus diagnostics \u2014 lifecycle events, status changes, and\n * periodic latency histograms \u2014 and forwards them to a user-supplied sink.\n *\n * Latency is measured in a bucketed histogram (20 buckets \u00D7 50ms) rather than\n * storing every sample, keeping memory bounded even under high throughput.\n */\nexport class DataBusTraceReporter {\n private readonly enabled: boolean;\n private readonly mode: DataBusTraceMode;\n private readonly metricsIntervalMs: number;\n private readonly sink: (event: DataBusTraceEvent) => void;\n private readonly now: () => number;\n private readonly asyncSink: boolean;\n private pendingEvents: DataBusTraceEvent[] = [];\n private sinkFlushScheduled = false;\n private intervalHandle: ReturnType<typeof setInterval> | null = null;\n private intervalStartedAt = 0;\n // A reporter may be flushed explicitly before start(), but once stop() is\n // called it must remain inert until a new start() begins another session.\n private stopped = false;\n private received = 0;\n private dispatched = 0;\n private latencySamples = 0;\n private readonly topics = new Set<string>();\n // Per-topic FIFO of received timestamps, used to compute dispatch latency.\n private readonly receivedAt = new Map<string, number[]>();\n // Bucketed histogram: bucket index = floor(delayMs / 50), capped at 19.\n private readonly latencyBuckets = new Array<number>(LATENCY_BUCKET_COUNT).fill(0);\n private latencySumMs = 0;\n private dedupAccepted = 0;\n private dedupSuppressed = 0;\n\n constructor(options?: DataBusTraceOptions, now: () => number = options?.now ?? Date.now) {\n this.enabled = options?.enabled ?? false;\n this.mode = options?.mode ?? 'all';\n this.metricsIntervalMs = normalizeInterval(options?.metricsIntervalMs);\n this.sink = options?.sink ?? (() => undefined);\n this.asyncSink = options?.asyncSink ?? false;\n this.now = now;\n }\n\n /** Start the periodic metrics flush interval. No-op when mode is 'events'\n * (no metrics to emit), when disabled, or when already running. */\n start(): void {\n if (!this.enabled) return;\n this.stopped = false;\n if (this.intervalHandle || this.mode === TRACE_MODE.EVENTS) return;\n this.intervalStartedAt = this.now();\n this.intervalHandle = setInterval(() => this.flush(), this.metricsIntervalMs);\n }\n\n /** Pause the metrics interval and reset accumulated counters. */\n pause(): void {\n if (this.intervalHandle) clearInterval(this.intervalHandle);\n this.intervalHandle = null;\n this.intervalStartedAt = 0;\n this.resetMetrics();\n }\n\n stop(): void {\n this.stopped = true;\n // Events produced before stop are part of the old session and must not\n // leak through the queued async-sink microtask after teardown.\n this.pendingEvents = [];\n this.pause();\n }\n\n /** Synchronous sink state for diagnostics: whether delivery is async and how\n * many events are queued behind the microtask flush. A growing queue under\n * `asyncSink: true` is the first sign of sink back-pressure. */\n getSinkState(): { asyncSink: boolean; pendingEvents: number } {\n return { asyncSink: this.asyncSink, pendingEvents: this.pendingEvents.length };\n }\n\n /** Record an instantaneous trace event (lifecycle, status, error, etc.). */\n event(event: DataBusTraceEventInput): void {\n if (!this.enabled || this.stopped || this.mode === TRACE_MODE.METRICS) return;\n this.emit({ ...event, timestamp: this.now() } as DataBusTraceEvent);\n }\n\n /** Synchronous snapshot of the current metrics window without resetting it.\n * Returns the same derived counters as a periodic `message_metrics` event,\n * or null when metrics recording is inactive (disabled or events-only mode).\n * The window keeps accumulating until the next interval flush. */\n getMetrics(): DataBusMetricsSnapshot | null {\n if (!this.metricsActive || this.stopped) return null;\n const timestamp = this.now();\n const samples = this.latencySamples;\n return {\n durationMs: Math.max(0, timestamp - this.intervalStartedAt),\n received: this.received,\n dispatched: this.dispatched,\n topics: this.topics.size,\n dispatchSamples: samples,\n dispatchAvgMs: roundMs(samples === 0 ? 0 : this.latencySumMs / samples),\n dispatchP50Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.5)),\n dispatchP95Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.95)),\n dispatchMaxMs: roundMs(percentileMs(this.latencyBuckets, samples, 1)),\n dedupAccepted: this.dedupAccepted,\n dedupSuppressed: this.dedupSuppressed,\n timestamp\n };\n }\n\n /** Record that a message was received on `topic`; stores its timestamp for latency tracking. */\n recordReceived(topic: string): void {\n if (!this.metricsActive) return;\n this.received += 1;\n this.topics.add(topic);\n const queue = this.receivedAt.get(topic);\n // First receive for this topic creates a new FIFO queue (subject to the\n // topic cap); subsequent receives append to the existing queue (subject to\n // the per-topic cap). Both caps prevent a single misbehaving topic from\n // exhausting memory.\n if (!queue) {\n if (this.receivedAt.size >= MAX_PENDING_TOPICS) return;\n this.receivedAt.set(topic, [this.now()]);\n return;\n }\n if (queue.length >= MAX_PENDING_MESSAGES_PER_TOPIC) return;\n queue.push(this.now());\n }\n\n /**\n * Record that a received message will never be dispatched locally. Pops the\n * matching FIFO slot so a later dispatch on the same topic does not pair\n * with a stale receive timestamp.\n */\n recordDiscarded(topic: string): void {\n if (!this.metricsActive) return;\n const queue = this.receivedAt.get(topic);\n if (!queue) return;\n queue.shift();\n if (queue.length === 0) this.receivedAt.delete(topic);\n }\n\n /**\n * Record that a message was dispatched on `topic`. Pops the oldest receive\n * timestamp (FIFO) and increments the latency histogram. Dispatches without\n * a matching receive (e.g. broadcast fan-out from another tab) still count\n * as dispatched but do not produce a latency sample.\n */\n recordDispatched(topic: string): void {\n if (!this.metricsActive) return;\n this.dispatched += 1;\n this.topics.add(topic);\n const queue = this.receivedAt.get(topic);\n const receivedTimestamp = queue?.shift();\n if (queue && queue.length === 0) this.receivedAt.delete(topic);\n if (receivedTimestamp === undefined) return;\n this.latencySamples += 1;\n const delayMs = Math.max(0, this.now() - receivedTimestamp);\n // Map the raw delay to a 50ms-wide bucket, capped at the last bucket.\n const bucketIndex = Math.min(LATENCY_BUCKET_COUNT - 1, Math.floor(delayMs / LATENCY_BUCKET_SIZE_MS));\n this.latencyBuckets[bucketIndex] = (this.latencyBuckets[bucketIndex] ?? 0) + 1;\n this.latencySumMs += delayMs;\n }\n\n /** Record deduplication outcomes for the next metrics window. */\n recordDedupAccepted(): void {\n if (this.metricsActive) this.dedupAccepted += 1;\n }\n\n recordDedupSuppressed(): void {\n if (this.metricsActive) this.dedupSuppressed += 1;\n }\n\n /** True when metrics recording is active: enabled and mode includes metrics.\n * Extracted so the four record / flush methods share one guard expression\n * instead of repeating `!this.enabled || this.mode === 'events'` at each. */\n private get metricsActive(): boolean {\n return this.enabled && !this.stopped && this.mode !== TRACE_MODE.EVENTS;\n }\n\n /** Emit the accumulated metrics snapshot if the interval is active. */\n flush(): void {\n if (!this.metricsActive || this.stopped) return;\n this.flushNow();\n }\n\n private flushNow(): void {\n const timestamp = this.now();\n // Only emit when there was activity in this window \u2014 an all-zero metrics\n // snapshot adds noise without information. The interval still advances\n // intervalStartedAt so the next window's duration is measured correctly.\n if (this.received > 0 || this.dispatched > 0 || this.dedupAccepted > 0 || this.dedupSuppressed > 0) {\n const samples = this.latencySamples;\n this.emit({\n type: TRACE_EVENT_TYPE.MESSAGE_METRICS,\n durationMs: Math.max(0, timestamp - this.intervalStartedAt),\n received: this.received,\n dispatched: this.dispatched,\n topics: this.topics.size,\n dispatchSamples: samples,\n dispatchAvgMs: roundMs(samples === 0 ? 0 : this.latencySumMs / samples),\n // Percentiles are derived from the histogram, not sorted samples.\n dispatchP50Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.5)),\n dispatchP95Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.95)),\n dispatchMaxMs: roundMs(percentileMs(this.latencyBuckets, samples, 1)),\n dedupAccepted: this.dedupAccepted,\n dedupSuppressed: this.dedupSuppressed,\n timestamp\n });\n this.resetMetrics();\n }\n this.intervalStartedAt = timestamp;\n }\n\n private resetMetrics(): void {\n this.received = 0;\n this.dispatched = 0;\n this.latencySamples = 0;\n this.topics.clear();\n this.receivedAt.clear();\n this.latencyBuckets.fill(0);\n this.latencySumMs = 0;\n this.dedupAccepted = 0;\n this.dedupSuppressed = 0;\n }\n\n private emit(event: DataBusTraceEvent): void {\n if (this.asyncSink) {\n this.pendingEvents.push(event);\n if (!this.sinkFlushScheduled) {\n this.sinkFlushScheduled = true;\n queueMicrotask(() => {\n this.sinkFlushScheduled = false;\n const events = this.pendingEvents;\n this.pendingEvents = [];\n for (const queued of events) this.emitSync(queued);\n });\n }\n return;\n }\n this.emitSync(event);\n }\n\n private emitSync(event: DataBusTraceEvent): void {\n try {\n this.sink(event);\n } catch (error) {\n // Diagnostics must never affect data delivery, but surface a broken sink\n // so instrumentation bugs are not silently hidden.\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(`[${DEFAULT_STORAGE_PREFIX}] trace sink threw:`, error);\n }\n }\n }\n}\n\n/** Validate the metrics interval, falling back to the default when omitted.\n * @throws {RangeError} when `value` is not a positive finite number. */\nfunction normalizeInterval(value: number | undefined): number {\n if (value === undefined) return DEFAULT_METRICS_INTERVAL_MS;\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError('trace.metricsIntervalMs must be a positive finite number.');\n }\n return value;\n}\n\n/**\n * Approximate a percentile from the bucketed histogram. Walks buckets in\n * order, accumulating counts until the cumulative total reaches the rank\n * (`percentile * sampleCount`), and returns the bucket's midpoint as the\n * estimate. Returns the histogram ceiling when the rank exceeds all counts.\n */\nfunction percentileMs(buckets: readonly number[], sampleCount: number, percentile: number): number {\n if (sampleCount <= 0) return 0;\n const rank = Math.max(1, Math.ceil(percentile * sampleCount));\n let seen = 0;\n for (let index = 0; index < buckets.length; index += 1) {\n seen += buckets[index] ?? 0;\n if (seen >= rank) return (index + 0.5) * LATENCY_BUCKET_SIZE_MS;\n }\n return buckets.length * LATENCY_BUCKET_SIZE_MS;\n}\n\n/** Round to one decimal place for stable, readable metrics output.\n * Avoids floating-point noise like 12.300000000001 in the trace sink. */\nfunction roundMs(value: number): number {\n return Math.round(value * 10) / 10;\n}\n", "import type { DataBusMessage } from './types';\nimport { PRUNE_STRATEGY } from '../utils/constants';\n\n/** Inputs shared by the in-memory ring and durable replay adapters. */\nexport interface ReplayPruningOptions {\n maxPerTopic: number;\n pruneStrategy: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n retentionMs: number | undefined;\n now: number;\n}\n\n/**\n * Apply the public replay pruning policy to an insertion-ordered history.\n *\n * `count` keeps the newest `maxPerTopic` entries. `both` applies the retention\n * cutoff first and then the count cap. `age` intentionally leaves timestamped\n * entries uncapped so the retention window is the only bound for them, while\n * timestamp-less legacy entries are still capped by `maxPerTopic` because they\n * have no timestamp by which they can ever expire. The returned array is the\n * same instance when no entries need to be removed.\n */\nexport function pruneReplayHistory<TData>(\n messages: DataBusMessage<TData>[],\n options: ReplayPruningOptions\n): DataBusMessage<TData>[] {\n const { maxPerTopic, pruneStrategy, retentionMs, now } = options;\n const ageEnabled = pruneStrategy !== PRUNE_STRATEGY.COUNT && retentionMs !== undefined;\n if (!ageEnabled) {\n return messages.length > maxPerTopic ? messages.slice(-maxPerTopic) : messages;\n }\n\n const cutoff = now - retentionMs;\n let hasExpired = false;\n let timestamplessCount = 0;\n for (const message of messages) {\n if (message.timestamp === undefined) timestamplessCount += 1;\n else if (message.timestamp < cutoff) hasExpired = true;\n }\n\n let pruned = hasExpired\n ? messages.filter(message => message.timestamp === undefined || message.timestamp >= cutoff)\n : messages;\n\n if (pruneStrategy === PRUNE_STRATEGY.BOTH) {\n return pruned.length > maxPerTopic ? pruned.slice(-maxPerTopic) : pruned;\n }\n\n if (timestamplessCount <= maxPerTopic) return pruned;\n let timestamplessToDrop = timestamplessCount - maxPerTopic;\n pruned = pruned.filter(message => {\n if (message.timestamp === undefined && timestamplessToDrop > 0) {\n timestamplessToDrop -= 1;\n return false;\n }\n return true;\n });\n return pruned;\n}\n", "/**\n * ReplayManager \u2014 bounded per-topic replay history with optional durable\n * persistence.\n *\n * Extracted from CrossTabDataBus so the ring buffers, IndexedDB append/load\n * lifecycle, retention cleanup, and retry policy live in one self-contained\n * unit. The DataBus keeps a thin delegation: `record()` on dispatch,\n * `deliverReplay()` on late-joining handlers, `start`/`stop`/`suspend()` on\n * lifecycle transitions, and `clear*()` on the public replay API.\n *\n * Replay is opt-in: an instance is created with `enabled: false` when the\n * DataBus has no `replay` options, making the zero-overhead default (no ring,\n * no timer, no persistence calls) explicit.\n *\n * Persistence failures are reported through the injected `onPersistenceError`\n * sink (the DataBus routes these to its persistence failure ledger and health\n * summary). Transient failures are retried with exponential backoff; a\n * `PersistenceRetryCancelledError` is thrown when a lifecycle transition\n * (suspend/stop) supersedes the in-flight operation, and is swallowed by the\n * DataBus's persistence error sink so teardown never surfaces noise.\n */\nimport { isWildcardTopic, topicMatchesPattern } from './routing';\nimport { pruneReplayHistory } from './replay-pruning';\nimport type { DataBusReplayPersistence } from './replay-persistence';\nimport type { DataBusTraceReporter } from './trace';\nimport type { DataBusMessage, DataBusMessageHandler } from './types';\nimport { approximatePayloadBytes } from './routing';\nimport { PERSISTENCE_OPERATION, RELIABILITY_OPERATION, TRACE_EVENT_TYPE } from '../utils/constants';\nimport type { PRUNE_STRATEGY } from '../utils/constants';\n\n/** Thrown when a lifecycle transition cancels an in-flight persistence retry. */\nexport class PersistenceRetryCancelledError extends Error {\n constructor() {\n super('Persistence retry cancelled by lifecycle transition.');\n this.name = 'PersistenceRetryCancelledError';\n }\n}\n\n/** Resolved constructor options after the DataBus applies defaults. */\nexport interface ReplayManagerDeps<TData = unknown> {\n /** Whether replay buffering is enabled at all (false \u2192 no-op instance). */\n enabled: boolean;\n /** Per-topic count cap. AGE bounds timestamped entries by retention and\n * still applies this cap to timestamp-less legacy entries. */\n maxPerTopic: number;\n /** Optional durable history backend; null \u2192 in-memory only. */\n persistence?: DataBusReplayPersistence<TData> | null;\n /** Optional producer-timestamp retention window in milliseconds. */\n retentionMs?: number | undefined;\n /** History trimming policy. */\n pruneStrategy: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n /** Optional periodic sweep interval for durable retention cleanup. */\n retentionSweepMs?: number | undefined;\n /** Total persistence attempts including the initial operation. */\n persistenceRetryMaxAttempts: number;\n /** Initial delay between persistence retry attempts. */\n persistenceRetryBackoffMs: number;\n /** Injectable epoch clock; used for retention cutoffs. */\n now: () => number;\n /** Trace sink for persistence retry/cleanup diagnostics. */\n trace: DataBusTraceReporter;\n /** Sink for persistence failures (routes to the DataBus failure ledger). */\n onPersistenceError: (error: unknown) => void;\n /** Sink for a throwing replay-delivery handler (dispatch error source). */\n onDispatchError: (error: unknown) => void;\n}\n\n/** Cap on the exponential backoff delay (ms) for persistence retries. */\nconst MAX_RETRY_DELAY_MS = 1_600;\n\nexport class ReplayManager<TData = unknown> {\n private readonly buffers: Map<string, DataBusMessage<TData>[]> | null;\n private readonly maxPerTopic: number;\n private readonly persistence: DataBusReplayPersistence<TData> | null;\n private readonly retentionMs: number | undefined;\n private readonly pruneStrategy: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n private readonly retentionSweepMs: number | undefined;\n private readonly persistenceRetryMaxAttempts: number;\n private readonly persistenceRetryBackoffMs: number;\n private readonly now: () => number;\n private readonly trace: DataBusTraceReporter;\n private readonly onPersistenceError: (error: unknown) => void;\n private readonly onDispatchError: (error: unknown) => void;\n /** Bumped on suspend/stop so in-flight persistence retries are cancelled. */\n private retryGeneration = 0;\n private pendingReplayPersistence: DataBusMessage<TData>[] = [];\n private persistenceFlushScheduled = false;\n private readonly hydration: Promise<void>;\n /** Coalesced retention cleanup: the newest cutoff wins while one is running. */\n private retentionCleanup: Promise<void> | null = null;\n private retentionCutoff: number | null = null;\n private retentionTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(private readonly deps: ReplayManagerDeps<TData>) {\n this.buffers = deps.enabled ? new Map() : null;\n this.maxPerTopic = deps.maxPerTopic;\n this.persistence = deps.persistence ?? null;\n this.retentionMs = deps.retentionMs;\n this.pruneStrategy = deps.pruneStrategy;\n this.retentionSweepMs = deps.retentionSweepMs;\n this.persistenceRetryMaxAttempts = deps.persistenceRetryMaxAttempts;\n this.persistenceRetryBackoffMs = deps.persistenceRetryBackoffMs;\n this.now = deps.now;\n this.trace = deps.trace;\n this.onPersistenceError = deps.onPersistenceError;\n this.onDispatchError = deps.onDispatchError;\n this.hydration = this.hydrate();\n }\n\n /** True when replay buffering is enabled. */\n get enabled(): boolean {\n return this.buffers !== null;\n }\n\n /** Append a dispatched publication to the topic's replay ring buffer.\n * No-op when replay is disabled. */\n record(message: DataBusMessage<TData>): void {\n if (!this.buffers) return;\n let buffer = this.buffers.get(message.topic);\n if (!buffer) {\n buffer = [];\n this.buffers.set(message.topic, buffer);\n }\n // Preserve the public message shape for legacy adapters. Retention pruning\n // applies to messages that carry an explicit producer timestamp.\n buffer.push(message);\n const pruned = pruneReplayHistory(buffer, {\n maxPerTopic: this.maxPerTopic,\n pruneStrategy: this.pruneStrategy,\n retentionMs: this.retentionMs,\n now: this.now()\n });\n if (pruned !== buffer) {\n buffer = pruned;\n this.buffers.set(message.topic, buffer);\n }\n if (!this.persistence) return;\n if (this.persistence.appendBatch) {\n this.pendingReplayPersistence.push(message);\n this.schedulePersistenceFlush();\n } else {\n void this.withPersistenceRetry(PERSISTENCE_OPERATION.APPEND, () => this.persistence!.append(message))\n .catch(error => this.onPersistenceError(error));\n }\n if (this.retentionMs !== undefined && this.persistence.clearBefore) {\n this.scheduleRetentionCleanup(this.now() - this.retentionMs);\n }\n }\n\n /** Deliver buffered history to a newly-registered handler. For an exact\n * topic this is that topic's ring; for a wildcard subscription every\n * buffered topic matching the pattern contributes (in buffer insertion\n * order). Replay deliveries are marked `replayed: true` and are not counted\n * into trace metrics.\n *\n * When a durable persistence backend is present, delivery waits for the\n * hydration load to settle first; `isHandlerActive` is then consulted so a\n * handler that unsubscribed during the async load does not receive history.\n */\n deliverReplay(\n topic: string,\n replayOption: boolean | number,\n handler: DataBusMessageHandler<TData>,\n isHandlerActive?: () => boolean\n ): void {\n if (!this.buffers) return;\n const limit = typeof replayOption === 'number'\n ? Math.min(Math.floor(replayOption), this.maxPerTopic)\n : this.maxPerTopic;\n if (this.persistence) {\n void this.hydration.then(() => {\n if (isHandlerActive?.() ?? true) this.deliver(topic, limit, handler);\n });\n return;\n }\n this.deliver(topic, limit, handler);\n }\n\n /** Clean up a topic that lost its last local handler: drop the ring buffer,\n * filter queued batch flushes (so an in-flight append cannot undo the\n * clearTopic), and prune durable history. */\n onTopicUnsubscribed(topic: string): void {\n if (!this.buffers) return;\n this.buffers.delete(topic);\n // A batched persistence flush may still be queued behind this task; drop\n // the topic's pending entries so clearTopic is not undone by the append.\n this.pendingReplayPersistence = this.pendingReplayPersistence.filter(message => message.topic !== topic);\n if (this.persistence?.clearTopic) {\n void this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence!.clearTopic!(topic))\n .catch(error => this.onPersistenceError(error));\n }\n }\n\n /** Clear all in-memory replay buffers and, when supported, durable history.\n * Reports persistence failures and rethrows, mirroring the public API\n * contract that callers can observe a failed clear. */\n async clearAll(): Promise<void> {\n if (!this.buffers) return;\n this.buffers.clear();\n // Cancel any queued batch flush so cleared history is not re-appended.\n this.pendingReplayPersistence = [];\n if (this.persistence?.clear) {\n try {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR, () => this.persistence!.clear!());\n } catch (error) {\n this.onPersistenceError(error);\n throw error;\n }\n }\n }\n\n /** Clear replay history for one exact topic, including durable storage. */\n async clearTopic(topic: string): Promise<void> {\n if (!this.buffers) return;\n this.buffers.delete(topic);\n this.pendingReplayPersistence = this.pendingReplayPersistence.filter(message => message.topic !== topic);\n if (this.persistence?.clearTopic) {\n try {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence!.clearTopic!(topic));\n } catch (error) {\n this.onPersistenceError(error);\n throw error;\n }\n }\n }\n\n /** Remove replay entries older than an epoch-millisecond cutoff. */\n async clearBefore(timestamp: number): Promise<void> {\n if (!Number.isFinite(timestamp)) throw new TypeError('timestamp must be finite.');\n if (this.buffers) {\n for (const [topic, messages] of this.buffers) {\n const kept = messages.filter(message => message.timestamp === undefined || message.timestamp >= timestamp);\n if (kept.length) this.buffers.set(topic, kept);\n else this.buffers.delete(topic);\n }\n }\n // A queued batch flush must not resurrect pruned entries.\n this.pendingReplayPersistence = this.pendingReplayPersistence.filter(\n message => message.timestamp === undefined || message.timestamp >= timestamp\n );\n if (this.persistence?.clearBefore) {\n try {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_BEFORE, () => this.persistence!.clearBefore!(timestamp));\n } catch (error) {\n this.onPersistenceError(error);\n throw error;\n }\n }\n }\n\n /** Start the periodic retention sweep. No-op when no durable retention\n * config makes it necessary. */\n start(): void {\n if (this.retentionTimer || !this.retentionMs || !this.retentionSweepMs || !this.persistence?.clearBefore) return;\n this.retentionTimer = setInterval(() => {\n this.scheduleRetentionCleanup(this.now() - this.retentionMs!);\n }, this.retentionSweepMs);\n }\n\n /** Stop the periodic retention sweep. */\n stop(): void {\n if (this.retentionTimer) clearInterval(this.retentionTimer);\n this.retentionTimer = null;\n }\n\n /** Suspend the manager: cancel in-flight persistence retries (so a hidden tab\n * or stopped bus does not keep hammering the store) and stop the sweep. */\n suspend(): void {\n this.retryGeneration += 1;\n this.pendingReplayPersistence = [];\n // A flush that has not reached persistence yet belongs to the session being\n // suspended. Dropping it prevents the queued microtask from starting under\n // the next lifecycle generation and resurrecting stopped-session history.\n // A cutoff queued behind an in-flight cleanup belongs to the session that\n // is being suspended. Drop it so the loop cannot issue another transaction\n // after teardown has started.\n this.retentionCutoff = null;\n this.stop();\n }\n\n /** Drop all in-memory buffers (used on full teardown). */\n resetBuffers(): void {\n this.buffers?.clear();\n }\n\n /** Buffer occupancy for diagnostics. `bytes` is an approximate in-memory\n * payload footprint (same heuristic as adaptive load weighting), computed on\n * demand so the hot append path never pays for it. */\n getStats(): { enabled: boolean; topics: number; messages: number; bytes: number } {\n let messages = 0;\n let bytes = 0;\n if (this.buffers) {\n for (const buffer of this.buffers.values()) {\n messages += buffer.length;\n for (const message of buffer) bytes += approximatePayloadBytes(message.data);\n }\n }\n return { enabled: this.enabled, topics: this.buffers?.size ?? 0, messages, bytes };\n }\n\n /** Deliver history from one topic's ring to a handler, isolating a throwing\n * handler so the remaining buffers are still delivered. */\n private deliver(topic: string, limit: number, handler: DataBusMessageHandler<TData>): void {\n if (!this.buffers || limit <= 0) return;\n const deliverBuffer = (buffer: DataBusMessage<TData>[]) => {\n for (const message of buffer.slice(-limit)) {\n try {\n handler({ ...message, replayed: true });\n } catch (error) {\n this.onDispatchError(error);\n }\n }\n };\n if (isWildcardTopic(topic)) {\n for (const [bufferedTopic, buffer] of this.buffers) {\n if (topicMatchesPattern(topic, bufferedTopic)) deliverBuffer(buffer);\n }\n return;\n }\n const buffer = this.buffers.get(topic);\n if (buffer) deliverBuffer(buffer);\n }\n\n /** Coalesce queued persistence appends into a single microtask batch so a\n * burst of publications does not issue one IndexedDB transaction each.\n * Only reachable when the backend advertises `appendBatch` (the sole queuer,\n * `record()`, guards on it), so the batched path is unconditional here. */\n private schedulePersistenceFlush(): void {\n if (this.persistenceFlushScheduled) return;\n this.persistenceFlushScheduled = true;\n queueMicrotask(() => {\n this.persistenceFlushScheduled = false;\n const batch = this.pendingReplayPersistence.splice(0);\n if (batch.length === 0 || !this.persistence) return;\n void this.withPersistenceRetry(PERSISTENCE_OPERATION.APPEND, () => this.persistence!.appendBatch!(batch))\n .catch(error => this.onPersistenceError(error));\n });\n }\n\n /** Load durable history into the in-memory rings once at startup, pruning\n * entries past the retention window first. Failures are reported but do not\n * block startup \u2014 the bus runs with whatever survived. */\n private async hydrate(): Promise<void> {\n if (!this.buffers || !this.persistence) {\n return;\n }\n try {\n if (this.retentionMs !== undefined && this.persistence.clearBefore) {\n await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_BEFORE, () => this.persistence!.clearBefore!(this.now() - this.retentionMs!));\n }\n const loaded = await this.withPersistenceRetry(PERSISTENCE_OPERATION.LOAD, () => this.persistence!.load());\n for (const message of loaded) {\n let buffer = this.buffers.get(message.topic);\n if (!buffer) {\n buffer = [];\n this.buffers.set(message.topic, buffer);\n }\n buffer.push(message);\n }\n const hydrationNow = this.now();\n for (const [topic, buffer] of this.buffers) {\n const pruned = pruneReplayHistory(buffer, {\n maxPerTopic: this.maxPerTopic,\n pruneStrategy: this.pruneStrategy,\n retentionMs: this.retentionMs,\n now: hydrationNow\n });\n if (pruned !== buffer) this.buffers.set(topic, pruned);\n }\n } catch (error) {\n this.onPersistenceError(error);\n }\n }\n\n /** Coalesce retention cleanup: the newest cutoff wins while one pass runs,\n * so a burst of publications issues at most one clearBefore transaction. */\n private scheduleRetentionCleanup(cutoff: number): void {\n if (!this.persistence?.clearBefore) return;\n if (this.retentionCutoff === null || cutoff > this.retentionCutoff) {\n this.retentionCutoff = cutoff;\n }\n if (this.retentionCleanup) return;\n const generation = this.retryGeneration;\n this.retentionCleanup = (async () => {\n while (this.retentionCutoff !== null && generation === this.retryGeneration) {\n const nextCutoff = this.retentionCutoff;\n this.retentionCutoff = null;\n try {\n await this.persistence!.clearBefore!(nextCutoff);\n } catch (error) {\n if (generation === this.retryGeneration) this.onPersistenceError(error);\n }\n }\n })().finally(() => {\n this.retentionCleanup = null;\n if (this.retentionCutoff !== null && generation === this.retryGeneration) {\n this.scheduleRetentionCleanup(this.retentionCutoff);\n }\n });\n }\n\n /** Run a persistence operation with exponential backoff on transient failure.\n * Bumped `retryGeneration` (suspend/stop) cancels the loop early; a\n * structurally failing operation throws after `persistenceRetryMaxAttempts`,\n * leaving the ring buffer intact so the bus keeps working. */\n private async withPersistenceRetry<T>(\n persistenceOperation: (typeof PERSISTENCE_OPERATION)[keyof typeof PERSISTENCE_OPERATION],\n operation: () => Promise<T>\n ): Promise<T> {\n const generation = this.retryGeneration;\n let attempt = 0;\n let delay = this.persistenceRetryBackoffMs;\n while (true) {\n attempt += 1;\n try {\n if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();\n const result = await operation();\n // A lifecycle transition may complete while an async backend operation\n // is in flight. Do not let its successful result mutate application\n // state after suspend/stop has already cleared that state.\n if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();\n return result;\n } catch (error) {\n if (error instanceof PersistenceRetryCancelledError || generation !== this.retryGeneration) {\n throw new PersistenceRetryCancelledError();\n }\n if (attempt >= this.persistenceRetryMaxAttempts) throw error;\n this.trace.event({\n type: TRACE_EVENT_TYPE.RELIABILITY,\n operation: RELIABILITY_OPERATION.PERSISTENCE_RETRY,\n persistenceOperation,\n attempt,\n });\n if (delay > 0) await new Promise<void>(resolve => setTimeout(resolve, delay));\n if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();\n delay = Math.min(delay * 2, MAX_RETRY_DELAY_MS);\n }\n }\n }\n}\n", "/**\n * DedupManager \u2014 bounded duplicate suppression for publications carrying\n * `messageId`.\n *\n * Extracted from CrossTabDataBus so the seen-ID map, adaptive TTL, and sweep\n * timer live in one self-contained unit with their own lifecycle. The DataBus\n * keeps a thin delegation: `isDuplicate()` on the inbound path, `start`/`stop`\n * on the lifecycle transitions, and `getStats()`/`reset()` on the diagnostics\n * surface.\n *\n * Deduplication is opt-in: an instance is only created when the DataBus was\n * configured with `dedup` options, so the zero-overhead default (no map, no\n * timer) is preserved.\n */\nimport type { DataBusTraceReporter } from './trace';\nimport { RELIABILITY_OPERATION, TRACE_EVENT_TYPE } from '../utils/constants';\n\n/** Opt-in bounded duplicate suppression for publications carrying `messageId`. */\nexport interface DataBusDedupOptions {\n /** Max remembered message IDs before the oldest (FIFO) entry is evicted.\n * Default 1_000. */\n maxEntries?: number;\n /** Time-to-live for a remembered ID. Default 60_000 ms. */\n ttlMs?: number;\n /** Optional periodic sweep interval for quiet-topic expiry. */\n sweepMs?: number;\n /** Injectable epoch clock for deterministic tests and non-wall-clock hosts. */\n now?: () => number;\n /** Optional adaptive TTL bounds; enabled only when both are provided. */\n adaptiveTtl?: { minMs: number; maxMs: number };\n}\n\n/** Bounded deduplication counters for diagnostics and health checks. */\nexport interface DataBusDedupStats {\n enabled: boolean;\n /** Currently remembered message IDs. */\n tracked: number;\n /** Publications suppressed as duplicates since the last reset. */\n suppressed: number;\n /** Publications accepted (tracked) since the last reset. */\n accepted: number;\n /** Effective TTL when adaptive bounds are configured. */\n ttlMs?: number;\n}\n\n/** Resolved constructor options after defaults are applied. */\nexport interface DedupManagerOptions {\n /** Whether deduplication is enabled at all (false \u2192 no-op instance). */\n enabled: boolean;\n maxEntries: number;\n ttlMs: number;\n adaptiveBounds?: { minMs: number; maxMs: number } | undefined;\n /** Optional periodic sweep interval for quiet-topic expiry. */\n sweepMs?: number | undefined;\n /** Injectable epoch clock; also used as the message-arrival clock. */\n now: () => number;\n /** Trace sink for suppression events and metrics counters. */\n trace: DataBusTraceReporter;\n}\n\n/** Fixed observation window for adaptive TTL rate computation (5 s). */\nconst ADAPTIVE_WINDOW_MS = 5_000;\n/** Message rate per ms treated as \"quiet\" \u2014 at or below this the adaptive TTL\n * relaxes toward `maxMs`. */\nconst QUIET_RATE_PER_MS = 0.01;\n\nexport class DedupManager {\n private readonly enabled: boolean;\n private readonly maxEntries: number;\n private readonly ttlMs: number;\n private readonly adaptiveBounds: { minMs: number; maxMs: number } | undefined;\n private readonly sweepMs: number | undefined;\n private readonly now: () => number;\n private readonly trace: DataBusTraceReporter;\n private readonly seenMessageIds = new Map<string, number>();\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n private windowStartedAt = 0;\n private windowAccepted = 0;\n private suppressed = 0;\n private accepted = 0;\n\n constructor(options: DedupManagerOptions) {\n this.enabled = options.enabled;\n this.maxEntries = options.maxEntries;\n this.ttlMs = options.ttlMs;\n this.adaptiveBounds = options.adaptiveBounds;\n this.sweepMs = options.sweepMs;\n this.now = options.now;\n this.trace = options.trace;\n this.windowStartedAt = this.now();\n }\n\n /** True when a publication carrying `messageId` was already seen. Records the\n * ID and updates counters/trace on acceptance. Disabled or ID-less messages\n * always pass through. */\n isDuplicate(messageId: string, topic: string): boolean {\n if (!this.enabled || !messageId) return false;\n const now = this.now();\n // Opportunistic expiry on the hot path keeps the map bounded between sweeps.\n // This must use the effective (adaptive) TTL, not the fixed one: otherwise\n // a burst that shrinks the window toward minMs would still retain IDs for\n // the full fixed ttlMs here while the sweep prunes them early.\n const ttlMs = this.currentTtl();\n for (const [id, timestamp] of this.seenMessageIds) {\n if (now - timestamp > ttlMs) this.seenMessageIds.delete(id);\n }\n if (this.seenMessageIds.has(messageId)) {\n this.suppressed += 1;\n this.trace.event({\n type: TRACE_EVENT_TYPE.RELIABILITY,\n operation: RELIABILITY_OPERATION.DEDUP_SUPPRESSED,\n topic\n });\n this.trace.recordDedupSuppressed();\n return true;\n }\n this.seenMessageIds.set(messageId, now);\n this.accepted += 1;\n this.windowAccepted += 1;\n this.trace.recordDedupAccepted();\n // Cap growth: evict oldest (FIFO) entries when the map exceeds the cap, so a\n // high-cardinality burst of distinct IDs cannot exhaust memory.\n while (this.seenMessageIds.size > this.maxEntries) {\n const oldest = this.seenMessageIds.keys().next().value;\n if (oldest === undefined) break;\n this.seenMessageIds.delete(oldest);\n }\n return false;\n }\n\n /** Start the periodic expiry sweep. No-op when disabled or no sweepMs was\n * configured (the hot-path opportunistic expiry still bounds the map). */\n start(): void {\n if (this.sweepTimer || !this.enabled || !this.sweepMs) return;\n this.sweepTimer = setInterval(() => this.pruneExpired(), this.sweepMs);\n }\n\n /** Stop the periodic expiry sweep. */\n stop(): void {\n if (this.sweepTimer) clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n\n /** Return bounded deduplication counters for diagnostics and health checks. */\n getStats(): DataBusDedupStats {\n return {\n enabled: this.enabled,\n tracked: this.seenMessageIds.size,\n suppressed: this.suppressed,\n accepted: this.accepted,\n ...(this.adaptiveBounds ? { ttlMs: this.currentTtl() } : {})\n };\n }\n\n /** Drop all remembered IDs and reset dedup counters. */\n reset(): void {\n this.seenMessageIds.clear();\n this.suppressed = 0;\n this.accepted = 0;\n this.windowStartedAt = this.now();\n this.windowAccepted = 0;\n }\n\n /** Remove IDs whose timestamp predates the effective TTL cutoff. */\n private pruneExpired(): void {\n const cutoff = this.now() - this.currentTtl();\n for (const [id, timestamp] of this.seenMessageIds) {\n if (timestamp < cutoff) this.seenMessageIds.delete(id);\n }\n }\n\n /** Effective TTL: the fixed `ttlMs` unless adaptive bounds are configured, in\n * which case a higher recent message rate shortens the window (dedup only\n * needs to live long enough to bridge duplicate bursts). The window resets\n * every ADAPTIVE_WINDOW_MS. */\n private currentTtl(): number {\n if (!this.adaptiveBounds) return this.ttlMs;\n const now = this.now();\n const elapsed = now - this.windowStartedAt;\n if (elapsed >= ADAPTIVE_WINDOW_MS) {\n this.windowStartedAt = now;\n this.windowAccepted = 0;\n return this.adaptiveBounds.maxMs;\n }\n const rate = this.windowAccepted / Math.max(1, elapsed);\n const factor = Math.min(1, rate / QUIET_RATE_PER_MS);\n return this.adaptiveBounds.maxMs - (this.adaptiveBounds.maxMs - this.adaptiveBounds.minMs) * factor;\n }\n}\n", "/**\n * SDK version reported in diagnostics and health summaries.\n *\n * The value is injected at bundle time (esbuild `define`) from package.json so\n * it can never drift from the release. Source-level consumers (typecheck,\n * vitest) get the same value via the vitest `define`; the declare keeps tsc\n * happy when no define is present.\n */\ndeclare const __SDK_VERSION__: string | undefined;\n\nexport const SDK_VERSION: string =\n typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '';\n", "/**\n * CrossTabDataBus \u2014 the primary public API for cross-tab data distribution.\n *\n * Wraps WorkerClusterRuntime for cluster coordination and a DataBusTransport\n * for the real connection. Handles local handler reference counting, subscription\n * queuing, message dispatch, and clean lifecycle management (start/stop/BFCache).\n */\nimport { WorkerClusterRuntime } from './cluster';\nimport type { WorkerClusterOptions, WorkerClusterSnapshot } from './cluster';\nimport { topicMatchesPattern } from './routing';\nimport type {\n DataBusErrorHandler,\n DataBusMessage,\n DataBusMessageHandler,\n DataBusPublishOptions,\n DataBusStatusHandler,\n DataBusTransport,\n WorkerStatus\n} from './types';\nimport { DataBusTraceReporter } from './trace';\nimport type { DataBusMetricsSnapshot, DataBusTraceOptions } from './trace';\nimport type { DataBusReplayPersistence } from './replay-persistence';\nimport { PersistenceRetryCancelledError, ReplayManager } from './replay-manager';\nimport { DedupManager } from './dedup-manager';\nimport type { DataBusDedupOptions, DataBusDedupStats } from './dedup-manager';\nimport { SDK_VERSION } from './version';\nimport {\n CONTROL_ACTION,\n DEFAULT_STORAGE_PREFIX,\n FAILURE_SOURCE,\n HEALTH_STATE,\n INVOKE_LABEL,\n PRUNE_STRATEGY,\n PUBLICATION_EVENT,\n RECOVERY_OUTCOME,\n RELIABILITY_OPERATION,\n SUBSCRIPTION_ACTION,\n TRACE_EVENT_TYPE,\n TRACE_ERROR_SOURCE,\n TRACE_LIFECYCLE_ACTION,\n WORKER_ROLE,\n WORKER_STATUS\n} from '../utils/constants';\nimport { publicationMetadata } from '../utils/metadata';\nimport { assertDedupOptions, assertReplayOptions, assertRecoveryOptions } from '../utils/validation';\n\n/** Default ring size per topic when replay is enabled without a limit. */\nconst DEFAULT_REPLAY_MAX_PER_TOPIC = 100;\n\n/** Constructor options for {@link CrossTabDataBus}. Extends WorkerClusterOptions\n * (cluster coordination config) with the transport, initial connection config,\n * and trace options. */\n/** Replay (bounded local history) configuration. When present, the DataBus\n * keeps a bounded ring buffer of the most recent dispatched publications per\n * topic, and `subscribe()` can deliver that history to late-joining handlers.\n * Buffers live in memory by default; an optional persistence backend can make\n * them durable. */\nexport interface DataBusReplayOptions<TData = unknown> {\n /** Maximum buffered publications per topic under 'count'/'both'. With 'age',\n * timestamped history is bounded by `retentionMs` and timestamp-less legacy\n * entries are capped by this value. Oldest entries are evicted first.\n * Default 100. */\n maxPerTopic?: number;\n /** Optional durable history backend. Defaults to in-memory only. */\n persistence?: DataBusReplayPersistence<TData>;\n /** Optional producer-timestamp retention window in milliseconds. */\n retentionMs?: number;\n /** History trimming policy: 'count' (default) caps each topic at\n * `maxPerTopic`, 'age' prunes by `retentionMs`, and 'both' applies both.\n * With 'age', timestamped entries are bounded by the retention window and\n * timestamp-less legacy entries are capped by `maxPerTopic`. An 'age'\n * strategy without `retentionMs` falls back to the count cap. */\n pruneStrategy?: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n /** Optional periodic sweep interval for durable retention cleanup. */\n retentionSweepMs?: number;\n /** Optional bounded retry policy for transient persistence failures. */\n persistenceRetry?: DataBusPersistenceRetryOptions;\n}\n\nexport interface DataBusPersistenceRetryOptions {\n /** Total attempts including the initial operation. Default 1. */\n maxAttempts?: number;\n /** Initial delay between attempts. Default 50ms. */\n backoffMs?: number;\n}\n\nexport type { DataBusDedupOptions, DataBusDedupStats };\n\nexport interface DataBusDiagnostics {\n sdkVersion: string;\n status: WorkerStatus;\n started: boolean;\n transportReady: boolean;\n recovery: { attempt: number; exhausted: boolean; maxAttempts: number; hasError: boolean; errorMessage: string | null; errorAt: number | null; generation: number; lastSuccessAt: number | null };\n dedup: DataBusDedupStats;\n replay: { enabled: boolean; topics: number; messages: number; bytes: number };\n persistence: DataBusPersistenceHealth;\n protocol: { version: number; unknownMessages: number; lastUnknownMessageType: string | null; peers: Record<string, number | null> };\n transport: { name: string; backend: string | null; status: WorkerStatus; suspended: boolean };\n cluster: WorkerClusterSnapshot;\n /** Current trace metrics window counters, or null when metrics are inactive. */\n metrics: DataBusMetricsSnapshot | null;\n /** Trace sink delivery mode and queued-event depth (asyncSink back-pressure). */\n trace: { asyncSink: boolean; pendingEvents: number };\n}\n\n/** Where a retained failure originated, as surfaced by {@link DataBusHealthSummary}. */\nexport type DataBusFailureSource = (typeof FAILURE_SOURCE)[keyof typeof FAILURE_SOURCE];\n\nexport interface DataBusLastFailure {\n source: DataBusFailureSource;\n message: string;\n at: number;\n}\n\n/** Bounded failure counters for the optional replay persistence backend. */\nexport interface DataBusPersistenceHealth {\n /** Total persistence failures reported since the last explicit start(). */\n failures: number;\n lastFailureAt: number | null;\n lastErrorMessage: string | null;\n}\n\n/** Compact single-object health verdict for dashboards and readiness probes.\n * Unlike {@link DataBusDiagnostics} this answers one question first \u2014 is the\n * bus usable right now \u2014 then attaches the failure and recovery context that\n * explains the verdict. */\nexport interface DataBusHealthSummary {\n /** True only while the bus is started, not suspended, and the transport is ready. */\n healthy: boolean;\n /** Lifecycle-derived verdict: 'stopped' | 'starting' | 'healthy' | 'recovering' | 'suspended' | 'degraded'.\n * 'degraded' means automatic recovery is exhausted and the transport is still down \u2014 a manual\n * start() (or resume) is required. */\n state: (typeof HEALTH_STATE)[keyof typeof HEALTH_STATE];\n status: WorkerStatus;\n sdkVersion: string;\n started: boolean;\n suspended: boolean;\n transport: { name: string; backend: string | null; ready: boolean; status: WorkerStatus };\n recovery: ReturnType<CrossTabDataBus['getRecoveryStats']>;\n /** Most recent failure of any source since the last explicit start(). */\n lastFailure: DataBusLastFailure | null;\n persistence: DataBusPersistenceHealth;\n /** Current trace metrics window, or null when trace metrics are inactive. */\n metrics: DataBusMetricsSnapshot | null;\n /** Trace sink delivery mode and queued-event depth (asyncSink back-pressure). */\n trace: { asyncSink: boolean; pendingEvents: number };\n}\n\nexport interface CrossTabDataBusOptions<TConfig, TData>\n extends Omit<WorkerClusterOptions, 'handlers'> {\n transport: DataBusTransport<TConfig, TData>;\n initialConfig?: TConfig;\n autoStart?: boolean;\n trace?: DataBusTraceOptions;\n /** Opt-in bounded per-topic history. Absent \u2192 no buffering, zero overhead. */\n replay?: DataBusReplayOptions<TData>;\n /** Optional duplicate suppression; absent means every publication is delivered. */\n dedup?: DataBusDedupOptions;\n /** Automatic transport recovery pacing. */\n recovery?: { cooldownMs?: number; maxAttempts?: number };\n}\n\n/**\n * High-level cross-tab pub/sub client.\n *\n * Orchestrates a transport (e.g. Centrifuge WebSocket inside a Worker) and a\n * WorkerClusterRuntime for cross-tab coordination. Messages arriving from the\n * transport are fanned out to all tabs in the cluster and dispatched locally\n * to registered handlers.\n */\nexport class CrossTabDataBus<TConfig = unknown, TData = unknown> {\n private readonly transport: DataBusTransport<TConfig, TData>;\n private readonly cluster: WorkerClusterRuntime;\n // Map of topic \u2192 set of local subscribers.\n private readonly topicHandlers = new Map<string, Set<DataBusMessageHandler<TData>>>();\n // Topics for which the transport has been asked to subscribe (used to avoid\n // duplicate subscribe calls during reconnection).\n private readonly transportSubscribedTopics = new Set<string>();\n private readonly statusHandlers = new Set<DataBusStatusHandler>();\n private readonly errorHandlers = new Set<DataBusErrorHandler>();\n private readonly replayManager: ReplayManager<TData>;\n private readonly initialConfig: TConfig | undefined;\n private readonly hasInitialConfig: boolean;\n private readonly trace: DataBusTraceReporter;\n private readonly dedupManager: DedupManager;\n private readonly now: () => number;\n private activeConfig: TConfig | undefined;\n private status: WorkerStatus = WORKER_STATUS.DISCONNECTED;\n private started = false;\n private stopping = false;\n private transportReady = false;\n // Whether the installed transport has reported `connected` at least once\n // since the current open began. A clean `disconnected` after this point is\n // a lost working connection, not the pre-connect window of a worker-style\n // backend whose start() resolves before it reports the connection.\n private transportHasConnected = false;\n // Last transport failure, retained so ready() can surface it to callers who\n // never awaited start() directly. Cleared on the next successful start.\n private lastError: unknown = null;\n private lastErrorAt: number | null = null;\n // Unified failure ledger for the health summary: the most recent failure of\n // any source (transport, persistence, dispatch) since the last explicit start.\n private lastFailure: DataBusLastFailure | null = null;\n private persistenceFailureCount = 0;\n private persistenceLastFailureAt: number | null = null;\n private persistenceLastErrorMessage: string | null = null;\n // Gate that serialises start/stop/suspend/resume \u2014 only one lifecycle\n // transition at a time. Resets to null once the operation settles.\n private startPromise: Promise<void> | null = null;\n // Gate for an explicit stop(). Concurrent stop() calls share it, and a\n // start() received while stopping chains a fresh start after it.\n private stopPromise: Promise<void> | null = null;\n // A start() requested while an explicit stop() is still settling. Kept\n // separate from startPromise because stop()'s finally block clears the\n // ordinary lifecycle gate before the queued start is allowed to run.\n private queuedStart: Promise<void> | null = null;\n // Lazy readiness view of queuedStart. start() keeps its documented\n // resolve-on-cancellation contract, while ready() must reject when the\n // queued intent was superseded by a later stop().\n private queuedStartReady: Promise<void> | null = null;\n private queuedStartReadyToken = 0;\n // The queued continuation is chained to the stop promise and cannot be\n // un-scheduled once scheduled. A later stop() therefore invalidates the\n // current intent by recording its token; a subsequent start() issues a\n // higher token so the latest lifecycle request still wins.\n private queuedStartToken = 0;\n private canceledQueuedStartToken = 0;\n // Timestamp of the last automatic transport recovery attempt.\n // Used to avoid a tight retry loop when the transport fails repeatedly.\n private lastRecoveryAt = 0;\n // Monotonic attempt number within one runtime recovery sequence; reset once\n // a transport reopen succeeds so traces can correlate repeated failures.\n private recoveryAttempt = 0;\n private recoveryExhausted = false;\n // Gate that holds transport operations issued after a runtime `error` until\n // the scheduled recovery attempt has actually run. Without it, a dead\n // transport still has `transportReady === true` during the cooldown, so\n // publishes/subscribes would be written to the failed connection and lost.\n private recoveryGate: Promise<void> | null = null;\n private recoveryGateRelease: (() => void) | null = null;\n private recoveryTimer: ReturnType<typeof setTimeout> | null = null;\n private recoveryTimerToken = 0;\n // Once an automatic attempt fails, an explicit transport operation may\n // recover immediately instead of waiting for the next paced attempt. The\n // gate still stays closed so the operation cannot reach the failed\n // transport; it is released by the successful on-demand reopen.\n private recoveryDemandAllowed = false;\n /** Monotonic generation incremented on every successful transport open.\n * Stays in lockstep with `lastSuccessAt` so callers can detect that the\n * transport has been reopened even if the timestamp window is short. */\n private recoveryGeneration = 0;\n /** Timestamp of the most recent successful transport open. Null until the\n * transport has reached the `ready` state at least once. */\n private lastSuccessAt: number | null = null;\n // True while the tab is hidden so an in-flight transport start does not mark\n // the transport ready after suspendTransport() has stopped it.\n private suspended = false;\n // Single gate for async transport.stop() cleanup, shared by failed opens and\n // page-hide suspension. Kept separate from startPromise so ready() still\n // surfaces a failure while later opens and automatic recovery wait for the\n // stop to settle.\n private pendingStop: Promise<void> | null = null;\n // Ownership token for asynchronous transport opens. Every lifecycle\n // transition invalidates callbacks and failure cleanup from older opens.\n private lifecycleEpoch = 0;\n // Minimum interval in ms between automatic recovery attempts.\n private readonly recoveryCooldownMs: number;\n private readonly recoveryMaxAttempts: number;\n\n constructor(options: CrossTabDataBusOptions<TConfig, TData>) {\n const replay = options.replay;\n assertReplayOptions(replay);\n const { autoStart, initialConfig, trace, transport, dedup, recovery, ...clusterOptions } = options;\n assertRecoveryOptions(recovery);\n this.recoveryCooldownMs = recovery?.cooldownMs ?? 1000;\n this.recoveryMaxAttempts = recovery?.maxAttempts ?? Number.POSITIVE_INFINITY;\n this.now = dedup?.now ?? Date.now;\n this.transport = transport;\n this.initialConfig = initialConfig;\n this.hasInitialConfig = 'initialConfig' in options;\n this.trace = new DataBusTraceReporter(trace);\n this.replayManager = new ReplayManager<TData>({\n enabled: replay !== undefined,\n maxPerTopic: replay?.maxPerTopic ?? DEFAULT_REPLAY_MAX_PER_TOPIC,\n persistence: (replay?.persistence as DataBusReplayPersistence<TData> | undefined) ?? null,\n retentionMs: replay?.retentionMs,\n pruneStrategy: replay?.pruneStrategy ?? PRUNE_STRATEGY.COUNT,\n retentionSweepMs: replay?.retentionSweepMs,\n persistenceRetryMaxAttempts: replay?.persistenceRetry?.maxAttempts ?? 1,\n persistenceRetryBackoffMs: replay?.persistenceRetry?.backoffMs ?? 50,\n now: this.now,\n trace: this.trace,\n onPersistenceError: error => this.reportPersistenceError(error),\n onDispatchError: error => this.reportError(error, FAILURE_SOURCE.DISPATCH)\n });\n assertDedupOptions(dedup);\n this.dedupManager = new DedupManager({\n enabled: dedup !== undefined,\n maxEntries: dedup?.maxEntries ?? 1_000,\n ttlMs: dedup?.ttlMs ?? 60_000,\n adaptiveBounds: dedup?.adaptiveTtl,\n sweepMs: dedup?.sweepMs,\n now: this.now,\n trace: this.trace\n });\n this.cluster = new WorkerClusterRuntime({\n ...clusterOptions,\n handlers: {\n // The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH\n // control message \u2014 meaning the owning Worker has delegated the action to us.\n onControl: (action, topic, data, messageId, timestamp) => {\n switch (action) {\n case CONTROL_ACTION.SUBSCRIBE:\n if (this.subscribeTransport(topic)) this.traceSubscription(SUBSCRIPTION_ACTION.SUBSCRIBE, topic);\n break;\n case CONTROL_ACTION.UNSUBSCRIBE:\n if (this.unsubscribeTransport(topic)) this.traceSubscription(SUBSCRIPTION_ACTION.UNSUBSCRIBE, topic);\n break;\n case CONTROL_ACTION.PUBLISH:\n this.runTransport(() => this.transport.publish(topic, data, publicationMetadata(messageId, timestamp)));\n break;\n default:\n break;\n }\n },\n // Batched variant of the PUBLISH action (CONTROL frames carrying\n // multiple items, and the local publishBatch fast path). Uses the\n // transport's one-frame publishBatch when available, preserving\n // per-item metadata; otherwise falls back to per-item publishes.\n onPublishBatch: (topic, items) => {\n if (typeof this.transport.publishBatch === 'function') {\n this.runTransport(() => this.transport.publishBatch!(topic, items));\n return;\n }\n for (const item of items) {\n this.runTransport(() => this.transport.publish(\n topic,\n item.data,\n publicationMetadata(item.messageId, item.timestamp)\n ));\n }\n },\n // The cluster calls `onEvent` when a publication broadcast arrives from\n // another tab. Dispatch locally if we have subscribers. The payload is\n // typed `unknown` at the cluster boundary (the cluster is transport-\n // agnostic); here we narrow it to DataBusMessage \u2014 the sender is our\n // own broadcastEvent call, which always posts a DataBusMessage.\n onEvent: (eventType, payload, _sourceWorkerId, originTabId) => {\n if (eventType !== PUBLICATION_EVENT) return;\n const incoming = payload as DataBusMessage<TData>;\n // Prefer the originTabId the sender stamped; only fall back to the\n // broadcast cluster tabId when the older cluster version is in use.\n const message: DataBusMessage<TData> = incoming.originTabId !== undefined\n ? incoming\n : originTabId !== undefined\n ? { ...incoming, originTabId }\n : incoming;\n if (this.cluster.hasLocalSubscriber(message.topic)) this.dispatch(message);\n },\n onSuspend: () => {\n // Suppress the suspend trace event during an explicit stop() so\n // the trace log ends on 'stop' rather than 'suspend'\u2192'stop'.\n if (!this.stopping) this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.SUSPEND });\n this.trace.pause();\n this.replayManager.suspend();\n this.stopDedupSweep();\n this.suspendTransport();\n },\n onResume: () => {\n this.resumeSuspendedResources();\n this.resumeTransport();\n },\n onDiagnostic: event => {\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, ...event });\n }\n }\n });\n // Auto-start when initialConfig is provided, or when autoStart is explicitly true.\n if (autoStart ?? this.hasInitialConfig) this.ensureStarted();\n }\n\n /**\n * Start the DataBus with the given transport config.\n *\n * The first call starts the cluster and opens the transport. Concurrent calls\n * during an in-flight open return the same promise. A call received while an\n * explicit stop() is settling queues one fresh start after cleanup; a later\n * stop() before that queued start runs cancels it, so the latest lifecycle\n * intent wins. Once an operation settles (success or failure) its promise\n * gate is cleared so a subsequent start() or resumeTransport() can open a\n * fresh lifecycle.\n */\n start(config: TConfig): Promise<void> {\n if (this.queuedStart) return this.queuedStart;\n if (this.stopping) return this.queueStartAfterStop(config);\n // A suspended transport uses the same promise for startPromise and\n // pendingStop. Treat it as a stop gate here so an explicit start() queues a\n // real reopen instead of returning a promise that only waits for cleanup.\n if (this.startPromise && this.startPromise !== this.pendingStop) return this.startPromise;\n if (this.started) {\n const transportDown =\n !this.transportReady ||\n this.status === WORKER_STATUS.ERROR ||\n this.status === WORKER_STATUS.DISCONNECTED;\n if (!transportDown) return Promise.resolve();\n // An explicit start() is a manual recovery path after the automatic\n // recovery budget is exhausted. Keep the cluster and subscriptions\n // intact, but begin a fresh failure/recovery ledger before reopening.\n this.activeConfig = config;\n this.resetFailureState();\n // An explicit start() is also a documented resume path out of BFCache\n // suspension: it clears `suspended` and reopens the transport. The\n // cluster keeps its own paused flag and is normally resumed by the\n // pageshow listener, so resume it here too. Otherwise the bus reports a\n // healthy transport while cross-tab coordination stays dormant (closed\n // channel, no heartbeat, cleared assignments) and incoming publications\n // are discarded by isAssigned() until the next pageshow. reopenTransport()\n // clears `suspended` and installs the opening first so the cluster's\n // re-subscription traffic parks behind it instead of hitting the stopped\n // transport; cluster.start() is idempotent and a no-op when not paused.\n const resumingFromSuspend = this.suspended;\n if (resumingFromSuspend) this.resumeSuspendedResources();\n const opening = this.reopenTransport();\n if (resumingFromSuspend) this.cluster.start();\n return opening;\n }\n this.started = true;\n this.stopping = false;\n this.suspended = false;\n this.activeConfig = config;\n // A fresh start begins a new failure ledger so health consumers correlate\n // failures with the current session, not the previous one.\n this.resetFailureState();\n this.trace.start();\n this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });\n this.startDedupSweep();\n this.replayManager.start();\n this.updateStatus(WORKER_STATUS.CONNECTING);\n this.cluster.start();\n // Establish the opening before replaying topicHandlers: cluster.subscribe()\n // can synchronously invoke onControl for self-owned topics, and those\n // callbacks would otherwise see startPromise=null and open a second transport.\n const lifecycleEpoch = ++this.lifecycleEpoch;\n const opening = this.openTransport(\n config,\n this.pendingStop ?? Promise.resolve(),\n true,\n lifecycleEpoch\n );\n this.startPromise = opening;\n // Replay subscriptions that were registered before start() or that were lost\n // during a previous failure recovery. The cluster.stop() call in the failure\n // path clears subscribedTopics, but topicHandlers retains the intent.\n // Iterating topicHandlers (not transportSubscribedTopics) because the\n // transport hasn't subscribed to anything yet on a fresh start.\n for (const topic of this.topicHandlers.keys()) {\n this.cluster.subscribe(topic);\n }\n // Once startup settles (success or failure), clear the pending gate so a\n // later start()/resumeTransport() can open a fresh operation. Guard against\n // clobbering a promise that suspend/resume may have already swapped in.\n void opening.then(\n () => {\n if (this.startPromise !== opening) return;\n // Emit the coordination snapshot only after the transport has opened\n // and the just-issued subscriptions have flushed, so the routes list\n // (and the role/assignment picture) is populated rather than always\n // empty \u2014 the synchronous pre-open snapshot would see no routes.\n this.emitCoordinationTrace();\n this.startPromise = null;\n },\n () => {\n if (this.startPromise === opening) this.startPromise = null;\n }\n );\n return opening;\n }\n\n /** Return a cancellation-aware readiness view of the current queued start. */\n private getQueuedStartReady(): Promise<void> {\n const queued = this.queuedStart;\n if (!queued) {\n return Promise.reject(new Error('No queued start is in flight.'));\n }\n const token = this.queuedStartToken;\n if (this.queuedStartReady && this.queuedStartReadyToken === token) {\n return this.queuedStartReady;\n }\n this.queuedStartReadyToken = token;\n this.queuedStartReady = queued.then(() => {\n if (token <= this.canceledQueuedStartToken) {\n throw new Error(\n 'CrossTabDataBus start was canceled by a later stop(); ready() cannot report readiness. ' +\n 'Call start() again after stop() resolves.'\n );\n }\n if (!this.started || !this.transportReady) {\n throw new Error('CrossTabDataBus restart completed without a ready transport.');\n }\n });\n return this.queuedStartReady;\n }\n\n /** Queue exactly one fresh start after an in-flight explicit stop settles. */\n private queueStartAfterStop(config: TConfig): Promise<void> {\n if (this.queuedStart) return this.queuedStart;\n const stop = this.stopPromise ?? Promise.resolve();\n const token = ++this.queuedStartToken;\n const queued = stop\n .catch(() => undefined)\n .then(() => {\n // Clear before invoking start(), which installs its own startPromise.\n if (this.queuedStart === queued) this.queuedStart = null;\n // stop() may have arrived after this restart was queued. The queued\n // continuation still runs (it is already chained), but it must not\n // reopen the transport: the latest lifecycle intent was a stop.\n if (token <= this.canceledQueuedStartToken) return;\n return this.start(config);\n });\n this.queuedStart = queued;\n return queued;\n }\n\n /** Release every operation waiting on the scheduled recovery attempt. */\n private releaseRecoveryGate(): void {\n const release = this.recoveryGateRelease;\n this.recoveryGate = null;\n this.recoveryGateRelease = null;\n this.recoveryDemandAllowed = false;\n release?.();\n }\n\n /** Cancel a pending automatic retry when an explicit lifecycle transition\n * supersedes it. The released gate re-enters runTransport(), which then\n * follows the newest start/stop/suspend intent. */\n private cancelScheduledRecovery(): void {\n this.recoveryTimerToken += 1;\n if (this.recoveryTimer !== null) {\n clearTimeout(this.recoveryTimer);\n this.recoveryTimer = null;\n }\n this.releaseRecoveryGate();\n }\n\n /** Keep the recovery gate closed after a failed attempt while allowing the\n * next explicit transport operation to start an immediate on-demand reopen.\n * If no gate/successor retry remains, release any waiters. */\n private allowDemandRecovery(): void {\n if (\n this.recoveryGate !== null &&\n this.started &&\n !this.stopping &&\n !this.suspended &&\n this.status === WORKER_STATUS.ERROR\n ) {\n this.recoveryDemandAllowed = true;\n return;\n }\n this.releaseRecoveryGate();\n }\n\n /** Reset failure and recovery diagnostics for a new explicit start session. */\n private resetFailureState(): void {\n this.cancelScheduledRecovery();\n this.lastError = null;\n this.lastErrorAt = null;\n this.lastFailure = null;\n this.persistenceFailureCount = 0;\n this.persistenceLastFailureAt = null;\n this.persistenceLastErrorMessage = null;\n this.recoveryAttempt = 0;\n this.recoveryExhausted = false;\n this.lastRecoveryAt = 0;\n }\n\n /**\n * Open the transport, chained after `before` to ensure lifecycle ordering.\n * When `stopClusterOnFailure` is true (initial start), a transport failure\n * tears down the cluster as well.\n */\n private openTransport(\n config: TConfig,\n before: Promise<unknown>,\n stopClusterOnFailure: boolean,\n lifecycleEpoch: number\n ): Promise<void> {\n this.transportReady = false;\n const chainedPendingStop = this.pendingStop;\n const isCurrentLifecycle = () => lifecycleEpoch === this.lifecycleEpoch;\n // A transport can report `error` synchronously before start() settles.\n // Suppress the user-facing status notification until openTransport's catch\n // has created the stop gate and cleared startPromise; otherwise an onStatus\n // retry runs while the failed opening still owns the gate.\n let startupInProgress = true;\n return before\n .catch(() => undefined)\n .then(() => {\n // stop(), suspendTransport(), or a newer reopen may have arrived while\n // this opening was queued behind a pending stop. Abandon the open and\n // keep the settled stop gate visible so stop() does not issue a second\n // transport.stop().\n if (!isCurrentLifecycle() || this.stopping || this.suspended) return;\n // A stop we actually chained after has settled; this opening now owns\n // the lifecycle. A stop created concurrently (e.g. by suspendTransport)\n // is a different promise and must stay visible to later catch/resume\n // paths so cleanup is not duplicated.\n if (this.pendingStop === chainedPendingStop) this.pendingStop = null;\n // A fresh transport instance starts from scratch: until it reports\n // `connected` again its status is \"not connected yet\".\n this.transportHasConnected = false;\n return Promise.resolve(\n this.transport.start(config, {\n onMessage: message => {\n if (isCurrentLifecycle()) this.handleTransportMessage(message);\n },\n onStatus: status => {\n if (isCurrentLifecycle()) {\n this.updateStatus(status, status !== WORKER_STATUS.ERROR || !startupInProgress);\n }\n },\n onError: error => {\n if (isCurrentLifecycle()) this.reportError(error);\n }\n })\n ).then(() => {\n startupInProgress = false;\n if (!isCurrentLifecycle()) return;\n // A transport may report 'error' synchronously during start() (e.g. a\n // Worker that fails to boot) while still returning normally. Treat that\n // as a startup failure instead of marking the transport ready, so a\n // later subscribe/unsubscribe triggers a reopen rather than being\n // silently dropped on a dead transport.\n if (this.status === WORKER_STATUS.ERROR) {\n throw new Error('Transport failed during startup.');\n }\n if (!this.suspended && !this.stopping) {\n this.recoveryGeneration += 1;\n this.lastSuccessAt = this.now();\n this.transportReady = true;\n // Release operations held during an automatic or on-demand reopen\n // only after the ready flag is visible. Releasing inside the\n // CONNECTED callback would make those operations bounce off the\n // still-clearing startPromise and can let ready() win the race.\n this.releaseRecoveryGate();\n }\n });\n })\n .catch(error => {\n // A newer suspend/resume/stop owns the lifecycle now. Do not let this\n // superseded open tear down the newer operation or clear its gate.\n if (!isCurrentLifecycle()) throw error;\n startupInProgress = false;\n // Reset started before reporting so an initial-start failure does not\n // schedule automatic recovery; only the caller can retry a first start.\n if (stopClusterOnFailure) this.started = false;\n // Keep the stop cleanup in a single gate so a subsequent\n // start()/reopenTransport() cannot overlap an asynchronous\n // transport.stop(). If suspendTransport() already chained a stop for\n // the tab hiding mid-open, reuse it instead of stopping twice.\n if (!this.pendingStop) {\n this.pendingStop = this.createStopPromise();\n }\n this.transportReady = false;\n if (stopClusterOnFailure) {\n this.stopping = true;\n this.cluster.stop();\n this.stopping = false;\n }\n // Record the failure before any user callback can retry. The status\n // notification below runs re-entrantly and may legitimately call\n // start(); that explicit lifecycle must be able to reset this ledger.\n this.recordError(error);\n // Make the failed opening observable as settled before notifying any\n // status or error handler. Leaving the old rejecting promise in\n // startPromise would make a synchronous retry return the failure it is\n // reacting to instead of opening a new lifecycle. Settlement handlers\n // only clear this field when they still own the gate, so a reentrant\n // retry and any later error notification remain safe.\n this.startPromise = null;\n this.updateStatus(WORKER_STATUS.ERROR);\n this.notifyError(error);\n throw error;\n });\n }\n\n /**\n * Await the DataBus to be fully started (lazy init when using initialConfig).\n * Returns a rejected promise when the transport has failed and no start is in\n * flight \u2014 the caller can retry by calling start() or ready() again. While an\n * explicit stop() is settling, this rejects unless a restart is queued behind\n * it; false readiness during teardown is never reported. While the tab is\n * BFCache-suspended (pagehide without a following pageshow), this also\n * rejects: the suspended start promise is the transport-stop gate, not a\n * readiness signal.\n */\n ready(): Promise<void> {\n // A start() queued behind an in-flight stop is the newest lifecycle intent;\n // ready() remains its shared completion gate. Without that queued intent,\n // reporting readiness while teardown is in progress would be false.\n if (this.queuedStart) return this.getQueuedStartReady();\n if (this.stopping) {\n return Promise.reject(new Error(\n 'CrossTabDataBus is stopping; ready() cannot report readiness until stop() resolves. ' +\n 'Wait for stop() to resolve, then call start() before awaiting ready().'\n ));\n }\n // A page-hide suspension reuses startPromise as the async transport-stop\n // gate. That promise proves cleanup completed, not that the transport is\n // ready, so never let ready() resolve while the tab is intentionally\n // suspended. An explicit start()/pageshow clears the flag and installs a\n // real reopen promise before this check runs.\n if (this.suspended) {\n return Promise.reject(new Error(\n 'CrossTabDataBus is suspended; ready() cannot report readiness until pageshow resumes the transport.'\n ));\n }\n // An explicit start(config) does not become an implicit initialConfig.\n // When that attempted start failed, ready() must still surface its real\n // transport error instead of masking it with \"requires initialConfig\".\n if (!this.started && !this.hasInitialConfig && this.lastError !== null) {\n return Promise.reject(this.lastError);\n }\n try {\n this.ensureStarted();\n } catch (error) {\n return Promise.reject(error);\n }\n if (this.startPromise) return this.startPromise;\n if (this.transportReady) return Promise.resolve();\n // Surface the last failure so callers can distinguish a transient retry\n // from a dead transport. The promise is rejected, not thrown, so the\n // caller can retry by calling ready() or start() again.\n if (this.lastError !== null) return Promise.reject(this.lastError);\n return Promise.reject(\n new Error('Transport is not ready and no start operation is in flight')\n );\n }\n\n /**\n * Register a handler for `topic`. The handler fires on every publication\n * delivered to this tab, regardless of which tab published it. Returns an\n * unsubscribe function for convenience. During an explicit stop() the\n * registration is rejected through onError and a no-op cleanup is returned,\n * so a late subscriber cannot leak into a future restart.\n */\n subscribe(\n topic: string,\n handler: DataBusMessageHandler<TData>,\n options?: { replay?: boolean | number }\n ): () => void {\n // A subscription requested during teardown would either be erased by\n // topicHandlers.clear() or leak into the next start while its handler was\n // already dropped. Reject it explicitly, consistent with publish(), and\n // return a safe cleanup function so callers can keep uniform teardown code.\n if (this.stopping) {\n this.reportError(new Error(\n 'CrossTabDataBus is stopping; subscribe() was not registered. ' +\n 'Wait for stop() to resolve, then call start() before subscribing again.'\n ));\n return () => {};\n }\n this.ensureStarted();\n const handlers = this.topicHandlers.get(topic) ?? new Set<DataBusMessageHandler<TData>>();\n const wasUnused = handlers.size === 0;\n handlers.add(handler);\n this.topicHandlers.set(topic, handlers);\n // This 0\u21921 transition is the SINGLE entry point into the cluster\n // subscription. The cluster's subscribedTopics is a Set, so repeated\n // installs of the same topic after a drop cannot double-subscribe:\n // wasUnused is the only gate, and cluster.subscribe() is idempotent by\n // construction. The matching n\u21920 gate is in the unsubscribe path below.\n if (wasUnused) this.cluster.subscribe(topic);\n if (options?.replay) {\n this.replayManager.deliverReplay(topic, options.replay, handler, () =>\n Boolean(this.topicHandlers.get(topic)?.has(handler))\n );\n }\n return () => this.unsubscribe(topic, handler);\n }\n\n /** Remove a specific handler, or all handlers for `topic`.\n * When `handler` is omitted, clears every handler for the topic \u2014 the\n * caller used the `unsubscribe(topic)` form expecting a full teardown.\n * The cluster is only notified on the n\u21920 transition (handlers.size === 0). */\n unsubscribe(topic: string, handler?: DataBusMessageHandler<TData>): void {\n const handlers = this.topicHandlers.get(topic);\n if (!handlers) return;\n if (handler) handlers.delete(handler);\n else handlers.clear();\n if (handlers.size > 0) return;\n this.topicHandlers.delete(topic);\n this.replayManager.onTopicUnsubscribed(topic);\n this.cluster.unsubscribe(topic);\n }\n\n /** Clear all in-memory replay buffers and, when supported, durable history. */\n async clearReplay(): Promise<void> {\n await this.replayManager.clearAll();\n }\n\n /** Clear replay history for one exact topic, including durable storage. */\n async clearReplayTopic(topic: string): Promise<void> {\n await this.replayManager.clearTopic(topic);\n }\n\n /** Remove replay entries older than an epoch-millisecond cutoff. */\n async clearReplayBefore(timestamp: number): Promise<void> {\n await this.replayManager.clearBefore(timestamp);\n }\n\n /** Return bounded deduplication counters for diagnostics and health checks. */\n getDedupStats(): DataBusDedupStats {\n return this.dedupManager.getStats();\n }\n\n /** Drop all remembered IDs and reset dedup counters. */\n resetDedup(): void {\n this.dedupManager.reset();\n }\n\n /** Publish a message to `topic`. The owning Worker delivers it to the transport. */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): void {\n this.ensureStarted();\n if (this.rejectPublishDuringStop('publish')) return;\n if (!this.cluster.publish(topic, data, options)) {\n this.reportError(\n new Error('Failed to send the publish control message to the owning worker.')\n );\n }\n }\n\n /**\n * Burst-friendly variant of `publish()`: delivers `items` in a single\n * BroadcastChannel postMessage so the receiving owner can dispatch them all\n * in one tick. Per-item dedup / replay / ordering is preserved; each item\n * may carry its own `messageId` / `timestamp` via `options`. Empty array is\n * a no-op; single-item array delegates to `publish()`.\n */\n publishBatch(\n topic: string,\n items: ReadonlyArray<{ data: unknown; options?: DataBusPublishOptions }>\n ): void {\n this.ensureStarted();\n if (items.length === 0) return;\n if (this.rejectPublishDuringStop('publishBatch')) return;\n if (items.length === 1) {\n const first = items[0]!;\n this.publish(topic, first.data, first.options);\n return;\n }\n const mapped = items.map(item => ({\n data: item.data,\n ...publicationMetadata(item.options?.messageId, item.options?.timestamp)\n }));\n if (!this.cluster.publishBatch(topic, mapped)) {\n this.reportError(\n new Error('Failed to send the batched publish control message to the owning worker.')\n );\n }\n }\n\n /** Register a handler that fires on every transport status change. Immediately invoked with the current status. */\n onStatus(handler: DataBusStatusHandler): () => void {\n this.statusHandlers.add(handler);\n try {\n handler(this.status);\n } catch (error) {\n this.reportError(error);\n }\n return () => this.statusHandlers.delete(handler);\n }\n\n /** Register a handler for transport errors. */\n onError(handler: DataBusErrorHandler): () => void {\n this.errorHandlers.add(handler);\n return () => this.errorHandlers.delete(handler);\n }\n\n /** Current transport connection status. */\n getStatus(): WorkerStatus {\n return this.status;\n }\n\n /** Return the current automatic transport recovery state plus diagnostics.\n * `hasError`/`errorMessage`/`errorAt` describe the most recent retained\n * *transport* failure \u2014 from a transport open or a runtime `onError`. They\n * share the lifetime of the unified `lastFailure` ledger: a successful\n * recovery keeps the last failure visible, and only an explicit `start()`\n * clears it. `generation` increments on every successful transport open\n * (initial start and every recovery); `lastSuccessAt` is the timestamp of\n * the most recent successful open, or `null` until the transport reaches\n * `ready`. */\n getRecoveryStats(): {\n attempt: number;\n exhausted: boolean;\n maxAttempts: number;\n hasError: boolean;\n errorMessage: string | null;\n errorAt: number | null;\n generation: number;\n lastSuccessAt: number | null;\n } {\n const errorMessage = this.lastError instanceof Error ? this.lastError.message : this.lastError === null ? null : String(this.lastError);\n return {\n attempt: this.recoveryAttempt,\n exhausted: this.recoveryExhausted,\n maxAttempts: this.recoveryMaxAttempts,\n hasError: this.lastError !== null,\n errorMessage,\n errorAt: this.lastErrorAt,\n generation: this.recoveryGeneration,\n lastSuccessAt: this.lastSuccessAt\n };\n }\n\n /** Bounded failure counters for the replay persistence backend. */\n getPersistenceStats(): DataBusPersistenceHealth {\n return {\n failures: this.persistenceFailureCount,\n lastFailureAt: this.persistenceLastFailureAt,\n lastErrorMessage: this.persistenceLastErrorMessage\n };\n }\n\n /** Compact health verdict for dashboards, readiness probes, and support\n * bundles. Answers \"is the bus usable right now\" first, then attaches the\n * unified failure ledger and recovery context that explains the verdict. */\n getHealthSummary(): DataBusHealthSummary {\n const transport = this.transport;\n // The live transport status is the source of truth for serviceability. A\n // transport that reports 'connected' is healthy even during the short\n // window before start() settles and the DataBus sets transportReady:\n // operations are queued behind that in-flight start promise rather than\n // dropped. `transportReady` stays in the snapshot as a diagnostic.\n const transportDown = this.status !== WORKER_STATUS.CONNECTED;\n // `stopping` means every operation is already rejected (publish/subscribe\n // return through onError, ready() rejects) even though the transport may\n // still report `connected` because teardown is async. Reporting HEALTHY\n // here would contradict that verdict, so an in-flight stop is surfaced as\n // STOPPED: the bus is not usable, and a queued restart behind this stop is\n // reported as STARTING once it actually owns the lifecycle.\n const state: DataBusHealthSummary['state'] =\n !this.started || this.stopping\n ? HEALTH_STATE.STOPPED\n : this.suspended\n ? HEALTH_STATE.SUSPENDED\n : transportDown\n ? this.recoveryExhausted\n ? HEALTH_STATE.DEGRADED\n : this.status === WORKER_STATUS.CONNECTING && this.recoveryAttempt === 0\n ? HEALTH_STATE.STARTING\n : HEALTH_STATE.RECOVERING\n : HEALTH_STATE.HEALTHY;\n return {\n healthy: state === HEALTH_STATE.HEALTHY,\n state,\n status: this.status,\n sdkVersion: SDK_VERSION,\n started: this.started,\n suspended: this.suspended,\n transport: {\n name: transport.diagnosticsName ?? transport.constructor.name,\n backend: transport.diagnosticsBackend ?? null,\n ready: this.transportReady,\n status: this.status\n },\n recovery: this.getRecoveryStats(),\n lastFailure: this.lastFailure,\n persistence: this.getPersistenceStats(),\n metrics: this.trace.getMetrics(),\n trace: this.trace.getSinkState()\n };\n }\n\n /** Snapshot of the cluster state (workers, routes, assignments).\n * For diagnostics only \u2014 the returned object is a shallow copy but\n * nested arrays are snapshots at call time. */\n getClusterSnapshot() {\n return this.cluster.getSnapshot();\n }\n\n /** Return a single health snapshot combining lifecycle, recovery, dedup, replay, and cluster state. */\n getDiagnostics(): DataBusDiagnostics {\n const replay = this.replayManager.getStats();\n const cluster = this.cluster.getSnapshot();\n const unknownMessages = this.cluster.getUnknownMessageStats();\n const transport = this.transport;\n return {\n status: this.status,\n sdkVersion: SDK_VERSION,\n started: this.started,\n transportReady: this.transportReady,\n recovery: this.getRecoveryStats(),\n dedup: this.getDedupStats(),\n replay: { enabled: replay.enabled, topics: replay.topics, messages: replay.messages, bytes: replay.bytes },\n persistence: this.getPersistenceStats(),\n protocol: { version: cluster.protocolVersion, unknownMessages: unknownMessages.count, lastUnknownMessageType: unknownMessages.lastType, peers: cluster.peerProtocolVersions },\n transport: {\n name: transport.diagnosticsName ?? transport.constructor.name,\n backend: transport.diagnosticsBackend ?? null,\n status: this.status,\n suspended: this.suspended\n },\n cluster,\n metrics: this.trace.getMetrics(),\n trace: this.trace.getSinkState()\n };\n }\n\n /** Synchronous snapshot of the current trace metrics window (throughput,\n * dispatch latency, dedup outcomes), without flushing or resetting it.\n * Returns null when trace metrics are inactive (disabled or events-only). */\n getMetrics(): DataBusMetricsSnapshot | null {\n return this.trace.getMetrics();\n }\n\n /**\n * Gracefully stop the DataBus: unsubscribe all topics, stop the cluster,\n * and close the transport. Concurrent and repeated calls share the in-flight\n * stop promise. A start() received while stopping runs after this completes,\n * unless another stop() arrives first and cancels that queued restart.\n */\n stop(): Promise<void> {\n // A restart queued behind an in-flight stop is stale as soon as another\n // stop() is requested. Invalidate it and release the single queue slot so\n // a later start() can still queue a fresh restart with a higher token.\n if (this.queuedStart) {\n this.canceledQueuedStartToken = this.queuedStartToken;\n this.queuedStart = null;\n }\n // `stopPromise` is only the shared in-flight gate while the teardown is\n // still running. It is cleared in a microtask once performStop() settles,\n // so a stop() issued in that window (for example from code that observed\n // an earlier stop settle without awaiting it) would otherwise receive the\n // settled promise and skip a teardown the caller asked for, leaving a\n // concurrently restarted bus running. `stopping` flips to false inside\n // performStop()'s finally, so it is the authoritative \"still stopping\"\n // signal; a settled gate is stale and must fall through to a fresh stop.\n if (this.stopPromise && this.stopping) return this.stopPromise;\n if (!this.started && !this.startPromise && !this.pendingStop && !this.transportReady) {\n return Promise.resolve();\n }\n const stopPromise = this.performStop();\n this.stopPromise = stopPromise;\n void stopPromise.then(\n () => {\n if (this.stopPromise === stopPromise) this.stopPromise = null;\n },\n () => {\n if (this.stopPromise === stopPromise) this.stopPromise = null;\n }\n );\n return stopPromise;\n }\n\n private async performStop(): Promise<void> {\n this.lifecycleEpoch += 1;\n this.stopping = true;\n this.cancelScheduledRecovery();\n this.replayManager.suspend();\n this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.STOP });\n this.trace.stop();\n this.stopDedupSweep();\n this.topicHandlers.clear();\n this.replayManager.resetBuffers();\n this.cluster.stop();\n try {\n await this.startPromise?.catch(() => undefined);\n // A failed-open or suspend cleanup already stopped the transport;\n // awaiting it is enough, so stop() is not called a second time.\n const pendingStop = this.pendingStop;\n if (pendingStop) await pendingStop.catch(() => undefined);\n else await this.transport.stop();\n } catch (error) {\n // A transport whose stop() rejects must not reject stop() itself: the\n // finally below completes the teardown either way, concurrent/repeated\n // callers share this one promise, and the React/Vue adapters legitimately\n // fire-and-forget `void bus.stop()`, where a rejection would surface as\n // an unhandled rejection. Route the failure through the same\n // ledger/onError channel suspendTransport() and createStopPromise() use.\n this.reportError(error);\n } finally {\n this.transportSubscribedTopics.clear();\n this.resetDedup();\n this.started = false;\n this.stopping = false;\n this.suspended = false;\n this.transportReady = false;\n this.startPromise = null;\n this.pendingStop = null;\n this.lastError = null;\n this.lastErrorAt = null;\n this.activeConfig = undefined;\n this.recoveryAttempt = 0;\n this.recoveryExhausted = false;\n this.updateStatus(WORKER_STATUS.DISCONNECTED);\n }\n }\n\n /**\n * Incoming message from the transport.\n * Records metrics, checks ownership via the cluster, broadcasts to other tabs,\n * and dispatches locally.\n */\n private handleTransportMessage(message: DataBusMessage<TData>): void {\n if (this.dedupManager.isDuplicate(message.messageId ?? '', message.topic)) return;\n this.trace.recordReceived(message.topic);\n // Drop messages for topics we do not own \u2014 the owning Worker fans out.\n if (!this.cluster.isAssigned(message.topic)) {\n this.trace.recordDiscarded(message.topic);\n return;\n }\n // Stamp the originating tab BEFORE broadcasting so neighbors replaying\n // history can attribute each entry to the tab that produced it. Locally\n // we publish first and dispatch second to keep the contract: a handler\n // called before the broadcast settled would still observe originTabId.\n const stamped: DataBusMessage<TData> = message.originTabId === undefined\n ? { ...message, originTabId: this.cluster.tabId }\n : message;\n this.cluster.broadcastEvent(PUBLICATION_EVENT, stamped, stamped.originTabId);\n if (this.cluster.hasLocalSubscriber(message.topic)) {\n this.dispatch(stamped);\n return;\n }\n this.trace.recordDiscarded(message.topic);\n }\n\n /** Start enqueuing the dedup expiry sweep (delegated to {@link DedupManager}). */\n private startDedupSweep(): void {\n this.dedupManager.start();\n }\n\n /** Stop enqueuing the dedup expiry sweep. */\n private stopDedupSweep(): void {\n this.dedupManager.stop();\n }\n\n /** Deliver a message to every local handler registered for its topic,\n * plus every handler registered with a wildcard subscription that matches\n * (e.g. a handler subscribed to \"chat.*\" receives \"chat.room.1\"). */\n private dispatch(message: DataBusMessage<TData>): void {\n this.trace.recordDispatched(message.topic);\n this.invokeHandlers(this.topicHandlers.get(message.topic) ?? [], handler => handler(message));\n for (const [pattern, handlers] of this.topicHandlers) {\n if (pattern !== message.topic && topicMatchesPattern(pattern, message.topic)) {\n this.invokeHandlers(handlers, handler => handler(message));\n }\n }\n this.replayManager.record(message);\n }\n\n /**\n * Propagate a status change to the cluster, trace, and all registered\n * status handlers. On reconnect, re-subscribe any topics assigned to us.\n */\n private updateStatus(status: WorkerStatus, notifyHandlers = true): void {\n const previousStatus = this.status;\n this.status = status;\n if (status === WORKER_STATUS.CONNECTED) this.transportHasConnected = true;\n if (previousStatus !== status) this.trace.event({ type: TRACE_EVENT_TYPE.STATUS, status });\n this.cluster.setStatus(status);\n // Clear transport subscriptions on disconnect; the transport is gone.\n if (status === WORKER_STATUS.DISCONNECTED || status === WORKER_STATUS.ERROR) this.transportSubscribedTopics.clear();\n // Re-subscribe assigned topics when the transport reconnects.\n if (status === WORKER_STATUS.CONNECTED && previousStatus !== WORKER_STATUS.CONNECTED) {\n // A transport may recover itself without a DataBus reopen (for example a\n // protocol-level reconnect). In that case the installed transport is\n // already ready and can drain operations held during recovery. During a\n // DataBus reopen transportReady is false until openTransport succeeds;\n // that success path releases the gate after publishing the ready state.\n if (this.transportReady) this.releaseRecoveryGate();\n for (const topic of this.cluster.getSnapshot().assignedTopics) this.subscribeTransport(topic);\n }\n // Auto-recover from a runtime transport failure (e.g. a crashed Worker)\n // while the bus is still meant to be started. Guarded by a cooldown to\n // avoid a tight retry loop when the transport fails immediately.\n // Uses setTimeout so the recovery does not run re-entrantly inside the\n // callback that produced this status (e.g. openTransport's catch).\n if (status === WORKER_STATUS.ERROR && this.started && !this.stopping) {\n const now = this.now();\n if (now - this.lastRecoveryAt >= this.recoveryCooldownMs) {\n this.lastRecoveryAt = now;\n const attempt = ++this.recoveryAttempt;\n if (attempt > this.recoveryMaxAttempts) {\n if (!this.recoveryExhausted) {\n this.recoveryExhausted = true;\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: this.recoveryMaxAttempts, outcome: RECOVERY_OUTCOME.EXHAUSTED });\n }\n // No automatic attempt is left. Release demand-driven operations so\n // subscribe/publish can still start an explicit manual recovery.\n this.releaseRecoveryGate();\n return;\n }\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt, outcome: RECOVERY_OUTCOME.SCHEDULED });\n // Arm the gate before the timer so operations arriving in the\n // cooldown window cannot slip past onto the failed connection.\n if (this.recoveryGate === null) {\n let release!: () => void;\n this.recoveryGate = new Promise<void>(resolve => {\n release = resolve;\n });\n this.recoveryGateRelease = release;\n }\n this.recoveryDemandAllowed = false;\n const timerToken = ++this.recoveryTimerToken;\n this.recoveryTimer = setTimeout(() => {\n if (timerToken !== this.recoveryTimerToken) return;\n this.recoveryTimer = null;\n if (this.stopping || !this.started || this.suspended || this.status !== WORKER_STATUS.ERROR) {\n this.releaseRecoveryGate();\n return;\n }\n this.recoveryDemandAllowed = false;\n const opening = this.reopenTransport(attempt);\n void opening.then(\n () => this.releaseRecoveryGate(),\n () => this.allowDemandRecovery()\n );\n }, this.recoveryCooldownMs);\n }\n } else if (status === WORKER_STATUS.ERROR) {\n // An error outside an active recovery sequence (for example after an\n // initial start failure) must not leave demand-driven operations gated.\n this.releaseRecoveryGate();\n }\n if (notifyHandlers) this.invokeHandlers(this.statusHandlers, handler => handler(status));\n }\n\n private recordError(error: unknown, source: DataBusFailureSource = FAILURE_SOURCE.TRANSPORT): void {\n const at = this.now();\n // Transport failures must land in *both* ledgers. lastFailure is the\n // unified record exposed by getHealthSummary(); lastError/lastErrorAt are\n // the transport-failure ledger behind getRecoveryStats().hasError and\n // ready()'s \"surface the last failure\" path. Recording only lastFailure\n // let a single health snapshot report a retained transport failure while\n // recovery claimed hasError: false / errorMessage: null.\n if (source === FAILURE_SOURCE.TRANSPORT) {\n this.lastError = error;\n this.lastErrorAt = at;\n }\n this.lastFailure = {\n source,\n message: error instanceof Error ? error.message : String(error),\n at\n };\n if (source === FAILURE_SOURCE.PERSISTENCE) {\n this.persistenceFailureCount += 1;\n this.persistenceLastFailureAt = this.lastFailure.at;\n this.persistenceLastErrorMessage = this.lastFailure.message;\n }\n this.trace.event({\n type: TRACE_EVENT_TYPE.ERROR,\n source: source === FAILURE_SOURCE.TRANSPORT ? TRACE_ERROR_SOURCE.TRANSPORT : TRACE_ERROR_SOURCE.OPERATION\n });\n }\n\n private notifyError(error: unknown): void {\n this.invokeHandlers(this.errorHandlers, handler => handler(error), INVOKE_LABEL.ERROR_HANDLER);\n }\n\n private reportError(error: unknown, source: DataBusFailureSource = FAILURE_SOURCE.TRANSPORT): void {\n this.recordError(error, source);\n this.notifyError(error);\n }\n\n /** Report a persistence failure to the trace and the unified failure ledger,\n * unless it is a {@link PersistenceRetryCancelledError} cancellation from a\n * lifecycle transition (teardown should stay quiet). */\n private reportPersistenceError(error: unknown): void {\n if (error instanceof PersistenceRetryCancelledError) return;\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.PERSISTENCE_CLEANUP });\n this.reportError(error, FAILURE_SOURCE.PERSISTENCE);\n }\n\n private traceSubscription(action: (typeof SUBSCRIPTION_ACTION)[keyof typeof SUBSCRIPTION_ACTION], topic: string): void {\n this.trace.event({\n type: TRACE_EVENT_TYPE.SUBSCRIPTION,\n action,\n topic,\n activeTopics: this.transportSubscribedTopics.size\n });\n }\n\n /** Emit the coordination trace snapshot from the current cluster state.\n * Called after a transport opens (start and recovery), when the role and\n * route picture has settled \u2014 the synchronous pre-open snapshot would see no\n * routes because their writes are still coalesced in the batch writer. */\n private emitCoordinationTrace(): void {\n const snapshot = this.cluster.getSnapshot();\n this.trace.event({\n type: TRACE_EVENT_TYPE.COORDINATION,\n coordinated: snapshot.coordinated,\n activeWorkers: snapshot.workers.filter(worker => worker.role === WORKER_ROLE.ACTIVE).length,\n workers: snapshot.workers.map(formatWorkerTrace),\n routes: snapshot.routes.map(formatRouteTrace)\n });\n }\n\n /** Ask the transport to subscribe to a topic (idempotent). */\n private subscribeTransport(topic: string): boolean {\n if (this.transportSubscribedTopics.has(topic)) return false;\n this.transportSubscribedTopics.add(topic);\n this.runTransport(() => this.transport.subscribe(topic));\n return true;\n }\n\n private unsubscribeTransport(topic: string): boolean {\n if (!this.transportSubscribedTopics.delete(topic)) return false;\n this.runTransport(() => this.transport.unsubscribe(topic));\n return true;\n }\n\n /** Invoke `callback` for each item in `handlers`, isolating a throwing\n * callback so the remaining ones still run. Dispatch/status handler failures\n * are routed to `reportError` (which surfaces them to error subscribers);\n * error-handler failures are logged to the console to avoid infinite\n * recursion through reportError itself. */\n private invokeHandlers<T>(\n handlers: Iterable<T>,\n callback: (handler: T) => void,\n label: (typeof INVOKE_LABEL)[keyof typeof INVOKE_LABEL] = INVOKE_LABEL.DISPATCH\n ): void {\n for (const handler of handlers) {\n try {\n callback(handler);\n } catch (error) {\n if (label === INVOKE_LABEL.ERROR_HANDLER) {\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(`[${DEFAULT_STORAGE_PREFIX}] error handler threw:`, error);\n }\n } else {\n this.reportError(error, FAILURE_SOURCE.DISPATCH);\n }\n }\n }\n }\n\n /** Resume the resources paused by a pagehide suspension. Both the native\n * pageshow path and explicit start() must run this so an explicit resume\n * cannot leave trace metrics and periodic cleanup timers permanently off. */\n private resumeSuspendedResources(): void {\n this.trace.start();\n this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.RESUME });\n this.startDedupSweep();\n this.replayManager.start();\n }\n\n /**\n * Suspend the transport when the tab goes hidden. Stops the transport and\n * clears subscription state so it will be re-established on resume.\n */\n private suspendTransport(): void {\n if (this.stopping) return;\n this.lifecycleEpoch += 1;\n this.suspended = true;\n this.cancelScheduledRecovery();\n this.transportReady = false;\n this.transportSubscribedTopics.clear();\n this.updateStatus(WORKER_STATUS.DISCONNECTED);\n // Repeated hide/show rounds can leave a resume opening queued behind an\n // older stop gate. If this suspend is already represented by that gate,\n // reuse it. Otherwise the current startPromise is a newer opening (which\n // may already have called transport.start), so chain a fresh idempotent\n // stop after it. This restores the invariant that startPromise and\n // pendingStop are the same promise while suspended; without it a later\n // pageShow reuses the now-superseded opening and the bus stays hidden.\n if (this.pendingStop && (this.startPromise === null || this.startPromise === this.pendingStop)) {\n // A failed open may already own the stop cleanup; reuse it instead of\n // issuing a redundant idempotent stop. Restore the suspended invariant\n // so a later pageshow/start chains its reopen behind this same gate.\n this.startPromise = this.pendingStop;\n return;\n }\n // Chain the stop after any in-flight start so an async open settles first.\n const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();\n const stopping = pending\n .catch(() => undefined)\n .then(() => this.transport.stop())\n .catch(error => this.reportError(error));\n this.startPromise = stopping;\n this.pendingStop = stopping;\n }\n\n /** Create an immediate stop promise (no prior chain). Used by openTransport's\n * failure path where there is no in-flight start to wait for. */\n private createStopPromise(): Promise<void> {\n return Promise.resolve()\n .then(() => this.transport.stop())\n .catch(stopError => this.reportError(stopError));\n }\n\n /**\n * Resume the transport when the tab becomes visible again, or recover from a\n * runtime transport failure. Re-opens the transport with the stored active\n * config, chained after any pending operation so an async transport stop\n * completes before the new start. Returns the opening promise.\n */\n private resumeTransport(): void {\n void this.reopenTransport();\n }\n\n /**\n * Re-open the transport with the previously stored active config. Chains\n * after any in-flight lifecycle operation (e.g. a suspend stop), swallowing\n * its rejection so the reopen is not blocked. Returns the opening promise so\n * callers can queue operations behind it.\n */\n private reopenTransport(recoveryAttempt?: number): Promise<void> {\n if (this.stopping || this.activeConfig === undefined) return Promise.resolve();\n // A resume/recovery already has an opening in flight. Reuse it so a stale\n // recovery timer or a second caller cannot open a second transport. A\n // page-hide stop gate (startPromise === pendingStop) must not be reused\n // as an opening \u2014 that would make resume return a promise that resolves\n // on stop completion, not on a ready transport. Instead, fall through and\n // chain the new open after that pending stop.\n if (this.startPromise && this.startPromise !== this.pendingStop) return this.startPromise;\n const config = this.activeConfig;\n const traceAttempt = recoveryAttempt ?? (this.recoveryAttempt > 0 ? this.recoveryAttempt : undefined);\n // A resume/recovery means the bus is meant to keep running, even after an\n // initial start failed and then recovered while hidden. Without this, a\n // later stop() would be a no-op and leave the reopened transport running.\n this.started = true;\n this.suspended = false;\n this.updateStatus(WORKER_STATUS.CONNECTING);\n const lifecycleEpoch = ++this.lifecycleEpoch;\n const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();\n const opening = pending\n .catch(() => undefined)\n .then(() => this.openTransport(config, Promise.resolve(), false, lifecycleEpoch));\n this.startPromise = opening;\n // Reset the gate on success too, so a later runtime failure can schedule a\n // fresh reopen instead of reusing this settled promise.\n void opening.then(\n () => {\n if (this.startPromise === opening) this.startPromise = null;\n if (lifecycleEpoch !== this.lifecycleEpoch) return;\n if (traceAttempt !== undefined) {\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: traceAttempt, outcome: RECOVERY_OUTCOME.SUCCEEDED });\n this.recoveryAttempt = 0;\n this.recoveryExhausted = false;\n }\n },\n () => {\n if (this.startPromise === opening) this.startPromise = null;\n if (lifecycleEpoch !== this.lifecycleEpoch) return;\n if (traceAttempt !== undefined) {\n this.trace.event({ type: TRACE_EVENT_TYPE.RELIABILITY, operation: RELIABILITY_OPERATION.TRANSPORT_RECOVERY, attempt: traceAttempt, outcome: RECOVERY_OUTCOME.FAILED });\n }\n }\n );\n void opening.catch(() => undefined);\n return opening;\n }\n\n /**\n * Run a transport operation now if the transport is ready, otherwise queue\n * it behind the start promise. This ensures subscribe/unsubscribe calls made\n * during startup are not lost.\n */\n private runTransport(operation: () => void | Promise<void>): void {\n // A hidden tab's transport is intentionally stopped; subscriptions are\n // re-established by the cluster on resume, and publications must not be\n // sent to a stopped transport.\n if (this.suspended) return;\n // Automatic recovery is scheduled but has not run yet. Hold the operation\n // until that attempt settles instead of writing it to the connection that\n // just reported `error`.\n if (this.recoveryGate && !this.stopping) {\n // A failed automatic attempt leaves the gate closed but enables explicit\n // demand recovery. The first transport operation starts that reopen once;\n // every waiter remains queued behind the gate and runs after success.\n if (this.recoveryDemandAllowed && this.status === WORKER_STATUS.ERROR && !this.suspended) {\n this.recoveryDemandAllowed = false;\n const opening = this.reopenTransport();\n void opening.then(\n () => this.releaseRecoveryGate(),\n () => this.allowDemandRecovery()\n );\n }\n const gate = this.recoveryGate;\n void gate.then(() => {\n if (this.stopping || this.suspended) return;\n this.runTransport(operation);\n });\n return;\n }\n // `transportReady` is intentionally retained through a runtime error so\n // ready() keeps tracking the installed transport. Operations, however,\n // must not be written to a connection that is gone. `error` always falls\n // through to recovery, and a clean `disconnected` *after* the transport\n // actually reached `connected` means the working connection dropped (a\n // WebSocket `close`); both fall through to the demand-driven reopen below\n // so the operation is flushed against the replacement instead of being\n // handed to a closed socket that can only report a dropped frame. A\n // transport that resolved start() before reporting its first `connected`\n // (worker-style backends report the connection asynchronously) keeps the\n // previous behaviour: its `disconnected` status is \"not connected yet\".\n const droppedAfterConnect =\n this.transportHasConnected && this.status === WORKER_STATUS.DISCONNECTED;\n if (this.transportReady && this.status !== WORKER_STATUS.ERROR && !droppedAfterConnect && !this.stopping) {\n try {\n void Promise.resolve(operation()).catch(error => this.reportError(error));\n } catch (error) {\n this.reportError(error);\n }\n return;\n }\n // Transport is down but we are still meant to be started \u2014 reopen so the\n // operation is not silently dropped. This covers the case where a resume\n // or recovery attempt failed, leaving transportReady=false, startPromise=null.\n let ready = this.startPromise;\n if (!ready && this.started && !this.stopping && this.activeConfig !== undefined) {\n ready = this.reopenTransport();\n }\n if (!ready || this.stopping) return;\n void ready\n .then(\n () => {\n if (!this.started || this.stopping || this.suspended) return;\n return operation();\n },\n // The opening promise reports its own lifecycle failure through\n // openTransport(). Swallowing it here prevents a stale startup\n // rejection from being recorded again after an onStatus/onError\n // callback has already started and reset the ledger for a retry.\n () => undefined\n )\n .catch(error => this.reportError(error));\n }\n\n /**\n * Publications started after teardown begins cannot reach any transport.\n * Surface that as a normal asynchronous API failure instead of letting\n * runTransport() return silently. Empty publishBatch() calls remain a no-op\n * and are filtered by the caller before this check.\n */\n private rejectPublishDuringStop(operation: 'publish' | 'publishBatch'): boolean {\n if (!this.stopping) return false;\n this.reportError(new Error(\n `CrossTabDataBus is stopping; ${operation}() was not sent. ` +\n 'Wait for stop() to resolve, then call start() before publishing again.'\n ));\n return true;\n }\n\n /**\n * Ensure the DataBus is started, throwing if no initialConfig was provided.\n * Called automatically by subscribe/publish/ready when autoStart is true.\n */\n private ensureStarted(): void {\n if (this.started) return;\n if (!this.hasInitialConfig) {\n throw new Error(\n 'CrossTabDataBus requires initialConfig for automatic startup, or an explicit start(config) call.'\n );\n }\n const starting = this.start(this.initialConfig as TConfig);\n void starting.catch(() => undefined);\n }\n}\n\n/** Format a WorkerRecord for the coordination trace event. */\nfunction formatWorkerTrace(worker: { workerId: string; status: string; load: number; tabId: string }): string {\n return `${worker.workerId}|${worker.status}|load=${worker.load}|tab=${worker.tabId}`;\n}\n\n/** Format a route for the coordination trace event. */\nfunction formatRouteTrace(route: { topicKey: string; workerId: string; confirmedAt?: number }): string {\n return `${route.topicKey}@${route.workerId}|confirmed=${route.confirmedAt !== undefined}`;\n}\n", "/**\n * Worker-mode types and backend selection.\n *\n * Defines the WorkerMode preference flags and the selectWorkerBackend function\n * that resolves the preference against browser capability to pick the actual\n * backend (dedicated, shared, or local fallback).\n *\n * The literal values are derived from `utils/constants.ts` so the preference and\n * resolved-backend strings referenced across the transport stay in one place.\n */\nimport { WORKER_BACKEND, WORKER_MODE } from './utils/constants';\n\n/** Preferred Worker mode.\n * - `dedicated` \u2192 try Dedicated Worker first (one WebSocket per tab).\n * - `shared` \u2192 try SharedWorker first (one process, per-port connections).\n * - `auto` \u2192 same as `shared` (alias for forward compatibility). */\nexport type WorkerMode = (typeof WORKER_MODE)[keyof typeof WORKER_MODE];\n\n/** Resolved backend that was actually created. `local` means the session\n * runs on the main thread (fallback when no Worker API is available). */\nexport type WorkerBackend = (typeof WORKER_BACKEND)[keyof typeof WORKER_BACKEND];\n\n/** Override Worker availability for testing or environments where feature\n * detection is unreliable (e.g. sandboxed iframes). When a field is omitted,\n * the global `typeof Worker` / `typeof SharedWorker` check is used. */\nexport interface WorkerAvailability {\n /** When provided, overrides `typeof Worker !== 'undefined'`. */\n worker?: boolean;\n /** When provided, overrides `typeof SharedWorker !== 'undefined'`. */\n sharedWorker?: boolean;\n}\n\n/**\n * Resolves the worker backend by feature detection, without touching globals\n * that may be missing in SSR or embedded environments.\n *\n * - `dedicated` prefers Dedicated Worker, then SharedWorker, then local mode.\n * - `shared` and `auto` prefer SharedWorker, then Dedicated Worker, then local.\n * Returns `'local'` when neither Worker API is available (main-thread fallback).\n */\nexport function selectWorkerBackend(\n mode: WorkerMode,\n availability: WorkerAvailability = {}\n): WorkerBackend {\n const hasDedicated = availability.worker ?? typeof Worker !== 'undefined';\n const hasShared = availability.sharedWorker ?? typeof SharedWorker !== 'undefined';\n if (mode === WORKER_MODE.SHARED || mode === WORKER_MODE.AUTO) {\n return hasShared ? WORKER_BACKEND.SHARED : hasDedicated ? WORKER_BACKEND.DEDICATED : WORKER_BACKEND.LOCAL;\n }\n return hasDedicated ? WORKER_BACKEND.DEDICATED : hasShared ? WORKER_BACKEND.SHARED : WORKER_BACKEND.LOCAL;\n}", "import type { DataBusPublication } from './types';\n\n/**\n * Normalize legacy flat publications, metadata payload envelopes, and the\n * canonical `{ op: 'publication', publication: ... }` shape.\n *\n * `fallbackTopic` is used by transports such as Centrifuge where the channel\n * is supplied out-of-band by the client library rather than inside the data.\n */\nexport function parseDataBusPublication<TData = unknown>(\n value: unknown,\n fallbackTopic?: string\n): DataBusPublication<TData> | null {\n if (!value || typeof value !== 'object') {\n return fallbackTopic ? { topic: fallbackTopic, data: value as TData } : null;\n }\n const frame = value as Record<string, unknown>;\n const nested = frame.publication && typeof frame.publication === 'object'\n ? frame.publication as Record<string, unknown>\n : null;\n const publication = nested ?? frame;\n const topic = typeof publication.topic === 'string' ? publication.topic : fallbackTopic;\n if (!topic) return null;\n\n // Centrifuge's legacy metadata envelope carries `{ data, messageId }`\n // without a topic because the channel is provided by PublicationContext.\n const hasMetadataEnvelope = fallbackTopic !== undefined\n && Object.prototype.hasOwnProperty.call(publication, 'data')\n && (typeof publication.messageId === 'string' || typeof publication.timestamp === 'number');\n const data = nested || fallbackTopic === undefined || hasMetadataEnvelope\n ? publication.data\n : value;\n const messageId = typeof publication.messageId === 'string' && publication.messageId.length > 0\n ? publication.messageId\n : undefined;\n const timestamp = typeof publication.timestamp === 'number' && Number.isFinite(publication.timestamp)\n ? publication.timestamp\n : undefined;\n return {\n topic,\n data: data as TData,\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["sum", "MAX_RETRY_DELAY_MS", "buffer", "opening"]
7
7
  }