cross-tab-worker-databus 0.5.0 → 0.7.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.
- package/CHANGELOG.md +23 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/centrifuge.js +1 -2
- package/dist/centrifuge.js.map +1 -1
- package/dist/{chunk-W7DAXK4D.js → chunk-SI7N5KNJ.js} +50 -2
- package/dist/chunk-SI7N5KNJ.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +49 -1
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/hooks.cjs +1 -1913
- package/dist/cjs/hooks.cjs.map +4 -4
- package/dist/cjs/index.cjs +98 -1
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/vue.cjs +93 -0
- package/dist/cjs/vue.cjs.map +7 -0
- package/dist/core/cluster.d.ts +5 -0
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +12 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/replay-persistence.d.ts +4 -0
- package/dist/core/replay-persistence.d.ts.map +1 -1
- package/dist/core/trace.d.ts +19 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks.js +7 -1911
- package/dist/hooks.js.map +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -2
- package/dist/index.js.map +2 -2
- package/dist/vue.d.ts +10 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +72 -0
- package/dist/vue.js.map +7 -0
- package/dist/websocket.d.ts +2 -1
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +21 -0
- package/docs/capabilities.md +3 -3
- package/docs/roadmap.md +7 -7
- package/docs/transports.md +4 -0
- package/docs/zh/api.md +19 -0
- package/docs/zh/capabilities.md +3 -3
- package/docs/zh/roadmap.md +7 -7
- package/docs/zh/transports.md +4 -0
- package/package.json +13 -3
- package/dist/chunk-5WRI5ZAA.js +0 -31
- package/dist/chunk-5WRI5ZAA.js.map +0 -7
- package/dist/chunk-W7DAXK4D.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
|
package/dist/vue.js.map
ADDED
|
@@ -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
|
+
}
|
package/dist/websocket.d.ts
CHANGED
|
@@ -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. */
|
package/dist/websocket.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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"}
|
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.
|
|
@@ -81,6 +87,7 @@ Registers a local subscription and returns a cleanup function.
|
|
|
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
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.
|
|
90
|
+
- Persistent replay stores may also implement `clearTopic(topic)`; the bus calls it on final topic unsubscribe. A store may expose `clear()` for application-controlled retention cleanup; `stop()` deliberately preserves durable history for reload/BFCache recovery.
|
|
84
91
|
|
|
85
92
|
### `unsubscribe(topic, handler?)`
|
|
86
93
|
|
|
@@ -104,6 +111,8 @@ Published data must satisfy the serialization constraints of the underlying tran
|
|
|
104
111
|
|
|
105
112
|
When the owning Worker is a remote Tab and the publish control message cannot be posted (for example the BroadcastChannel fails to clone the payload), `publish()` reports the failure through `onError` instead of silently dropping it.
|
|
106
113
|
|
|
114
|
+
Incoming messages may include a caller/server supplied `messageId`. Enable bounded duplicate suppression with `dedup: { maxEntries, ttlMs }`; repeated IDs within the window are ignored. This is disabled by default and does not provide an exactly-once server guarantee.
|
|
115
|
+
|
|
107
116
|
### `onStatus(handler)`
|
|
108
117
|
|
|
109
118
|
```ts
|
|
@@ -289,6 +298,18 @@ Attaches a message handler with automatic cleanup. The handler is read through a
|
|
|
289
298
|
|
|
290
299
|
Mirrors `bus.onStatus()` into React state and reads the current value synchronously whenever the bus identity changes. Returns `'connecting' | 'connected' | 'disconnected' | 'error'`.
|
|
291
300
|
|
|
301
|
+
## Vue Composables (`cross-tab-worker-databus/vue`)
|
|
302
|
+
|
|
303
|
+
Vue 3.3+ is an optional peer dependency; this entry is separate from the core package.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
const bus = useVueCrossTabDataBus(() => createWebSocketDataBus({ connection: { url } }));
|
|
307
|
+
const status = useVueCrossTabStatus(bus);
|
|
308
|
+
useVueCrossTabSubscription(bus, 'chat.*', message => console.log(message.data));
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`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()`.
|
|
312
|
+
|
|
292
313
|
## `WorkerClusterRuntime`
|
|
293
314
|
|
|
294
315
|
Advanced API responsible for Worker registration, heartbeat, visibility, routing, BroadcastChannel protocol, and migration. Business modules should not operate on it directly.
|
package/docs/capabilities.md
CHANGED
|
@@ -22,14 +22,14 @@ Status Legend: `✅ Implemented` means the current version has code and test cov
|
|
|
22
22
|
| Degradation | Runs locally when localStorage or BroadcastChannel is unavailable | ✅ Implemented | Preserves the current Tab's connection and subscription capabilities |
|
|
23
23
|
| Centrifuge | Built-in Dedicated / Shared Worker transport | ✅ Implemented | Supports subscribe, unsubscribe, publish, connection status, and error reporting; `auto` degrades from SharedWorker → Dedicated Worker → main-thread WebSocket |
|
|
24
24
|
| Security Boundary | localStorage uses opaque keys derived from connection and Topic; BroadcastChannel coordination messages carry plaintext topic names | ✅ Implemented | Does not persist URLs, raw Topic names, credentials, or publication payloads. BroadcastChannel coordination messages are in-memory only and carry plaintext topic names — they are not persisted. |
|
|
25
|
-
| Diagnostics | Aggregates lifecycle events, throughput,
|
|
25
|
+
| Diagnostics | Aggregates lifecycle events, throughput, delivery latency, recovery retries, route acknowledgments, and migrations | ✅ Implemented | Disabled by default; metrics are emitted every 5 seconds by default, with bounded reliability events for recovery and route coordination |
|
|
26
26
|
| Performance | Batched writes of coordination metadata with backoff retry | ✅ Implemented | Heartbeat, route, and subscriber writes are merged and flushed in a microtask; failures use exponential backoff; `pagehide` / `stop()` flush synchronously |
|
|
27
27
|
| Performance | Optional ArrayBuffer Transferable transport | ✅ Implemented | With `transferable: true`, binary publish / receive bypasses structured clone copying; the object message API is unchanged |
|
|
28
28
|
| Message Semantics | exactly-once delivery | Not Implemented | Graceful handoff avoids overlap, but crash recovery and transport/server behavior still do not provide an exactly-once guarantee |
|
|
29
|
-
| Message Semantics | Pluggable publication deduplication |
|
|
29
|
+
| Message Semantics | Pluggable publication deduplication | ✅ Implemented | Opt-in bounded inbound suppression by `DataBusMessage.messageId`; default is disabled and transport/server IDs remain caller-controlled |
|
|
30
30
|
| Authentication | Async credential refresh bridge inside the Worker | Planned | Current Worker config must be structured-cloneable and cannot pass functions |
|
|
31
31
|
| Load Policy | Adaptive weighting by message rate, byte count, or CPU | Planned | Load is currently computed only from the number of owner Topics |
|
|
32
|
-
| Observability | Metrics for owner
|
|
32
|
+
| Observability | Metrics/events for owner acknowledgments, migrations, and recovery attempts | ✅ Implemented | `DataBusReliabilityTraceEvent` reports bounded route ack/migration and transport recovery events; exact server-side ack remains transport-specific |
|
|
33
33
|
| Runtime Model | SharedWorker / Dedicated Worker transport | ✅ Implemented | `workerMode` supports `dedicated`, `shared`, and `auto`, defaulting to `dedicated` |
|
|
34
34
|
| Runtime Model | Service Worker transport | Not Implemented | Service Worker hosting of real-time connections is not currently provided |
|
|
35
35
|
| Durable Messages | Persisting publications or publish commands across page close | Not Implemented | The SDK does not persist business payloads, nor does it replay publish commands after restoration |
|
package/docs/roadmap.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
0.
|
|
3
|
+
0.6.0 is released. The next work is organized as 0.7.0 candidates, with reliability and diagnostics landing first so the new behavior is observable before expanding the protocol surface.
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.7.0 candidates
|
|
6
6
|
|
|
7
|
-
1. **
|
|
8
|
-
2. **
|
|
9
|
-
3. **
|
|
10
|
-
4. **
|
|
11
|
-
5. **
|
|
7
|
+
1. **Replay lifecycle and retention** — add explicit persistence cleanup (`clear`, `clearTopic`), make unsubscribe/replacement remove stale history, and surface persistence failures through trace and error handlers.
|
|
8
|
+
2. **Reliability diagnostics** — emit structured recovery/retry, owner-acknowledgment, and route-migration events with bounded metadata while keeping tracing opt-in.
|
|
9
|
+
3. **Publication deduplication** — design and implement an opt-in, bounded message-ID window that works across local dispatch, BroadcastChannel fan-out, WebSocket, and replay without changing the default behavior.
|
|
10
|
+
4. **Adapter and protocol parity** — align React/Vue lifecycle and type contracts, document binary framing and recovery semantics, and add compatibility fixtures for custom transports.
|
|
11
|
+
5. **Operational validation** — extend browser and package-consumption tests, add regression benchmarks for dedup/recovery/replay cleanup, and keep push CI as a release gate.
|
|
12
12
|
|
|
13
13
|
## Release checklist
|
|
14
14
|
|
package/docs/transports.md
CHANGED
|
@@ -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/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。
|
|
@@ -81,6 +87,7 @@ subscribe(
|
|
|
81
87
|
- transport 尚未 ready 时订阅自动排队。
|
|
82
88
|
- 通配符订阅:以 `.*` 结尾的 Topic(如 `chat.*`)匹配任意后缀,`*` 匹配全部。pattern 以字面量参与路由、归属与传输订阅;携带匹配的具体 topic(或 pattern 本身)的发布都会投递给通配 handler。匹配规则见下方 `topicMatchesPattern`。
|
|
83
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` 报告,不影响实时投递。
|
|
90
|
+
- 持久化 replay store 还可实现 `clearTopic(topic)`;bus 会在最后一个 handler 退订时调用。应用可自行保留 `clear()` 做全量留存清理;`stop()` 会保留 durable history,以支持 reload/BFCache 恢复。
|
|
84
91
|
|
|
85
92
|
### `unsubscribe(topic, handler?)`
|
|
86
93
|
|
|
@@ -289,6 +296,18 @@ React(>= 18)是可选 peer 依赖;独立入口保证非 React 消费者不
|
|
|
289
296
|
|
|
290
297
|
把 `bus.onStatus()` 镜像为 React 状态,bus 身份变化时同步读取当前值。返回 `'connecting' | 'connected' | 'disconnected' | 'error'`。
|
|
291
298
|
|
|
299
|
+
## Vue Composables(`cross-tab-worker-databus/vue`)
|
|
300
|
+
|
|
301
|
+
Vue 3.3+ 是可选 peer 依赖;独立入口不会影响核心包。
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const bus = useVueCrossTabDataBus(() => createWebSocketDataBus({ connection: { url } }));
|
|
305
|
+
const status = useVueCrossTabStatus(bus);
|
|
306
|
+
useVueCrossTabSubscription(bus, 'chat.*', message => console.log(message.data));
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`useCrossTabDataBus` 返回 Vue `Ref`,在组件挂载时创建 bus、卸载时停止。`useCrossTabSubscription` 接受字符串或 `Ref<string>` topic,在 bus/topic 变化时自动重绑。`useCrossTabStatus` 返回与 `bus.onStatus()` 同步的 `Ref<WorkerStatus>`。
|
|
310
|
+
|
|
292
311
|
## `WorkerClusterRuntime`
|
|
293
312
|
|
|
294
313
|
高级 API,负责 Worker 注册、心跳、可见性、路由、BroadcastChannel 协议和迁移。业务模块不应直接操作它。
|
package/docs/zh/capabilities.md
CHANGED
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
| 降级 | localStorage 或 BroadcastChannel 不可用时本地运行 | ✅ 已实现 | 保留当前 Tab 的连接和订阅能力 |
|
|
23
23
|
| Centrifuge | 内置 Dedicated / Shared Worker transport | ✅ 已实现 | 支持 subscribe、unsubscribe、publish、连接状态和错误上报;`auto` 从 SharedWorker → Dedicated Worker → 主线程 WebSocket 降级 |
|
|
24
24
|
| 安全边界 | localStorage 使用连接和 Topic 派生不透明 key;BroadcastChannel 协调消息以明文传输 Topic 名称 | ✅ 已实现 | 不持久化 URL、原始 Topic 名称、凭证或 publication payload。BroadcastChannel 协调消息仅存在于内存中,以明文传输 Topic 名称——不会被持久化。 |
|
|
25
|
-
| 诊断 |
|
|
25
|
+
| 诊断 | 聚合生命周期、吞吐量、分发延迟、恢复重试、路由确认和迁移 | ✅ 已实现 | 默认关闭;默认每 5 秒输出指标,并以有界 reliability 事件记录恢复和路由协调 |
|
|
26
26
|
| 性能 | 协调元数据批量写入 + 退避重试 | ✅ 已实现 | 心跳、路由和 subscriber 写入合并后在微任务中 flush;失败时指数退避;`pagehide` / `stop()` 同步 flush |
|
|
27
27
|
| 性能 | 可选 ArrayBuffer Transferable 传输 | ✅ 已实现 | 开启 `transferable: true` 后,二进制 publish/receive 跳过 structured clone 复制;对象消息 API 不变 |
|
|
28
28
|
| 消息语义 | exactly-once 投递 | 未实现 | 正常交接会避免重叠,但异常恢复和 transport/服务端行为仍不提供 exactly-once 保证 |
|
|
29
|
-
| 消息语义 | 可插拔的 publication 去重 |
|
|
29
|
+
| 消息语义 | 可插拔的 publication 去重 | ✅ 已实现 | 按 `DataBusMessage.messageId` 做可选有界入站抑制;默认关闭,ID 仍由调用方/服务端控制 |
|
|
30
30
|
| 认证 | Worker 内异步凭证刷新桥接 | 规划中 | 当前 Worker 配置必须可结构化克隆,不能传递函数 |
|
|
31
31
|
| 负载策略 | 按消息速率、字节数或 CPU 自适应加权 | 规划中 | 当前负载仅按 owner Topic 数量计算 |
|
|
32
|
-
| 可观测性 | owner
|
|
32
|
+
| 可观测性 | owner 确认、迁移和恢复尝试的指标/事件 | ✅ 已实现 | `DataBusReliabilityTraceEvent` 记录有界的 route ack/migration 与 transport recovery;服务端最终确认仍由 transport 决定 |
|
|
33
33
|
| 运行时模型 | SharedWorker / Dedicated Worker transport | ✅ 已实现 | `workerMode` 支持 `dedicated`、`shared` 和 `auto`,默认 `dedicated` |
|
|
34
34
|
| 运行时模型 | Service Worker transport | 未实现 | 当前不提供使用 Service Worker 承载实时连接 |
|
|
35
35
|
| 持久消息 | 跨页面关闭持久化 publication 或发布命令 | 未实现 | SDK 不持久化业务 payload,也不在恢复后重放发布命令 |
|
package/docs/zh/roadmap.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# 路线图
|
|
2
2
|
|
|
3
|
-
0.
|
|
3
|
+
0.6.0 已发布。下一阶段按 0.7.0 候选推进,先做可靠性与诊断,让新行为在扩展协议前可观测。
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.7.0 候选
|
|
6
6
|
|
|
7
|
-
1. **
|
|
8
|
-
2.
|
|
9
|
-
3.
|
|
10
|
-
4.
|
|
11
|
-
5.
|
|
7
|
+
1. **Replay 生命周期与留存**:增加持久化 `clear`/`clearTopic`,退订/替换时清理旧历史,并通过 trace 与 error handler 暴露持久化失败;
|
|
8
|
+
2. **可靠性诊断**:增加恢复/重试、owner ack、路由迁移结构化事件,元数据有界且默认关闭 trace;
|
|
9
|
+
3. **发布去重**:设计并实现可选、有界的 message-ID 窗口,覆盖本地分发、BroadcastChannel、WebSocket 与 replay,默认行为保持不变;
|
|
10
|
+
4. **适配层与协议对齐**:统一 React/Vue 生命周期和类型契约,补充二进制帧与恢复语义文档,并增加自定义 transport 兼容夹具;
|
|
11
|
+
5. **运维验证**:扩展浏览器和打包消费测试,增加去重/恢复/replay 清理回归基准,Push CI 继续作为发版门禁。
|
|
12
12
|
|
|
13
13
|
## 发版检查清单
|
|
14
14
|
|
package/docs/zh/transports.md
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.7.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": [
|
|
@@ -78,7 +83,8 @@
|
|
|
78
83
|
},
|
|
79
84
|
"peerDependencies": {
|
|
80
85
|
"centrifuge": "^5.5.3",
|
|
81
|
-
"react": ">=18"
|
|
86
|
+
"react": ">=18",
|
|
87
|
+
"vue": ">=3.3"
|
|
82
88
|
},
|
|
83
89
|
"peerDependenciesMeta": {
|
|
84
90
|
"centrifuge": {
|
|
@@ -86,6 +92,9 @@
|
|
|
86
92
|
},
|
|
87
93
|
"react": {
|
|
88
94
|
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"vue": {
|
|
97
|
+
"optional": true
|
|
89
98
|
}
|
|
90
99
|
},
|
|
91
100
|
"devDependencies": {
|
|
@@ -103,7 +112,8 @@
|
|
|
103
112
|
"react-dom": "^18.3.1",
|
|
104
113
|
"typescript": "^5.9.0",
|
|
105
114
|
"typescript-eslint": "^8.68.0",
|
|
106
|
-
"vitest": "^3.2.0"
|
|
115
|
+
"vitest": "^3.2.0",
|
|
116
|
+
"vue": "^3.5.42"
|
|
107
117
|
},
|
|
108
118
|
"engines": {
|
|
109
119
|
"node": ">=18.0.0"
|
package/dist/chunk-5WRI5ZAA.js
DELETED
|
@@ -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
|