cross-tab-worker-databus 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -0
  3. package/README.zh.md +1 -0
  4. package/dist/centrifuge.js +1 -2
  5. package/dist/centrifuge.js.map +1 -1
  6. package/dist/{chunk-ZGQRELIV.js → chunk-YUSQSNCX.js} +41 -2
  7. package/dist/chunk-YUSQSNCX.js.map +7 -0
  8. package/dist/cjs/centrifuge.cjs +40 -1
  9. package/dist/cjs/centrifuge.cjs.map +2 -2
  10. package/dist/cjs/hooks.cjs +1 -1913
  11. package/dist/cjs/hooks.cjs.map +4 -4
  12. package/dist/cjs/index.cjs +120 -1
  13. package/dist/cjs/index.cjs.map +3 -3
  14. package/dist/cjs/vue.cjs +93 -0
  15. package/dist/cjs/vue.cjs.map +7 -0
  16. package/dist/core/cluster.d.ts.map +1 -1
  17. package/dist/core/data-bus.d.ts +12 -5
  18. package/dist/core/data-bus.d.ts.map +1 -1
  19. package/dist/core/replay-persistence.d.ts +13 -0
  20. package/dist/core/replay-persistence.d.ts.map +1 -0
  21. package/dist/hooks.js +7 -1911
  22. package/dist/hooks.js.map +4 -4
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +81 -2
  26. package/dist/index.js.map +3 -3
  27. package/dist/vue.d.ts +10 -0
  28. package/dist/vue.d.ts.map +1 -0
  29. package/dist/vue.js +72 -0
  30. package/dist/vue.js.map +7 -0
  31. package/dist/websocket.d.ts +2 -1
  32. package/dist/websocket.d.ts.map +1 -1
  33. package/docs/README.md +2 -1
  34. package/docs/api.md +19 -1
  35. package/docs/roadmap.md +18 -0
  36. package/docs/transports.md +4 -0
  37. package/docs/zh/README.md +2 -1
  38. package/docs/zh/api.md +19 -1
  39. package/docs/zh/roadmap.md +18 -0
  40. package/docs/zh/transports.md +4 -0
  41. package/package.json +14 -3
  42. package/dist/chunk-5WRI5ZAA.js +0 -31
  43. package/dist/chunk-5WRI5ZAA.js.map +0 -7
  44. package/dist/chunk-ZGQRELIV.js.map +0 -7
