cross-tab-worker-databus 0.21.11 → 0.21.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +2 -2
- package/README.zh.md +3 -1
- package/dist/centrifuge-protocol.d.ts.map +1 -1
- package/dist/centrifuge-session.d.ts +10 -3
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.d.ts +10 -4
- package/dist/centrifuge.d.ts.map +1 -1
- package/dist/centrifuge.js +23 -11
- package/dist/centrifuge.js.map +2 -2
- package/dist/centrifuge.shared.worker.js +28 -11
- package/dist/centrifuge.shared.worker.js.map +2 -2
- package/dist/centrifuge.worker.js +10 -3
- package/dist/centrifuge.worker.js.map +2 -2
- package/dist/{chunk-UO57SLQD.js → chunk-4HW6PYIR.js} +67 -33
- package/dist/chunk-4HW6PYIR.js.map +7 -0
- package/dist/{chunk-2TTJ7LGZ.js → chunk-NPFTD7YE.js} +1 -1
- package/dist/chunk-NPFTD7YE.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +86 -40
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/hooks.cjs.map +2 -2
- package/dist/cjs/index.cjs +80 -36
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/vue.cjs.map +2 -2
- package/dist/core/cluster.d.ts +33 -16
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +19 -5
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/dedup-manager.d.ts +6 -3
- package/dist/core/dedup-manager.d.ts.map +1 -1
- package/dist/core/environment.d.ts +14 -5
- package/dist/core/environment.d.ts.map +1 -1
- package/dist/core/hash.d.ts +10 -4
- package/dist/core/hash.d.ts.map +1 -1
- package/dist/core/replay-manager.d.ts +2 -1
- package/dist/core/replay-manager.d.ts.map +1 -1
- package/dist/core/routing.d.ts +18 -9
- package/dist/core/routing.d.ts.map +1 -1
- package/dist/core/storage-batch.d.ts +2 -2
- package/dist/core/storage-batch.d.ts.map +1 -1
- package/dist/core/trace.d.ts +21 -9
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/types.d.ts +13 -5
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +1 -1
- package/dist/hooks.js.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +2 -2
- package/dist/utils/constants.d.ts +7 -4
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/validation.d.ts +12 -4
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/vue.js +1 -1
- package/dist/websocket.d.ts +24 -6
- package/dist/websocket.d.ts.map +1 -1
- package/dist/workers/port-reaper.d.ts +18 -8
- package/dist/workers/port-reaper.d.ts.map +1 -1
- package/docs/api.md +6 -5
- package/docs/benchmarks.md +9 -9
- package/docs/release-checklist.md +6 -6
- package/docs/roadmap.md +57 -2
- package/docs/transports.md +3 -2
- package/docs/zh/api.md +6 -5
- package/docs/zh/benchmarks.md +9 -9
- package/docs/zh/release-checklist.md +6 -6
- package/docs/zh/roadmap.md +42 -2
- package/docs/zh/transports.md +2 -1
- package/package.json +2 -1
- package/dist/chunk-2TTJ7LGZ.js.map +0 -7
- package/dist/chunk-UO57SLQD.js.map +0 -7
package/dist/core/trace.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,gBAAgB,EAChB,UAAU,EACX,MAAM,oBAAoB,CAAC;AAE5B,uEAAuE;AACvE
|
|
1
|
+
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -57,10 +57,15 @@ export interface WorkerThroughputSample {
|
|
|
57
57
|
messageCount: number;
|
|
58
58
|
/** Approximate payload bytes shipped by this Worker within the window. */
|
|
59
59
|
byteCount: number;
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
/** Positive scheduling overrun **of the last sample window**, in ms:
|
|
61
|
+
* `max(0, windowMs - heartbeatIntervalMs)`. Not an accumulator — each sample
|
|
62
|
+
* recomputes it and resets the window, so it reports the most recent gap, not
|
|
63
|
+
* a running total. The window is anchored at the previous worker-record write,
|
|
64
|
+
* which the heartbeat tick causes but a status, visibility or load change also
|
|
65
|
+
* causes, so a frequently-writing worker sees a small window and a low value.
|
|
66
|
+
* A starved event loop (the browser-observable equivalent of CPU saturation)
|
|
67
|
+
* stretches the gap, so peers can steer new routes away from a throttled
|
|
68
|
+
* Worker. */
|
|
64
69
|
overrunMs: number;
|
|
65
70
|
/** Timestamp (ms) when the sample was captured. */
|
|
66
71
|
sampledAt: number;
|
|
@@ -86,7 +91,10 @@ export interface LoadWeightingOptions {
|
|
|
86
91
|
export interface WorkerRecord {
|
|
87
92
|
/** Cluster protocol version advertised by this worker. */
|
|
88
93
|
protocolVersion?: number;
|
|
89
|
-
/**
|
|
94
|
+
/** Identity of *this runtime instance*, not of the tab: random-suffixed at
|
|
95
|
+
* construction and never persisted, so a refresh produces a new one. What
|
|
96
|
+
* survives a refresh is `tabId` below, which is what lets a reloaded tab
|
|
97
|
+
* reclaim its routes instead of looking brand new. */
|
|
90
98
|
workerId: string;
|
|
91
99
|
/** Identity of the browser tab hosting this worker. Survives refresh. */
|
|
92
100
|
tabId: string;
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,WAAW,EACX,aAAa,EACd,MAAM,oBAAoB,CAAC;AAE5B;;+DAE+D;AAC/D,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C;;iFAEiF;AACjF,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAExE;0EAC0E;AAC1E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE9E;oFACoF;AACpF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEvF,4DAA4D;AAC5D,MAAM,WAAW,0BAA0B;IACzC,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6DAA6D;AAC7D,MAAM,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAE/D,qDAAqD;AACrD,MAAM,WAAW,kBAAkB,CAAC,KAAK,GAAG,OAAO,CAAE,SAAQ,0BAA0B;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,CAAC;CACb;AAED,8EAA8E;AAC9E,MAAM,WAAW,0BAA0B,CAAC,KAAK,GAAG,OAAO;IACzD,EAAE,EAAE,OAAO,uBAAuB,CAAC;IACnC,WAAW,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;CACxC;AAED;iFACiF;AACjF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEtF;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,KAAK,EAC/C,MAAM,EAAE,MAAM,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,EAC7C,IAAI,GAAE,cAAmB,GACxB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,CAkBxC;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,KAAK,EACpD,GAAG,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,EAC3C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,GAChD,IAAI,CAON;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAC9C,GAAG,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,GAC1C,YAAY,CAWd;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAC9C,GAAG,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,EAC3C,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,oBAAoB,GAAG,IAAI,CAuB7B"}
|
package/dist/hooks.js
CHANGED
package/dist/hooks.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/hooks.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * React hooks adapter for cross-tab-worker-databus.\n *\n * A thin, transport-agnostic bridge between the imperative CrossTabDataBus\n * API and React component lifecycles. React is an optional peer dependency \u2014\n * this module is a separate entry point so consumers who don't use React\n * never load it.\n *\n * - `useCrossTabDataBus` owns the bus lifecycle: created on mount, stopped on\n * unmount. It is StrictMode-safe: the double-invoked effect
|
|
5
|
-
"mappings": ";;;;;
|
|
4
|
+
"sourcesContent": ["/**\n * React hooks adapter for cross-tab-worker-databus.\n *\n * A thin, transport-agnostic bridge between the imperative CrossTabDataBus\n * API and React component lifecycles. React is an optional peer dependency \u2014\n * this module is a separate entry point so consumers who don't use React\n * never load it.\n *\n * - `useCrossTabDataBus` owns the bus lifecycle: created on mount, stopped on\n * unmount. It is StrictMode-safe: the cleanup calls `instance.stop()`, so the\n * double-invoked effect runs a full create \u2192 stop \u2192 create across *separate*\n * instances. That is not the BFCache path and does not stand in for it \u2014\n * page-hide takes `onSuspend`, which keeps this same bus, its `topicHandlers`\n * and its replay buffers, and is reversed by resume. So suspend/resume\n * coverage has to come from a real hide/show, not from this hook.\n * - `useCrossTabSubscription` attaches a message handler with automatic\n * cleanup; the handler is read through a ref, so you can pass inline\n * closures without resubscribing on every render.\n * - `useCrossTabStatus` mirrors `bus.onStatus()` into React state.\n * - `useCrossTabHealth` polls `bus.getHealthSummary()` into React state,\n * with event-driven refreshes on status changes and errors.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport type { DependencyList } from 'react';\nimport type { CrossTabDataBus, DataBusHealthSummary } from './core/data-bus';\nimport type { DataBusMessage, WorkerStatus } from './core/types';\nimport { WORKER_STATUS } from './utils/constants';\n\n/**\n * Create a CrossTabDataBus for the component's lifetime.\n *\n * @param create Factory invoked once per effect run. Return a fresh bus \u2014\n * do not share a bus instance between effects, or StrictMode's\n * mount \u2192 stop \u2192 mount cycle will stop the shared instance out from\n * under the second mount.\n * @param deps Re-create the bus when these change (default: create once).\n * @returns The active bus, or `null` before the first effect has run (SSR\n * and the initial render).\n */\nexport function useCrossTabDataBus<TConfig, TData>(\n create: () => CrossTabDataBus<TConfig, TData>,\n deps: DependencyList = []\n): CrossTabDataBus<TConfig, TData> | null {\n const [bus, setBus] = useState<CrossTabDataBus<TConfig, TData> | null>(null);\n useEffect(() => {\n // No \"is this effect still current?\" check is needed: React runs an\n // effect's cleanup before its next invocation, and nothing between the\n // lines below yields, so `create()` cannot be interleaved with a newer\n // mount and this cleanup always stops the instance its own run created.\n const instance = create();\n setBus(instance);\n void instance.ready().catch(() => {});\n return () => {\n setBus(null);\n void instance.stop();\n };\n // The factory is intentionally not a dependency: callers pass an inline\n // closure and key recreation through `deps` instead.\n }, deps);\n return bus;\n}\n\n/**\n * Subscribe to `topic` for the component's lifetime. The handler is read\n * through a ref on each delivery, so inline closures are safe without\n * unsubscribing/resubscribing on re-renders.\n *\n * When `bus` is null (not yet created) the subscription is queued until the\n * bus appears \u2014 the bus itself queues it until the transport is ready.\n */\nexport function useCrossTabSubscription<TConfig, TData>(\n bus: CrossTabDataBus<TConfig, TData> | null,\n topic: string,\n handler: (message: DataBusMessage<TData>) => void\n): void {\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => {\n if (!bus) return;\n return bus.subscribe(topic, message => handlerRef.current(message));\n }, [bus, topic]);\n}\n\n/**\n * Mirror the bus connection status into React state. Reports the live value\n * via `onStatus` and reads the current value synchronously whenever `bus`\n * changes identity.\n */\nexport function useCrossTabStatus<TConfig, TData>(\n bus: CrossTabDataBus<TConfig, TData> | null\n): WorkerStatus {\n const [status, setStatus] = useState<WorkerStatus>(WORKER_STATUS.CONNECTING);\n useEffect(() => {\n if (!bus) {\n setStatus(WORKER_STATUS.CONNECTING);\n return;\n }\n setStatus(bus.getStatus());\n return bus.onStatus(setStatus);\n }, [bus]);\n return status;\n}\n\n/**\n * Mirror the bus health summary into React state.\n *\n * `getHealthSummary()` is a snapshot, not an event stream, so the hook polls\n * it on an interval (default 1000 ms) and refreshes immediately on status\n * changes and errors. Pass `intervalMs: 0` to rely on event-driven refreshes\n * only; changing the interval replaces the timer without recreating the bus.\n * Returns `null` while the bus has not been created yet.\n */\nexport function useCrossTabHealth<TConfig, TData>(\n bus: CrossTabDataBus<TConfig, TData> | null,\n options?: { intervalMs?: number }\n): DataBusHealthSummary | null {\n const [health, setHealth] = useState<DataBusHealthSummary | null>(null);\n const intervalMs = options?.intervalMs ?? 1_000;\n useEffect(() => {\n if (!bus) {\n setHealth(null);\n return;\n }\n const refresh = () => setHealth(bus.getHealthSummary());\n refresh();\n const unsubscribeStatus = bus.onStatus(refresh);\n const unsubscribeError = bus.onError(refresh);\n const timer = intervalMs > 0 ? setInterval(refresh, intervalMs) : null;\n return () => {\n unsubscribeStatus();\n unsubscribeError();\n if (timer) clearInterval(timer);\n };\n // Depend on the normalized cadence rather than the options object so inline\n // option literals do not restart the effect on every render, while a real\n // intervalMs change still replaces the polling timer.\n }, [bus, intervalMs]);\n return health;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAsBA,SAAS,WAAW,QAAQ,gBAAgB;AAiBrC,SAAS,mBACd,QACA,OAAuB,CAAC,GACgB;AACxC,QAAM,CAAC,KAAK,MAAM,IAAI,SAAiD,IAAI;AAC3E,YAAU,MAAM;AAKd,UAAM,WAAW,OAAO;AACxB,WAAO,QAAQ;AACf,SAAK,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACpC,WAAO,MAAM;AACX,aAAO,IAAI;AACX,WAAK,SAAS,KAAK;AAAA,IACrB;AAAA,EAGF,GAAG,IAAI;AACP,SAAO;AACT;AAUO,SAAS,wBACd,KACA,OACA,SACM;AACN,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AACrB,YAAU,MAAM;AACd,QAAI,CAAC,IAAK;AACV,WAAO,IAAI,UAAU,OAAO,aAAW,WAAW,QAAQ,OAAO,CAAC;AAAA,EACpE,GAAG,CAAC,KAAK,KAAK,CAAC;AACjB;AAOO,SAAS,kBACd,KACc;AACd,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAuB,cAAc,UAAU;AAC3E,YAAU,MAAM;AACd,QAAI,CAAC,KAAK;AACR,gBAAU,cAAc,UAAU;AAClC;AAAA,IACF;AACA,cAAU,IAAI,UAAU,CAAC;AACzB,WAAO,IAAI,SAAS,SAAS;AAAA,EAC/B,GAAG,CAAC,GAAG,CAAC;AACR,SAAO;AACT;AAWO,SAAS,kBACd,KACA,SAC6B;AAC7B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,IAAI;AACtE,QAAM,aAAa,SAAS,cAAc;AAC1C,YAAU,MAAM;AACd,QAAI,CAAC,KAAK;AACR,gBAAU,IAAI;AACd;AAAA,IACF;AACA,UAAM,UAAU,MAAM,UAAU,IAAI,iBAAiB,CAAC;AACtD,YAAQ;AACR,UAAM,oBAAoB,IAAI,SAAS,OAAO;AAC9C,UAAM,mBAAmB,IAAI,QAAQ,OAAO;AAC5C,UAAM,QAAQ,aAAa,IAAI,YAAY,SAAS,UAAU,IAAI;AAClE,WAAO,MAAM;AACX,wBAAkB;AAClB,uBAAiB;AACjB,UAAI,MAAO,eAAc,KAAK;AAAA,IAChC;AAAA,EAIF,GAAG,CAAC,KAAK,UAAU,CAAC;AACpB,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { createOpaqueKey } from './core/hash';
|
|
|
21
21
|
export { WebSocketTransport, createWebSocketDataBus } from './websocket';
|
|
22
22
|
export type { CreateWebSocketDataBusOptions, WebSocketDataBusConfig, WebSocketLike } from './websocket';
|
|
23
23
|
export { selectWorkerBackend } from './worker-mode';
|
|
24
|
-
export type { WorkerBackend, WorkerMode } from './worker-mode';
|
|
24
|
+
export type { WorkerAvailability, WorkerBackend, WorkerMode } from './worker-mode';
|
|
25
25
|
export { DEFAULT_MAX_ACTIVE_WORKERS, approximatePayloadBytes, effectiveWorkerLoad, hasActiveOwner, isWildcardTopic, selectActiveWorkers, selectLeastLoadedWorker, selectRebalanceTarget, topicMatchesPattern } from './core/routing';
|
|
26
|
-
export type { DataBusErrorHandler, DataBusMessage, DataBusPublication, DataBusPublicationEnvelope, DataBusPublicationMetadata, DataBusPublishOptions, DataBusMessageHandler, DataBusStatusHandler, DataBusTransport, DataBusTransportHandlers, LoadWeightingOptions, MaybePromise, TabVisibilityState, TopicSubscriberRecord, WorkerClusterMessage, WorkerControlAction, WorkerRecord, WorkerRole, WorkerRoute, WorkerStatus, WorkerThroughputSample } from './core/types';
|
|
26
|
+
export type { DataBusErrorHandler, DataBusMessage, DataBusPublication, DataBusPublicationEnvelope, DataBusPublicationItem, DataBusPublicationMetadata, DataBusPublishOptions, DataBusMessageHandler, DataBusStatusHandler, DataBusTransport, DataBusTransportHandlers, LoadWeightingOptions, MaybePromise, TabVisibilityState, TopicSubscriberRecord, WorkerClusterMessage, WorkerControlAction, WorkerRecord, WorkerRole, WorkerRoute, WorkerStatus, WorkerThroughputSample } from './core/types';
|
|
27
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC1Q,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,YAAY,EAAE,wBAAwB,EAAE,iCAAiC,EAAE,MAAM,2BAA2B,CAAC;AAC7G,YAAY,EACV,6BAA6B,EAC7B,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3G,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,6BAA6B,EAC7B,sBAAsB,EACtB,aAAa,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC1Q,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,YAAY,EAAE,wBAAwB,EAAE,iCAAiC,EAAE,MAAM,2BAA2B,CAAC;AAC7G,YAAY,EACV,6BAA6B,EAC7B,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3G,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,6BAA6B,EAC7B,sBAAsB,EACtB,aAAa,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,sBAAsB,EACvB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
selectRebalanceTarget,
|
|
21
21
|
selectWorkerBackend,
|
|
22
22
|
topicMatchesPattern
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-4HW6PYIR.js";
|
|
24
24
|
import {
|
|
25
25
|
DEFAULT_STORAGE_PREFIX,
|
|
26
26
|
PRUNE_STRATEGY,
|
|
27
27
|
WORKER_STATUS,
|
|
28
28
|
WS_OP
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-NPFTD7YE.js";
|
|
30
30
|
|
|
31
31
|
// src/core/replay-persistence.ts
|
|
32
32
|
function createIndexedDbReplayPersistence(options) {
|
|
@@ -417,7 +417,8 @@ var WebSocketTransport = class {
|
|
|
417
417
|
this.subscribedTopics.add(topic);
|
|
418
418
|
this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
|
|
419
419
|
}
|
|
420
|
-
/** Idempotent:
|
|
420
|
+
/** Idempotent for local tracking: an unknown topic has nothing to delete, but
|
|
421
|
+
* the UNSUBSCRIBE frame is still sent, exactly as SUBSCRIBE is re-sent above. */
|
|
421
422
|
unsubscribe(topic) {
|
|
422
423
|
this.subscribedTopics.delete(topic);
|
|
423
424
|
this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });
|
|
@@ -507,8 +508,14 @@ var WebSocketTransport = class {
|
|
|
507
508
|
}
|
|
508
509
|
}
|
|
509
510
|
/** Send one JSON frame. Frames are dropped with an `onError` report when
|
|
510
|
-
* the socket is not open
|
|
511
|
-
*
|
|
511
|
+
* the socket is not open. A dropped SUBSCRIBE is recovered — `onopen`
|
|
512
|
+
* re-asserts every topic in `subscribedTopics` — but the other two are not:
|
|
513
|
+
* a dropped PUBLISH is the documented at-most-once loss, and a dropped
|
|
514
|
+
* UNSUBSCRIBE is the quieter one, because `unsubscribe()` has already deleted
|
|
515
|
+
* the topic from that set, so no later re-assert resends it and the server
|
|
516
|
+
* keeps streaming a channel the app left. Through the DataBus neither of the
|
|
517
|
+
* two is reachable while closed — `runTransport()` queues both operations
|
|
518
|
+
* behind recovery — so this reads as a direct-transport-contract note. */
|
|
512
519
|
sendFrame(payload) {
|
|
513
520
|
if (this.socket?.readyState !== WS_OPEN) {
|
|
514
521
|
this.handlers?.onError(new Error(`WebSocket is not open; dropped "${payload.op}" frame.`));
|
|
@@ -544,8 +551,11 @@ var WebSocketTransport = class {
|
|
|
544
551
|
this.socket.send(frame.buffer);
|
|
545
552
|
}
|
|
546
553
|
/** Parse a server frame. Only objects carrying a string `topic` are
|
|
547
|
-
* publications
|
|
548
|
-
*
|
|
554
|
+
* publications, and unknown shapes are dropped silently so a chatty server
|
|
555
|
+
* cannot crash the message path. Malformed JSON is *not* dropped silently: it
|
|
556
|
+
* goes to `handlers.onError` as "WebSocket server sent a non-JSON frame.",
|
|
557
|
+
* which also records it in the bus's last-failure diagnostics. Neither case
|
|
558
|
+
* throws out of the socket listener. */
|
|
549
559
|
async handleMessage(raw) {
|
|
550
560
|
if (typeof Blob !== "undefined" && raw instanceof Blob) {
|
|
551
561
|
const socket = this.socket;
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/core/replay-persistence.ts", "../src/websocket.ts"],
|
|
4
|
-
"sourcesContent": ["import type { DataBusMessage } from './types';\nimport { pruneReplayHistory } from './replay-pruning';\nimport { DEFAULT_STORAGE_PREFIX, PRUNE_STRATEGY } from '../utils/constants';\nimport { assertPositiveFiniteNumber, assertPositiveSafeInteger, assertPruneStrategy } from '../utils/validation';\n\n/** Optional persistence backend for replay history. */\nexport interface DataBusReplayPersistence<TData = unknown> {\n load(): Promise<ReadonlyArray<DataBusMessage<TData>>>;\n append(message: DataBusMessage<TData>): Promise<void>;\n /** Optional bulk append used to amortize IndexedDB transaction overhead. */\n appendBatch?(messages: ReadonlyArray<DataBusMessage<TData>>): Promise<void>;\n /** Remove all persisted replay history. */\n clear?(): Promise<void>;\n /** Remove persisted replay history for one exact topic. */\n clearTopic?(topic: string): Promise<void>;\n /** Remove persisted messages older than the given epoch-millisecond cutoff. */\n clearBefore?(timestamp: number): Promise<void>;\n}\n\nexport interface IndexedDbReplayPersistenceOptions {\n dbName?: string;\n maxPerTopic: number;\n pruneStrategy?: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n retentionMs?: number;\n}\n\n/** Create a browser IndexedDB-backed replay store. */\nexport function createIndexedDbReplayPersistence<TData = unknown>(\n options: IndexedDbReplayPersistenceOptions\n): DataBusReplayPersistence<TData> {\n const indexedDb = globalThis.indexedDB;\n if (!indexedDb) throw new Error('IndexedDB is unavailable in this environment.');\n const dbName = options.dbName ?? DEFAULT_STORAGE_PREFIX;\n const storeName = 'replay';\n const maxPerTopic = options.maxPerTopic;\n const pruneStrategy = options.pruneStrategy ?? PRUNE_STRATEGY.COUNT;\n const retentionMs = options.retentionMs;\n assertPruneStrategy(pruneStrategy);\n if (retentionMs !== undefined) assertPositiveFiniteNumber(retentionMs, 'retentionMs');\n assertPositiveSafeInteger(maxPerTopic, 'maxPerTopic');\n let dbPromise: Promise<IDBDatabase> | null = null;\n const invalidate = (db: IDBDatabase): void => {\n if (dbPromise) {\n // The rejection arm is what keeps this total: `invalidate` can chain onto a\n // *replacement* open that is still in flight, and `.then(current => \u2026)`\n // alone would derive a rejected promise nobody observes, turning that\n // open's failure into an unhandled rejection on top of the caller's. No\n // test reaches it \u2014 it needs a scheduled failure racing a rejecting reopen\n // \u2014 and it is not the `open()` catch below, which is registered on the\n // promise it guards and only clears the cache.\n void dbPromise.then(current => {\n if (current === db) {\n current.close();\n dbPromise = null;\n }\n }, () => undefined);\n }\n };\n // IndexedDB transactions are atomic, but a read-modify-write append can\n // still lose updates when callers start several appends concurrently.\n // Serialize all mutations per adapter instance while keeping reads free.\n // Consecutive append/appendBatch entries are coalesced into a single\n // transaction at the head of the queue, so a burst spanning many microtask\n // flushes issues one transaction instead of one per flush. Ordering against\n // clears is preserved: coalescing only merges adjacent batch entries and\n // never reorders them relative to a clear.\n type QueuedMutation =\n | { kind: 'batch'; messages: ReadonlyArray<DataBusMessage<TData>> }\n | { kind: 'run'; run: () => Promise<void> };\n const pending: Array<{\n mutation: QueuedMutation;\n resolve: () => void;\n reject: (error: unknown) => void;\n }> = [];\n let draining = false;\n\n const drain = async (): Promise<void> => {\n if (draining) return;\n draining = true;\n try {\n // Let a burst of synchronous enqueues accumulate into `pending` before\n // the first coalescing pass, so a single flush cycle's batches merge\n // into one transaction instead of two.\n await Promise.resolve();\n while (pending.length > 0) {\n const head = pending[0]!.mutation;\n if (head.kind === 'batch') {\n // Merge every adjacent batch entry into one transaction.\n const merged: DataBusMessage<TData>[] = [];\n const entries: Array<{ resolve: () => void; reject: (error: unknown) => void }> = [];\n while (pending.length > 0) {\n const next = pending[0]!.mutation;\n if (next.kind !== 'batch') break;\n merged.push(...next.messages);\n entries.push({ resolve: pending[0]!.resolve, reject: pending[0]!.reject });\n pending.shift();\n }\n try {\n await appendTransaction(merged);\n for (const entry of entries) entry.resolve();\n } catch (error) {\n for (const entry of entries) entry.reject(error);\n }\n } else {\n const entry = pending.shift()!;\n try {\n await head.run();\n entry.resolve();\n } catch (error) {\n entry.reject(error);\n }\n }\n }\n } finally {\n draining = false;\n }\n };\n\n const enqueue = (mutation: QueuedMutation): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n pending.push({ mutation, resolve, reject });\n void drain();\n });\n\n /** Read-modify-write one topic-batched append inside a single transaction. */\n const appendTransaction = (messages: ReadonlyArray<DataBusMessage<TData>>): Promise<void> =>\n (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try {\n transaction = db.transaction(storeName, 'readwrite');\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n const store = transaction.objectStore(storeName);\n const grouped = new Map<string, DataBusMessage<TData>[]>();\n for (const message of messages) {\n grouped.set(message.topic, [...(grouped.get(message.topic) ?? []), message]);\n }\n let hasError = false;\n const fail = (error: unknown): void => {\n if (hasError) return;\n hasError = true;\n invalidate(db);\n reject(error);\n };\n for (const [topic, topicMessages] of grouped) {\n const request = store.get(topic);\n request.onsuccess = () => {\n if (hasError) return;\n const history = pruneReplayHistory(\n ((request.result?.messages ?? []) as DataBusMessage<TData>[]).concat(topicMessages),\n { maxPerTopic, pruneStrategy, retentionMs, now: Date.now() }\n );\n store.put({ topic, messages: history });\n };\n request.onerror = () => fail(request.error ?? new Error('Failed to read replay history.'));\n }\n transaction.oncomplete = () => {\n if (!hasError) resolve();\n };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to persist replay history.'));\n // A connection loss can abort a transaction without first dispatching a\n // request error. Without this path, the serialized mutation queue would\n // stay blocked forever after the promise never settles.\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to persist replay history.'));\n });\n })();\n const open = (): Promise<IDBDatabase> => {\n // The two `if (dbPromise \u2026)` guards inside have never had their false arm\n // execute. Reaching either needs a connection that is live but no longer\n // cached, so a versionchange could land on an empty slot, or a slot replaced\n // before its own rejection catch runs \u2014 and both are excluded here because a\n // replacement can only start from a cleared slot, which is what the clearing\n // path itself performs. Defensive, so recorded rather than pinned.\n if (dbPromise) return dbPromise;\n const pending = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDb.open(dbName, 1);\n request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: 'topic' });\n request.onsuccess = () => {\n const db = request.result;\n // A schema upgrade in another tab invalidates this connection. Close\n // it and clear the cached promise so the next operation reopens a\n // usable connection instead of repeatedly targeting a dead database.\n db.onversionchange = () => {\n db.close();\n if (dbPromise) dbPromise = null;\n };\n resolve(db);\n };\n request.onerror = () => reject(request.error ?? new Error('Failed to open replay database.'));\n });\n dbPromise = pending;\n // Do not permanently cache a rejected open promise. IndexedDB can fail\n // transiently (quota, private-mode initialization, a closing connection,\n // or a browser shutdown); the next operation must be able to retry.\n void pending.catch(() => {\n if (dbPromise === pending) dbPromise = null;\n });\n return pending;\n };\n return {\n async load() {\n const db = await open();\n return new Promise((resolve, reject) => {\n let transaction: IDBTransaction;\n let request: IDBRequest;\n try {\n transaction = db.transaction(storeName, 'readonly');\n request = transaction.objectStore(storeName).getAll();\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n // One-shot settlement, the same latch in all four `invalidate`+`reject`\n // closures here. Entering it twice is reachable \u2014 one aborting\n // transaction dispatches `error` and then `abort` \u2014 but measured\n // harmless: `reject` on a settled promise is a no-op, and the extra\n // `invalidate` cannot see a replaced cache because both signals are\n // dispatched inside one microtask batch, so their callbacks run before\n // the serialized queue reopens. Deleting all four latches leaves the\n // suite green and the connection count unchanged; they stay because that\n // proof rests on dispatch timing outside this file. The stale-signal case\n // that is inside this file is pinned by tests/replay-persistence.test.ts's\n // 'keeps the connection a later operation reopened when an older signal\n // lands late'.\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n let records: Array<{ messages: DataBusMessage<TData>[] }> = [];\n request.onsuccess = () => {\n records = request.result as Array<{ messages: DataBusMessage<TData>[] }>;\n };\n request.onerror = () => fail(request.error ?? new Error('Failed to load replay history.'));\n transaction.oncomplete = () => {\n if (settled) return;\n settled = true;\n resolve(records.flatMap(record => record.messages));\n };\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to load replay history.'));\n });\n },\n append(message) {\n return enqueue({ kind: 'batch', messages: [message] });\n },\n appendBatch(messages) {\n if (messages.length === 0) return Promise.resolve();\n return enqueue({ kind: 'batch', messages });\n },\n clear() {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n transaction.objectStore(storeName).clear();\n transaction.oncomplete = () => { settled = true; resolve(); };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to clear replay history.'));\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to clear replay history.'));\n });\n })()\n });\n },\n clearTopic(topic) {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n transaction.objectStore(storeName).delete(topic);\n transaction.oncomplete = () => { settled = true; resolve(); };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to clear topic replay history.'));\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to clear topic replay history.'));\n });\n })()\n });\n },\n clearBefore(timestamp) {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n const store = transaction.objectStore(storeName);\n const request = store.getAll();\n request.onsuccess = () => {\n for (const record of request.result as Array<{ topic: string; messages: DataBusMessage<TData>[] }>) {\n const messages = record.messages.filter(message => message.timestamp === undefined || message.timestamp >= timestamp);\n if (messages.length === 0) store.delete(record.topic);\n else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });\n }\n };\n request.onerror = () => fail(request.error ?? new Error('Failed to read replay history.'));\n transaction.oncomplete = () => { settled = true; resolve(); };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to prune replay history.'));\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to prune replay history.'));\n });\n })()\n });\n }\n };\n}\n", "/**\n * WebSocketTransport \u2014 a dependency-free transport over a plain WebSocket.\n *\n * Validates the `DataBusTransport` abstraction with a second, minimal backend:\n * any WebSocket server that speaks the tiny JSON protocol below can back the\n * same cross-tab clustering stack (owner dedup, sticky routes, EVENT fan-out)\n * that the Centrifuge backend uses.\n *\n * Wire protocol (JSON text frames):\n * - client \u2192 server: `{\"op\":\"subscribe\"|\"unsubscribe\"|\"publish\",\"topic\":...,\"data\":...}`\n * - client \u2192 server (batched): `{\"op\":\"publishBatch\",\"topic\":...,\"items\":[{data,...}]}`\n * - server \u2192 client: `{\"topic\":...,\"data\":...}` for publications; anything\n * without a string `topic` field is ignored (forward-compatible).\n */\nimport { CrossTabDataBus } from './core/data-bus';\nimport { parseDataBusPublication } from './core/publication';\nimport type { CrossTabDataBusOptions } from './core/data-bus';\nimport { WS_OP, WORKER_STATUS } from './utils/constants';\nimport type {\n DataBusTransport,\n DataBusTransportHandlers,\n DataBusPublishOptions,\n DataBusPublicationItem,\n DataBusMessage,\n MaybePromise,\n WorkerStatus\n} from './core/types';\n\n/** Minimal WebSocket surface used by the transport. Matches the browser\n * `WebSocket` subset the transport touches; injectable for tests and runtimes. */\nexport interface WebSocketLike {\n /** Current connection state; 1 (OPEN) means frames may be sent. */\n readonly readyState?: number;\n send(data: string | ArrayBuffer): void;\n close(code?: number, reason?: string): void;\n onopen: (() => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/** Connection configuration for {@link WebSocketTransport}. */\nexport interface WebSocketDataBusConfig {\n /** WebSocket endpoint, e.g. `wss://example.test/ws`. */\n url: string;\n /** Subprotocol(s) passed to the WebSocket handshake. */\n protocols?: string | string[];\n /** Custom socket factory. Defaults to the global `WebSocket`; injectable\n * for tests and non-browser runtimes. */\n webSocketFactory?: (url: string, protocols?: string | string[]) => WebSocketLike;\n /** Milliseconds to wait for the handshake before reporting `error` and\n * failing the start. Defaults to 30000 ms; pass\n * `0` or `Infinity` to wait indefinitely. The timeout exists because\n * `start()` resolves on connect, so a socket that never opens and never\n * errors would otherwise leave the DataBus start gate (and every operation\n * queued behind it) pending forever. */\n connectTimeoutMs?: number;\n}\n\n/** Options for creating a fully-configured CrossTabDataBus with a WebSocket transport. */\nexport interface CreateWebSocketDataBusOptions<TData = unknown>\n extends Omit<\n CrossTabDataBusOptions<WebSocketDataBusConfig, TData>,\n 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'\n > {\n /** WebSocket connection configuration. */\n connection: WebSocketDataBusConfig;\n /** Cluster key for cross-tab coordination. Defaults to the connection URL. */\n clusterKey?: string;\n}\n\n/** Default handshake budget. A socket that never opens and never fires\n * error/close would otherwise keep a started transport stuck in `connecting`\n * forever, with every queued operation parked behind an unsettled `start()`. */\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\n\nconst WS_OPEN = 1;\n\n/** Transport that talks a minimal JSON protocol over a plain WebSocket.\n * Connection lifecycle maps directly to the DataBus status vocabulary:\n * open \u2192 `connected`, close \u2192 `disconnected`, error \u2192 `error` (which the\n * DataBus treats as its auto-recovery trigger). The transport holds no\n * reconnection logic of its own \u2014 reopening is the DataBus's job. */\nexport class WebSocketTransport<TData = unknown>\n implements DataBusTransport<WebSocketDataBusConfig, TData>\n{\n readonly diagnosticsName = 'websocket';\n readonly diagnosticsBackend = 'native-websocket';\n private socket: WebSocketLike | null = null;\n private socketActive = false;\n private handlers: DataBusTransportHandlers<TData> | null = null;\n private readonly subscribedTopics = new Set<string>();\n // Handshake gate for the current start(). Resolves once the socket opens,\n // rejects when the attempt fails, so the DataBus start Promise \u2014 and every\n // operation parked behind it \u2014 settles at the real connection boundary.\n private connectPromise: Promise<void> | null = null;\n private connectResolve: (() => void) | null = null;\n private connectReject: ((error: unknown) => void) | null = null;\n private connectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly connection: WebSocketDataBusConfig) {}\n\n /** Open the WebSocket and wire lifecycle listeners. Resolves once the\n * handshake completes and rejects when the attempt fails, matching the\n * `DataBusTransport.start` contract (\"resolves on connect or rejects on\n * failure\"). A factory failure is reported through `onStatus('error')` so\n * the DataBus can recover. */\n start(config: WebSocketDataBusConfig, handlers: DataBusTransportHandlers<TData>): MaybePromise<void> {\n if (this.socket && this.socketActive) {\n // Reuse the live socket instead of orphaning it. While the first\n // attempt is still connecting, share its handshake gate so a duplicate\n // start() cannot report readiness before the socket is usable.\n return this.connectPromise ?? undefined;\n }\n // A failed or closed socket is one-shot; retain its object only long\n // enough for a transparent same-object reopen to fire, but replace it\n // whenever start() is called again. Clearing the reference here also\n // makes every late callback from the old socket a no-op.\n this.socket = null;\n this.socketActive = false;\n this.handlers = handlers;\n // The factory may live on the constructor connection (createWebSocketDataBus\n // path) or on the runtime config (direct transport use) \u2014 accept both.\n const factory = config.webSocketFactory ?? this.connection.webSocketFactory ?? defaultWebSocketFactory;\n const protocols = config.protocols ?? this.connection.protocols;\n let socket: WebSocketLike;\n try {\n socket = factory(config.url, protocols);\n } catch (error) {\n handlers.onStatus(WORKER_STATUS.ERROR);\n handlers.onError(error);\n return;\n }\n const opening = new Promise<void>((resolve, reject) => {\n this.connectResolve = resolve;\n this.connectReject = reject;\n // Per-attempt handshake state. A socket that opens, then closes and\n // re-opens in place (a protocol-level recovery) may reuse the same\n // attempt; a timeout or a close/error before the first open permanently\n // invalidates it so a late onopen cannot report readiness.\n let handshakeCompleted = false;\n let handshakeFailed = false;\n const timeoutMs =\n config.connectTimeoutMs ?? this.connection.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n if (Number.isFinite(timeoutMs) && timeoutMs > 0) {\n this.connectTimer = setTimeout(() => {\n // The `handshakeCompleted` term cannot be reached today: `onopen`\n // cancels this timer directly and again through `settleConnect()`, so a\n // completed attempt has no live timer left to fire. Measured \u2014 deleting\n // only that term leaves all 880 tests green, while deleting it *and*\n // both cancels makes the transport report a phantom\n // `onStatus('error')` and abort a socket that had already connected.\n // The term stays because that is the asymmetric cost, and because\n // tests/websocket.test.ts's 'never lets the connect timer tear down a\n // handshake that already completed' now holds the pair.\n if (this.socket !== socket || this.handlers !== handlers || handshakeCompleted) return;\n handshakeFailed = true;\n this.connectTimer = null;\n this.socketActive = false;\n const error = new Error(`WebSocket did not open within ${timeoutMs}ms.`);\n handlers.onStatus(WORKER_STATUS.ERROR);\n handlers.onError(error);\n this.failConnect(error);\n // Abort the half-open handshake so the timed-out attempt cannot\n // linger in CONNECTING or deliver a late onopen.\n this.abortSocket(socket);\n }, timeoutMs);\n }\n socket.onopen = () => {\n if (this.socket !== socket || this.handlers !== handlers || handshakeFailed) return;\n this.socketActive = true;\n this.clearConnectTimer();\n // Re-assert every topic so a reopened socket (recovery path) restores\n // the server-side subscriptions without DataBus involvement.\n for (const topic of this.subscribedTopics) {\n this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });\n }\n handlers.onStatus(WORKER_STATUS.CONNECTED);\n if (!handshakeCompleted) {\n handshakeCompleted = true;\n this.settleConnect();\n }\n };\n socket.onclose = () => {\n if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;\n this.socketActive = false;\n handlers.onStatus(WORKER_STATUS.DISCONNECTED);\n if (!handshakeCompleted) {\n handshakeFailed = true;\n this.failConnect(new Error('WebSocket closed before the handshake completed.'));\n }\n };\n socket.onerror = () => {\n if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;\n this.socketActive = false;\n handlers.onStatus(WORKER_STATUS.ERROR);\n if (!handshakeCompleted) {\n handshakeFailed = true;\n this.failConnect(new Error('WebSocket failed to open.'));\n }\n // `error` is not guaranteed to be followed by `close` (especially for\n // injected WebSocket implementations). Abort here so DataBus recovery\n // cannot orphan a socket that this transport has already marked dead.\n this.abortSocket(socket);\n };\n socket.onmessage = event => {\n if (this.socket === socket && this.handlers === handlers && this.socketActive) {\n void this.handleMessage(event.data);\n }\n };\n this.socket = socket;\n this.socketActive = true;\n });\n this.connectPromise = opening;\n return opening;\n }\n\n /** Idempotent: re-subscribing an active topic re-sends the frame but does\n * not duplicate the local tracking entry. */\n subscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.add(topic);\n this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });\n }\n\n /** Idempotent: unsubscribing an unknown topic is a no-op. */\n unsubscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.delete(topic);\n this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });\n }\n\n /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): MaybePromise<void> {\n if (data instanceof ArrayBuffer) {\n this.sendBinaryFrame(topic, data, options?.messageId, options?.timestamp);\n return;\n }\n this.sendFrame({\n op: WS_OP.PUBLISH,\n topic,\n data,\n ...(options?.messageId === undefined ? {} : { messageId: options.messageId }),\n ...(options?.timestamp === undefined ? {} : { timestamp: options.timestamp })\n });\n }\n\n /** Publish many items for one topic as a single wire frame. One-item\n * batches delegate to `publish` so the legacy single-publication frame\n * shape (including binary framing) is preserved. */\n publishBatch(topic: string, items: ReadonlyArray<DataBusPublicationItem>): MaybePromise<void> {\n if (items.length === 0) return;\n if (items.length === 1) {\n const single = items[0]!;\n return this.publish(topic, single.data, {\n ...(single.messageId === undefined ? {} : { messageId: single.messageId }),\n ...(single.timestamp === undefined ? {} : { timestamp: single.timestamp })\n });\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publishBatch\" frame.'));\n return;\n }\n // Binary payloads are embedded as byte arrays so the whole batch stays in\n // one JSON frame; the server re-fans them out as individual publications.\n this.socket.send(JSON.stringify({\n op: WS_OP.PUBLISH_BATCH,\n topic,\n items: items.map(item => ({\n data: item.data instanceof ArrayBuffer ? Array.from(new Uint8Array(item.data)) : item.data,\n ...(item.messageId === undefined ? {} : { messageId: item.messageId }),\n ...(item.timestamp === undefined ? {} : { timestamp: item.timestamp })\n }))\n }));\n }\n\n /** Close the socket and drop all state. Safe to call multiple times. */\n stop(): MaybePromise<void> {\n const socket = this.socket;\n const shouldClose = this.socketActive;\n this.socket = null;\n this.socketActive = false;\n this.handlers = null;\n this.subscribedTopics.clear();\n // Settle an in-flight handshake gate: a DataBus stop() awaits the start\n // Promise, so leaving it pending would hang teardown. Resolving (rather\n // than rejecting) keeps an intentional stop from surfacing as an error.\n this.settleConnect();\n this.connectPromise = null;\n if (shouldClose) socket?.close();\n }\n\n /** Resolve the in-flight handshake gate. Idempotent: once the socket has\n * opened (or a newer attempt replaced it) later calls are no-ops. */\n private settleConnect(): void {\n this.clearConnectTimer();\n const resolve = this.connectResolve;\n this.connectResolve = null;\n this.connectReject = null;\n resolve?.();\n }\n\n /** Reject the in-flight handshake gate. Idempotent on the same terms as\n * {@link settleConnect}. */\n private failConnect(error: unknown): void {\n this.clearConnectTimer();\n const reject = this.connectReject;\n this.connectResolve = null;\n this.connectReject = null;\n reject?.(error);\n }\n\n /** Best-effort close for a socket that can no longer carry transport data.\n * The error that invalidated it has already been reported by the caller. */\n private abortSocket(socket: WebSocketLike): void {\n try {\n socket.close();\n } catch {\n // Some injected implementations throw when close races a failed\n // handshake. The original failure remains the actionable error.\n }\n }\n\n private clearConnectTimer(): void {\n if (this.connectTimer !== null) {\n clearTimeout(this.connectTimer);\n this.connectTimer = null;\n }\n }\n\n /** Send one JSON frame. Frames are dropped with an `onError` report when\n * the socket is not open \u2014 subscribe frames are re-sent on open, so the\n * only real loss is a publish during a disconnect window. */\n private sendFrame(payload: { op: string; topic: string; data?: unknown; messageId?: string; timestamp?: number }): void {\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error(`WebSocket is not open; dropped \"${payload.op}\" frame.`));\n return;\n }\n this.socket.send(JSON.stringify(payload));\n }\n\n private sendBinaryFrame(topic: string, data: ArrayBuffer, messageId?: string, timestamp?: number): void {\n if (messageId !== undefined || timestamp !== undefined) {\n // Binary frames retain their compact legacy shape; metadata is sent as a\n // JSON envelope so IDs are never silently lost.\n this.sendFrame({\n op: WS_OP.PUBLISH,\n topic,\n data: Array.from(new Uint8Array(data)),\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n });\n return;\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publish\" frame.'));\n return;\n }\n const topicBytes = new TextEncoder().encode(topic);\n if (topicBytes.length > 0xffff) {\n this.handlers?.onError(new Error('WebSocket topic is too long for a binary frame.'));\n return;\n }\n const frame = new Uint8Array(3 + topicBytes.length + data.byteLength);\n frame[0] = 0xc7;\n new DataView(frame.buffer).setUint16(1, topicBytes.length);\n frame.set(topicBytes, 3);\n frame.set(new Uint8Array(data), 3 + topicBytes.length);\n this.socket.send(frame.buffer);\n }\n\n /** Parse a server frame. Only objects carrying a string `topic` are\n * publications; malformed JSON and unknown shapes are ignored so a chatty\n * server cannot crash the message path. */\n private async handleMessage(raw: unknown): Promise<void> {\n // Browser WebSockets may deliver binary frames as Blob unless\n // `binaryType = 'arraybuffer'` is explicitly configured by the host.\n // Normalize Blob asynchronously and reuse the exact ArrayBuffer parser.\n if (typeof Blob !== 'undefined' && raw instanceof Blob) {\n // Blob conversion is asynchronous. Capture the socket/handler pair so a\n // stop/start that replaces the connection while arrayBuffer() is pending\n // cannot let the stale frame leak into the new connection's handlers.\n const socket = this.socket;\n const handlers = this.handlers;\n try {\n const buffer = await raw.arrayBuffer();\n if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;\n await this.handleMessage(buffer);\n } catch (error) {\n if (this.socket === socket && this.handlers === handlers && this.socketActive) {\n this.handlers?.onError(error);\n }\n }\n return;\n }\n let parsed: unknown;\n if (raw instanceof ArrayBuffer) {\n const bytes = new Uint8Array(raw);\n if (bytes[0] !== 0xc7 || bytes.length < 3) return;\n const topicLength = new DataView(raw).getUint16(1);\n if (bytes.length < 3 + topicLength) return;\n const topic = new TextDecoder().decode(bytes.subarray(3, 3 + topicLength));\n const data = bytes.slice(3 + topicLength).buffer;\n this.handlers?.onMessage({ topic, data: data as TData });\n return;\n }\n if (typeof raw !== 'string') return;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.handlers?.onError(new Error('WebSocket server sent a non-JSON frame.'));\n return;\n }\n if (!parsed || typeof parsed !== 'object') return;\n const publication = parseDataBusPublication<TData>(parsed);\n if (publication) this.handlers?.onMessage(publication as DataBusMessage<TData>);\n }\n}\n\n/** Resolve the platform WebSocket, or null in runtimes without one (SSR/Node). */\nfunction defaultWebSocketFactory(url: string, protocols?: string | string[]): WebSocketLike {\n if (typeof WebSocket === 'undefined') {\n throw new Error('WebSocketTransport requires a WebSocket implementation.');\n }\n return new WebSocket(url, protocols) as unknown as WebSocketLike;\n}\n\n/** Create a CrossTabDataBus backed by a plain WebSocket transport.\n * Cross-tab clustering (owner dedup, sticky routes, failover) works identically\n * to the Centrifuge backend \u2014 only the transport I/O differs. */\nexport function createWebSocketDataBus<TData = unknown>(\n options: CreateWebSocketDataBusOptions<TData>\n): CrossTabDataBus<WebSocketDataBusConfig, TData> {\n const { clusterKey, connection, ...dataBusOptions } = options;\n return new CrossTabDataBus({\n ...dataBusOptions,\n autoStart: true,\n clusterKey: clusterKey ?? connection.url,\n initialConfig: connection,\n transport: new WebSocketTransport<TData>(connection)\n });\n}\n\n/** Re-export for convenience: the status type used by the transport. */\nexport type { WorkerStatus };\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { DataBusMessage } from './types';\nimport { pruneReplayHistory } from './replay-pruning';\nimport { DEFAULT_STORAGE_PREFIX, PRUNE_STRATEGY } from '../utils/constants';\nimport { assertPositiveFiniteNumber, assertPositiveSafeInteger, assertPruneStrategy } from '../utils/validation';\n\n/** Optional persistence backend for replay history. */\nexport interface DataBusReplayPersistence<TData = unknown> {\n load(): Promise<ReadonlyArray<DataBusMessage<TData>>>;\n append(message: DataBusMessage<TData>): Promise<void>;\n /** Optional bulk append used to amortize IndexedDB transaction overhead. */\n appendBatch?(messages: ReadonlyArray<DataBusMessage<TData>>): Promise<void>;\n /** Remove all persisted replay history. */\n clear?(): Promise<void>;\n /** Remove persisted replay history for one exact topic. */\n clearTopic?(topic: string): Promise<void>;\n /** Remove persisted messages older than the given epoch-millisecond cutoff. */\n clearBefore?(timestamp: number): Promise<void>;\n}\n\nexport interface IndexedDbReplayPersistenceOptions {\n dbName?: string;\n maxPerTopic: number;\n pruneStrategy?: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n retentionMs?: number;\n}\n\n/** Create a browser IndexedDB-backed replay store. */\nexport function createIndexedDbReplayPersistence<TData = unknown>(\n options: IndexedDbReplayPersistenceOptions\n): DataBusReplayPersistence<TData> {\n const indexedDb = globalThis.indexedDB;\n if (!indexedDb) throw new Error('IndexedDB is unavailable in this environment.');\n const dbName = options.dbName ?? DEFAULT_STORAGE_PREFIX;\n const storeName = 'replay';\n const maxPerTopic = options.maxPerTopic;\n const pruneStrategy = options.pruneStrategy ?? PRUNE_STRATEGY.COUNT;\n const retentionMs = options.retentionMs;\n assertPruneStrategy(pruneStrategy);\n if (retentionMs !== undefined) assertPositiveFiniteNumber(retentionMs, 'retentionMs');\n assertPositiveSafeInteger(maxPerTopic, 'maxPerTopic');\n let dbPromise: Promise<IDBDatabase> | null = null;\n const invalidate = (db: IDBDatabase): void => {\n if (dbPromise) {\n // The rejection arm is what keeps this total: `invalidate` can chain onto a\n // *replacement* open that is still in flight, and `.then(current => \u2026)`\n // alone would derive a rejected promise nobody observes, turning that\n // open's failure into an unhandled rejection on top of the caller's. No\n // test reaches it \u2014 it needs a scheduled failure racing a rejecting reopen\n // \u2014 and it is not the `open()` catch below, which is registered on the\n // promise it guards and only clears the cache.\n void dbPromise.then(current => {\n if (current === db) {\n current.close();\n dbPromise = null;\n }\n }, () => undefined);\n }\n };\n // IndexedDB transactions are atomic, but a read-modify-write append can\n // still lose updates when callers start several appends concurrently.\n // Serialize all mutations per adapter instance while keeping reads free.\n // Consecutive append/appendBatch entries are coalesced into a single\n // transaction at the head of the queue, so a burst spanning many microtask\n // flushes issues one transaction instead of one per flush. Ordering against\n // clears is preserved: coalescing only merges adjacent batch entries and\n // never reorders them relative to a clear.\n type QueuedMutation =\n | { kind: 'batch'; messages: ReadonlyArray<DataBusMessage<TData>> }\n | { kind: 'run'; run: () => Promise<void> };\n const pending: Array<{\n mutation: QueuedMutation;\n resolve: () => void;\n reject: (error: unknown) => void;\n }> = [];\n let draining = false;\n\n const drain = async (): Promise<void> => {\n if (draining) return;\n draining = true;\n try {\n // Let a burst of synchronous enqueues accumulate into `pending` before\n // the first coalescing pass, so a single flush cycle's batches merge\n // into one transaction instead of two.\n await Promise.resolve();\n while (pending.length > 0) {\n const head = pending[0]!.mutation;\n if (head.kind === 'batch') {\n // Merge every adjacent batch entry into one transaction.\n const merged: DataBusMessage<TData>[] = [];\n const entries: Array<{ resolve: () => void; reject: (error: unknown) => void }> = [];\n while (pending.length > 0) {\n const next = pending[0]!.mutation;\n if (next.kind !== 'batch') break;\n merged.push(...next.messages);\n entries.push({ resolve: pending[0]!.resolve, reject: pending[0]!.reject });\n pending.shift();\n }\n try {\n await appendTransaction(merged);\n for (const entry of entries) entry.resolve();\n } catch (error) {\n for (const entry of entries) entry.reject(error);\n }\n } else {\n const entry = pending.shift()!;\n try {\n await head.run();\n entry.resolve();\n } catch (error) {\n entry.reject(error);\n }\n }\n }\n } finally {\n draining = false;\n }\n };\n\n const enqueue = (mutation: QueuedMutation): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n pending.push({ mutation, resolve, reject });\n void drain();\n });\n\n /** Read-modify-write one topic-batched append inside a single transaction. */\n const appendTransaction = (messages: ReadonlyArray<DataBusMessage<TData>>): Promise<void> =>\n (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try {\n transaction = db.transaction(storeName, 'readwrite');\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n const store = transaction.objectStore(storeName);\n const grouped = new Map<string, DataBusMessage<TData>[]>();\n for (const message of messages) {\n grouped.set(message.topic, [...(grouped.get(message.topic) ?? []), message]);\n }\n let hasError = false;\n const fail = (error: unknown): void => {\n if (hasError) return;\n hasError = true;\n invalidate(db);\n reject(error);\n };\n for (const [topic, topicMessages] of grouped) {\n const request = store.get(topic);\n request.onsuccess = () => {\n if (hasError) return;\n const history = pruneReplayHistory(\n ((request.result?.messages ?? []) as DataBusMessage<TData>[]).concat(topicMessages),\n { maxPerTopic, pruneStrategy, retentionMs, now: Date.now() }\n );\n store.put({ topic, messages: history });\n };\n request.onerror = () => fail(request.error ?? new Error('Failed to read replay history.'));\n }\n transaction.oncomplete = () => {\n if (!hasError) resolve();\n };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to persist replay history.'));\n // A connection loss can abort a transaction without first dispatching a\n // request error. Without this path, the serialized mutation queue would\n // stay blocked forever after the promise never settles.\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to persist replay history.'));\n });\n })();\n const open = (): Promise<IDBDatabase> => {\n // The two `if (dbPromise \u2026)` guards inside have never had their false arm\n // execute. Reaching either needs a connection that is live but no longer\n // cached, so a versionchange could land on an empty slot, or a slot replaced\n // before its own rejection catch runs \u2014 and both are excluded here because a\n // replacement can only start from a cleared slot, which is what the clearing\n // path itself performs. Defensive, so recorded rather than pinned.\n if (dbPromise) return dbPromise;\n const pending = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDb.open(dbName, 1);\n request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: 'topic' });\n request.onsuccess = () => {\n const db = request.result;\n // A schema upgrade in another tab invalidates this connection. Close\n // it and clear the cached promise so the next operation reopens a\n // usable connection instead of repeatedly targeting a dead database.\n db.onversionchange = () => {\n db.close();\n if (dbPromise) dbPromise = null;\n };\n resolve(db);\n };\n request.onerror = () => reject(request.error ?? new Error('Failed to open replay database.'));\n });\n dbPromise = pending;\n // Do not permanently cache a rejected open promise. IndexedDB can fail\n // transiently (quota, private-mode initialization, a closing connection,\n // or a browser shutdown); the next operation must be able to retry.\n void pending.catch(() => {\n if (dbPromise === pending) dbPromise = null;\n });\n return pending;\n };\n return {\n async load() {\n const db = await open();\n return new Promise((resolve, reject) => {\n let transaction: IDBTransaction;\n let request: IDBRequest;\n try {\n transaction = db.transaction(storeName, 'readonly');\n request = transaction.objectStore(storeName).getAll();\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n // One-shot settlement, the same latch in all four `invalidate`+`reject`\n // closures here. Entering it twice is reachable \u2014 one aborting\n // transaction dispatches `error` and then `abort` \u2014 but measured\n // harmless: `reject` on a settled promise is a no-op, and the extra\n // `invalidate` cannot see a replaced cache because both signals are\n // dispatched inside one microtask batch, so their callbacks run before\n // the serialized queue reopens. Deleting all four latches leaves the\n // suite green and the connection count unchanged; they stay because that\n // proof rests on dispatch timing outside this file. The stale-signal case\n // that is inside this file is pinned by tests/replay-persistence.test.ts's\n // 'keeps the connection a later operation reopened when an older signal\n // lands late'.\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n let records: Array<{ messages: DataBusMessage<TData>[] }> = [];\n request.onsuccess = () => {\n records = request.result as Array<{ messages: DataBusMessage<TData>[] }>;\n };\n request.onerror = () => fail(request.error ?? new Error('Failed to load replay history.'));\n transaction.oncomplete = () => {\n if (settled) return;\n settled = true;\n resolve(records.flatMap(record => record.messages));\n };\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to load replay history.'));\n });\n },\n append(message) {\n return enqueue({ kind: 'batch', messages: [message] });\n },\n appendBatch(messages) {\n if (messages.length === 0) return Promise.resolve();\n return enqueue({ kind: 'batch', messages });\n },\n clear() {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n transaction.objectStore(storeName).clear();\n transaction.oncomplete = () => { settled = true; resolve(); };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to clear replay history.'));\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to clear replay history.'));\n });\n })()\n });\n },\n clearTopic(topic) {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n transaction.objectStore(storeName).delete(topic);\n transaction.oncomplete = () => { settled = true; resolve(); };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to clear topic replay history.'));\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to clear topic replay history.'));\n });\n })()\n });\n },\n clearBefore(timestamp) {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n let settled = false;\n const fail = (error: unknown): void => {\n if (settled) return;\n settled = true;\n invalidate(db);\n reject(error);\n };\n const store = transaction.objectStore(storeName);\n const request = store.getAll();\n request.onsuccess = () => {\n for (const record of request.result as Array<{ topic: string; messages: DataBusMessage<TData>[] }>) {\n const messages = record.messages.filter(message => message.timestamp === undefined || message.timestamp >= timestamp);\n if (messages.length === 0) store.delete(record.topic);\n else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });\n }\n };\n request.onerror = () => fail(request.error ?? new Error('Failed to read replay history.'));\n transaction.oncomplete = () => { settled = true; resolve(); };\n transaction.onerror = () => fail(transaction.error ?? new Error('Failed to prune replay history.'));\n transaction.onabort = () => fail(transaction.error ?? new Error('Failed to prune replay history.'));\n });\n })()\n });\n }\n };\n}\n", "/**\n * WebSocketTransport \u2014 a dependency-free transport over a plain WebSocket.\n *\n * Validates the `DataBusTransport` abstraction with a second, minimal backend:\n * any WebSocket server that speaks the tiny JSON protocol below can back the\n * same cross-tab clustering stack (owner dedup, sticky routes, EVENT fan-out)\n * that the Centrifuge backend uses.\n *\n * Wire protocol \u2014 two frame formats, both over text/binary WebSocket messages:\n * - client \u2192 server: `{\"op\":\"subscribe\"|\"unsubscribe\"|\"publish\",\"topic\":...,\"data\":...}`\n * - client \u2192 server (batched): `{\"op\":\"publishBatch\",\"topic\":...,\"items\":[{data,...}]}`\n * - server \u2192 client: `{\"topic\":...,\"data\":...}` for publications; anything\n * without a string `topic` field is ignored (forward-compatible).\n * - binary publications use a *separate* tagged frame, not JSON:\n * `0xc7 | uint16 topicLength | topic UTF-8 | raw payload`, written by\n * `sendBinaryFrame` when `publish()` receives an `ArrayBuffer` with no\n * `messageId`/`timestamp` (with metadata it degrades to a JSON PUBLISH whose\n * `data` is a number array, so an id is never lost to the compact shape), and\n * parsed by the matching reader. A server that implements only the JSON arm\n * above silently drops those \u2014 see `scripts/demo-ws-server.mjs`, which\n * implements both directions.\n */\nimport { CrossTabDataBus } from './core/data-bus';\nimport { parseDataBusPublication } from './core/publication';\nimport type { CrossTabDataBusOptions } from './core/data-bus';\nimport { WS_OP, WORKER_STATUS } from './utils/constants';\nimport type {\n DataBusTransport,\n DataBusTransportHandlers,\n DataBusPublishOptions,\n DataBusPublicationItem,\n DataBusMessage,\n MaybePromise,\n WorkerStatus\n} from './core/types';\n\n/** Minimal WebSocket surface used by the transport. Matches the browser\n * `WebSocket` subset the transport touches; injectable for tests and runtimes. */\nexport interface WebSocketLike {\n /** Current connection state; 1 (OPEN) means frames may be sent. */\n readonly readyState?: number;\n send(data: string | ArrayBuffer): void;\n close(code?: number, reason?: string): void;\n onopen: (() => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/** Connection configuration for {@link WebSocketTransport}. */\nexport interface WebSocketDataBusConfig {\n /** WebSocket endpoint, e.g. `wss://example.test/ws`. */\n url: string;\n /** Subprotocol(s) passed to the WebSocket handshake. */\n protocols?: string | string[];\n /** Custom socket factory. Defaults to the global `WebSocket`; injectable\n * for tests and non-browser runtimes. */\n webSocketFactory?: (url: string, protocols?: string | string[]) => WebSocketLike;\n /** Milliseconds to wait for the handshake before reporting `error` and\n * failing the start. Defaults to 30000 ms; pass\n * `0` or `Infinity` to wait indefinitely. The timeout exists because\n * `start()` resolves on connect, so a socket that never opens and never\n * errors would otherwise leave the DataBus start gate (and every operation\n * queued behind it) pending forever. */\n connectTimeoutMs?: number;\n}\n\n/** Options for creating a fully-configured CrossTabDataBus with a WebSocket transport. */\nexport interface CreateWebSocketDataBusOptions<TData = unknown>\n extends Omit<\n CrossTabDataBusOptions<WebSocketDataBusConfig, TData>,\n 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'\n > {\n /** WebSocket connection configuration. */\n connection: WebSocketDataBusConfig;\n /** Cluster key for cross-tab coordination. Defaults to the connection URL. */\n clusterKey?: string;\n}\n\n/** Default handshake budget. A socket that never opens and never fires\n * error/close would otherwise keep a started transport stuck in `connecting`\n * forever, with every queued operation parked behind an unsettled `start()`. */\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\n\nconst WS_OPEN = 1;\n\n/** Transport that talks a minimal JSON protocol over a plain WebSocket.\n * Connection lifecycle maps directly to the DataBus status vocabulary:\n * open \u2192 `connected`, close \u2192 `disconnected`, error \u2192 `error` (which the\n * DataBus treats as its auto-recovery trigger). The transport holds no\n * reconnection logic of its own \u2014 reopening is the DataBus's job. */\nexport class WebSocketTransport<TData = unknown>\n implements DataBusTransport<WebSocketDataBusConfig, TData>\n{\n readonly diagnosticsName = 'websocket';\n readonly diagnosticsBackend = 'native-websocket';\n private socket: WebSocketLike | null = null;\n private socketActive = false;\n private handlers: DataBusTransportHandlers<TData> | null = null;\n private readonly subscribedTopics = new Set<string>();\n // Handshake gate for the current start(). Resolves once the socket opens,\n // rejects when the attempt fails, so the DataBus start Promise \u2014 and every\n // operation parked behind it \u2014 settles at the real connection boundary.\n private connectPromise: Promise<void> | null = null;\n private connectResolve: (() => void) | null = null;\n private connectReject: ((error: unknown) => void) | null = null;\n private connectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly connection: WebSocketDataBusConfig) {}\n\n /** Open the WebSocket and wire lifecycle listeners. Resolves once the\n * handshake completes and rejects when the attempt fails, matching the\n * `DataBusTransport.start` contract (\"resolves on connect or rejects on\n * failure\"). A factory failure is reported through `onStatus('error')` so\n * the DataBus can recover. */\n start(config: WebSocketDataBusConfig, handlers: DataBusTransportHandlers<TData>): MaybePromise<void> {\n if (this.socket && this.socketActive) {\n // Reuse the live socket instead of orphaning it. While the first\n // attempt is still connecting, share its handshake gate so a duplicate\n // start() cannot report readiness before the socket is usable.\n return this.connectPromise ?? undefined;\n }\n // A failed or closed socket is one-shot; retain its object only long\n // enough for a transparent same-object reopen to fire, but replace it\n // whenever start() is called again. Clearing the reference here also\n // makes every late callback from the old socket a no-op.\n this.socket = null;\n this.socketActive = false;\n this.handlers = handlers;\n // The factory may live on the constructor connection (createWebSocketDataBus\n // path) or on the runtime config (direct transport use) \u2014 accept both.\n const factory = config.webSocketFactory ?? this.connection.webSocketFactory ?? defaultWebSocketFactory;\n const protocols = config.protocols ?? this.connection.protocols;\n let socket: WebSocketLike;\n try {\n socket = factory(config.url, protocols);\n } catch (error) {\n handlers.onStatus(WORKER_STATUS.ERROR);\n handlers.onError(error);\n return;\n }\n const opening = new Promise<void>((resolve, reject) => {\n this.connectResolve = resolve;\n this.connectReject = reject;\n // Per-attempt handshake state. A socket that opens, then closes and\n // re-opens in place (a protocol-level recovery) may reuse the same\n // attempt; a timeout or a close/error before the first open permanently\n // invalidates it so a late onopen cannot report readiness.\n let handshakeCompleted = false;\n let handshakeFailed = false;\n const timeoutMs =\n config.connectTimeoutMs ?? this.connection.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n if (Number.isFinite(timeoutMs) && timeoutMs > 0) {\n this.connectTimer = setTimeout(() => {\n // The `handshakeCompleted` term cannot be reached today: `onopen`\n // cancels this timer directly and again through `settleConnect()`, so a\n // completed attempt has no live timer left to fire. Measured \u2014 deleting\n // only that term leaves all 880 tests green, while deleting it *and*\n // both cancels makes the transport report a phantom\n // `onStatus('error')` and abort a socket that had already connected.\n // The term stays because that is the asymmetric cost, and because\n // tests/websocket.test.ts's 'never lets the connect timer tear down a\n // handshake that already completed' now holds the pair.\n if (this.socket !== socket || this.handlers !== handlers || handshakeCompleted) return;\n handshakeFailed = true;\n this.connectTimer = null;\n this.socketActive = false;\n const error = new Error(`WebSocket did not open within ${timeoutMs}ms.`);\n handlers.onStatus(WORKER_STATUS.ERROR);\n handlers.onError(error);\n this.failConnect(error);\n // Abort the half-open handshake so the timed-out attempt cannot\n // linger in CONNECTING or deliver a late onopen.\n this.abortSocket(socket);\n }, timeoutMs);\n }\n socket.onopen = () => {\n if (this.socket !== socket || this.handlers !== handlers || handshakeFailed) return;\n this.socketActive = true;\n this.clearConnectTimer();\n // Re-assert every topic so a reopened socket (recovery path) restores\n // the server-side subscriptions without DataBus involvement.\n for (const topic of this.subscribedTopics) {\n this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });\n }\n handlers.onStatus(WORKER_STATUS.CONNECTED);\n if (!handshakeCompleted) {\n handshakeCompleted = true;\n this.settleConnect();\n }\n };\n socket.onclose = () => {\n if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;\n this.socketActive = false;\n handlers.onStatus(WORKER_STATUS.DISCONNECTED);\n if (!handshakeCompleted) {\n handshakeFailed = true;\n this.failConnect(new Error('WebSocket closed before the handshake completed.'));\n }\n };\n socket.onerror = () => {\n if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;\n this.socketActive = false;\n handlers.onStatus(WORKER_STATUS.ERROR);\n if (!handshakeCompleted) {\n handshakeFailed = true;\n this.failConnect(new Error('WebSocket failed to open.'));\n }\n // `error` is not guaranteed to be followed by `close` (especially for\n // injected WebSocket implementations). Abort here so DataBus recovery\n // cannot orphan a socket that this transport has already marked dead.\n this.abortSocket(socket);\n };\n socket.onmessage = event => {\n if (this.socket === socket && this.handlers === handlers && this.socketActive) {\n void this.handleMessage(event.data);\n }\n };\n this.socket = socket;\n this.socketActive = true;\n });\n this.connectPromise = opening;\n return opening;\n }\n\n /** Idempotent: re-subscribing an active topic re-sends the frame but does\n * not duplicate the local tracking entry. */\n subscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.add(topic);\n this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });\n }\n\n /** Idempotent for local tracking: an unknown topic has nothing to delete, but\n * the UNSUBSCRIBE frame is still sent, exactly as SUBSCRIBE is re-sent above. */\n unsubscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.delete(topic);\n this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });\n }\n\n /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): MaybePromise<void> {\n if (data instanceof ArrayBuffer) {\n this.sendBinaryFrame(topic, data, options?.messageId, options?.timestamp);\n return;\n }\n this.sendFrame({\n op: WS_OP.PUBLISH,\n topic,\n data,\n ...(options?.messageId === undefined ? {} : { messageId: options.messageId }),\n ...(options?.timestamp === undefined ? {} : { timestamp: options.timestamp })\n });\n }\n\n /** Publish many items for one topic as a single wire frame. One-item\n * batches delegate to `publish` so the legacy single-publication frame\n * shape (including binary framing) is preserved. */\n publishBatch(topic: string, items: ReadonlyArray<DataBusPublicationItem>): MaybePromise<void> {\n if (items.length === 0) return;\n if (items.length === 1) {\n const single = items[0]!;\n return this.publish(topic, single.data, {\n ...(single.messageId === undefined ? {} : { messageId: single.messageId }),\n ...(single.timestamp === undefined ? {} : { timestamp: single.timestamp })\n });\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publishBatch\" frame.'));\n return;\n }\n // Binary payloads are embedded as byte arrays so the whole batch stays in\n // one JSON frame; the server re-fans them out as individual publications.\n this.socket.send(JSON.stringify({\n op: WS_OP.PUBLISH_BATCH,\n topic,\n items: items.map(item => ({\n data: item.data instanceof ArrayBuffer ? Array.from(new Uint8Array(item.data)) : item.data,\n ...(item.messageId === undefined ? {} : { messageId: item.messageId }),\n ...(item.timestamp === undefined ? {} : { timestamp: item.timestamp })\n }))\n }));\n }\n\n /** Close the socket and drop all state. Safe to call multiple times. */\n stop(): MaybePromise<void> {\n const socket = this.socket;\n const shouldClose = this.socketActive;\n this.socket = null;\n this.socketActive = false;\n this.handlers = null;\n this.subscribedTopics.clear();\n // Settle an in-flight handshake gate: a DataBus stop() awaits the start\n // Promise, so leaving it pending would hang teardown. Resolving (rather\n // than rejecting) keeps an intentional stop from surfacing as an error.\n this.settleConnect();\n this.connectPromise = null;\n if (shouldClose) socket?.close();\n }\n\n /** Resolve the in-flight handshake gate. Idempotent: once the socket has\n * opened (or a newer attempt replaced it) later calls are no-ops. */\n private settleConnect(): void {\n this.clearConnectTimer();\n const resolve = this.connectResolve;\n this.connectResolve = null;\n this.connectReject = null;\n resolve?.();\n }\n\n /** Reject the in-flight handshake gate. Idempotent on the same terms as\n * {@link settleConnect}. */\n private failConnect(error: unknown): void {\n this.clearConnectTimer();\n const reject = this.connectReject;\n this.connectResolve = null;\n this.connectReject = null;\n reject?.(error);\n }\n\n /** Best-effort close for a socket that can no longer carry transport data.\n * The error that invalidated it has already been reported by the caller. */\n private abortSocket(socket: WebSocketLike): void {\n try {\n socket.close();\n } catch {\n // Some injected implementations throw when close races a failed\n // handshake. The original failure remains the actionable error.\n }\n }\n\n private clearConnectTimer(): void {\n if (this.connectTimer !== null) {\n clearTimeout(this.connectTimer);\n this.connectTimer = null;\n }\n }\n\n /** Send one JSON frame. Frames are dropped with an `onError` report when\n * the socket is not open. A dropped SUBSCRIBE is recovered \u2014 `onopen`\n * re-asserts every topic in `subscribedTopics` \u2014 but the other two are not:\n * a dropped PUBLISH is the documented at-most-once loss, and a dropped\n * UNSUBSCRIBE is the quieter one, because `unsubscribe()` has already deleted\n * the topic from that set, so no later re-assert resends it and the server\n * keeps streaming a channel the app left. Through the DataBus neither of the\n * two is reachable while closed \u2014 `runTransport()` queues both operations\n * behind recovery \u2014 so this reads as a direct-transport-contract note. */\n private sendFrame(payload: { op: string; topic: string; data?: unknown; messageId?: string; timestamp?: number }): void {\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error(`WebSocket is not open; dropped \"${payload.op}\" frame.`));\n return;\n }\n this.socket.send(JSON.stringify(payload));\n }\n\n private sendBinaryFrame(topic: string, data: ArrayBuffer, messageId?: string, timestamp?: number): void {\n if (messageId !== undefined || timestamp !== undefined) {\n // Binary frames retain their compact legacy shape; metadata is sent as a\n // JSON envelope so IDs are never silently lost.\n this.sendFrame({\n op: WS_OP.PUBLISH,\n topic,\n data: Array.from(new Uint8Array(data)),\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n });\n return;\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publish\" frame.'));\n return;\n }\n const topicBytes = new TextEncoder().encode(topic);\n if (topicBytes.length > 0xffff) {\n this.handlers?.onError(new Error('WebSocket topic is too long for a binary frame.'));\n return;\n }\n const frame = new Uint8Array(3 + topicBytes.length + data.byteLength);\n frame[0] = 0xc7;\n new DataView(frame.buffer).setUint16(1, topicBytes.length);\n frame.set(topicBytes, 3);\n frame.set(new Uint8Array(data), 3 + topicBytes.length);\n this.socket.send(frame.buffer);\n }\n\n /** Parse a server frame. Only objects carrying a string `topic` are\n * publications, and unknown shapes are dropped silently so a chatty server\n * cannot crash the message path. Malformed JSON is *not* dropped silently: it\n * goes to `handlers.onError` as \"WebSocket server sent a non-JSON frame.\",\n * which also records it in the bus's last-failure diagnostics. Neither case\n * throws out of the socket listener. */\n private async handleMessage(raw: unknown): Promise<void> {\n // Browser WebSockets may deliver binary frames as Blob unless\n // `binaryType = 'arraybuffer'` is explicitly configured by the host.\n // Normalize Blob asynchronously and reuse the exact ArrayBuffer parser.\n if (typeof Blob !== 'undefined' && raw instanceof Blob) {\n // Blob conversion is asynchronous. Capture the socket/handler pair so a\n // stop/start that replaces the connection while arrayBuffer() is pending\n // cannot let the stale frame leak into the new connection's handlers.\n const socket = this.socket;\n const handlers = this.handlers;\n try {\n const buffer = await raw.arrayBuffer();\n if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;\n await this.handleMessage(buffer);\n } catch (error) {\n if (this.socket === socket && this.handlers === handlers && this.socketActive) {\n this.handlers?.onError(error);\n }\n }\n return;\n }\n let parsed: unknown;\n if (raw instanceof ArrayBuffer) {\n const bytes = new Uint8Array(raw);\n if (bytes[0] !== 0xc7 || bytes.length < 3) return;\n const topicLength = new DataView(raw).getUint16(1);\n if (bytes.length < 3 + topicLength) return;\n const topic = new TextDecoder().decode(bytes.subarray(3, 3 + topicLength));\n const data = bytes.slice(3 + topicLength).buffer;\n this.handlers?.onMessage({ topic, data: data as TData });\n return;\n }\n if (typeof raw !== 'string') return;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.handlers?.onError(new Error('WebSocket server sent a non-JSON frame.'));\n return;\n }\n if (!parsed || typeof parsed !== 'object') return;\n const publication = parseDataBusPublication<TData>(parsed);\n if (publication) this.handlers?.onMessage(publication as DataBusMessage<TData>);\n }\n}\n\n/** Resolve the platform WebSocket. Throws in a runtime without one (SSR/Node);\n * it has no null arm, and `start()`'s catch is what turns that throw into an\n * `onStatus('error')` + `onError` report. */\nfunction defaultWebSocketFactory(url: string, protocols?: string | string[]): WebSocketLike {\n if (typeof WebSocket === 'undefined') {\n throw new Error('WebSocketTransport requires a WebSocket implementation.');\n }\n return new WebSocket(url, protocols) as unknown as WebSocketLike;\n}\n\n/** Create a CrossTabDataBus backed by a plain WebSocket transport.\n * Cross-tab clustering (owner dedup, sticky routes, failover) works identically\n * to the Centrifuge backend \u2014 only the transport I/O differs. */\nexport function createWebSocketDataBus<TData = unknown>(\n options: CreateWebSocketDataBusOptions<TData>\n): CrossTabDataBus<WebSocketDataBusConfig, TData> {\n const { clusterKey, connection, ...dataBusOptions } = options;\n return new CrossTabDataBus({\n ...dataBusOptions,\n autoStart: true,\n clusterKey: clusterKey ?? connection.url,\n initialConfig: connection,\n transport: new WebSocketTransport<TData>(connection)\n });\n}\n\n/** Re-export for convenience: the status type used by the transport. */\nexport type { WorkerStatus };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["pending"]
|
|
7
7
|
}
|
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
*
|
|
12
12
|
* - **新增或修改字符串字面量时,先到这里查找/增补,不要在业务文件里直接写
|
|
13
13
|
* 魔法字符串。** 扫描残留字面量:`rg "'(['a-z]+)'" src/`。
|
|
14
|
-
* -
|
|
15
|
-
* `import type
|
|
16
|
-
* `TRACE_EVENT_TYPE.LIFECYCLE
|
|
17
|
-
*
|
|
14
|
+
* - **只用于类型位置的常量**(即只出现在 `typeof X[keyof typeof X]` 这类派生里、
|
|
15
|
+
* 从不参与运行时比较的)在调用方用 `import type`;**在运行时比较/构造中使用的**
|
|
16
|
+
* (如 `WORKER_STATUS.ERROR`、`TRACE_EVENT_TYPE.LIFECYCLE`、
|
|
17
|
+
* `PERSISTENCE_OPERATION.APPEND`)必须用值导入,否则会报 "cannot be used as
|
|
18
|
+
* a value because it was imported using 'import type'"。`PERSISTENCE_OPERATION`
|
|
19
|
+
* 属于后者而不是类型侧的例子:`replay-manager.ts` 以值导入它,并把它的成员作为
|
|
20
|
+
* `withPersistenceRetry` 的第一个实参传进去。
|
|
18
21
|
* - **值派生类型**:`export type WorkerStatus = (typeof WORKER_STATUS)[keyof
|
|
19
22
|
* typeof WORKER_STATUS]`。这样新增枚举分支时类型自动收窄,编译器会指出
|
|
20
23
|
* 每个遗漏的 switch 分支。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,eAAO,MAAM,WAAW;;;CAGd,CAAC;AAEX,eAAO,MAAM,cAAc;;;CAGjB,CAAC;AAGX,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAEX,eAAO,MAAM,cAAc;;;;CAIjB,CAAC;AAGX,eAAO,MAAM,gBAAgB;;;CAGnB,CAAC;AAKX,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAGX,eAAO,MAAM,cAAc;;;;CAIjB,CAAC;AAGX,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAGX,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AACjE,eAAO,MAAM,sBAAsB,sCAAuC,CAAC;AAC3E,eAAO,MAAM,kBAAkB,oCAAqC,CAAC;AACrE,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAGvD,eAAO,MAAM,uBAAuB,gBAAgB,CAAC;AAGrD,eAAO,MAAM,gBAAgB;;;;;;;;CAQnB,CAAC;AAGX,eAAO,MAAM,sBAAsB;;;;;CAKzB,CAAC;AAGX,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAGX,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAGX,eAAO,MAAM,kBAAkB;;;CAGrB,CAAC;AAGX,eAAO,MAAM,qBAAqB;;;;;;;;CAWxB,CAAC;AAGX,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAC;AAGX,eAAO,MAAM,mBAAmB;;;CAGtB,CAAC;AAGX,eAAO,MAAM,cAAc;;;;CAIjB,CAAC;AAGX,eAAO,MAAM,qBAAqB;;;;;;CAMxB,CAAC;AAGX,eAAO,MAAM,cAAc;;;;CAIjB,CAAC;AAGX,eAAO,MAAM,YAAY;;;;;;;CAOf,CAAC;AAGX,eAAO,MAAM,KAAK;;;;;CAKR,CAAC;AAGX,eAAO,MAAM,qBAAqB;;;;;;;;;;CAUxB,CAAC;AAGX,eAAO,MAAM,sBAAsB;;;;;;;CAOzB,CAAC"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 参数校验工具 —— CrossTabDataBus 构造选项与持久化配置的入口校验。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 绝大多数 `throw new TypeError(...)` 校验集中在此,DataBus 构造器与
|
|
5
5
|
* IndexedDbReplayPersistence 共用同一组断言,错误消息与原有语义保持一致。
|
|
6
|
+
* 唯一的例外是 `replay-manager.ts` 里 `clearBefore(timestamp)` 的
|
|
7
|
+
* `Number.isFinite` 检查:它贴在自己的公共方法上,不在这里。新增校验时若
|
|
8
|
+
* 想继续集中,请把这类只服务单个公共方法入口的检查也搬进来,否则请像它
|
|
9
|
+
* 一样在这里留一句指向。
|
|
6
10
|
*
|
|
7
11
|
* 语义约定:可选字段只在**显式提供**时校验(undefined 由调用方落到默认值,
|
|
8
12
|
* 默认值始终合法);必填字段总是校验。
|
|
@@ -39,9 +43,13 @@ export declare function assertRecoveryOptions(recovery: {
|
|
|
39
43
|
* Each weight is a non-negative finite number of "topic-equivalents" added per
|
|
40
44
|
* unit of the sampled signal; `0` (the default) disables that signal. A
|
|
41
45
|
* negative weight would invert the documented policy — biasing NEW routes
|
|
42
|
-
* toward the *busiest* Worker instead of the quietest
|
|
43
|
-
*
|
|
44
|
-
*
|
|
46
|
+
* toward the *busiest* Worker instead of the quietest. A non-finite one cannot
|
|
47
|
+
* poison an individual score either: `effectiveWorkerLoad` ends in a
|
|
48
|
+
* `Number.isFinite` fallback (`routing.ts:64-70`), which is what its own
|
|
49
|
+
* comment says a non-finite weight is for. What it does instead is worse to
|
|
50
|
+
* notice — that fallback fires for *every* worker, so all loads collapse to the
|
|
51
|
+
* raw topic count and the adaptive term is switched off silently. Both are
|
|
52
|
+
* rejected here so the misconfiguration fails loudly. */
|
|
45
53
|
export declare function assertLoadWeightingOptions(loadWeighting: LoadWeightingOptions | undefined): void;
|
|
46
54
|
/** Validate the cluster coordination options shared by `WorkerClusterRuntime`
|
|
47
55
|
* and `CrossTabDataBus`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,8BAA8B,EAC9B,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAI1D;;;yEAGyE;AACzE,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAI5E;AAED,gFAAgF;AAChF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAI7E;AAED,oFAAoF;AACpF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAIhF;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,GAAG,KAAK,GAAG,MAAM,CAK7F;AAED;8DAC8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CASlF;AAED,oDAAoD;AACpD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,8BAA8B,GAAG,IAAI,CAOzF;AAED;8DAC8D;AAC9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,SAAS,GAAG,IAAI,CAiB/E;AAED;wEACwE;AACxE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,SAAS,GAAG,IAAI,CAanB;AAED;;;;;;;;;;;yDAWyD;AACzD,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAUhG;AAED;;;;;;;;;;yCAUyC;AACzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC,GAAG,IAAI,CAcP;AAED;;;;;mFAKmF;AACnF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM3D;AAED;;;;;;2DAM2D;AAC3D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAKxE;AAED;;;;;;kCAMkC;AAClC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAU9D"}
|