cross-tab-worker-databus 0.20.86 → 0.20.87

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/dist/cjs/vue.cjs CHANGED
@@ -121,7 +121,7 @@ function useCrossTabHealth(bus, options) {
121
121
  for (const cleanup of cleanups) cleanup();
122
122
  cleanups = [];
123
123
  };
124
- (0, import_vue.watch)(bus, (next) => {
124
+ (0, import_vue.watch)([bus, () => options?.intervalMs], ([next]) => {
125
125
  teardown();
126
126
  if (!next) {
127
127
  health.value = null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/vue.ts", "../../src/utils/constants.ts"],
4
- "sourcesContent": ["/** Vue 3 composables adapter for cross-tab-worker-databus.\n * Vue is an optional peer dependency; this module is a separate entry point.\n */\nimport { onBeforeUnmount, onMounted, ref, shallowRef, watch, type Ref } from 'vue';\nimport type { CrossTabDataBus, DataBusHealthSummary } from './core/data-bus';\nimport type { DataBusMessage, WorkerStatus } from './core/types';\nimport { WORKER_STATUS } from './utils/constants';\n\nexport function useCrossTabDataBus<TConfig, TData>(\n create: () => CrossTabDataBus<TConfig, TData>,\n deps: ReadonlyArray<Ref<unknown> | (() => unknown)> = []\n): Ref<CrossTabDataBus<TConfig, TData> | null> {\n const bus = shallowRef<CrossTabDataBus<TConfig, TData> | null>(null);\n let instance: CrossTabDataBus<TConfig, TData> | null = null;\n let lifecycleGeneration = 0;\n const stop = async () => { const current = instance; instance = null; bus.value = null; if (current) await current.stop(); };\n const start = () => {\n const generation = ++lifecycleGeneration;\n void stop().then(() => {\n if (generation !== lifecycleGeneration) return;\n const next = create();\n instance = next;\n bus.value = next;\n void next.ready().catch(() => {});\n });\n };\n onMounted(start);\n onBeforeUnmount(() => {\n // Bump the generation so a start() still awaiting its stop() sees itself\n // superseded. Without this the pending continuation would run create()\n // after the component is gone, leaving a live bus with no owner to stop\n // it (the React adapter has no such window: its create() is synchronous\n // inside useEffect).\n lifecycleGeneration += 1;\n void stop();\n });\n if (deps.length > 0) watch(deps, start);\n return bus as Ref<CrossTabDataBus<TConfig, TData> | null>;\n}\n\nexport function useCrossTabSubscription<TConfig, TData>(\n bus: Ref<CrossTabDataBus<TConfig, TData> | null>, topic: Ref<string> | string,\n handler: (message: DataBusMessage<TData>) => void\n): void {\n let currentBus: CrossTabDataBus<TConfig, TData> | null = null;\n let currentTopic: string | null = null;\n let cleanup: (() => void) | undefined;\n let latestHandler = handler;\n const stop = () => { cleanup?.(); cleanup = undefined; currentBus = null; currentTopic = null; };\n const sync = () => {\n const nextBus = bus.value;\n const nextTopic = typeof topic === 'string' ? topic : topic.value;\n if (nextBus === currentBus && currentTopic === nextTopic && cleanup) return;\n stop();\n if (!nextBus) return;\n currentBus = nextBus;\n currentTopic = nextTopic;\n cleanup = nextBus.subscribe(nextTopic, message => latestHandler(message));\n };\n watch(bus, sync, { immediate: true });\n if (typeof topic !== 'string') watch(topic, sync);\n watch(() => handler, value => { latestHandler = value; });\n onBeforeUnmount(stop);\n}\n\nexport function useCrossTabStatus<TConfig, TData>(\n bus: Ref<CrossTabDataBus<TConfig, TData> | null>\n): Ref<WorkerStatus> {\n const status = ref<WorkerStatus>(WORKER_STATUS.CONNECTING);\n let cleanup: (() => void) | undefined;\n watch(bus, next => {\n cleanup?.(); cleanup = undefined;\n status.value = next?.getStatus() ?? WORKER_STATUS.CONNECTING;\n if (next) cleanup = next.onStatus(value => { status.value = value; });\n }, { immediate: true });\n onBeforeUnmount(() => cleanup?.());\n return status;\n}\n\n/** Options for {@link useCrossTabHealth}. */\nexport interface UseCrossTabHealthOptions {\n /** Polling cadence in ms for the health snapshot. Default 1000; `0` disables\n * polling and relies on status/error events only. */\n intervalMs?: number;\n}\n\n/**\n * Mirror the bus health summary into a Vue ref. `getHealthSummary()` is a\n * snapshot, so the composable polls it on an interval (default 1000 ms) and\n * refreshes on status changes and errors. Returns `null` until the bus exists.\n */\nexport function useCrossTabHealth<TConfig, TData>(\n bus: Ref<CrossTabDataBus<TConfig, TData> | null>,\n options?: UseCrossTabHealthOptions\n): Ref<DataBusHealthSummary | null> {\n const health = ref<DataBusHealthSummary | null>(null);\n let timer: ReturnType<typeof setInterval> | null = null;\n let cleanups: Array<() => void> = [];\n const teardown = () => {\n if (timer) clearInterval(timer);\n timer = null;\n for (const cleanup of cleanups) cleanup();\n cleanups = [];\n };\n watch(bus, next => {\n teardown();\n if (!next) {\n health.value = null;\n return;\n }\n const refresh = () => { health.value = next.getHealthSummary(); };\n refresh();\n cleanups.push(next.onStatus(refresh));\n cleanups.push(next.onError(refresh));\n const intervalMs = options?.intervalMs ?? 1_000;\n if (intervalMs > 0) timer = setInterval(refresh, intervalMs);\n }, { immediate: true });\n onBeforeUnmount(teardown);\n return health as Ref<DataBusHealthSummary | null>;\n}\n", "/**\n * \u516C\u5171\u5B57\u7B26\u4E32\u5E38\u91CF \u2014\u2014 \u751F\u547D\u5468\u671F\u72B6\u6001\u3001\u89D2\u8272\u3001\u63A7\u5236\u52A8\u4F5C\u3001\u534F\u8BAE\u6D88\u606F\u7C7B\u578B\u3001\u679A\u4E3E\u4E0E\n * \u547D\u540D\u7A7A\u95F4\u524D\u7F00\u3002\n *\n * \u6240\u6709\u8FD0\u884C\u65F6\u4F7F\u7528\u7684\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u96C6\u4E2D\u5728\u6B64\u4E00\u4EFD\u503C\uFF0C\u7C7B\u578B\u5B9A\u4E49\uFF08src/core/types.ts\n * \u7B49\uFF09\u901A\u8FC7 `(typeof X)[keyof typeof X]` \u4ECE\u8FD9\u4E9B\u5E38\u91CF\u6D3E\u751F\uFF0C\u6BD4\u8F83/switch \u5904\u5F15\u7528\n * \u540C\u4E00\u5BF9\u8C61\u6210\u5458\u3002\u8FD9\u6837\u65E2\u80FD\u6D88\u9664\u6563\u843D\u5728\u5404\u6587\u4EF6\u91CC\u7684\u91CD\u590D\u5B57\u7B26\u4E32\uFF08\u907F\u514D\u5927\u5C0F\u5199\u4E0D\u7EDF\u4E00\n * \u6216\u7B14\u8BEF\uFF09\uFF0C\u53C8\u4FDD\u8BC1\u7C7B\u578B\u4E0E\u503C\u6C38\u4E0D\u9519\u4F4D\u3002\n *\n * ## \u4F7F\u7528\u7EA6\u5B9A\n *\n * - **\u65B0\u589E\u6216\u4FEE\u6539\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u65F6\uFF0C\u5148\u5230\u8FD9\u91CC\u67E5\u627E/\u589E\u8865\uFF0C\u4E0D\u8981\u5728\u4E1A\u52A1\u6587\u4EF6\u91CC\u76F4\u63A5\u5199\n * \u9B54\u6CD5\u5B57\u7B26\u4E32\u3002** \u626B\u63CF\u6B8B\u7559\u5B57\u9762\u91CF\uFF1A`rg \"'(['a-z]+)'\" src/`\u3002\n * - **\u53EA\u7528\u4E8E\u7C7B\u578B\u4F4D\u7F6E\u7684\u5E38\u91CF**\uFF08\u5982 `PERSISTENCE_OPERATION`\uFF09\u5728\u8C03\u7528\u65B9\u7528\n * `import type`\uFF1B**\u5728\u8FD0\u884C\u65F6\u6BD4\u8F83/\u6784\u9020\u4E2D\u4F7F\u7528\u7684**\uFF08\u5982 `WORKER_STATUS.ERROR`\u3001\n * `TRACE_EVENT_TYPE.LIFECYCLE`\uFF09\u5FC5\u987B\u7528\u503C\u5BFC\u5165\uFF0C\u5426\u5219\u4F1A\u62A5 \"cannot be used as\n * a value because it was imported using 'import type'\"\u3002\n * - **\u503C\u6D3E\u751F\u7C7B\u578B**\uFF1A`export type WorkerStatus = (typeof WORKER_STATUS)[keyof\n * typeof WORKER_STATUS]`\u3002\u8FD9\u6837\u65B0\u589E\u679A\u4E3E\u5206\u652F\u65F6\u7C7B\u578B\u81EA\u52A8\u6536\u7A84\uFF0C\u7F16\u8BD1\u5668\u4F1A\u6307\u51FA\n * \u6BCF\u4E2A\u9057\u6F0F\u7684 switch \u5206\u652F\u3002\n * - \u5206\u7EC4\u7684\u987A\u5E8F\uFF08\u72B6\u6001 / \u89D2\u8272 / \u540E\u7AEF / \u6D88\u606F\u7C7B\u578B / \u679A\u4E3E / \u524D\u7F00\uFF09\u4FDD\u6301\u7A33\u5B9A\uFF0C\u65B9\u4FBF\n * \u7EF4\u62A4\u8005\u4E00\u773C\u5B9A\u4F4D\u3002\n */\n\n// ---- \u751F\u547D\u5468\u671F\u72B6\u6001 / \u89D2\u8272 / \u53EF\u89C1\u6027 ----\nexport const WORKER_STATUS = {\n CONNECTING: 'connecting',\n CONNECTED: 'connected',\n DISCONNECTED: 'disconnected',\n ERROR: 'error',\n} as const;\n\nexport const WORKER_ROLE = {\n ACTIVE: 'active',\n STANDBY: 'standby',\n} as const;\n\nexport const TAB_VISIBILITY = {\n VISIBLE: 'visible',\n HIDDEN: 'hidden',\n} as const;\n\n// ---- Worker \u540E\u7AEF\u6A21\u5F0F\u4E0E\u89E3\u6790\u7ED3\u679C\uFF08WorkerMode / WorkerBackend\uFF09----\nexport const WORKER_MODE = {\n DEDICATED: 'dedicated',\n SHARED: 'shared',\n AUTO: 'auto',\n} as const;\n\nexport const WORKER_BACKEND = {\n DEDICATED: 'dedicated',\n SHARED: 'shared',\n LOCAL: 'local',\n} as const;\n\n// ---- \u5B58\u50A8\u4E8B\u4EF6\u901A\u9053\u56DE\u9000 ----\nexport const CHANNEL_FALLBACK = {\n NONE: 'none',\n STORAGE_EVENT: 'storage-event',\n} as const;\n\n// ---- Worker / MessagePort / BroadcastChannel \u7684 EventTarget \u4E8B\u4EF6\u540D\u5224\u522B ----\n// MessageEvent.type\uFF08'message' / 'messageerror'\uFF09\u4E0E Worker \u4E0A\u7684 'error' \u4E8B\u4EF6\u540D\uFF0C\n// \u7528\u4E8E\u73AF\u5883\u9002\u914D\u5668\u63A5\u53E3\u7B7E\u540D\u4E0E\u6D4B\u8BD5\u66FF\u8EAB\u7684 addEventListener \u771F\u5047\u5206\u652F\u3002\nexport const EVENT_TYPE = {\n MESSAGE: 'message',\n MESSAGEERROR: 'messageerror',\n ERROR: 'error',\n} as const;\n\n// ---- \u63A7\u5236\u5E73\u9762\u52A8\u4F5C\uFF08WorkerControlAction\uFF09----\nexport const CONTROL_ACTION = {\n SUBSCRIBE: 'SUBSCRIBE',\n UNSUBSCRIBE: 'UNSUBSCRIBE',\n PUBLISH: 'PUBLISH',\n} as const;\n\n// ---- BroadcastChannel \u96C6\u7FA4\u6D88\u606F\u7C7B\u578B ----\nexport const CLUSTER_MESSAGE_TYPE = {\n CONTROL: 'CONTROL',\n EVENT: 'EVENT',\n REGISTRY: 'REGISTRY',\n ROUTE_RELEASED: 'ROUTE_RELEASED',\n} as const;\n\n// ---- \u547D\u540D\u7A7A\u95F4\u524D\u7F00\u4E0E\u4E8B\u4EF6\u540D ----\nexport const DEFAULT_STORAGE_PREFIX = 'cross-tab-worker-databus';\nexport const STORAGE_CHANNEL_PREFIX = `${DEFAULT_STORAGE_PREFIX}:channel:`;\nexport const TAB_ID_STORAGE_KEY = `${DEFAULT_STORAGE_PREFIX}:tab-id`;\nexport const PUBLICATION_EVENT = 'DATABUS_PUBLICATION';\n\n// ---- \u89C4\u8303 JSON \u4FE1\u5C01\u64CD\u4F5C\u7801 ----\nexport const PUBLICATION_ENVELOPE_OP = 'publication';\n\n// ---- trace \u4E8B\u4EF6\u7C7B\u578B ----\nexport const TRACE_EVENT_TYPE = {\n LIFECYCLE: 'lifecycle',\n STATUS: 'status',\n SUBSCRIPTION: 'subscription',\n COORDINATION: 'coordination',\n ERROR: 'error',\n RELIABILITY: 'reliability',\n MESSAGE_METRICS: 'message_metrics',\n} as const;\n\n// ---- trace \u751F\u547D\u5468\u671F\u52A8\u4F5C ----\nexport const TRACE_LIFECYCLE_ACTION = {\n START: 'start',\n STOP: 'stop',\n SUSPEND: 'suspend',\n RESUME: 'resume',\n} as const;\n\n// ---- \u5185\u90E8 handler \u8C03\u7528\u6807\u7B7E\uFF08\u9694\u79BB\u5F02\u5E38\u65F6\u7684\u5206\u7C7B\u6765\u6E90\uFF09----\nexport const INVOKE_LABEL = {\n DISPATCH: 'dispatch',\n STATUS: 'status',\n ERROR_HANDLER: 'error handler',\n} as const;\n\n// ---- trace \u6A21\u5F0F\uFF08DataBusTraceMode\uFF09----\nexport const TRACE_MODE = {\n EVENTS: 'events',\n METRICS: 'metrics',\n ALL: 'all',\n} as const;\n\n// ---- trace \u9519\u8BEF\u6765\u6E90 ----\nexport const TRACE_ERROR_SOURCE = {\n TRANSPORT: 'transport',\n OPERATION: 'operation',\n} as const;\n\n// ---- reliability \u8BCA\u65AD\u64CD\u4F5C ----\nexport const RELIABILITY_OPERATION = {\n TRANSPORT_RECOVERY: 'transport_recovery',\n ROUTE_ACK: 'route_ack',\n ROUTE_MIGRATION: 'route_migration',\n // A re-election that recovered a stranded unconfirmed handoff (previous\n // owner gone, ACK never arrived). Distinct from ROUTE_MIGRATION so trace\n // consumers can tell recoveries apart from routine graceful handoffs.\n ROUTE_MIGRATION_RECOVERY: 'route_migration_recovery',\n PERSISTENCE_CLEANUP: 'persistence_cleanup',\n PERSISTENCE_RETRY: 'persistence_retry',\n DEDUP_SUPPRESSED: 'dedup_suppressed',\n} as const;\n\n// ---- \u81EA\u52A8\u6062\u590D\u5C1D\u8BD5\u7ED3\u679C ----\nexport const RECOVERY_OUTCOME = {\n SCHEDULED: 'scheduled',\n SUCCEEDED: 'succeeded',\n FAILED: 'failed',\n EXHAUSTED: 'exhausted',\n} as const;\n\n// ---- \u8BA2\u9605\u52A8\u4F5C ----\nexport const SUBSCRIPTION_ACTION = {\n SUBSCRIBE: 'subscribe',\n UNSUBSCRIBE: 'unsubscribe',\n} as const;\n\n// ---- \u56DE\u653E\u4FEE\u526A\u7B56\u7565 ----\nexport const PRUNE_STRATEGY = {\n COUNT: 'count',\n AGE: 'age',\n BOTH: 'both',\n} as const;\n\n// ---- \u6301\u4E45\u5316\u64CD\u4F5C ----\nexport const PERSISTENCE_OPERATION = {\n LOAD: 'load',\n APPEND: 'append',\n CLEAR: 'clear',\n CLEAR_TOPIC: 'clearTopic',\n CLEAR_BEFORE: 'clearBefore',\n} as const;\n\n// ---- \u6545\u969C\u6765\u6E90\uFF08DataBusFailureSource\uFF09----\nexport const FAILURE_SOURCE = {\n TRANSPORT: 'transport',\n PERSISTENCE: 'persistence',\n DISPATCH: 'dispatch',\n} as const;\n\n// ---- \u5065\u5EB7\u5224\u5B9A\u72B6\u6001\uFF08DataBusHealthSummary['state']\uFF09----\nexport const HEALTH_STATE = {\n STOPPED: 'stopped',\n STARTING: 'starting',\n HEALTHY: 'healthy',\n RECOVERING: 'recovering',\n SUSPENDED: 'suspended',\n DEGRADED: 'degraded',\n} as const;\n\n// ---- WebSocket wire \u534F\u8BAE\u64CD\u4F5C\u7801 ----\nexport const WS_OP = {\n SUBSCRIBE: 'subscribe',\n UNSUBSCRIBE: 'unsubscribe',\n PUBLISH: 'publish',\n PUBLISH_BATCH: 'publishBatch',\n} as const;\n\n// ---- Centrifuge Worker \u8F93\u5165\u6D88\u606F\u7C7B\u578B ----\nexport const CENTRIFUGE_INPUT_TYPE = {\n INIT: 'INIT',\n SUBSCRIBE: 'SUBSCRIBE',\n UNSUBSCRIBE: 'UNSUBSCRIBE',\n PUBLISH: 'PUBLISH',\n PUBLISH_BIN: 'PUBLISH_BIN',\n PING: 'PING',\n STOP: 'STOP',\n TOKEN_RESPONSE: 'TOKEN_RESPONSE',\n TOKEN_ERROR: 'TOKEN_ERROR',\n} as const;\n\n// ---- Centrifuge Worker \u8F93\u51FA\u6D88\u606F\u7C7B\u578B ----\nexport const CENTRIFUGE_OUTPUT_TYPE = {\n STATUS: 'STATUS',\n MESSAGE: 'MESSAGE',\n MESSAGE_BIN: 'MESSAGE_BIN',\n ERROR: 'ERROR',\n TOKEN_REQUEST: 'TOKEN_REQUEST',\n} as const;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/** Vue 3 composables adapter for cross-tab-worker-databus.\n * Vue is an optional peer dependency; this module is a separate entry point.\n */\nimport { onBeforeUnmount, onMounted, ref, shallowRef, watch, type Ref } from 'vue';\nimport type { CrossTabDataBus, DataBusHealthSummary } from './core/data-bus';\nimport type { DataBusMessage, WorkerStatus } from './core/types';\nimport { WORKER_STATUS } from './utils/constants';\n\nexport function useCrossTabDataBus<TConfig, TData>(\n create: () => CrossTabDataBus<TConfig, TData>,\n deps: ReadonlyArray<Ref<unknown> | (() => unknown)> = []\n): Ref<CrossTabDataBus<TConfig, TData> | null> {\n const bus = shallowRef<CrossTabDataBus<TConfig, TData> | null>(null);\n let instance: CrossTabDataBus<TConfig, TData> | null = null;\n let lifecycleGeneration = 0;\n const stop = async () => { const current = instance; instance = null; bus.value = null; if (current) await current.stop(); };\n const start = () => {\n const generation = ++lifecycleGeneration;\n void stop().then(() => {\n if (generation !== lifecycleGeneration) return;\n const next = create();\n instance = next;\n bus.value = next;\n void next.ready().catch(() => {});\n });\n };\n onMounted(start);\n onBeforeUnmount(() => {\n // Bump the generation so a start() still awaiting its stop() sees itself\n // superseded. Without this the pending continuation would run create()\n // after the component is gone, leaving a live bus with no owner to stop\n // it (the React adapter has no such window: its create() is synchronous\n // inside useEffect).\n lifecycleGeneration += 1;\n void stop();\n });\n if (deps.length > 0) watch(deps, start);\n return bus as Ref<CrossTabDataBus<TConfig, TData> | null>;\n}\n\nexport function useCrossTabSubscription<TConfig, TData>(\n bus: Ref<CrossTabDataBus<TConfig, TData> | null>, topic: Ref<string> | string,\n handler: (message: DataBusMessage<TData>) => void\n): void {\n let currentBus: CrossTabDataBus<TConfig, TData> | null = null;\n let currentTopic: string | null = null;\n let cleanup: (() => void) | undefined;\n let latestHandler = handler;\n const stop = () => { cleanup?.(); cleanup = undefined; currentBus = null; currentTopic = null; };\n const sync = () => {\n const nextBus = bus.value;\n const nextTopic = typeof topic === 'string' ? topic : topic.value;\n if (nextBus === currentBus && currentTopic === nextTopic && cleanup) return;\n stop();\n if (!nextBus) return;\n currentBus = nextBus;\n currentTopic = nextTopic;\n cleanup = nextBus.subscribe(nextTopic, message => latestHandler(message));\n };\n watch(bus, sync, { immediate: true });\n if (typeof topic !== 'string') watch(topic, sync);\n watch(() => handler, value => { latestHandler = value; });\n onBeforeUnmount(stop);\n}\n\nexport function useCrossTabStatus<TConfig, TData>(\n bus: Ref<CrossTabDataBus<TConfig, TData> | null>\n): Ref<WorkerStatus> {\n const status = ref<WorkerStatus>(WORKER_STATUS.CONNECTING);\n let cleanup: (() => void) | undefined;\n watch(bus, next => {\n cleanup?.(); cleanup = undefined;\n status.value = next?.getStatus() ?? WORKER_STATUS.CONNECTING;\n if (next) cleanup = next.onStatus(value => { status.value = value; });\n }, { immediate: true });\n onBeforeUnmount(() => cleanup?.());\n return status;\n}\n\n/** Options for {@link useCrossTabHealth}. */\nexport interface UseCrossTabHealthOptions {\n /** Polling cadence in ms for the health snapshot. Default 1000; `0` disables\n * polling and relies on status/error events only. */\n intervalMs?: number;\n}\n\n/**\n * Mirror the bus health summary into a Vue ref. `getHealthSummary()` is a\n * snapshot, so the composable polls it on an interval (default 1000 ms) and\n * refreshes on status changes and errors. A reactive interval change replaces\n * the timer without rebuilding the bus. Returns `null` until the bus exists.\n */\nexport function useCrossTabHealth<TConfig, TData>(\n bus: Ref<CrossTabDataBus<TConfig, TData> | null>,\n options?: UseCrossTabHealthOptions\n): Ref<DataBusHealthSummary | null> {\n const health = ref<DataBusHealthSummary | null>(null);\n let timer: ReturnType<typeof setInterval> | null = null;\n let cleanups: Array<() => void> = [];\n const teardown = () => {\n if (timer) clearInterval(timer);\n timer = null;\n for (const cleanup of cleanups) cleanup();\n cleanups = [];\n };\n watch([bus, () => options?.intervalMs], ([next]) => {\n teardown();\n if (!next) {\n health.value = null;\n return;\n }\n const refresh = () => { health.value = next.getHealthSummary(); };\n refresh();\n cleanups.push(next.onStatus(refresh));\n cleanups.push(next.onError(refresh));\n const intervalMs = options?.intervalMs ?? 1_000;\n if (intervalMs > 0) timer = setInterval(refresh, intervalMs);\n }, { immediate: true });\n onBeforeUnmount(teardown);\n return health as Ref<DataBusHealthSummary | null>;\n}\n", "/**\n * \u516C\u5171\u5B57\u7B26\u4E32\u5E38\u91CF \u2014\u2014 \u751F\u547D\u5468\u671F\u72B6\u6001\u3001\u89D2\u8272\u3001\u63A7\u5236\u52A8\u4F5C\u3001\u534F\u8BAE\u6D88\u606F\u7C7B\u578B\u3001\u679A\u4E3E\u4E0E\n * \u547D\u540D\u7A7A\u95F4\u524D\u7F00\u3002\n *\n * \u6240\u6709\u8FD0\u884C\u65F6\u4F7F\u7528\u7684\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u96C6\u4E2D\u5728\u6B64\u4E00\u4EFD\u503C\uFF0C\u7C7B\u578B\u5B9A\u4E49\uFF08src/core/types.ts\n * \u7B49\uFF09\u901A\u8FC7 `(typeof X)[keyof typeof X]` \u4ECE\u8FD9\u4E9B\u5E38\u91CF\u6D3E\u751F\uFF0C\u6BD4\u8F83/switch \u5904\u5F15\u7528\n * \u540C\u4E00\u5BF9\u8C61\u6210\u5458\u3002\u8FD9\u6837\u65E2\u80FD\u6D88\u9664\u6563\u843D\u5728\u5404\u6587\u4EF6\u91CC\u7684\u91CD\u590D\u5B57\u7B26\u4E32\uFF08\u907F\u514D\u5927\u5C0F\u5199\u4E0D\u7EDF\u4E00\n * \u6216\u7B14\u8BEF\uFF09\uFF0C\u53C8\u4FDD\u8BC1\u7C7B\u578B\u4E0E\u503C\u6C38\u4E0D\u9519\u4F4D\u3002\n *\n * ## \u4F7F\u7528\u7EA6\u5B9A\n *\n * - **\u65B0\u589E\u6216\u4FEE\u6539\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u65F6\uFF0C\u5148\u5230\u8FD9\u91CC\u67E5\u627E/\u589E\u8865\uFF0C\u4E0D\u8981\u5728\u4E1A\u52A1\u6587\u4EF6\u91CC\u76F4\u63A5\u5199\n * \u9B54\u6CD5\u5B57\u7B26\u4E32\u3002** \u626B\u63CF\u6B8B\u7559\u5B57\u9762\u91CF\uFF1A`rg \"'(['a-z]+)'\" src/`\u3002\n * - **\u53EA\u7528\u4E8E\u7C7B\u578B\u4F4D\u7F6E\u7684\u5E38\u91CF**\uFF08\u5982 `PERSISTENCE_OPERATION`\uFF09\u5728\u8C03\u7528\u65B9\u7528\n * `import type`\uFF1B**\u5728\u8FD0\u884C\u65F6\u6BD4\u8F83/\u6784\u9020\u4E2D\u4F7F\u7528\u7684**\uFF08\u5982 `WORKER_STATUS.ERROR`\u3001\n * `TRACE_EVENT_TYPE.LIFECYCLE`\uFF09\u5FC5\u987B\u7528\u503C\u5BFC\u5165\uFF0C\u5426\u5219\u4F1A\u62A5 \"cannot be used as\n * a value because it was imported using 'import type'\"\u3002\n * - **\u503C\u6D3E\u751F\u7C7B\u578B**\uFF1A`export type WorkerStatus = (typeof WORKER_STATUS)[keyof\n * typeof WORKER_STATUS]`\u3002\u8FD9\u6837\u65B0\u589E\u679A\u4E3E\u5206\u652F\u65F6\u7C7B\u578B\u81EA\u52A8\u6536\u7A84\uFF0C\u7F16\u8BD1\u5668\u4F1A\u6307\u51FA\n * \u6BCF\u4E2A\u9057\u6F0F\u7684 switch \u5206\u652F\u3002\n * - \u5206\u7EC4\u7684\u987A\u5E8F\uFF08\u72B6\u6001 / \u89D2\u8272 / \u540E\u7AEF / \u6D88\u606F\u7C7B\u578B / \u679A\u4E3E / \u524D\u7F00\uFF09\u4FDD\u6301\u7A33\u5B9A\uFF0C\u65B9\u4FBF\n * \u7EF4\u62A4\u8005\u4E00\u773C\u5B9A\u4F4D\u3002\n */\n\n// ---- \u751F\u547D\u5468\u671F\u72B6\u6001 / \u89D2\u8272 / \u53EF\u89C1\u6027 ----\nexport const WORKER_STATUS = {\n CONNECTING: 'connecting',\n CONNECTED: 'connected',\n DISCONNECTED: 'disconnected',\n ERROR: 'error',\n} as const;\n\nexport const WORKER_ROLE = {\n ACTIVE: 'active',\n STANDBY: 'standby',\n} as const;\n\nexport const TAB_VISIBILITY = {\n VISIBLE: 'visible',\n HIDDEN: 'hidden',\n} as const;\n\n// ---- Worker \u540E\u7AEF\u6A21\u5F0F\u4E0E\u89E3\u6790\u7ED3\u679C\uFF08WorkerMode / WorkerBackend\uFF09----\nexport const WORKER_MODE = {\n DEDICATED: 'dedicated',\n SHARED: 'shared',\n AUTO: 'auto',\n} as const;\n\nexport const WORKER_BACKEND = {\n DEDICATED: 'dedicated',\n SHARED: 'shared',\n LOCAL: 'local',\n} as const;\n\n// ---- \u5B58\u50A8\u4E8B\u4EF6\u901A\u9053\u56DE\u9000 ----\nexport const CHANNEL_FALLBACK = {\n NONE: 'none',\n STORAGE_EVENT: 'storage-event',\n} as const;\n\n// ---- Worker / MessagePort / BroadcastChannel \u7684 EventTarget \u4E8B\u4EF6\u540D\u5224\u522B ----\n// MessageEvent.type\uFF08'message' / 'messageerror'\uFF09\u4E0E Worker \u4E0A\u7684 'error' \u4E8B\u4EF6\u540D\uFF0C\n// \u7528\u4E8E\u73AF\u5883\u9002\u914D\u5668\u63A5\u53E3\u7B7E\u540D\u4E0E\u6D4B\u8BD5\u66FF\u8EAB\u7684 addEventListener \u771F\u5047\u5206\u652F\u3002\nexport const EVENT_TYPE = {\n MESSAGE: 'message',\n MESSAGEERROR: 'messageerror',\n ERROR: 'error',\n} as const;\n\n// ---- \u63A7\u5236\u5E73\u9762\u52A8\u4F5C\uFF08WorkerControlAction\uFF09----\nexport const CONTROL_ACTION = {\n SUBSCRIBE: 'SUBSCRIBE',\n UNSUBSCRIBE: 'UNSUBSCRIBE',\n PUBLISH: 'PUBLISH',\n} as const;\n\n// ---- BroadcastChannel \u96C6\u7FA4\u6D88\u606F\u7C7B\u578B ----\nexport const CLUSTER_MESSAGE_TYPE = {\n CONTROL: 'CONTROL',\n EVENT: 'EVENT',\n REGISTRY: 'REGISTRY',\n ROUTE_RELEASED: 'ROUTE_RELEASED',\n} as const;\n\n// ---- \u547D\u540D\u7A7A\u95F4\u524D\u7F00\u4E0E\u4E8B\u4EF6\u540D ----\nexport const DEFAULT_STORAGE_PREFIX = 'cross-tab-worker-databus';\nexport const STORAGE_CHANNEL_PREFIX = `${DEFAULT_STORAGE_PREFIX}:channel:`;\nexport const TAB_ID_STORAGE_KEY = `${DEFAULT_STORAGE_PREFIX}:tab-id`;\nexport const PUBLICATION_EVENT = 'DATABUS_PUBLICATION';\n\n// ---- \u89C4\u8303 JSON \u4FE1\u5C01\u64CD\u4F5C\u7801 ----\nexport const PUBLICATION_ENVELOPE_OP = 'publication';\n\n// ---- trace \u4E8B\u4EF6\u7C7B\u578B ----\nexport const TRACE_EVENT_TYPE = {\n LIFECYCLE: 'lifecycle',\n STATUS: 'status',\n SUBSCRIPTION: 'subscription',\n COORDINATION: 'coordination',\n ERROR: 'error',\n RELIABILITY: 'reliability',\n MESSAGE_METRICS: 'message_metrics',\n} as const;\n\n// ---- trace \u751F\u547D\u5468\u671F\u52A8\u4F5C ----\nexport const TRACE_LIFECYCLE_ACTION = {\n START: 'start',\n STOP: 'stop',\n SUSPEND: 'suspend',\n RESUME: 'resume',\n} as const;\n\n// ---- \u5185\u90E8 handler \u8C03\u7528\u6807\u7B7E\uFF08\u9694\u79BB\u5F02\u5E38\u65F6\u7684\u5206\u7C7B\u6765\u6E90\uFF09----\nexport const INVOKE_LABEL = {\n DISPATCH: 'dispatch',\n STATUS: 'status',\n ERROR_HANDLER: 'error handler',\n} as const;\n\n// ---- trace \u6A21\u5F0F\uFF08DataBusTraceMode\uFF09----\nexport const TRACE_MODE = {\n EVENTS: 'events',\n METRICS: 'metrics',\n ALL: 'all',\n} as const;\n\n// ---- trace \u9519\u8BEF\u6765\u6E90 ----\nexport const TRACE_ERROR_SOURCE = {\n TRANSPORT: 'transport',\n OPERATION: 'operation',\n} as const;\n\n// ---- reliability \u8BCA\u65AD\u64CD\u4F5C ----\nexport const RELIABILITY_OPERATION = {\n TRANSPORT_RECOVERY: 'transport_recovery',\n ROUTE_ACK: 'route_ack',\n ROUTE_MIGRATION: 'route_migration',\n // A re-election that recovered a stranded unconfirmed handoff (previous\n // owner gone, ACK never arrived). Distinct from ROUTE_MIGRATION so trace\n // consumers can tell recoveries apart from routine graceful handoffs.\n ROUTE_MIGRATION_RECOVERY: 'route_migration_recovery',\n PERSISTENCE_CLEANUP: 'persistence_cleanup',\n PERSISTENCE_RETRY: 'persistence_retry',\n DEDUP_SUPPRESSED: 'dedup_suppressed',\n} as const;\n\n// ---- \u81EA\u52A8\u6062\u590D\u5C1D\u8BD5\u7ED3\u679C ----\nexport const RECOVERY_OUTCOME = {\n SCHEDULED: 'scheduled',\n SUCCEEDED: 'succeeded',\n FAILED: 'failed',\n EXHAUSTED: 'exhausted',\n} as const;\n\n// ---- \u8BA2\u9605\u52A8\u4F5C ----\nexport const SUBSCRIPTION_ACTION = {\n SUBSCRIBE: 'subscribe',\n UNSUBSCRIBE: 'unsubscribe',\n} as const;\n\n// ---- \u56DE\u653E\u4FEE\u526A\u7B56\u7565 ----\nexport const PRUNE_STRATEGY = {\n COUNT: 'count',\n AGE: 'age',\n BOTH: 'both',\n} as const;\n\n// ---- \u6301\u4E45\u5316\u64CD\u4F5C ----\nexport const PERSISTENCE_OPERATION = {\n LOAD: 'load',\n APPEND: 'append',\n CLEAR: 'clear',\n CLEAR_TOPIC: 'clearTopic',\n CLEAR_BEFORE: 'clearBefore',\n} as const;\n\n// ---- \u6545\u969C\u6765\u6E90\uFF08DataBusFailureSource\uFF09----\nexport const FAILURE_SOURCE = {\n TRANSPORT: 'transport',\n PERSISTENCE: 'persistence',\n DISPATCH: 'dispatch',\n} as const;\n\n// ---- \u5065\u5EB7\u5224\u5B9A\u72B6\u6001\uFF08DataBusHealthSummary['state']\uFF09----\nexport const HEALTH_STATE = {\n STOPPED: 'stopped',\n STARTING: 'starting',\n HEALTHY: 'healthy',\n RECOVERING: 'recovering',\n SUSPENDED: 'suspended',\n DEGRADED: 'degraded',\n} as const;\n\n// ---- WebSocket wire \u534F\u8BAE\u64CD\u4F5C\u7801 ----\nexport const WS_OP = {\n SUBSCRIBE: 'subscribe',\n UNSUBSCRIBE: 'unsubscribe',\n PUBLISH: 'publish',\n PUBLISH_BATCH: 'publishBatch',\n} as const;\n\n// ---- Centrifuge Worker \u8F93\u5165\u6D88\u606F\u7C7B\u578B ----\nexport const CENTRIFUGE_INPUT_TYPE = {\n INIT: 'INIT',\n SUBSCRIBE: 'SUBSCRIBE',\n UNSUBSCRIBE: 'UNSUBSCRIBE',\n PUBLISH: 'PUBLISH',\n PUBLISH_BIN: 'PUBLISH_BIN',\n PING: 'PING',\n STOP: 'STOP',\n TOKEN_RESPONSE: 'TOKEN_RESPONSE',\n TOKEN_ERROR: 'TOKEN_ERROR',\n} as const;\n\n// ---- Centrifuge Worker \u8F93\u51FA\u6D88\u606F\u7C7B\u578B ----\nexport const CENTRIFUGE_OUTPUT_TYPE = {\n STATUS: 'STATUS',\n MESSAGE: 'MESSAGE',\n MESSAGE_BIN: 'MESSAGE_BIN',\n ERROR: 'ERROR',\n TOKEN_REQUEST: 'TOKEN_REQUEST',\n} as const;\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -172,6 +172,7 @@ export declare class CrossTabDataBus<TConfig = unknown, TData = unknown> {
172
172
  private started;
173
173
  private stopping;
174
174
  private transportReady;
175
+ private transportHasConnected;
175
176
  private lastError;
176
177
  private lastErrorAt;
177
178
  private lastFailure;
@@ -188,6 +189,11 @@ export declare class CrossTabDataBus<TConfig = unknown, TData = unknown> {
188
189
  private lastRecoveryAt;
189
190
  private recoveryAttempt;
190
191
  private recoveryExhausted;
192
+ private recoveryGate;
193
+ private recoveryGateRelease;
194
+ private recoveryTimer;
195
+ private recoveryTimerToken;
196
+ private recoveryDemandAllowed;
191
197
  /** Monotonic generation incremented on every successful transport open.
192
198
  * Stays in lockstep with `lastSuccessAt` so callers can detect that the
193
199
  * transport has been reopened even if the timestamp window is short. */
@@ -217,6 +223,16 @@ export declare class CrossTabDataBus<TConfig = unknown, TData = unknown> {
217
223
  private getQueuedStartReady;
218
224
  /** Queue exactly one fresh start after an in-flight explicit stop settles. */
219
225
  private queueStartAfterStop;
226
+ /** Release every operation waiting on the scheduled recovery attempt. */
227
+ private releaseRecoveryGate;
228
+ /** Cancel a pending automatic retry when an explicit lifecycle transition
229
+ * supersedes it. The released gate re-enters runTransport(), which then
230
+ * follows the newest start/stop/suspend intent. */
231
+ private cancelScheduledRecovery;
232
+ /** Keep the recovery gate closed after a failed attempt while allowing the
233
+ * next explicit transport operation to start an immediate on-demand reopen.
234
+ * If no gate/successor retry remains, release any waiters. */
235
+ private allowDemandRecovery;
220
236
  /** Reset failure and recovery diagnostics for a new explicit start session. */
221
237
  private resetFailureState;
222
238
  /**
@@ -230,7 +246,10 @@ export declare class CrossTabDataBus<TConfig = unknown, TData = unknown> {
230
246
  * Returns a rejected promise when the transport has failed and no start is in
231
247
  * flight — the caller can retry by calling start() or ready() again. While an
232
248
  * explicit stop() is settling, this rejects unless a restart is queued behind
233
- * it; false readiness during teardown is never reported.
249
+ * it; false readiness during teardown is never reported. While the tab is
250
+ * BFCache-suspended (pagehide without a following pageshow), this also
251
+ * rejects: the suspended start promise is the transport-stop gate, not a
252
+ * readiness signal.
234
253
  */
235
254
  ready(): Promise<void>;
236
255
  /**
@@ -277,11 +296,15 @@ export declare class CrossTabDataBus<TConfig = unknown, TData = unknown> {
277
296
  onError(handler: DataBusErrorHandler): () => void;
278
297
  /** Current transport connection status. */
279
298
  getStatus(): WorkerStatus;
280
- /** Return the current automatic transport recovery state. `hasError` means a transport error is currently retained. */
281
299
  /** Return the current automatic transport recovery state plus diagnostics.
282
- * `generation` increments on every successful transport open (initial start
283
- * and every recovery); `lastSuccessAt` is the timestamp of the most recent
284
- * successful open, or `null` until the transport reaches `ready`. */
300
+ * `hasError`/`errorMessage`/`errorAt` describe the most recent retained
301
+ * *transport* failure from a transport open or a runtime `onError`. They
302
+ * share the lifetime of the unified `lastFailure` ledger: a successful
303
+ * recovery keeps the last failure visible, and only an explicit `start()`
304
+ * clears it. `generation` increments on every successful transport open
305
+ * (initial start and every recovery); `lastSuccessAt` is the timestamp of
306
+ * the most recent successful open, or `null` until the transport reaches
307
+ * `ready`. */
285
308
  getRecoveryStats(): {
286
309
  attempt: number;
287
310
  exhausted: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"data-bus.d.ts","sourceRoot":"","sources":["../../src/core/data-bus.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"data-bus.d.ts","sourceRoot":"","sources":["../../src/core/data-bus.ts"],"names":[],"mappings":"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"}
package/dist/hooks.d.ts CHANGED
@@ -34,7 +34,8 @@ export declare function useCrossTabStatus<TConfig, TData>(bus: CrossTabDataBus<T
34
34
  * `getHealthSummary()` is a snapshot, not an event stream, so the hook polls
35
35
  * it on an interval (default 1000 ms) and refreshes immediately on status
36
36
  * changes and errors. Pass `intervalMs: 0` to rely on event-driven refreshes
37
- * only. Returns `null` while the bus has not been created yet.
37
+ * only; changing the interval replaces the timer without recreating the bus.
38
+ * Returns `null` while the bus has not been created yet.
38
39
  */
39
40
  export declare function useCrossTabHealth<TConfig, TData>(bus: CrossTabDataBus<TConfig, TData> | null, options?: {
40
41
  intervalMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAmBA,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,CAiBxC;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;;;;;;;GAOG;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,CAsB7B"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAmBA,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,CAiBxC;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
@@ -43,6 +43,7 @@ function useCrossTabStatus(bus) {
43
43
  }
44
44
  function useCrossTabHealth(bus, options) {
45
45
  const [health, setHealth] = useState(null);
46
+ const intervalMs = options?.intervalMs ?? 1e3;
46
47
  useEffect(() => {
47
48
  if (!bus) {
48
49
  setHealth(null);
@@ -52,14 +53,13 @@ function useCrossTabHealth(bus, options) {
52
53
  refresh();
53
54
  const unsubscribeStatus = bus.onStatus(refresh);
54
55
  const unsubscribeError = bus.onError(refresh);
55
- const intervalMs = options?.intervalMs ?? 1e3;
56
56
  const timer = intervalMs > 0 ? setInterval(refresh, intervalMs) : null;
57
57
  return () => {
58
58
  unsubscribeStatus();
59
59
  unsubscribeError();
60
60
  if (timer) clearInterval(timer);
61
61
  };
62
- }, [bus]);
62
+ }, [bus, intervalMs]);
63
63
  return health;
64
64
  }
65
65
  export {
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 exercises the\n * same stop/recreate path as BFCache suspend/resume.\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 const lifecycleGeneration = useRef(0);\n useEffect(() => {\n const generation = ++lifecycleGeneration.current;\n const instance = create();\n if (generation !== lifecycleGeneration.current) return;\n setBus(instance);\n void instance.ready().catch(() => {});\n return () => {\n if (generation === lifecycleGeneration.current) 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. 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 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 intervalMs = options?.intervalMs ?? 1_000;\n const timer = intervalMs > 0 ? setInterval(refresh, intervalMs) : null;\n return () => {\n unsubscribeStatus();\n unsubscribeError();\n if (timer) clearInterval(timer);\n };\n // The options object is intentionally not a dependency: callers pass an\n // inline literal and the interval only affects polling cadence.\n }, [bus]);\n return health;\n}\n"],
5
- "mappings": ";;;;;AAkBA,SAAS,WAAW,QAAQ,gBAAgB;AAiBrC,SAAS,mBACd,QACA,OAAuB,CAAC,GACgB;AACxC,QAAM,CAAC,KAAK,MAAM,IAAI,SAAiD,IAAI;AAC3E,QAAM,sBAAsB,OAAO,CAAC;AACpC,YAAU,MAAM;AACd,UAAM,aAAa,EAAE,oBAAoB;AACzC,UAAM,WAAW,OAAO;AACxB,QAAI,eAAe,oBAAoB,QAAS;AAChD,WAAO,QAAQ;AACf,SAAK,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACpC,WAAO,MAAM;AACX,UAAI,eAAe,oBAAoB,QAAS,QAAO,IAAI;AAC3D,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;AAUO,SAAS,kBACd,KACA,SAC6B;AAC7B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,IAAI;AACtE,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,aAAa,SAAS,cAAc;AAC1C,UAAM,QAAQ,aAAa,IAAI,YAAY,SAAS,UAAU,IAAI;AAClE,WAAO,MAAM;AACX,wBAAkB;AAClB,uBAAiB;AACjB,UAAI,MAAO,eAAc,KAAK;AAAA,IAChC;AAAA,EAGF,GAAG,CAAC,GAAG,CAAC;AACR,SAAO;AACT;",
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 exercises the\n * same stop/recreate path as BFCache suspend/resume.\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 const lifecycleGeneration = useRef(0);\n useEffect(() => {\n const generation = ++lifecycleGeneration.current;\n const instance = create();\n if (generation !== lifecycleGeneration.current) return;\n setBus(instance);\n void instance.ready().catch(() => {});\n return () => {\n if (generation === lifecycleGeneration.current) 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": ";;;;;AAkBA,SAAS,WAAW,QAAQ,gBAAgB;AAiBrC,SAAS,mBACd,QACA,OAAuB,CAAC,GACgB;AACxC,QAAM,CAAC,KAAK,MAAM,IAAI,SAAiD,IAAI;AAC3E,QAAM,sBAAsB,OAAO,CAAC;AACpC,YAAU,MAAM;AACd,UAAM,aAAa,EAAE,oBAAoB;AACzC,UAAM,WAAW,OAAO;AACxB,QAAI,eAAe,oBAAoB,QAAS;AAChD,WAAO,QAAQ;AACf,SAAK,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACpC,WAAO,MAAM;AACX,UAAI,eAAe,oBAAoB,QAAS,QAAO,IAAI;AAC3D,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.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  selectRebalanceTarget,
21
21
  selectWorkerBackend,
22
22
  topicMatchesPattern
23
- } from "./chunk-SDOV3UHG.js";
23
+ } from "./chunk-ZNHJ5OMY.js";
24
24
  import {
25
25
  DEFAULT_STORAGE_PREFIX,
26
26
  PRUNE_STRATEGY,
@@ -307,6 +307,7 @@ function createIndexedDbReplayPersistence(options) {
307
307
  }
308
308
 
309
309
  // src/websocket.ts
310
+ var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
310
311
  var WS_OPEN = 1;
311
312
  var WebSocketTransport = class {
312
313
  constructor(connection) {
@@ -316,12 +317,27 @@ var WebSocketTransport = class {
316
317
  diagnosticsName = "websocket";
317
318
  diagnosticsBackend = "native-websocket";
318
319
  socket = null;
320
+ socketActive = false;
319
321
  handlers = null;
320
322
  subscribedTopics = /* @__PURE__ */ new Set();
321
- /** Open the WebSocket and wire lifecycle listeners. A factory failure is
322
- * reported through `onStatus('error')` so the DataBus can recover. */
323
+ // Handshake gate for the current start(). Resolves once the socket opens,
324
+ // rejects when the attempt fails, so the DataBus start Promise — and every
325
+ // operation parked behind it — settles at the real connection boundary.
326
+ connectPromise = null;
327
+ connectResolve = null;
328
+ connectReject = null;
329
+ connectTimer = null;
330
+ /** Open the WebSocket and wire lifecycle listeners. Resolves once the
331
+ * handshake completes and rejects when the attempt fails, matching the
332
+ * `DataBusTransport.start` contract ("resolves on connect or rejects on
333
+ * failure"). A factory failure is reported through `onStatus('error')` so
334
+ * the DataBus can recover. */
323
335
  start(config, handlers) {
324
- if (this.socket) return;
336
+ if (this.socket && this.socketActive) {
337
+ return this.connectPromise ?? void 0;
338
+ }
339
+ this.socket = null;
340
+ this.socketActive = false;
325
341
  this.handlers = handlers;
326
342
  const factory = config.webSocketFactory ?? this.connection.webSocketFactory ?? defaultWebSocketFactory;
327
343
  const protocols = config.protocols ?? this.connection.protocols;
@@ -333,23 +349,66 @@ var WebSocketTransport = class {
333
349
  handlers.onError(error);
334
350
  return;
335
351
  }
336
- socket.onopen = () => {
337
- if (this.socket !== socket || this.handlers !== handlers) return;
338
- for (const topic of this.subscribedTopics) {
339
- this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
352
+ const opening = new Promise((resolve, reject) => {
353
+ this.connectResolve = resolve;
354
+ this.connectReject = reject;
355
+ let handshakeCompleted = false;
356
+ let handshakeFailed = false;
357
+ const timeoutMs = config.connectTimeoutMs ?? this.connection.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
358
+ if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
359
+ this.connectTimer = setTimeout(() => {
360
+ if (this.socket !== socket || this.handlers !== handlers || handshakeCompleted) return;
361
+ handshakeFailed = true;
362
+ this.connectTimer = null;
363
+ this.socketActive = false;
364
+ const error = new Error(`WebSocket did not open within ${timeoutMs}ms.`);
365
+ handlers.onStatus(WORKER_STATUS.ERROR);
366
+ handlers.onError(error);
367
+ this.failConnect(error);
368
+ socket.close();
369
+ }, timeoutMs);
340
370
  }
341
- handlers.onStatus(WORKER_STATUS.CONNECTED);
342
- };
343
- socket.onclose = () => {
344
- if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.DISCONNECTED);
345
- };
346
- socket.onerror = () => {
347
- if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.ERROR);
348
- };
349
- socket.onmessage = (event) => {
350
- if (this.socket === socket && this.handlers === handlers) void this.handleMessage(event.data);
351
- };
352
- this.socket = socket;
371
+ socket.onopen = () => {
372
+ if (this.socket !== socket || this.handlers !== handlers || handshakeFailed) return;
373
+ this.socketActive = true;
374
+ this.clearConnectTimer();
375
+ for (const topic of this.subscribedTopics) {
376
+ this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
377
+ }
378
+ handlers.onStatus(WORKER_STATUS.CONNECTED);
379
+ if (!handshakeCompleted) {
380
+ handshakeCompleted = true;
381
+ this.settleConnect();
382
+ }
383
+ };
384
+ socket.onclose = () => {
385
+ if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;
386
+ this.socketActive = false;
387
+ handlers.onStatus(WORKER_STATUS.DISCONNECTED);
388
+ if (!handshakeCompleted) {
389
+ handshakeFailed = true;
390
+ this.failConnect(new Error("WebSocket closed before the handshake completed."));
391
+ }
392
+ };
393
+ socket.onerror = () => {
394
+ if (this.socket !== socket || this.handlers !== handlers || !this.socketActive) return;
395
+ this.socketActive = false;
396
+ handlers.onStatus(WORKER_STATUS.ERROR);
397
+ if (!handshakeCompleted) {
398
+ handshakeFailed = true;
399
+ this.failConnect(new Error("WebSocket failed to open."));
400
+ }
401
+ };
402
+ socket.onmessage = (event) => {
403
+ if (this.socket === socket && this.handlers === handlers && this.socketActive) {
404
+ void this.handleMessage(event.data);
405
+ }
406
+ };
407
+ this.socket = socket;
408
+ this.socketActive = true;
409
+ });
410
+ this.connectPromise = opening;
411
+ return opening;
353
412
  }
354
413
  /** Idempotent: re-subscribing an active topic re-sends the frame but does
355
414
  * not duplicate the local tracking entry. */
@@ -405,10 +464,38 @@ var WebSocketTransport = class {
405
464
  /** Close the socket and drop all state. Safe to call multiple times. */
406
465
  stop() {
407
466
  const socket = this.socket;
467
+ const shouldClose = this.socketActive;
408
468
  this.socket = null;
469
+ this.socketActive = false;
409
470
  this.handlers = null;
410
471
  this.subscribedTopics.clear();
411
- socket?.close();
472
+ this.settleConnect();
473
+ this.connectPromise = null;
474
+ if (shouldClose) socket?.close();
475
+ }
476
+ /** Resolve the in-flight handshake gate. Idempotent: once the socket has
477
+ * opened (or a newer attempt replaced it) later calls are no-ops. */
478
+ settleConnect() {
479
+ this.clearConnectTimer();
480
+ const resolve = this.connectResolve;
481
+ this.connectResolve = null;
482
+ this.connectReject = null;
483
+ resolve?.();
484
+ }
485
+ /** Reject the in-flight handshake gate. Idempotent on the same terms as
486
+ * {@link settleConnect}. */
487
+ failConnect(error) {
488
+ this.clearConnectTimer();
489
+ const reject = this.connectReject;
490
+ this.connectResolve = null;
491
+ this.connectReject = null;
492
+ reject?.(error);
493
+ }
494
+ clearConnectTimer() {
495
+ if (this.connectTimer !== null) {
496
+ clearTimeout(this.connectTimer);
497
+ this.connectTimer = null;
498
+ }
412
499
  }
413
500
  /** Send one JSON frame. Frames are dropped with an `onError` report when
414
501
  * the socket is not open — subscribe frames are re-sent on open, so the