package/dist/vue.js ADDED
@@ -0,0 +1,72 @@
1
+ // src/vue.ts
2
+ import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
3
+ function useCrossTabDataBus(create, deps = []) {
4
+ const bus = shallowRef(null);
5
+ let instance = null;
6
+ const stop = async () => {
7
+ const current = instance;
8
+ instance = null;
9
+ bus.value = null;
10
+ if (current) await current.stop();
11
+ };
12
+ const start = () => {
13
+ void stop().then(() => {
14
+ const next = create();
15
+ instance = next;
16
+ bus.value = next;
17
+ void next.ready().catch(() => {
18
+ });
19
+ });
20
+ };
21
+ onMounted(start);
22
+ onBeforeUnmount(() => {
23
+ void stop();
24
+ });
25
+ if (deps.length > 0) watch(deps, start);
26
+ return bus;
27
+ }
28
+ function useCrossTabSubscription(bus, topic, handler) {
29
+ let currentBus = null;
30
+ let cleanup;
31
+ let latestHandler = handler;
32
+ const stop = () => {
33
+ cleanup?.();
34
+ cleanup = void 0;
35
+ currentBus = null;
36
+ };
37
+ const sync = () => {
38
+ const nextBus = bus.value;
39
+ const nextTopic = typeof topic === "string" ? topic : topic.value;
40
+ if (nextBus === currentBus && cleanup) return;
41
+ stop();
42
+ if (!nextBus) return;
43
+ currentBus = nextBus;
44
+ cleanup = nextBus.subscribe(nextTopic, (message) => latestHandler(message));
45
+ };
46
+ watch(bus, sync, { immediate: true });
47
+ if (typeof topic !== "string") watch(topic, sync);
48
+ watch(() => handler, (value) => {
49
+ latestHandler = value;
50
+ });
51
+ onBeforeUnmount(stop);
52
+ }
53
+ function useCrossTabStatus(bus) {
54
+ const status = ref("connecting");
55
+ let cleanup;
56
+ watch(bus, (next) => {
57
+ cleanup?.();
58
+ cleanup = void 0;
59
+ status.value = next?.getStatus() ?? "connecting";
60
+ if (next) cleanup = next.onStatus((value) => {
61
+ status.value = value;
62
+ });
63
+ }, { immediate: true });
64
+ onBeforeUnmount(() => cleanup?.());
65
+ return status;
66
+ }
67
+ export {
68
+ useCrossTabDataBus,
69
+ useCrossTabStatus,
70
+ useCrossTabSubscription
71
+ };
72
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/vue.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 } from './core/data-bus';\nimport type { DataBusMessage, WorkerStatus } from './core/types';\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 const stop = async () => { const current = instance; instance = null; bus.value = null; if (current) await current.stop(); };\n const start = () => { void stop().then(() => { const next = create(); instance = next; bus.value = next; void next.ready().catch(() => {}); }); };\n onMounted(start);\n onBeforeUnmount(() => { void stop(); });\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 cleanup: (() => void) | undefined;\n let latestHandler = handler;\n const stop = () => { cleanup?.(); cleanup = undefined; currentBus = null; };\n const sync = () => {\n const nextBus = bus.value;\n const nextTopic = typeof topic === 'string' ? topic : topic.value;\n if (nextBus === currentBus && cleanup) return;\n stop();\n if (!nextBus) return;\n currentBus = nextBus;\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>('connecting');\n let cleanup: (() => void) | undefined;\n watch(bus, next => {\n cleanup?.(); cleanup = undefined;\n status.value = next?.getStatus() ?? 'connecting';\n if (next) cleanup = next.onStatus(value => { status.value = value; });\n }, { immediate: true });\n onBeforeUnmount(() => cleanup?.());\n return status;\n}\n"],
5
+ "mappings": ";AAGA,SAAS,iBAAiB,WAAW,KAAK,YAAY,aAAuB;AAItE,SAAS,mBACd,QACA,OAAsD,CAAC,GACV;AAC7C,QAAM,MAAM,WAAmD,IAAI;AACnE,MAAI,WAAmD;AACvD,QAAM,OAAO,YAAY;AAAE,UAAM,UAAU;AAAU,eAAW;AAAM,QAAI,QAAQ;AAAM,QAAI,QAAS,OAAM,QAAQ,KAAK;AAAA,EAAG;AAC3H,QAAM,QAAQ,MAAM;AAAE,SAAK,KAAK,EAAE,KAAK,MAAM;AAAE,YAAM,OAAO,OAAO;AAAG,iBAAW;AAAM,UAAI,QAAQ;AAAM,WAAK,KAAK,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAAG,CAAC;AAAA,EAAG;AAChJ,YAAU,KAAK;AACf,kBAAgB,MAAM;AAAE,SAAK,KAAK;AAAA,EAAG,CAAC;AACtC,MAAI,KAAK,SAAS,EAAG,OAAM,MAAM,KAAK;AACtC,SAAO;AACT;AAEO,SAAS,wBACd,KAAkD,OAClD,SACM;AACN,MAAI,aAAqD;AACzD,MAAI;AACJ,MAAI,gBAAgB;AACpB,QAAM,OAAO,MAAM;AAAE,cAAU;AAAG,cAAU;AAAW,iBAAa;AAAA,EAAM;AAC1E,QAAM,OAAO,MAAM;AACjB,UAAM,UAAU,IAAI;AACpB,UAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,MAAM;AAC5D,QAAI,YAAY,cAAc,QAAS;AACvC,SAAK;AACL,QAAI,CAAC,QAAS;AACd,iBAAa;AACb,cAAU,QAAQ,UAAU,WAAW,aAAW,cAAc,OAAO,CAAC;AAAA,EAC1E;AACA,QAAM,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC;AACpC,MAAI,OAAO,UAAU,SAAU,OAAM,OAAO,IAAI;AAChD,QAAM,MAAM,SAAS,WAAS;AAAE,oBAAgB;AAAA,EAAO,CAAC;AACxD,kBAAgB,IAAI;AACtB;AAEO,SAAS,kBACd,KACmB;AACnB,QAAM,SAAS,IAAkB,YAAY;AAC7C,MAAI;AACJ,QAAM,KAAK,UAAQ;AACjB,cAAU;AAAG,cAAU;AACvB,WAAO,QAAQ,MAAM,UAAU,KAAK;AACpC,QAAI,KAAM,WAAU,KAAK,SAAS,WAAS;AAAE,aAAO,QAAQ;AAAA,IAAO,CAAC;AAAA,EACtE,GAAG,EAAE,WAAW,KAAK,CAAC;AACtB,kBAAgB,MAAM,UAAU,CAAC;AACjC,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -19,7 +19,7 @@ import type { DataBusTransport, DataBusTransportHandlers, MaybePromise, WorkerSt
19
19
  export interface WebSocketLike {
20
20
  /** Current connection state; 1 (OPEN) means frames may be sent. */
21
21
  readonly readyState?: number;
22
- send(data: string): void;
22
+ send(data: string | ArrayBuffer): void;
23
23
  close(code?: number, reason?: string): void;
24
24
  onopen: (() => void) | null;
25
25
  onclose: (() => void) | null;
@@ -72,6 +72,7 @@ export declare class WebSocketTransport<TData = unknown> implements DataBusTrans
72
72
  * the socket is not open — subscribe frames are re-sent on open, so the
73
73
  * only real loss is a publish during a disconnect window. */
74
74
  private sendFrame;
75
+ private sendBinaryFrame;
75
76
  /** Parse a server frame. Only objects carrying a string `topic` are
76
77
  * publications; malformed JSON and unknown shapes are ignored so a chatty
77
78
  * server cannot crash the message path. */
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EACV,gBAAgB,EAChB,wBAAwB,EACxB,YAAY,EACZ,YAAY,EACb,MAAM,cAAc,CAAC;AAEtB;kFACkF;AAClF,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,+DAA+D;AAC/D,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B;6CACyC;IACzC,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,aAAa,CAAC;CAClF;AAED,0FAA0F;AAC1F,MAAM,WAAW,6BAA6B,CAAC,KAAK,GAAG,OAAO,CAC5D,SAAQ,IAAI,CACV,sBAAsB,CAAC,sBAAsB,EAAE,KAAK,CAAC,EACrD,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,WAAW,CAC3D;IACD,0CAA0C;IAC1C,UAAU,EAAE,sBAAsB,CAAC;IACnC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID;;;;qEAIqE;AACrE,qBAAa,kBAAkB,CAAC,KAAK,GAAG,OAAO,CAC7C,YAAW,gBAAgB,CAAC,sBAAsB,EAAE,KAAK,CAAC;IAM9C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJvC,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,QAAQ,CAAgD;IAChE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;gBAEzB,UAAU,EAAE,sBAAsB;IAE/D;0EACsE;IACtE,KAAK,CAAC,MAAM,EAAE,sBAAsB,EAAE,QAAQ,EAAE,wBAAwB,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC;IA6BpG;iDAC6C;IAC7C,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC;IAK5C,6DAA6D;IAC7D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC;IAK9C,0EAA0E;IAC1E,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC;IAQzD,wEAAwE;IACxE,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC;IAQ1B;;iEAE6D;IAC7D,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,eAAe;IAkBvB;;+CAE2C;IAC3C,OAAO,CAAC,aAAa;CAwBtB;AAUD;;iEAEiE;AACjE,wBAAgB,sBAAsB,CAAC,KAAK,GAAG,OAAO,EACpD,OAAO,EAAE,6BAA6B,CAAC,KAAK,CAAC,GAC5C,eAAe,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAShD;AAED,wEAAwE;AACxE,YAAY,EAAE,YAAY,EAAE,CAAC"}
package/docs/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
  | [API Reference](./api.md) | Public API, types, methods, return values, and behavior |
11
11
  | [Architecture](./architecture.md) | Worker cluster, routing, storage, migration, and degradation design |
12
12
  | [Capabilities Matrix](./capabilities.md) | Implemented, not implemented, and planned capabilities matrix |
13
+ | [Roadmap](./roadmap.md) | Release-oriented priorities and verification checklist |
13
14
  | [../examples/demo](../examples/demo) | Runnable multi-tab browser demo |
14
15
  | [../CHANGELOG.md](../CHANGELOG.md) | Version changelog |
15
16
 
@@ -19,4 +20,4 @@
19
20
  2. For production configuration, read [Configuration](./configuration.md).
20
21
  3. When developing wrappers or custom transports, read [API Reference](./api.md).
21
22
  4. When troubleshooting cross-tab behavior, read [Architecture](./architecture.md).
22
- 5. When evaluating current boundaries and future plans, read [Capabilities Matrix](./capabilities.md).
23
+ 5. When evaluating current boundaries and future plans, read [Capabilities Matrix](./capabilities.md).
package/docs/api.md CHANGED
@@ -30,6 +30,12 @@ import {
30
30
  useCrossTabStatus,
31
31
  useCrossTabSubscription
32
32
  } from 'cross-tab-worker-databus/hooks';
33
+
34
+ import {
35
+ useCrossTabDataBus as useVueCrossTabDataBus,
36
+ useCrossTabStatus as useVueCrossTabStatus,
37
+ useCrossTabSubscription as useVueCrossTabSubscription
38
+ } from 'cross-tab-worker-databus/vue';
33
39
  ```
34
40
 
35
41
  Business integration should prefer `CrossTabDataBus` or `createCentrifugeDataBus`. `WorkerClusterRuntime` is an advanced coordination API.
@@ -80,7 +86,7 @@ Registers a local subscription and returns a cleanup function.
80
86
  - The current tab only leaves the topic after the last handler is released.
81
87
  - Subscriptions are automatically queued when the transport is not yet ready.
82
88
  - Wildcard subscriptions: a topic ending in `.*` (`chat.*`) matches any remainder, and `*` matches everything. The pattern is routed, owned, and transport-subscribed as a literal channel; publications tagged with a matching concrete topic (or with the pattern itself) are delivered to wildcard handlers. See `topicMatchesPattern` below.
83
- - Replay (opt-in): construct the bus with `replay: { maxPerTopic }` and pass `{ replay: true | n }` as the third `subscribe()` argument. `maxPerTopic` must be a positive safe integer. The new handler immediately receives the buffered history (up to `n`, capped by `maxPerTopic`, default 100) with `message.replayed: true`, so late joiners do not miss earlier publications. Only dispatched publications are buffered (a topic with no local subscriber drops them as unowned); buffers are in-memory and cleared when the last handler for the topic unsubscribes. Wildcard subscriptions replay across every buffered topic matching the pattern.
89
+ - Replay (opt-in): construct the bus with `replay: { maxPerTopic }` and pass `{ replay: true | n }` as the third `subscribe()` argument. `maxPerTopic` must be a positive safe integer. The new handler immediately receives the buffered history (up to `n`, capped by `maxPerTopic`, default 100) with `message.replayed: true`, so late joiners do not miss earlier publications. Only dispatched publications are buffered (a topic with no local subscriber drops them as unowned); buffers are in-memory and cleared when the last handler for the topic unsubscribes. Wildcard subscriptions replay across every buffered topic matching the pattern. For reload/BFCache persistence, pass an optional `persistence` created by `createIndexedDbReplayPersistence({ maxPerTopic })`; persistence is asynchronous and failures are reported through `onError` without breaking live delivery.
84
90
 
85
91
  ### `unsubscribe(topic, handler?)`
86
92
 
@@ -289,6 +295,18 @@ Attaches a message handler with automatic cleanup. The handler is read through a
289
295
 
290
296
  Mirrors `bus.onStatus()` into React state and reads the current value synchronously whenever the bus identity changes. Returns `'connecting' | 'connected' | 'disconnected' | 'error'`.
291
297
 
298
+ ## Vue Composables (`cross-tab-worker-databus/vue`)
299
+
300
+ Vue 3.3+ is an optional peer dependency; this entry is separate from the core package.
301
+
302
+ ```ts
303
+ const bus = useVueCrossTabDataBus(() => createWebSocketDataBus({ connection: { url } }));
304
+ const status = useVueCrossTabStatus(bus);
305
+ useVueCrossTabSubscription(bus, 'chat.*', message => console.log(message.data));
306
+ ```
307
+
308
+ `useCrossTabDataBus` returns a Vue `Ref` that is populated on mount and stopped on unmount. `useCrossTabSubscription` accepts a string or `Ref<string>` topic and rebinds when the bus or topic changes. `useCrossTabStatus` returns a `Ref<WorkerStatus>` synchronized with `bus.onStatus()`.
309
+
292
310
  ## `WorkerClusterRuntime`
293
311
 
294
312
  Advanced API responsible for Worker registration, heartbeat, visibility, routing, BroadcastChannel protocol, and migration. Business modules should not operate on it directly.
@@ -0,0 +1,18 @@
1
+ # Roadmap
2
+
3
+ 0.5.0 is released. The next work is organized as 0.6.0 candidates, with publish-path improvements landing first because they are measurable without changing the public transport contract.
4
+
5
+ ## 0.6.0 candidates
6
+
7
+ 1. **Publish-path profiling and optimization** — continue isolating routing, storage, and transport overhead; the first 0.6.0 slice adds a synchronous owner fast path for local publishes.
8
+ 2. **Vue composables** — provide a Vue 3 adapter mirroring the React hooks lifecycle and subscription semantics without making Vue a core dependency. (Adapter implemented; contract coverage and examples remain.)
9
+ 3. **End-to-end binary demo** — exercise real `ArrayBuffer` frames through the WebSocket demo path; keep the existing base64 JSON fallback for servers that only support JSON.
10
+ 4. **Push CI browser gate** — run the existing Playwright suite on pushes with an installed Chromium fallback; keep local/manual execution as the fallback for contributors. (Workflow implemented.)
11
+ 5. **Operational polish** — document IndexedDB replay retention/cleanup and add diagnostics around persistence quota failures.
12
+
13
+ ## Release checklist
14
+
15
+ - Update the `[Unreleased]` section and version date.
16
+ - Run `pnpm check`, `pnpm lint`, `pnpm test:e2e`, `pnpm bench`, and `pnpm bench:browser`.
17
+ - Run ESM/CJS package-consumption smoke tests from the packed tarball.
18
+ - Tag the release and verify the GitHub Release and npm `latest` dist-tag.
@@ -146,6 +146,10 @@ Wire protocol (JSON text frames):
146
146
  without a string `topic` are ignored; malformed JSON is reported through
147
147
  `handlers.onError` without throwing.
148
148
 
149
+ When `data` is an `ArrayBuffer`, publish uses a binary frame with a small
150
+ header (`0xc7`, UTF-8 topic length, topic, payload). Servers may echo the same
151
+ frame unchanged; JSON remains the compatibility path for all other payloads.
152
+
149
153
  Lifecycle mapping: `open` → `connected`, `close` → `disconnected`,
150
154
  `error` → `error` (DataBus auto-recovery). Subscribe frames are re-sent when
151
155
  the socket reopens in place. A pattern-aware server may tag publications with
package/docs/zh/README.md CHANGED
@@ -12,6 +12,7 @@
12
12
  | [API 参考](./api.md) | 公共入口、类型、方法、返回值和行为 |
13
13
  | [架构说明](./architecture.md) | Worker 集群、路由、存储、迁移和降级设计 |
14
14
  | [能力矩阵](./capabilities.md) | 已实现、未实现和计划待实现的能力矩阵 |
15
+ | [路线图](./roadmap.md) | 面向版本的优先级与验证清单 |
15
16
  | [../..//examples/demo](../../examples/demo) | 可运行的多标签浏览器演示 |
16
17
  | [../../CHANGELOG.md](../../CHANGELOG.md) | 版本变更记录 |
17
18
 
@@ -21,4 +22,4 @@
21
22
  2. 生产配置阅读 [配置说明](./configuration.md)。
22
23
  3. 开发封装或自定义 transport 时阅读 [API 参考](./api.md)。
23
24
  4. 排查跨 Tab 行为时阅读 [架构说明](./architecture.md)。
24
- 5. 评估当前边界和后续计划时阅读 [能力矩阵](./capabilities.md)。
25
+ 5. 评估当前边界和后续计划时阅读 [能力矩阵](./capabilities.md)。
package/docs/zh/api.md CHANGED
@@ -30,6 +30,12 @@ import {
30
30
  useCrossTabStatus,
31
31
  useCrossTabSubscription
32
32
  } from 'cross-tab-worker-databus/hooks';
33
+
34
+ import {
35
+ useCrossTabDataBus as useVueCrossTabDataBus,
36
+ useCrossTabStatus as useVueCrossTabStatus,
37
+ useCrossTabSubscription as useVueCrossTabSubscription
38
+ } from 'cross-tab-worker-databus/vue';
33
39
  ```
34
40
 
35
41
  业务接入优先使用 `CrossTabDataBus` 或 `createCentrifugeDataBus`。`WorkerClusterRuntime` 属于高级协调 API。
@@ -80,7 +86,7 @@ subscribe(
80
86
  - 最后一个 handler 释放后,当前 Tab 才退出该 Topic。
81
87
  - transport 尚未 ready 时订阅自动排队。
82
88
  - 通配符订阅:以 `.*` 结尾的 Topic(如 `chat.*`)匹配任意后缀,`*` 匹配全部。pattern 以字面量参与路由、归属与传输订阅;携带匹配的具体 topic(或 pattern 本身)的发布都会投递给通配 handler。匹配规则见下方 `topicMatchesPattern`。
83
- - 重放(可选):构造 bus 时传 `replay: { maxPerTopic }` 开启缓冲,`maxPerTopic` 必须是正安全整数;`subscribe()` 第三个参数传 `{ replay: true | n }` 后,新 handler 会立即收到缓冲历史(最多 `n` 条,受 `maxPerTopic` 上限约束,默认 100),消息带 `message.replayed: true` 标记——晚加入的 handler 不会错过更早的发布。只有被分发过的消息才入缓冲(无本地订阅者的 topic 会被 owner 丢弃);缓冲仅存内存,该 topic 最后一个 handler 退订时清空。通配订阅会对所有匹配 pattern 的已缓冲 topic 做回放。
89
+ - 重放(可选):构造 bus 时传 `replay: { maxPerTopic }` 开启缓冲,`maxPerTopic` 必须是正安全整数;`subscribe()` 第三个参数传 `{ replay: true | n }` 后,新 handler 会立即收到缓冲历史(最多 `n` 条,受 `maxPerTopic` 上限约束,默认 100),消息带 `message.replayed: true` 标记——晚加入的 handler 不会错过更早的发布。只有被分发过的消息才入缓冲(无本地订阅者的 topic 会被 owner 丢弃);缓冲仅存内存,该 topic 最后一个 handler 退订时清空。通配订阅会对所有匹配 pattern 的已缓冲 topic 做回放。需要跨 reload/BFCache 持久化时,可传入 `createIndexedDbReplayPersistence({ maxPerTopic })` 创建的 `persistence`;持久化为异步操作,失败会通过 `onError` 报告,不影响实时投递。
84
90
 
85
91
  ### `unsubscribe(topic, handler?)`
86
92
 
@@ -289,6 +295,18 @@ React(>= 18)是可选 peer 依赖;独立入口保证非 React 消费者不
289
295
 
290
296
  把 `bus.onStatus()` 镜像为 React 状态,bus 身份变化时同步读取当前值。返回 `'connecting' | 'connected' | 'disconnected' | 'error'`。
291
297
 
298
+ ## Vue Composables(`cross-tab-worker-databus/vue`)
299
+
300
+ Vue 3.3+ 是可选 peer 依赖;独立入口不会影响核心包。
301
+
302
+ ```ts
303
+ const bus = useVueCrossTabDataBus(() => createWebSocketDataBus({ connection: { url } }));
304
+ const status = useVueCrossTabStatus(bus);
305
+ useVueCrossTabSubscription(bus, 'chat.*', message => console.log(message.data));
306
+ ```
307
+
308
+ `useCrossTabDataBus` 返回 Vue `Ref`,在组件挂载时创建 bus、卸载时停止。`useCrossTabSubscription` 接受字符串或 `Ref<string>` topic,在 bus/topic 变化时自动重绑。`useCrossTabStatus` 返回与 `bus.onStatus()` 同步的 `Ref<WorkerStatus>`。
309
+
292
310
  ## `WorkerClusterRuntime`
293
311
 
294
312
  高级 API,负责 Worker 注册、心跳、可见性、路由、BroadcastChannel 协议和迁移。业务模块不应直接操作它。
@@ -0,0 +1,18 @@
1
+ # 路线图
2
+
3
+ 0.5.0 已发布。下一阶段按 0.6.0 候选推进,先处理可用基准直接验证的 publish 路径优化,不改变公开 transport 契约。
4
+
5
+ ## 0.6.0 候选
6
+
7
+ 1. **publish 路径 profile 与优化**:继续拆分路由、存储和 transport 开销;0.6.0 首批加入 owner 本地 publish 的同步快路径;
8
+ 2. **Vue composable**:复制 React hooks 的生命周期与订阅语义,Vue 仍保持可选依赖。(适配层已实现,后续补契约覆盖与示例);
9
+ 3. **二进制 demo 真链路**:让 WebSocket demo 端到端覆盖真实 `ArrayBuffer` 帧,同时保留仅支持 JSON 服务端的 base64 fallback;
10
+ 4. **Push CI 浏览器门禁**:push 时运行 Playwright,CI 使用安装的 Chromium 作为 Chrome fallback,贡献者本地仍可手动执行。(workflow 已实现);
11
+ 5. **运维完善**:补充 IndexedDB replay 留存/清理说明,并为持久化配额失败增加诊断。
12
+
13
+ ## 发版检查清单
14
+
15
+ - 更新 `[Unreleased]` 与版本日期;
16
+ - 执行 `pnpm check`、`pnpm lint`、`pnpm test:e2e`、`pnpm bench`、`pnpm bench:browser`;
17
+ - 从打包 tarball 做 ESM/CJS 消费冒烟验证;
18
+ - 推送 tag 后核验 GitHub Release 与 npm `latest` dist-tag。
@@ -133,6 +133,10 @@ const bus = createWebSocketDataBus({
133
133
  - server → client:发布为 `{"topic":"...","data":...}`。没有字符串 `topic` 的帧
134
134
  会被忽略;非法 JSON 通过 `handlers.onError` 上报而不会抛出。
135
135
 
136
+ 当 `data` 是 `ArrayBuffer` 时,publish 使用二进制帧:帧头为 `0xc7`,随后是
137
+ UTF-8 topic 长度、topic 和 payload。服务器可以原样回显该帧;其他 payload 仍走
138
+ JSON 兼容路径。
139
+
136
140
  生命周期映射:`open` → `connected`,`close` → `disconnected`,`error` → `error`
137
141
  (触发 DataBus 自动恢复)。socket 原地重连时自动重发订阅帧。支持 pattern 的
138
142
  服务器可以以具体 topic 标注发布——见 [api.md](../api.md) 中的通配符订阅。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-tab-worker-databus",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Framework-agnostic cross-tab data bus with Dedicated/Shared Worker clustering and Centrifuge support.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -56,6 +56,11 @@
56
56
  "import": "./dist/hooks.js",
57
57
  "require": "./dist/cjs/hooks.cjs"
58
58
  },
59
+ "./vue": {
60
+ "types": "./dist/vue.d.ts",
61
+ "import": "./dist/vue.js",
62
+ "require": "./dist/cjs/vue.cjs"
63
+ },
59
64
  "./package.json": "./package.json"
60
65
  },
61
66
  "sideEffects": [
@@ -71,13 +76,15 @@
71
76
  "test:watch": "vitest",
72
77
  "test:coverage": "vitest run --coverage",
73
78
  "bench": "vitest bench --run",
79
+ "bench:browser": "pnpm build && node scripts/bench-browser.mjs",
74
80
  "test:e2e": "pnpm build && playwright test",
75
81
  "typecheck": "tsc --noEmit",
76
82
  "prepublishOnly": "pnpm check"
77
83
  },
78
84
  "peerDependencies": {
79
85
  "centrifuge": "^5.5.3",
80
- "react": ">=18"
86
+ "react": ">=18",
87
+ "vue": ">=3.3"
81
88
  },
82
89
  "peerDependenciesMeta": {
83
90
  "centrifuge": {
@@ -85,6 +92,9 @@
85
92
  },
86
93
  "react": {
87
94
  "optional": true
95
+ },
96
+ "vue": {
97
+ "optional": true
88
98
  }
89
99
  },
90
100
  "devDependencies": {
@@ -102,7 +112,8 @@
102
112
  "react-dom": "^18.3.1",
103
113
  "typescript": "^5.9.0",
104
114
  "typescript-eslint": "^8.68.0",
105
- "vitest": "^3.2.0"
115
+ "vitest": "^3.2.0",
116
+ "vue": "^3.5.42"
106
117
  },
107
118
  "engines": {
108
119
  "node": ">=18.0.0"
@@ -1,31 +0,0 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
8
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
- // If the importer is in node compatibility mode or this is not an ESM
20
- // file that has been converted to a CommonJS file using a Babel-
21
- // compatible transform (i.e. "__esModule" has not been set), then set
22
- // "default" to the CommonJS "module.exports" for node compatibility.
23
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
- mod
25
- ));
26
-
27
- export {
28
- __commonJS,
29
- __toESM
30
- };
31
- //# sourceMappingURL=chunk-5WRI5ZAA.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
6
- "names": []
7
- }