cross-tab-worker-databus 0.21.12 → 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.
Files changed (71) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/centrifuge-protocol.d.ts.map +1 -1
  3. package/dist/centrifuge-session.d.ts +10 -3
  4. package/dist/centrifuge-session.d.ts.map +1 -1
  5. package/dist/centrifuge.d.ts +10 -4
  6. package/dist/centrifuge.d.ts.map +1 -1
  7. package/dist/centrifuge.js +23 -11
  8. package/dist/centrifuge.js.map +2 -2
  9. package/dist/centrifuge.shared.worker.js +28 -11
  10. package/dist/centrifuge.shared.worker.js.map +2 -2
  11. package/dist/centrifuge.worker.js +10 -3
  12. package/dist/centrifuge.worker.js.map +2 -2
  13. package/dist/{chunk-TSTV3SDC.js → chunk-4HW6PYIR.js} +67 -33
  14. package/dist/chunk-4HW6PYIR.js.map +7 -0
  15. package/dist/{chunk-2TTJ7LGZ.js → chunk-NPFTD7YE.js} +1 -1
  16. package/dist/chunk-NPFTD7YE.js.map +7 -0
  17. package/dist/cjs/centrifuge.cjs +86 -40
  18. package/dist/cjs/centrifuge.cjs.map +2 -2
  19. package/dist/cjs/hooks.cjs.map +2 -2
  20. package/dist/cjs/index.cjs +80 -36
  21. package/dist/cjs/index.cjs.map +2 -2
  22. package/dist/cjs/vue.cjs.map +2 -2
  23. package/dist/core/cluster.d.ts +33 -16
  24. package/dist/core/cluster.d.ts.map +1 -1
  25. package/dist/core/data-bus.d.ts +19 -5
  26. package/dist/core/data-bus.d.ts.map +1 -1
  27. package/dist/core/dedup-manager.d.ts +6 -3
  28. package/dist/core/dedup-manager.d.ts.map +1 -1
  29. package/dist/core/environment.d.ts +14 -5
  30. package/dist/core/environment.d.ts.map +1 -1
  31. package/dist/core/hash.d.ts +10 -4
  32. package/dist/core/hash.d.ts.map +1 -1
  33. package/dist/core/replay-manager.d.ts +2 -1
  34. package/dist/core/replay-manager.d.ts.map +1 -1
  35. package/dist/core/routing.d.ts +18 -9
  36. package/dist/core/routing.d.ts.map +1 -1
  37. package/dist/core/storage-batch.d.ts +2 -2
  38. package/dist/core/storage-batch.d.ts.map +1 -1
  39. package/dist/core/trace.d.ts +21 -9
  40. package/dist/core/trace.d.ts.map +1 -1
  41. package/dist/core/types.d.ts +13 -5
  42. package/dist/core/types.d.ts.map +1 -1
  43. package/dist/hooks.d.ts.map +1 -1
  44. package/dist/hooks.js +1 -1
  45. package/dist/hooks.js.map +2 -2
  46. package/dist/index.d.ts +2 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +17 -7
  49. package/dist/index.js.map +2 -2
  50. package/dist/utils/constants.d.ts +7 -4
  51. package/dist/utils/constants.d.ts.map +1 -1
  52. package/dist/utils/validation.d.ts +12 -4
  53. package/dist/utils/validation.d.ts.map +1 -1
  54. package/dist/vue.js +1 -1
  55. package/dist/websocket.d.ts +24 -6
  56. package/dist/websocket.d.ts.map +1 -1
  57. package/dist/workers/port-reaper.d.ts +18 -8
  58. package/dist/workers/port-reaper.d.ts.map +1 -1
  59. package/docs/api.md +6 -5
  60. package/docs/benchmarks.md +9 -9
  61. package/docs/release-checklist.md +3 -3
  62. package/docs/roadmap.md +31 -1
  63. package/docs/transports.md +3 -2
  64. package/docs/zh/api.md +6 -5
  65. package/docs/zh/benchmarks.md +9 -9
  66. package/docs/zh/release-checklist.md +3 -3
  67. package/docs/zh/roadmap.md +22 -1
  68. package/docs/zh/transports.md +2 -1
  69. package/package.json +2 -1
  70. package/dist/chunk-2TTJ7LGZ.js.map +0 -7
  71. package/dist/chunk-TSTV3SDC.js.map +0 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hooks.ts", "../../src/utils/constants.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 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", "/**\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 SESSION_REAPED: 'SESSION_REAPED',\n} as const;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBA,mBAA4C;;;ACOrC,IAAM,gBAAgB;AAAA,EAC3B,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,OAAO;AACT;AAwDO,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB,GAAG,sBAAsB;AACxD,IAAM,qBAAqB,GAAG,sBAAsB;;;ADrDpD,SAAS,mBACd,QACA,OAAuB,CAAC,GACgB;AACxC,QAAM,CAAC,KAAK,MAAM,QAAI,uBAAiD,IAAI;AAC3E,8BAAU,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,iBAAa,qBAAO,OAAO;AACjC,aAAW,UAAU;AACrB,8BAAU,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,QAAI,uBAAuB,cAAc,UAAU;AAC3E,8BAAU,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,QAAI,uBAAsC,IAAI;AACtE,QAAM,aAAa,SAAS,cAAc;AAC1C,8BAAU,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;",
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", "/**\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\u5373\u53EA\u51FA\u73B0\u5728 `typeof X[keyof typeof X]` \u8FD9\u7C7B\u6D3E\u751F\u91CC\u3001\n * \u4ECE\u4E0D\u53C2\u4E0E\u8FD0\u884C\u65F6\u6BD4\u8F83\u7684\uFF09\u5728\u8C03\u7528\u65B9\u7528 `import type`\uFF1B**\u5728\u8FD0\u884C\u65F6\u6BD4\u8F83/\u6784\u9020\u4E2D\u4F7F\u7528\u7684**\n * \uFF08\u5982 `WORKER_STATUS.ERROR`\u3001`TRACE_EVENT_TYPE.LIFECYCLE`\u3001\n * `PERSISTENCE_OPERATION.APPEND`\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`PERSISTENCE_OPERATION`\n * \u5C5E\u4E8E\u540E\u8005\u800C\u4E0D\u662F\u7C7B\u578B\u4FA7\u7684\u4F8B\u5B50\uFF1A`replay-manager.ts` \u4EE5\u503C\u5BFC\u5165\u5B83\uFF0C\u5E76\u628A\u5B83\u7684\u6210\u5458\u4F5C\u4E3A\n * `withPersistenceRetry` \u7684\u7B2C\u4E00\u4E2A\u5B9E\u53C2\u4F20\u8FDB\u53BB\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 SESSION_REAPED: 'SESSION_REAPED',\n} as const;\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,mBAA4C;;;ACMrC,IAAM,gBAAgB;AAAA,EAC3B,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,OAAO;AACT;AAwDO,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB,GAAG,sBAAsB;AACxD,IAAM,qBAAqB,GAAG,sBAAsB;;;ADpDpD,SAAS,mBACd,QACA,OAAuB,CAAC,GACgB;AACxC,QAAM,CAAC,KAAK,MAAM,QAAI,uBAAiD,IAAI;AAC3E,8BAAU,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,iBAAa,qBAAO,OAAO;AACjC,aAAW,UAAU;AACrB,8BAAU,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,QAAI,uBAAuB,cAAc,UAAU;AAC3E,8BAAU,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,QAAI,uBAAsC,IAAI;AACtE,QAAM,aAAa,SAAS,cAAc;AAC1C,8BAAU,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
  }
@@ -459,8 +459,8 @@ var BatchingStorageWriter = class {
459
459
  retryHandle = null;
460
460
  retryDelayMs = INITIAL_RETRY_DELAY_MS;
461
461
  /** Number of writes queued in memory but not yet flushed to storage.
462
- * Used by tests to assert the coalescing window and by flush() to detect
463
- * the all-drained state. */
462
+ * Test-only as a reader: `flush()` tests `pending.size` directly rather than
463
+ * going through this getter, so it is not part of the drain decision. */
464
464
  get pendingSize() {
465
465
  return this.pending.size;
466
466
  }
@@ -749,8 +749,9 @@ var WorkerClusterRuntime = class {
749
749
  // Topics this tab has subscribed to (local interest, plaintext).
750
750
  subscribedTopics = /* @__PURE__ */ new Set();
751
751
  // Topics assigned to this Worker as owner (topicKey → topic). Authoritative:
752
- // membership drives isAssigned() and load. Grows only via CONTROL/SUBSCRIBE
753
- // (or local self-subscribe), never via the reverse cache.
752
+ // membership drives isAssigned() and load. Grown by exactly three writers —
753
+ // CONTROL/SUBSCRIBE, ROUTE_RELEASED (completing a handoff this worker accepted),
754
+ // and local self-subscribe — and never by the reverse cache.
754
755
  assignedTopics = /* @__PURE__ */ new Map();
755
756
  routeOwnerCache = /* @__PURE__ */ new Map();
756
757
  routeOwnerCacheMax;
@@ -1144,9 +1145,12 @@ var WorkerClusterRuntime = class {
1144
1145
  windowMs,
1145
1146
  messageCount: this.throughputWindow.messageCount,
1146
1147
  byteCount: this.throughputWindow.byteCount,
1147
- // How much later the heartbeat landed than its nominal interval. This
1148
- // window is anchored at the previous writeRecord (one heartbeat tick),
1149
- // so a starved event loop stretches windowMs past the interval and the
1148
+ // How much later the last record write landed relative to a nominal
1149
+ // heartbeat interval. The window is anchored at the previous writeRecord,
1150
+ // which the heartbeat tick causes but activation, a status change, a
1151
+ // visibility change and a load update also cause — so a worker writing on
1152
+ // any of those shorter paths yields a small window and a clamped zero.
1153
+ // A starved event loop stretches the window past the interval, and the
1150
1154
  // positive excess is the scheduling-overrun signal.
1151
1155
  overrunMs: Math.max(0, windowMs - this.heartbeatIntervalMs),
1152
1156
  sampledAt: now
@@ -1311,11 +1315,13 @@ var WorkerClusterRuntime = class {
1311
1315
  else this.handlers.onControl(message.action, message.topic, message.data);
1312
1316
  if (message.action !== CONTROL_ACTION.PUBLISH) this.updateLoad();
1313
1317
  }
1314
- /**
1315
- * When this worker is the previous owner in a graceful handoff and the new
1316
- * owner asks us to unsubscribe, release the old transport subscription and
1317
- * ACK the handoff with ROUTE_RELEASED. Returns true when the message was a
1318
- * handoff release (the generic CONTROL dispatch must not run as well).
1318
+ /** Drop this worker's ownership of the topic, then — only when this worker is
1319
+ * the previous owner in a graceful handoff and the new owner is asking us to
1320
+ * unsubscribe — release the old transport subscription and ACK the handoff with
1321
+ * ROUTE_RELEASED. The ownership delete runs for *every* CONTROL/UNSUBSCRIBE,
1322
+ * before the handoff test; the handoff legs below do not gate it. Returns true
1323
+ * when the message was a handoff release (the generic CONTROL dispatch must not
1324
+ * run as well).
1319
1325
  */
1320
1326
  releaseHandoffOnUnsubscribe(message) {
1321
1327
  this.assignedTopics.delete(message.topicKey);
@@ -1537,7 +1543,10 @@ var WorkerClusterRuntime = class {
1537
1543
  }
1538
1544
  return Array.from(subscribers);
1539
1545
  }
1540
- /** Read the current route for `topicKey`, returning null when no storage layer exists. */
1546
+ /** Read the current route for `topicKey`. With a storage layer this returns
1547
+ * the durable route, or null when none is recorded. Without one (degraded
1548
+ * mode) it returns the synthesized local route from `buildLocalRoute`, which
1549
+ * is itself null unless this worker actually holds the topic. */
1541
1550
  readRoute(topicKey) {
1542
1551
  if (!this.storage) return this.buildLocalRoute(topicKey);
1543
1552
  return readJson(this.storage, this.routeStorageKey(topicKey));
@@ -1618,8 +1627,9 @@ var WorkerClusterRuntime = class {
1618
1627
  * @param notify — when true, broadcast a REGISTRY nudge so peers reconcile
1619
1628
  * immediately instead of waiting for the next heartbeat. False on the
1620
1629
  * periodic heartbeat tick (peers will notice on their own heartbeat) to
1621
- * avoid a REGISTRY storm every 3 s; true on status/role changes that
1622
- * peers should observe promptly. */
1630
+ * avoid a REGISTRY storm every 3 s. Which sites pass what: activation,
1631
+ * status, visibility and load changes pass true; the heartbeat tick and a
1632
+ * role change pass false. */
1623
1633
  writeRecord(notify) {
1624
1634
  const now = this.environment.now();
1625
1635
  const sample = this.loadWeighting !== void 0 ? this.sampleThroughput(now) : void 0;
@@ -1655,10 +1665,15 @@ var WorkerClusterRuntime = class {
1655
1665
  * Despite the name, this is NOT a cache lookup — it unconditionally writes
1656
1666
  * the `topicKey → topic` pair. Hashing is cheap enough that a caller needing
1657
1667
  * the key should always call this rather than check `knownTopics` first;
1658
- * the cache's FIFO eviction below keeps it bounded. Only `isAssigned`
1659
- * deliberately bypasses this (it must not pollute the cache on a read-only
1660
- * query), so if you add a new call site, prefer `rememberTopic` unless you
1661
- * have the same "read-only query" reason.
1668
+ * the cache's FIFO eviction below keeps it bounded. Two sites deliberately
1669
+ * bypass it, and for the same kind of reason — neither may write a plaintext
1670
+ * into the reverse cache before it is authorized: `isAssigned` re-derives the
1671
+ * key as a read-only query, and the `ROUTE_RELEASED` handler hashes the
1672
+ * frame's own `topic` only to compare it against `topicKey`, on a channel
1673
+ * where that field is attacker-controlled until the comparison passes. (The
1674
+ * `CONTROL` handler performs the same re-derivation but then *does* call this
1675
+ * method, so it is not a bypass.) If you add a new call site, prefer
1676
+ * `rememberTopic` unless you have one of those reasons.
1662
1677
  */
1663
1678
  rememberTopic(topic) {
1664
1679
  const topicKey = createOpaqueKey(topic);
@@ -1732,8 +1747,10 @@ var DataBusTraceReporter = class {
1732
1747
  this.asyncSink = options?.asyncSink ?? false;
1733
1748
  this.now = now;
1734
1749
  }
1735
- /** Start the periodic metrics flush interval. No-op when mode is 'events'
1736
- * (no metrics to emit), when disabled, or when already running. */
1750
+ /** Start the periodic metrics flush interval. Only the `!enabled` return is a
1751
+ * true no-op: in 'events' mode, or with an interval already armed, it returns
1752
+ * without arming anything but has already cleared `stopped` one statement
1753
+ * earlier — which is what re-opens the event stream after a `stop()`. */
1737
1754
  start() {
1738
1755
  if (!this.enabled) return;
1739
1756
  this.stopped = false;
@@ -1840,9 +1857,11 @@ var DataBusTraceReporter = class {
1840
1857
  recordDedupSuppressed() {
1841
1858
  if (this.metricsActive) this.dedupSuppressed += 1;
1842
1859
  }
1843
- /** True when metrics recording is active: enabled and mode includes metrics.
1844
- * Extracted so the four record / flush methods share one guard expression
1845
- * instead of repeating `!this.enabled || this.mode === 'events'` at each. */
1860
+ /** True when metrics recording is active: enabled, not stopped, and the mode
1861
+ * includes metrics. Extracted so the seven readers — `getMetrics`, the five
1862
+ * `record*` methods and `flush` — share one guard instead of repeating it.
1863
+ * The `!stopped` term is part of the contract, not a bonus: `event()` does not
1864
+ * go through this getter and gates on `stopped` itself. */
1846
1865
  get metricsActive() {
1847
1866
  return this.enabled && !this.stopped && this.mode !== TRACE_MODE.EVENTS;
1848
1867
  }
@@ -2106,7 +2125,8 @@ var ReplayManager = class {
2106
2125
  }
2107
2126
  }
2108
2127
  }
2109
- /** Clear replay history for one exact topic, including durable storage. */
2128
+ /** Clear replay history for one exact topic, and for its durable history when
2129
+ * the injected adapter provides the optional `clearTopic`. */
2110
2130
  async clearTopic(topic) {
2111
2131
  if (!this.buffers) return;
2112
2132
  const clearing = this.persistence?.clearTopic ? this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence.clearTopic(topic)) : null;
@@ -2485,7 +2505,7 @@ var DedupManager = class {
2485
2505
  };
2486
2506
 
2487
2507
  // src/core/version.ts
2488
- var SDK_VERSION = true ? "0.21.12" : "";
2508
+ var SDK_VERSION = true ? "0.21.13" : "";
2489
2509
 
2490
2510
  // src/core/data-bus.ts
2491
2511
  var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
@@ -3016,7 +3036,10 @@ var CrossTabDataBus = class {
3016
3036
  async clearReplay() {
3017
3037
  await this.replayManager.clearAll();
3018
3038
  }
3019
- /** Clear replay history for one exact topic, including durable storage. */
3039
+ /** Clear replay history for one exact topic and, when the persistence adapter
3040
+ * implements `clearTopic`, its durable history too — an adapter without that
3041
+ * optional method leaves the persisted rows in place, exactly as `clearReplay`
3042
+ * does. */
3020
3043
  async clearReplayTopic(topic) {
3021
3044
  await this.replayManager.clearTopic(topic);
3022
3045
  }
@@ -3411,9 +3434,14 @@ var CrossTabDataBus = class {
3411
3434
  });
3412
3435
  }
3413
3436
  /** Emit the coordination trace snapshot from the current cluster state.
3414
- * Called after a transport opens (start and recovery), when the role and
3415
- * route picture has settled — the synchronous pre-open snapshot would see no
3416
- * routes because their writes are still coalesced in the batch writer. */
3437
+ * Called once per successful `start()`: `reopenTransport()`'s settle arms do not
3438
+ * emit, so a recovery produces no second snapshot. It runs after the opening
3439
+ * resolves — and not because a pre-open read would see no routes, which is the
3440
+ * wrong mechanism and was the shipped claim until it was measured: a snapshot
3441
+ * taken with the opening still unresolved already listed the route, its
3442
+ * confirmation and both tabs' worker records, because
3443
+ * `BatchingStorageWriter` serves pending writes on read (`storage-batch.ts:79-82`
3444
+ * for `getItem`, `:143-155` for the `keys()` union that `readAllByPrefix` uses). */
3417
3445
  emitCoordinationTrace() {
3418
3446
  const snapshot = this.cluster.getSnapshot();
3419
3447
  this.trace.event({
@@ -3607,7 +3635,13 @@ var CrossTabDataBus = class {
3607
3635
  }
3608
3636
  /**
3609
3637
  * Ensure the DataBus is started, throwing if no initialConfig was provided.
3610
- * Called automatically by subscribe/publish/ready when autoStart is true.
3638
+ * subscribe/publish/ready call this *unconditionally*: the gate is the
3639
+ * `hasInitialConfig` check below, not the `autoStart` option. `autoStart`
3640
+ * participates in exactly one place, the constructor's
3641
+ * `if (autoStart ?? this.hasInitialConfig)`, where it overrides the default rather
3642
+ * than enabling a later path — so `autoStart: true` with no `initialConfig` throws
3643
+ * from the constructor. `docs/configuration.md`'s "`true` when `initialConfig` is
3644
+ * provided" row describes that single site.
3611
3645
  */
3612
3646
  ensureStarted() {
3613
3647
  if (this.started) return;
@@ -4038,7 +4072,8 @@ var WebSocketTransport = class {
4038
4072
  this.subscribedTopics.add(topic);
4039
4073
  this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
4040
4074
  }
4041
- /** Idempotent: unsubscribing an unknown topic is a no-op. */
4075
+ /** Idempotent for local tracking: an unknown topic has nothing to delete, but
4076
+ * the UNSUBSCRIBE frame is still sent, exactly as SUBSCRIBE is re-sent above. */
4042
4077
  unsubscribe(topic) {
4043
4078
  this.subscribedTopics.delete(topic);
4044
4079
  this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });
@@ -4128,8 +4163,14 @@ var WebSocketTransport = class {
4128
4163
  }
4129
4164
  }
4130
4165
  /** Send one JSON frame. Frames are dropped with an `onError` report when
4131
- * the socket is not open — subscribe frames are re-sent on open, so the
4132
- * only real loss is a publish during a disconnect window. */
4166
+ * the socket is not open. A dropped SUBSCRIBE is recovered — `onopen`
4167
+ * re-asserts every topic in `subscribedTopics` — but the other two are not:
4168
+ * a dropped PUBLISH is the documented at-most-once loss, and a dropped
4169
+ * UNSUBSCRIBE is the quieter one, because `unsubscribe()` has already deleted
4170
+ * the topic from that set, so no later re-assert resends it and the server
4171
+ * keeps streaming a channel the app left. Through the DataBus neither of the
4172
+ * two is reachable while closed — `runTransport()` queues both operations
4173
+ * behind recovery — so this reads as a direct-transport-contract note. */
4133
4174
  sendFrame(payload) {
4134
4175
  if (this.socket?.readyState !== WS_OPEN) {
4135
4176
  this.handlers?.onError(new Error(`WebSocket is not open; dropped "${payload.op}" frame.`));
@@ -4165,8 +4206,11 @@ var WebSocketTransport = class {
4165
4206
  this.socket.send(frame.buffer);
4166
4207
  }
4167
4208
  /** Parse a server frame. Only objects carrying a string `topic` are
4168
- * publications; malformed JSON and unknown shapes are ignored so a chatty
4169
- * server cannot crash the message path. */
4209
+ * publications, and unknown shapes are dropped silently so a chatty server
4210
+ * cannot crash the message path. Malformed JSON is *not* dropped silently: it
4211
+ * goes to `handlers.onError` as "WebSocket server sent a non-JSON frame.",
4212
+ * which also records it in the bus's last-failure diagnostics. Neither case
4213
+ * throws out of the socket listener. */
4170
4214
  async handleMessage(raw) {
4171
4215
  if (typeof Blob !== "undefined" && raw instanceof Blob) {
4172
4216
  const socket = this.socket;