cross-tab-worker-databus 0.20.70 → 0.20.85

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 (95) hide show
  1. package/CHANGELOG.md +209 -2
  2. package/README.md +8 -0
  3. package/README.zh.md +4 -0
  4. package/dist/centrifuge-protocol.d.ts +59 -26
  5. package/dist/centrifuge-protocol.d.ts.map +1 -1
  6. package/dist/centrifuge-session.d.ts +9 -0
  7. package/dist/centrifuge-session.d.ts.map +1 -1
  8. package/dist/centrifuge.d.ts +12 -2
  9. package/dist/centrifuge.d.ts.map +1 -1
  10. package/dist/centrifuge.js +168 -88
  11. package/dist/centrifuge.js.map +3 -3
  12. package/dist/centrifuge.shared.worker.js +146 -38
  13. package/dist/centrifuge.shared.worker.js.map +3 -3
  14. package/dist/centrifuge.worker.js +140 -33
  15. package/dist/centrifuge.worker.js.map +3 -3
  16. package/dist/{chunk-IKBJIREH.js → chunk-PW63EWIK.js} +1090 -476
  17. package/dist/chunk-PW63EWIK.js.map +7 -0
  18. package/dist/chunk-TZ7ZP7YD.js +175 -0
  19. package/dist/chunk-TZ7ZP7YD.js.map +7 -0
  20. package/dist/cjs/centrifuge.cjs +1405 -621
  21. package/dist/cjs/centrifuge.cjs.map +4 -4
  22. package/dist/cjs/hooks.cjs +37 -2
  23. package/dist/cjs/hooks.cjs.map +3 -3
  24. package/dist/cjs/index.cjs +1469 -688
  25. package/dist/cjs/index.cjs.map +4 -4
  26. package/dist/cjs/vue.cjs +45 -2
  27. package/dist/cjs/vue.cjs.map +3 -3
  28. package/dist/core/cluster.d.ts +40 -4
  29. package/dist/core/cluster.d.ts.map +1 -1
  30. package/dist/core/data-bus.d.ts +93 -66
  31. package/dist/core/data-bus.d.ts.map +1 -1
  32. package/dist/core/dedup-manager.d.ts +98 -0
  33. package/dist/core/dedup-manager.d.ts.map +1 -0
  34. package/dist/core/environment.d.ts +43 -3
  35. package/dist/core/environment.d.ts.map +1 -1
  36. package/dist/core/replay-manager.d.ts +128 -0
  37. package/dist/core/replay-manager.d.ts.map +1 -0
  38. package/dist/core/replay-persistence.d.ts +4 -1
  39. package/dist/core/replay-persistence.d.ts.map +1 -1
  40. package/dist/core/routing.d.ts +22 -3
  41. package/dist/core/routing.d.ts.map +1 -1
  42. package/dist/core/storage-batch.d.ts +0 -4
  43. package/dist/core/storage-batch.d.ts.map +1 -1
  44. package/dist/core/trace.d.ts +55 -15
  45. package/dist/core/trace.d.ts.map +1 -1
  46. package/dist/core/types.d.ts +58 -9
  47. package/dist/core/types.d.ts.map +1 -1
  48. package/dist/core/version.d.ts +2 -0
  49. package/dist/core/version.d.ts.map +1 -0
  50. package/dist/hooks.d.ts +12 -1
  51. package/dist/hooks.d.ts.map +1 -1
  52. package/dist/hooks.js +28 -2
  53. package/dist/hooks.js.map +2 -2
  54. package/dist/index.d.ts +6 -6
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +231 -125
  57. package/dist/index.js.map +3 -3
  58. package/dist/utils/constants.d.ts +170 -0
  59. package/dist/utils/constants.d.ts.map +1 -0
  60. package/dist/utils/error-utils.d.ts +28 -0
  61. package/dist/utils/error-utils.d.ts.map +1 -0
  62. package/dist/utils/metadata.d.ts +16 -0
  63. package/dist/utils/metadata.d.ts.map +1 -0
  64. package/dist/utils/storage-utils.d.ts +24 -0
  65. package/dist/utils/storage-utils.d.ts.map +1 -0
  66. package/dist/utils/validation.d.ts +76 -0
  67. package/dist/utils/validation.d.ts.map +1 -0
  68. package/dist/vue.d.ts +13 -1
  69. package/dist/vue.d.ts.map +1 -1
  70. package/dist/vue.js +36 -2
  71. package/dist/vue.js.map +2 -2
  72. package/dist/websocket.d.ts +6 -1
  73. package/dist/websocket.d.ts.map +1 -1
  74. package/dist/worker-mode.d.ts +6 -2
  75. package/dist/worker-mode.d.ts.map +1 -1
  76. package/docs/README.md +1 -0
  77. package/docs/api.md +122 -2
  78. package/docs/architecture.md +80 -0
  79. package/docs/benchmarks.md +24 -0
  80. package/docs/capabilities.md +24 -3
  81. package/docs/configuration.md +58 -0
  82. package/docs/getting-started.md +35 -0
  83. package/docs/release-checklist.md +28 -5
  84. package/docs/roadmap.md +78 -5
  85. package/docs/zh/README.md +2 -1
  86. package/docs/zh/api.md +121 -2
  87. package/docs/zh/architecture.md +49 -0
  88. package/docs/zh/benchmarks.md +24 -0
  89. package/docs/zh/capabilities.md +21 -3
  90. package/docs/zh/configuration.md +53 -0
  91. package/docs/zh/getting-started.md +35 -0
  92. package/docs/zh/release-checklist.md +29 -6
  93. package/docs/zh/roadmap.md +90 -5
  94. package/package.json +39 -16
  95. package/dist/chunk-IKBJIREH.js.map +0 -7
package/dist/index.js CHANGED
@@ -2,8 +2,14 @@ import {
2
2
  CrossTabDataBus,
3
3
  DEFAULT_MAX_ACTIVE_WORKERS,
4
4
  WorkerClusterRuntime,
5
+ approximatePayloadBytes,
6
+ assertPositiveFiniteNumber,
7
+ assertPositiveSafeInteger,
8
+ assertPruneStrategy,
5
9
  createBrowserEnvironment,
6
10
  createOpaqueKey,
11
+ createStorageEventChannel,
12
+ effectiveWorkerLoad,
7
13
  getOrCreateTabId,
8
14
  hasActiveOwner,
9
15
  isWildcardTopic,
@@ -13,22 +19,26 @@ import {
13
19
  selectRebalanceTarget,
14
20
  selectWorkerBackend,
15
21
  topicMatchesPattern
16
- } from "./chunk-IKBJIREH.js";
22
+ } from "./chunk-PW63EWIK.js";
23
+ import {
24
+ DEFAULT_STORAGE_PREFIX,
25
+ PRUNE_STRATEGY,
26
+ WORKER_STATUS,
27
+ WS_OP
28
+ } from "./chunk-TZ7ZP7YD.js";
17
29
 
18
30
  // src/core/replay-persistence.ts
19
31
  function createIndexedDbReplayPersistence(options) {
20
32
  const indexedDb = globalThis.indexedDB;
21
33
  if (!indexedDb) throw new Error("IndexedDB is unavailable in this environment.");
22
- const dbName = options.dbName ?? "cross-tab-worker-databus";
34
+ const dbName = options.dbName ?? DEFAULT_STORAGE_PREFIX;
23
35
  const storeName = "replay";
24
36
  const maxPerTopic = options.maxPerTopic;
25
- const pruneStrategy = options.pruneStrategy ?? "count";
37
+ const pruneStrategy = options.pruneStrategy ?? PRUNE_STRATEGY.COUNT;
26
38
  const retentionMs = options.retentionMs;
27
- if (!["count", "age", "both"].includes(pruneStrategy)) throw new TypeError("pruneStrategy must be count, age, or both.");
28
- if (retentionMs !== void 0 && (!Number.isFinite(retentionMs) || retentionMs <= 0)) throw new TypeError("retentionMs must be a positive finite number.");
29
- if (!Number.isSafeInteger(maxPerTopic) || maxPerTopic <= 0) {
30
- throw new TypeError(`maxPerTopic must be a positive safe integer, got ${String(maxPerTopic)}.`);
31
- }
39
+ assertPruneStrategy(pruneStrategy);
40
+ if (retentionMs !== void 0) assertPositiveFiniteNumber(retentionMs, "retentionMs");
41
+ assertPositiveSafeInteger(maxPerTopic, "maxPerTopic");
32
42
  let dbPromise = null;
33
43
  const invalidate = (db) => {
34
44
  if (dbPromise) {
@@ -40,15 +50,100 @@ function createIndexedDbReplayPersistence(options) {
40
50
  }, () => void 0);
41
51
  }
42
52
  };
43
- let mutationQueue = Promise.resolve();
44
- const serializeMutation = (mutation) => {
45
- const next = mutationQueue.then(mutation, mutation);
46
- mutationQueue = next.catch(() => void 0);
47
- return next;
53
+ const pending = [];
54
+ let draining = false;
55
+ const drain = async () => {
56
+ if (draining) return;
57
+ draining = true;
58
+ try {
59
+ await Promise.resolve();
60
+ while (pending.length > 0) {
61
+ const head = pending[0].mutation;
62
+ if (head.kind === "batch") {
63
+ const merged = [];
64
+ const entries = [];
65
+ while (pending.length > 0) {
66
+ const next = pending[0].mutation;
67
+ if (next.kind !== "batch") break;
68
+ merged.push(...next.messages);
69
+ entries.push({ resolve: pending[0].resolve, reject: pending[0].reject });
70
+ pending.shift();
71
+ }
72
+ try {
73
+ await appendTransaction(merged);
74
+ for (const entry of entries) entry.resolve();
75
+ } catch (error) {
76
+ for (const entry of entries) entry.reject(error);
77
+ }
78
+ } else {
79
+ const entry = pending.shift();
80
+ try {
81
+ await head.run();
82
+ entry.resolve();
83
+ } catch (error) {
84
+ entry.reject(error);
85
+ }
86
+ }
87
+ }
88
+ } finally {
89
+ draining = false;
90
+ }
48
91
  };
92
+ const enqueue = (mutation) => new Promise((resolve, reject) => {
93
+ pending.push({ mutation, resolve, reject });
94
+ void drain();
95
+ });
96
+ const appendTransaction = (messages) => (async () => {
97
+ const db = await open();
98
+ return new Promise((resolve, reject) => {
99
+ let transaction;
100
+ try {
101
+ transaction = db.transaction(storeName, "readwrite");
102
+ } catch (error) {
103
+ invalidate(db);
104
+ reject(error);
105
+ return;
106
+ }
107
+ const store = transaction.objectStore(storeName);
108
+ const grouped = /* @__PURE__ */ new Map();
109
+ for (const message of messages) {
110
+ grouped.set(message.topic, [...grouped.get(message.topic) ?? [], message]);
111
+ }
112
+ let hasError = false;
113
+ for (const [topic, topicMessages] of grouped) {
114
+ const request = store.get(topic);
115
+ request.onsuccess = () => {
116
+ if (hasError) return;
117
+ let history = (request.result?.messages ?? []).concat(topicMessages);
118
+ const ageBounded = pruneStrategy !== PRUNE_STRATEGY.COUNT && retentionMs !== void 0;
119
+ if (ageBounded) {
120
+ const cutoff = Date.now() - retentionMs;
121
+ history = history.filter((item) => item.timestamp === void 0 || item.timestamp >= cutoff);
122
+ }
123
+ if (pruneStrategy !== PRUNE_STRATEGY.AGE || !ageBounded) history = history.slice(-maxPerTopic);
124
+ store.put({ topic, messages: history });
125
+ };
126
+ request.onerror = () => {
127
+ if (hasError) return;
128
+ hasError = true;
129
+ invalidate(db);
130
+ reject(request.error ?? new Error("Failed to read replay history."));
131
+ };
132
+ }
133
+ transaction.oncomplete = () => {
134
+ if (!hasError) resolve();
135
+ };
136
+ transaction.onerror = () => {
137
+ if (hasError) return;
138
+ hasError = true;
139
+ invalidate(db);
140
+ reject(transaction.error ?? new Error("Failed to persist replay history."));
141
+ };
142
+ });
143
+ })();
49
144
  const open = () => {
50
145
  if (dbPromise) return dbPromise;
51
- const pending = new Promise((resolve, reject) => {
146
+ const pending2 = new Promise((resolve, reject) => {
52
147
  const request = indexedDb.open(dbName, 1);
53
148
  request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: "topic" });
54
149
  request.onsuccess = () => {
@@ -61,11 +156,11 @@ function createIndexedDbReplayPersistence(options) {
61
156
  };
62
157
  request.onerror = () => reject(request.error ?? new Error("Failed to open replay database."));
63
158
  });
64
- dbPromise = pending;
65
- void pending.catch(() => {
66
- if (dbPromise === pending) dbPromise = null;
159
+ dbPromise = pending2;
160
+ void pending2.catch(() => {
161
+ if (dbPromise === pending2) dbPromise = null;
67
162
  });
68
- return pending;
163
+ return pending2;
69
164
  };
70
165
  return {
71
166
  async load() {
@@ -87,113 +182,94 @@ function createIndexedDbReplayPersistence(options) {
87
182
  });
88
183
  },
89
184
  append(message) {
90
- return serializeMutation(async () => {
91
- const db = await open();
92
- await new Promise((resolve, reject) => {
93
- let transaction;
94
- try {
95
- transaction = db.transaction(storeName, "readwrite");
96
- } catch (error) {
97
- invalidate(db);
98
- reject(error);
99
- return;
100
- }
101
- const store = transaction.objectStore(storeName);
102
- const request = store.get(message.topic);
103
- request.onsuccess = () => {
104
- let messages = (request.result?.messages ?? []).concat(message);
105
- if (pruneStrategy !== "count" && retentionMs !== void 0) {
106
- const cutoff = Date.now() - retentionMs;
107
- messages = messages.filter((item) => item.timestamp === void 0 || item.timestamp >= cutoff);
108
- }
109
- if (pruneStrategy !== "age") messages = messages.slice(-maxPerTopic);
110
- store.put({ topic: message.topic, messages });
111
- };
112
- request.onerror = () => {
113
- invalidate(db);
114
- reject(request.error ?? new Error("Failed to read replay history."));
115
- };
116
- transaction.oncomplete = () => resolve();
117
- transaction.onerror = () => {
118
- invalidate(db);
119
- reject(transaction.error ?? new Error("Failed to persist replay history."));
120
- };
121
- });
122
- });
185
+ return enqueue({ kind: "batch", messages: [message] });
186
+ },
187
+ appendBatch(messages) {
188
+ if (messages.length === 0) return Promise.resolve();
189
+ return enqueue({ kind: "batch", messages });
123
190
  },
124
191
  clear() {
125
- return serializeMutation(async () => {
126
- const db = await open();
127
- await new Promise((resolve, reject) => {
128
- let transaction;
129
- try {
130
- transaction = db.transaction(storeName, "readwrite");
131
- } catch (error) {
132
- invalidate(db);
133
- reject(error);
134
- return;
135
- }
136
- transaction.objectStore(storeName).clear();
137
- transaction.oncomplete = () => resolve();
138
- transaction.onerror = () => {
139
- invalidate(db);
140
- reject(transaction.error ?? new Error("Failed to clear replay history."));
141
- };
142
- });
192
+ return enqueue({
193
+ kind: "run",
194
+ run: () => (async () => {
195
+ const db = await open();
196
+ return new Promise((resolve, reject) => {
197
+ let transaction;
198
+ try {
199
+ transaction = db.transaction(storeName, "readwrite");
200
+ } catch (error) {
201
+ invalidate(db);
202
+ reject(error);
203
+ return;
204
+ }
205
+ transaction.objectStore(storeName).clear();
206
+ transaction.oncomplete = () => resolve();
207
+ transaction.onerror = () => {
208
+ invalidate(db);
209
+ reject(transaction.error ?? new Error("Failed to clear replay history."));
210
+ };
211
+ });
212
+ })()
143
213
  });
144
214
  },
145
215
  clearTopic(topic) {
146
- return serializeMutation(async () => {
147
- const db = await open();
148
- await new Promise((resolve, reject) => {
149
- let transaction;
150
- try {
151
- transaction = db.transaction(storeName, "readwrite");
152
- } catch (error) {
153
- invalidate(db);
154
- reject(error);
155
- return;
156
- }
157
- transaction.objectStore(storeName).delete(topic);
158
- transaction.oncomplete = () => resolve();
159
- transaction.onerror = () => {
160
- invalidate(db);
161
- reject(transaction.error ?? new Error("Failed to clear topic replay history."));
162
- };
163
- });
216
+ return enqueue({
217
+ kind: "run",
218
+ run: () => (async () => {
219
+ const db = await open();
220
+ return new Promise((resolve, reject) => {
221
+ let transaction;
222
+ try {
223
+ transaction = db.transaction(storeName, "readwrite");
224
+ } catch (error) {
225
+ invalidate(db);
226
+ reject(error);
227
+ return;
228
+ }
229
+ transaction.objectStore(storeName).delete(topic);
230
+ transaction.oncomplete = () => resolve();
231
+ transaction.onerror = () => {
232
+ invalidate(db);
233
+ reject(transaction.error ?? new Error("Failed to clear topic replay history."));
234
+ };
235
+ });
236
+ })()
164
237
  });
165
238
  },
166
239
  clearBefore(timestamp) {
167
- return serializeMutation(async () => {
168
- const db = await open();
169
- await new Promise((resolve, reject) => {
170
- let transaction;
171
- try {
172
- transaction = db.transaction(storeName, "readwrite");
173
- } catch (error) {
174
- invalidate(db);
175
- reject(error);
176
- return;
177
- }
178
- const store = transaction.objectStore(storeName);
179
- const request = store.getAll();
180
- request.onsuccess = () => {
181
- for (const record of request.result) {
182
- const messages = record.messages.filter((message) => message.timestamp === void 0 || message.timestamp >= timestamp);
183
- if (messages.length === 0) store.delete(record.topic);
184
- else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });
240
+ return enqueue({
241
+ kind: "run",
242
+ run: () => (async () => {
243
+ const db = await open();
244
+ return new Promise((resolve, reject) => {
245
+ let transaction;
246
+ try {
247
+ transaction = db.transaction(storeName, "readwrite");
248
+ } catch (error) {
249
+ invalidate(db);
250
+ reject(error);
251
+ return;
185
252
  }
186
- };
187
- request.onerror = () => {
188
- invalidate(db);
189
- reject(request.error ?? new Error("Failed to read replay history."));
190
- };
191
- transaction.oncomplete = () => resolve();
192
- transaction.onerror = () => {
193
- invalidate(db);
194
- reject(transaction.error ?? new Error("Failed to prune replay history."));
195
- };
196
- });
253
+ const store = transaction.objectStore(storeName);
254
+ const request = store.getAll();
255
+ request.onsuccess = () => {
256
+ for (const record of request.result) {
257
+ const messages = record.messages.filter((message) => message.timestamp === void 0 || message.timestamp >= timestamp);
258
+ if (messages.length === 0) store.delete(record.topic);
259
+ else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });
260
+ }
261
+ };
262
+ request.onerror = () => {
263
+ invalidate(db);
264
+ reject(request.error ?? new Error("Failed to read replay history."));
265
+ };
266
+ transaction.oncomplete = () => resolve();
267
+ transaction.onerror = () => {
268
+ invalidate(db);
269
+ reject(transaction.error ?? new Error("Failed to prune replay history."));
270
+ };
271
+ });
272
+ })()
197
273
  });
198
274
  }
199
275
  };
@@ -205,6 +281,7 @@ var WebSocketTransport = class {
205
281
  constructor(connection) {
206
282
  this.connection = connection;
207
283
  }
284
+ connection;
208
285
  diagnosticsName = "websocket";
209
286
  diagnosticsBackend = "native-websocket";
210
287
  socket = null;
@@ -221,22 +298,22 @@ var WebSocketTransport = class {
221
298
  try {
222
299
  socket = factory(config.url, protocols);
223
300
  } catch (error) {
224
- handlers.onStatus("error");
301
+ handlers.onStatus(WORKER_STATUS.ERROR);
225
302
  handlers.onError(error);
226
303
  return;
227
304
  }
228
305
  socket.onopen = () => {
229
306
  if (this.socket !== socket || this.handlers !== handlers) return;
230
307
  for (const topic of this.subscribedTopics) {
231
- this.sendFrame({ op: "subscribe", topic });
308
+ this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
232
309
  }
233
- handlers.onStatus("connected");
310
+ handlers.onStatus(WORKER_STATUS.CONNECTED);
234
311
  };
235
312
  socket.onclose = () => {
236
- if (this.socket === socket && this.handlers === handlers) handlers.onStatus("disconnected");
313
+ if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.DISCONNECTED);
237
314
  };
238
315
  socket.onerror = () => {
239
- if (this.socket === socket && this.handlers === handlers) handlers.onStatus("error");
316
+ if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.ERROR);
240
317
  };
241
318
  socket.onmessage = (event) => {
242
319
  if (this.socket === socket && this.handlers === handlers) void this.handleMessage(event.data);
@@ -247,12 +324,12 @@ var WebSocketTransport = class {
247
324
  * not duplicate the local tracking entry. */
248
325
  subscribe(topic) {
249
326
  this.subscribedTopics.add(topic);
250
- this.sendFrame({ op: "subscribe", topic });
327
+ this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
251
328
  }
252
329
  /** Idempotent: unsubscribing an unknown topic is a no-op. */
253
330
  unsubscribe(topic) {
254
331
  this.subscribedTopics.delete(topic);
255
- this.sendFrame({ op: "unsubscribe", topic });
332
+ this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });
256
333
  }
257
334
  /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */
258
335
  publish(topic, data, options) {
@@ -261,13 +338,39 @@ var WebSocketTransport = class {
261
338
  return;
262
339
  }
263
340
  this.sendFrame({
264
- op: "publish",
341
+ op: WS_OP.PUBLISH,
265
342
  topic,
266
343
  data,
267
344
  ...options?.messageId === void 0 ? {} : { messageId: options.messageId },
268
345
  ...options?.timestamp === void 0 ? {} : { timestamp: options.timestamp }
269
346
  });
270
347
  }
348
+ /** Publish many items for one topic as a single wire frame. One-item
349
+ * batches delegate to `publish` so the legacy single-publication frame
350
+ * shape (including binary framing) is preserved. */
351
+ publishBatch(topic, items) {
352
+ if (items.length === 0) return;
353
+ if (items.length === 1) {
354
+ const single = items[0];
355
+ return this.publish(topic, single.data, {
356
+ ...single.messageId === void 0 ? {} : { messageId: single.messageId },
357
+ ...single.timestamp === void 0 ? {} : { timestamp: single.timestamp }
358
+ });
359
+ }
360
+ if (this.socket?.readyState !== WS_OPEN) {
361
+ this.handlers?.onError(new Error('WebSocket is not open; dropped "publishBatch" frame.'));
362
+ return;
363
+ }
364
+ this.socket.send(JSON.stringify({
365
+ op: WS_OP.PUBLISH_BATCH,
366
+ topic,
367
+ items: items.map((item) => ({
368
+ data: item.data instanceof ArrayBuffer ? Array.from(new Uint8Array(item.data)) : item.data,
369
+ ...item.messageId === void 0 ? {} : { messageId: item.messageId },
370
+ ...item.timestamp === void 0 ? {} : { timestamp: item.timestamp }
371
+ }))
372
+ }));
373
+ }
271
374
  /** Close the socket and drop all state. Safe to call multiple times. */
272
375
  stop() {
273
376
  const socket = this.socket;
@@ -289,7 +392,7 @@ var WebSocketTransport = class {
289
392
  sendBinaryFrame(topic, data, messageId, timestamp) {
290
393
  if (messageId !== void 0 || timestamp !== void 0) {
291
394
  this.sendFrame({
292
- op: "publish",
395
+ op: WS_OP.PUBLISH,
293
396
  topic,
294
397
  data: Array.from(new Uint8Array(data)),
295
398
  ...messageId === void 0 ? {} : { messageId },
@@ -369,10 +472,13 @@ export {
369
472
  DEFAULT_MAX_ACTIVE_WORKERS,
370
473
  WebSocketTransport,
371
474
  WorkerClusterRuntime,
475
+ approximatePayloadBytes,
372
476
  createBrowserEnvironment,
373
477
  createIndexedDbReplayPersistence,
374
478
  createOpaqueKey,
479
+ createStorageEventChannel,
375
480
  createWebSocketDataBus,
481
+ effectiveWorkerLoad,
376
482
  getOrCreateTabId,
377
483
  hasActiveOwner,
378
484
  isWildcardTopic,
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/core/replay-persistence.ts", "../src/websocket.ts"],
4
- "sourcesContent": ["import type { DataBusMessage } from './types';\n\n/** Optional persistence backend for replay history. */\nexport interface DataBusReplayPersistence<TData = unknown> {\n load(): Promise<ReadonlyArray<DataBusMessage<TData>>>;\n append(message: DataBusMessage<TData>): Promise<void>;\n /** Remove all persisted replay history. */\n clear?(): Promise<void>;\n /** Remove persisted replay history for one exact topic. */\n clearTopic?(topic: string): Promise<void>;\n /** Remove persisted messages older than the given epoch-millisecond cutoff. */\n clearBefore?(timestamp: number): Promise<void>;\n}\n\nexport interface IndexedDbReplayPersistenceOptions {\n dbName?: string;\n maxPerTopic: number;\n pruneStrategy?: 'count' | 'age' | 'both';\n retentionMs?: number;\n}\n\n/** Create a browser IndexedDB-backed replay store. */\nexport function createIndexedDbReplayPersistence<TData = unknown>(\n options: IndexedDbReplayPersistenceOptions\n): DataBusReplayPersistence<TData> {\n const indexedDb = globalThis.indexedDB;\n if (!indexedDb) throw new Error('IndexedDB is unavailable in this environment.');\n const dbName = options.dbName ?? 'cross-tab-worker-databus';\n const storeName = 'replay';\n const maxPerTopic = options.maxPerTopic;\n const pruneStrategy = options.pruneStrategy ?? 'count';\n const retentionMs = options.retentionMs;\n if (!['count', 'age', 'both'].includes(pruneStrategy)) throw new TypeError('pruneStrategy must be count, age, or both.');\n if (retentionMs !== undefined && (!Number.isFinite(retentionMs) || retentionMs <= 0)) throw new TypeError('retentionMs must be a positive finite number.');\n if (!Number.isSafeInteger(maxPerTopic) || maxPerTopic <= 0) {\n throw new TypeError(`maxPerTopic must be a positive safe integer, got ${String(maxPerTopic)}.`);\n }\n let dbPromise: Promise<IDBDatabase> | null = null;\n const invalidate = (db: IDBDatabase): void => {\n if (dbPromise) {\n void dbPromise.then(current => {\n if (current === db) {\n current.close();\n dbPromise = null;\n }\n }, () => undefined);\n }\n };\n // IndexedDB transactions are atomic, but a read-modify-write append can\n // still lose updates when callers start several appends concurrently.\n // Serialize all mutations per adapter instance while keeping reads free.\n let mutationQueue: Promise<void> = Promise.resolve();\n const serializeMutation = (mutation: () => Promise<void>): Promise<void> => {\n const next = mutationQueue.then(mutation, mutation);\n mutationQueue = next.catch(() => undefined);\n return next;\n };\n const open = (): Promise<IDBDatabase> => {\n if (dbPromise) return dbPromise;\n const pending = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDb.open(dbName, 1);\n request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: 'topic' });\n request.onsuccess = () => {\n const db = request.result;\n // A schema upgrade in another tab invalidates this connection. Close\n // it and clear the cached promise so the next operation reopens a\n // usable connection instead of repeatedly targeting a dead database.\n db.onversionchange = () => {\n db.close();\n if (dbPromise) dbPromise = null;\n };\n resolve(db);\n };\n request.onerror = () => reject(request.error ?? new Error('Failed to open replay database.'));\n });\n dbPromise = pending;\n // Do not permanently cache a rejected open promise. IndexedDB can fail\n // transiently (quota, private-mode initialization, a closing connection,\n // or a browser shutdown); the next operation must be able to retry.\n void pending.catch(() => {\n if (dbPromise === pending) dbPromise = null;\n });\n return pending;\n };\n return {\n async load() {\n const db = await open();\n return new Promise((resolve, reject) => {\n let request: IDBRequest;\n try {\n request = db.transaction(storeName, 'readonly').objectStore(storeName).getAll();\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n request.onsuccess = () => resolve((request.result as Array<{ messages: DataBusMessage<TData>[] }>).flatMap(record => record.messages));\n request.onerror = () => {\n invalidate(db);\n reject(request.error ?? new Error('Failed to load replay history.'));\n };\n });\n },\n append(message) {\n return serializeMutation(async () => {\n const db = await open();\n await new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n const store = transaction.objectStore(storeName);\n const request = store.get(message.topic);\n request.onsuccess = () => {\n let messages = ((request.result?.messages ?? []) as DataBusMessage<TData>[]).concat(message);\n if (pruneStrategy !== 'count' && retentionMs !== undefined) {\n const cutoff = Date.now() - retentionMs;\n messages = messages.filter(item => item.timestamp === undefined || item.timestamp >= cutoff);\n }\n if (pruneStrategy !== 'age') messages = messages.slice(-maxPerTopic);\n store.put({ topic: message.topic, messages });\n };\n request.onerror = () => { invalidate(db); reject(request.error ?? new Error('Failed to read replay history.')); };\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to persist replay history.')); };\n });\n });\n },\n clear() {\n return serializeMutation(async () => {\n const db = await open();\n await new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n transaction.objectStore(storeName).clear();\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to clear replay history.')); };\n });\n });\n },\n clearTopic(topic) {\n return serializeMutation(async () => {\n const db = await open();\n await new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n transaction.objectStore(storeName).delete(topic);\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to clear topic replay history.')); };\n });\n });\n },\n clearBefore(timestamp) {\n return serializeMutation(async () => {\n const db = await open();\n await new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n const store = transaction.objectStore(storeName);\n const request = store.getAll();\n request.onsuccess = () => {\n for (const record of request.result as Array<{ topic: string; messages: DataBusMessage<TData>[] }>) {\n const messages = record.messages.filter(message => message.timestamp === undefined || message.timestamp >= timestamp);\n if (messages.length === 0) store.delete(record.topic);\n else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });\n }\n };\n request.onerror = () => { invalidate(db); reject(request.error ?? new Error('Failed to read replay history.')); };\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to prune replay history.')); };\n });\n });\n }\n };\n}\n", "/**\n * WebSocketTransport \u2014 a dependency-free transport over a plain WebSocket.\n *\n * Validates the `DataBusTransport` abstraction with a second, minimal backend:\n * any WebSocket server that speaks the tiny JSON protocol below can back the\n * same cross-tab clustering stack (owner dedup, sticky routes, EVENT fan-out)\n * that the Centrifuge backend uses.\n *\n * Wire protocol (JSON text frames):\n * - client \u2192 server: `{\"op\":\"subscribe\"|\"unsubscribe\"|\"publish\",\"topic\":...,\"data\":...}`\n * - server \u2192 client: `{\"topic\":...,\"data\":...}` for publications; anything\n * without a string `topic` field is ignored (forward-compatible).\n */\nimport { CrossTabDataBus } from './core/data-bus';\nimport { parseDataBusPublication } from './core/publication';\nimport type { CrossTabDataBusOptions } from './core/data-bus';\nimport type {\n DataBusTransport,\n DataBusTransportHandlers,\n DataBusPublishOptions,\n DataBusMessage,\n MaybePromise,\n WorkerStatus\n} from './core/types';\n\n/** Minimal WebSocket surface used by the transport. Matches the browser\n * `WebSocket` subset the transport touches; injectable for tests and runtimes. */\nexport interface WebSocketLike {\n /** Current connection state; 1 (OPEN) means frames may be sent. */\n readonly readyState?: number;\n send(data: string | ArrayBuffer): void;\n close(code?: number, reason?: string): void;\n onopen: (() => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/** Connection configuration for {@link WebSocketTransport}. */\nexport interface WebSocketDataBusConfig {\n /** WebSocket endpoint, e.g. `wss://example.test/ws`. */\n url: string;\n /** Subprotocol(s) passed to the WebSocket handshake. */\n protocols?: string | string[];\n /** Custom socket factory. Defaults to the global `WebSocket`; injectable\n * for tests and non-browser runtimes. */\n webSocketFactory?: (url: string, protocols?: string | string[]) => WebSocketLike;\n}\n\n/** Options for creating a fully-configured CrossTabDataBus with a WebSocket transport. */\nexport interface CreateWebSocketDataBusOptions<TData = unknown>\n extends Omit<\n CrossTabDataBusOptions<WebSocketDataBusConfig, TData>,\n 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'\n > {\n /** WebSocket connection configuration. */\n connection: WebSocketDataBusConfig;\n /** Cluster key for cross-tab coordination. Defaults to the connection URL. */\n clusterKey?: string;\n}\n\nconst WS_OPEN = 1;\n\n/** Transport that talks a minimal JSON protocol over a plain WebSocket.\n * Connection lifecycle maps directly to the DataBus status vocabulary:\n * open \u2192 `connected`, close \u2192 `disconnected`, error \u2192 `error` (which the\n * DataBus treats as its auto-recovery trigger). The transport holds no\n * reconnection logic of its own \u2014 reopening is the DataBus's job. */\nexport class WebSocketTransport<TData = unknown>\n implements DataBusTransport<WebSocketDataBusConfig, TData>\n{\n readonly diagnosticsName = 'websocket';\n readonly diagnosticsBackend = 'native-websocket';\n private socket: WebSocketLike | null = null;\n private handlers: DataBusTransportHandlers<TData> | null = null;\n private readonly subscribedTopics = new Set<string>();\n\n constructor(private readonly connection: WebSocketDataBusConfig) {}\n\n /** Open the WebSocket and wire lifecycle listeners. A factory failure is\n * reported through `onStatus('error')` so the DataBus can recover. */\n start(config: WebSocketDataBusConfig, handlers: DataBusTransportHandlers<TData>): MaybePromise<void> {\n if (this.socket) return;\n this.handlers = handlers;\n // The factory may live on the constructor connection (createWebSocketDataBus\n // path) or on the runtime config (direct transport use) \u2014 accept both.\n const factory = config.webSocketFactory ?? this.connection.webSocketFactory ?? defaultWebSocketFactory;\n const protocols = config.protocols ?? this.connection.protocols;\n let socket: WebSocketLike;\n try {\n socket = factory(config.url, protocols);\n } catch (error) {\n handlers.onStatus('error');\n handlers.onError(error);\n return;\n }\n socket.onopen = () => {\n if (this.socket !== socket || this.handlers !== handlers) return;\n // Re-assert every topic so a reopened socket (recovery path) restores\n // the server-side subscriptions without DataBus involvement.\n for (const topic of this.subscribedTopics) {\n this.sendFrame({ op: 'subscribe', topic });\n }\n handlers.onStatus('connected');\n };\n socket.onclose = () => {\n if (this.socket === socket && this.handlers === handlers) handlers.onStatus('disconnected');\n };\n socket.onerror = () => {\n if (this.socket === socket && this.handlers === handlers) handlers.onStatus('error');\n };\n socket.onmessage = event => {\n if (this.socket === socket && this.handlers === handlers) void this.handleMessage(event.data);\n };\n this.socket = socket;\n }\n\n /** Idempotent: re-subscribing an active topic re-sends the frame but does\n * not duplicate the local tracking entry. */\n subscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.add(topic);\n this.sendFrame({ op: 'subscribe', topic });\n }\n\n /** Idempotent: unsubscribing an unknown topic is a no-op. */\n unsubscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.delete(topic);\n this.sendFrame({ op: 'unsubscribe', topic });\n }\n\n /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): MaybePromise<void> {\n if (data instanceof ArrayBuffer) {\n this.sendBinaryFrame(topic, data, options?.messageId, options?.timestamp);\n return;\n }\n this.sendFrame({\n op: 'publish',\n topic,\n data,\n ...(options?.messageId === undefined ? {} : { messageId: options.messageId }),\n ...(options?.timestamp === undefined ? {} : { timestamp: options.timestamp })\n });\n }\n\n /** Close the socket and drop all state. Safe to call multiple times. */\n stop(): MaybePromise<void> {\n const socket = this.socket;\n this.socket = null;\n this.handlers = null;\n this.subscribedTopics.clear();\n socket?.close();\n }\n\n /** Send one JSON frame. Frames are dropped with an `onError` report when\n * the socket is not open \u2014 subscribe frames are re-sent on open, so the\n * only real loss is a publish during a disconnect window. */\n private sendFrame(payload: { op: string; topic: string; data?: unknown; messageId?: string; timestamp?: number }): void {\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error(`WebSocket is not open; dropped \"${payload.op}\" frame.`));\n return;\n }\n this.socket.send(JSON.stringify(payload));\n }\n\n private sendBinaryFrame(topic: string, data: ArrayBuffer, messageId?: string, timestamp?: number): void {\n if (messageId !== undefined || timestamp !== undefined) {\n // Binary frames retain their compact legacy shape; metadata is sent as a\n // JSON envelope so IDs are never silently lost.\n this.sendFrame({\n op: 'publish',\n topic,\n data: Array.from(new Uint8Array(data)),\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n });\n return;\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publish\" frame.'));\n return;\n }\n const topicBytes = new TextEncoder().encode(topic);\n if (topicBytes.length > 0xffff) {\n this.handlers?.onError(new Error('WebSocket topic is too long for a binary frame.'));\n return;\n }\n const frame = new Uint8Array(3 + topicBytes.length + data.byteLength);\n frame[0] = 0xc7;\n new DataView(frame.buffer).setUint16(1, topicBytes.length);\n frame.set(topicBytes, 3);\n frame.set(new Uint8Array(data), 3 + topicBytes.length);\n this.socket.send(frame.buffer);\n }\n\n /** Parse a server frame. Only objects carrying a string `topic` are\n * publications; malformed JSON and unknown shapes are ignored so a chatty\n * server cannot crash the message path. */\n private async handleMessage(raw: unknown): Promise<void> {\n // Browser WebSockets may deliver binary frames as Blob unless\n // `binaryType = 'arraybuffer'` is explicitly configured by the host.\n // Normalize Blob asynchronously and reuse the exact ArrayBuffer parser.\n if (typeof Blob !== 'undefined' && raw instanceof Blob) {\n try {\n await this.handleMessage(await raw.arrayBuffer());\n } catch (error) {\n this.handlers?.onError(error);\n }\n return;\n }\n let parsed: unknown;\n if (raw instanceof ArrayBuffer) {\n const bytes = new Uint8Array(raw);\n if (bytes[0] !== 0xc7 || bytes.length < 3) return;\n const topicLength = new DataView(raw).getUint16(1);\n if (bytes.length < 3 + topicLength) return;\n const topic = new TextDecoder().decode(bytes.subarray(3, 3 + topicLength));\n const data = bytes.slice(3 + topicLength).buffer;\n this.handlers?.onMessage({ topic, data: data as TData });\n return;\n }\n if (typeof raw !== 'string') return;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.handlers?.onError(new Error('WebSocket server sent a non-JSON frame.'));\n return;\n }\n if (!parsed || typeof parsed !== 'object') return;\n const publication = parseDataBusPublication<TData>(parsed);\n if (publication) this.handlers?.onMessage(publication as DataBusMessage<TData>);\n }\n}\n\n/** Resolve the platform WebSocket, or null in runtimes without one (SSR/Node). */\nfunction defaultWebSocketFactory(url: string, protocols?: string | string[]): WebSocketLike {\n if (typeof WebSocket === 'undefined') {\n throw new Error('WebSocketTransport requires a WebSocket implementation.');\n }\n return new WebSocket(url, protocols) as unknown as WebSocketLike;\n}\n\n/** Create a CrossTabDataBus backed by a plain WebSocket transport.\n * Cross-tab clustering (owner dedup, sticky routes, failover) works identically\n * to the Centrifuge backend \u2014 only the transport I/O differs. */\nexport function createWebSocketDataBus<TData = unknown>(\n options: CreateWebSocketDataBusOptions<TData>\n): CrossTabDataBus<WebSocketDataBusConfig, TData> {\n const { clusterKey, connection, ...dataBusOptions } = options;\n return new CrossTabDataBus({\n ...dataBusOptions,\n autoStart: true,\n clusterKey: clusterKey ?? connection.url,\n initialConfig: connection,\n transport: new WebSocketTransport<TData>(connection)\n });\n}\n\n/** Re-export for convenience: the status type used by the transport. */\nexport type { WorkerStatus };\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import type { DataBusMessage } from './types';\nimport { DEFAULT_STORAGE_PREFIX, PRUNE_STRATEGY } from '../utils/constants';\nimport { assertPositiveFiniteNumber, assertPositiveSafeInteger, assertPruneStrategy } from '../utils/validation';\n\n/** Optional persistence backend for replay history. */\nexport interface DataBusReplayPersistence<TData = unknown> {\n load(): Promise<ReadonlyArray<DataBusMessage<TData>>>;\n append(message: DataBusMessage<TData>): Promise<void>;\n /** Optional bulk append used to amortize IndexedDB transaction overhead. */\n appendBatch?(messages: ReadonlyArray<DataBusMessage<TData>>): Promise<void>;\n /** Remove all persisted replay history. */\n clear?(): Promise<void>;\n /** Remove persisted replay history for one exact topic. */\n clearTopic?(topic: string): Promise<void>;\n /** Remove persisted messages older than the given epoch-millisecond cutoff. */\n clearBefore?(timestamp: number): Promise<void>;\n}\n\nexport interface IndexedDbReplayPersistenceOptions {\n dbName?: string;\n maxPerTopic: number;\n pruneStrategy?: (typeof PRUNE_STRATEGY)[keyof typeof PRUNE_STRATEGY];\n retentionMs?: number;\n}\n\n/** Create a browser IndexedDB-backed replay store. */\nexport function createIndexedDbReplayPersistence<TData = unknown>(\n options: IndexedDbReplayPersistenceOptions\n): DataBusReplayPersistence<TData> {\n const indexedDb = globalThis.indexedDB;\n if (!indexedDb) throw new Error('IndexedDB is unavailable in this environment.');\n const dbName = options.dbName ?? DEFAULT_STORAGE_PREFIX;\n const storeName = 'replay';\n const maxPerTopic = options.maxPerTopic;\n const pruneStrategy = options.pruneStrategy ?? PRUNE_STRATEGY.COUNT;\n const retentionMs = options.retentionMs;\n assertPruneStrategy(pruneStrategy);\n if (retentionMs !== undefined) assertPositiveFiniteNumber(retentionMs, 'retentionMs');\n assertPositiveSafeInteger(maxPerTopic, 'maxPerTopic');\n let dbPromise: Promise<IDBDatabase> | null = null;\n const invalidate = (db: IDBDatabase): void => {\n if (dbPromise) {\n void dbPromise.then(current => {\n if (current === db) {\n current.close();\n dbPromise = null;\n }\n }, () => undefined);\n }\n };\n // IndexedDB transactions are atomic, but a read-modify-write append can\n // still lose updates when callers start several appends concurrently.\n // Serialize all mutations per adapter instance while keeping reads free.\n // Consecutive append/appendBatch entries are coalesced into a single\n // transaction at the head of the queue, so a burst spanning many microtask\n // flushes issues one transaction instead of one per flush. Ordering against\n // clears is preserved: coalescing only merges adjacent batch entries and\n // never reorders them relative to a clear.\n type QueuedMutation =\n | { kind: 'batch'; messages: ReadonlyArray<DataBusMessage<TData>> }\n | { kind: 'run'; run: () => Promise<void> };\n const pending: Array<{\n mutation: QueuedMutation;\n resolve: () => void;\n reject: (error: unknown) => void;\n }> = [];\n let draining = false;\n\n const drain = async (): Promise<void> => {\n if (draining) return;\n draining = true;\n try {\n // Let a burst of synchronous enqueues accumulate into `pending` before\n // the first coalescing pass, so a single flush cycle's batches merge\n // into one transaction instead of two.\n await Promise.resolve();\n while (pending.length > 0) {\n const head = pending[0]!.mutation;\n if (head.kind === 'batch') {\n // Merge every adjacent batch entry into one transaction.\n const merged: DataBusMessage<TData>[] = [];\n const entries: Array<{ resolve: () => void; reject: (error: unknown) => void }> = [];\n while (pending.length > 0) {\n const next = pending[0]!.mutation;\n if (next.kind !== 'batch') break;\n merged.push(...next.messages);\n entries.push({ resolve: pending[0]!.resolve, reject: pending[0]!.reject });\n pending.shift();\n }\n try {\n await appendTransaction(merged);\n for (const entry of entries) entry.resolve();\n } catch (error) {\n for (const entry of entries) entry.reject(error);\n }\n } else {\n const entry = pending.shift()!;\n try {\n await head.run();\n entry.resolve();\n } catch (error) {\n entry.reject(error);\n }\n }\n }\n } finally {\n draining = false;\n }\n };\n\n const enqueue = (mutation: QueuedMutation): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n pending.push({ mutation, resolve, reject });\n void drain();\n });\n\n /** Read-modify-write one topic-batched append inside a single transaction. */\n const appendTransaction = (messages: ReadonlyArray<DataBusMessage<TData>>): Promise<void> =>\n (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try {\n transaction = db.transaction(storeName, 'readwrite');\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n const store = transaction.objectStore(storeName);\n const grouped = new Map<string, DataBusMessage<TData>[]>();\n for (const message of messages) {\n grouped.set(message.topic, [...(grouped.get(message.topic) ?? []), message]);\n }\n let hasError = false;\n for (const [topic, topicMessages] of grouped) {\n const request = store.get(topic);\n request.onsuccess = () => {\n if (hasError) return;\n let history = ((request.result?.messages ?? []) as DataBusMessage<TData>[]).concat(topicMessages);\n // Mirrors ReplayManager: an AGE strategy with no retention window\n // has nothing to prune by, so the count cap still applies (else the\n // stored topic record would grow without bound).\n const ageBounded = pruneStrategy !== PRUNE_STRATEGY.COUNT && retentionMs !== undefined;\n if (ageBounded) {\n const cutoff = Date.now() - retentionMs;\n history = history.filter(item => item.timestamp === undefined || item.timestamp >= cutoff);\n }\n if (pruneStrategy !== PRUNE_STRATEGY.AGE || !ageBounded) history = history.slice(-maxPerTopic);\n store.put({ topic, messages: history });\n };\n request.onerror = () => {\n if (hasError) return;\n hasError = true;\n invalidate(db);\n reject(request.error ?? new Error('Failed to read replay history.'));\n };\n }\n transaction.oncomplete = () => {\n if (!hasError) resolve();\n };\n transaction.onerror = () => {\n if (hasError) return;\n hasError = true;\n invalidate(db);\n reject(transaction.error ?? new Error('Failed to persist replay history.'));\n };\n });\n })();\n const open = (): Promise<IDBDatabase> => {\n if (dbPromise) return dbPromise;\n const pending = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDb.open(dbName, 1);\n request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: 'topic' });\n request.onsuccess = () => {\n const db = request.result;\n // A schema upgrade in another tab invalidates this connection. Close\n // it and clear the cached promise so the next operation reopens a\n // usable connection instead of repeatedly targeting a dead database.\n db.onversionchange = () => {\n db.close();\n if (dbPromise) dbPromise = null;\n };\n resolve(db);\n };\n request.onerror = () => reject(request.error ?? new Error('Failed to open replay database.'));\n });\n dbPromise = pending;\n // Do not permanently cache a rejected open promise. IndexedDB can fail\n // transiently (quota, private-mode initialization, a closing connection,\n // or a browser shutdown); the next operation must be able to retry.\n void pending.catch(() => {\n if (dbPromise === pending) dbPromise = null;\n });\n return pending;\n };\n return {\n async load() {\n const db = await open();\n return new Promise((resolve, reject) => {\n let request: IDBRequest;\n try {\n request = db.transaction(storeName, 'readonly').objectStore(storeName).getAll();\n } catch (error) {\n invalidate(db);\n reject(error);\n return;\n }\n request.onsuccess = () => resolve((request.result as Array<{ messages: DataBusMessage<TData>[] }>).flatMap(record => record.messages));\n request.onerror = () => {\n invalidate(db);\n reject(request.error ?? new Error('Failed to load replay history.'));\n };\n });\n },\n append(message) {\n return enqueue({ kind: 'batch', messages: [message] });\n },\n appendBatch(messages) {\n if (messages.length === 0) return Promise.resolve();\n return enqueue({ kind: 'batch', messages });\n },\n clear() {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n transaction.objectStore(storeName).clear();\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to clear replay history.')); };\n });\n })()\n });\n },\n clearTopic(topic) {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n transaction.objectStore(storeName).delete(topic);\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to clear topic replay history.')); };\n });\n })()\n });\n },\n clearBefore(timestamp) {\n return enqueue({\n kind: 'run',\n run: () => (async () => {\n const db = await open();\n return new Promise<void>((resolve, reject) => {\n let transaction: IDBTransaction;\n try { transaction = db.transaction(storeName, 'readwrite'); }\n catch (error) { invalidate(db); reject(error); return; }\n const store = transaction.objectStore(storeName);\n const request = store.getAll();\n request.onsuccess = () => {\n for (const record of request.result as Array<{ topic: string; messages: DataBusMessage<TData>[] }>) {\n const messages = record.messages.filter(message => message.timestamp === undefined || message.timestamp >= timestamp);\n if (messages.length === 0) store.delete(record.topic);\n else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });\n }\n };\n request.onerror = () => { invalidate(db); reject(request.error ?? new Error('Failed to read replay history.')); };\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => { invalidate(db); reject(transaction.error ?? new Error('Failed to prune replay history.')); };\n });\n })()\n });\n }\n };\n}\n", "/**\n * WebSocketTransport \u2014 a dependency-free transport over a plain WebSocket.\n *\n * Validates the `DataBusTransport` abstraction with a second, minimal backend:\n * any WebSocket server that speaks the tiny JSON protocol below can back the\n * same cross-tab clustering stack (owner dedup, sticky routes, EVENT fan-out)\n * that the Centrifuge backend uses.\n *\n * Wire protocol (JSON text frames):\n * - client \u2192 server: `{\"op\":\"subscribe\"|\"unsubscribe\"|\"publish\",\"topic\":...,\"data\":...}`\n * - client \u2192 server (batched): `{\"op\":\"publishBatch\",\"topic\":...,\"items\":[{data,...}]}`\n * - server \u2192 client: `{\"topic\":...,\"data\":...}` for publications; anything\n * without a string `topic` field is ignored (forward-compatible).\n */\nimport { CrossTabDataBus } from './core/data-bus';\nimport { parseDataBusPublication } from './core/publication';\nimport type { CrossTabDataBusOptions } from './core/data-bus';\nimport { WS_OP, WORKER_STATUS } from './utils/constants';\nimport type {\n DataBusTransport,\n DataBusTransportHandlers,\n DataBusPublishOptions,\n DataBusPublicationItem,\n DataBusMessage,\n MaybePromise,\n WorkerStatus\n} from './core/types';\n\n/** Minimal WebSocket surface used by the transport. Matches the browser\n * `WebSocket` subset the transport touches; injectable for tests and runtimes. */\nexport interface WebSocketLike {\n /** Current connection state; 1 (OPEN) means frames may be sent. */\n readonly readyState?: number;\n send(data: string | ArrayBuffer): void;\n close(code?: number, reason?: string): void;\n onopen: (() => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/** Connection configuration for {@link WebSocketTransport}. */\nexport interface WebSocketDataBusConfig {\n /** WebSocket endpoint, e.g. `wss://example.test/ws`. */\n url: string;\n /** Subprotocol(s) passed to the WebSocket handshake. */\n protocols?: string | string[];\n /** Custom socket factory. Defaults to the global `WebSocket`; injectable\n * for tests and non-browser runtimes. */\n webSocketFactory?: (url: string, protocols?: string | string[]) => WebSocketLike;\n}\n\n/** Options for creating a fully-configured CrossTabDataBus with a WebSocket transport. */\nexport interface CreateWebSocketDataBusOptions<TData = unknown>\n extends Omit<\n CrossTabDataBusOptions<WebSocketDataBusConfig, TData>,\n 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'\n > {\n /** WebSocket connection configuration. */\n connection: WebSocketDataBusConfig;\n /** Cluster key for cross-tab coordination. Defaults to the connection URL. */\n clusterKey?: string;\n}\n\nconst WS_OPEN = 1;\n\n/** Transport that talks a minimal JSON protocol over a plain WebSocket.\n * Connection lifecycle maps directly to the DataBus status vocabulary:\n * open \u2192 `connected`, close \u2192 `disconnected`, error \u2192 `error` (which the\n * DataBus treats as its auto-recovery trigger). The transport holds no\n * reconnection logic of its own \u2014 reopening is the DataBus's job. */\nexport class WebSocketTransport<TData = unknown>\n implements DataBusTransport<WebSocketDataBusConfig, TData>\n{\n readonly diagnosticsName = 'websocket';\n readonly diagnosticsBackend = 'native-websocket';\n private socket: WebSocketLike | null = null;\n private handlers: DataBusTransportHandlers<TData> | null = null;\n private readonly subscribedTopics = new Set<string>();\n\n constructor(private readonly connection: WebSocketDataBusConfig) {}\n\n /** Open the WebSocket and wire lifecycle listeners. A factory failure is\n * reported through `onStatus('error')` so the DataBus can recover. */\n start(config: WebSocketDataBusConfig, handlers: DataBusTransportHandlers<TData>): MaybePromise<void> {\n if (this.socket) return;\n this.handlers = handlers;\n // The factory may live on the constructor connection (createWebSocketDataBus\n // path) or on the runtime config (direct transport use) \u2014 accept both.\n const factory = config.webSocketFactory ?? this.connection.webSocketFactory ?? defaultWebSocketFactory;\n const protocols = config.protocols ?? this.connection.protocols;\n let socket: WebSocketLike;\n try {\n socket = factory(config.url, protocols);\n } catch (error) {\n handlers.onStatus(WORKER_STATUS.ERROR);\n handlers.onError(error);\n return;\n }\n socket.onopen = () => {\n if (this.socket !== socket || this.handlers !== handlers) return;\n // Re-assert every topic so a reopened socket (recovery path) restores\n // the server-side subscriptions without DataBus involvement.\n for (const topic of this.subscribedTopics) {\n this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });\n }\n handlers.onStatus(WORKER_STATUS.CONNECTED);\n };\n socket.onclose = () => {\n if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.DISCONNECTED);\n };\n socket.onerror = () => {\n if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.ERROR);\n };\n socket.onmessage = event => {\n if (this.socket === socket && this.handlers === handlers) void this.handleMessage(event.data);\n };\n this.socket = socket;\n }\n\n /** Idempotent: re-subscribing an active topic re-sends the frame but does\n * not duplicate the local tracking entry. */\n subscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.add(topic);\n this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });\n }\n\n /** Idempotent: unsubscribing an unknown topic is a no-op. */\n unsubscribe(topic: string): MaybePromise<void> {\n this.subscribedTopics.delete(topic);\n this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });\n }\n\n /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): MaybePromise<void> {\n if (data instanceof ArrayBuffer) {\n this.sendBinaryFrame(topic, data, options?.messageId, options?.timestamp);\n return;\n }\n this.sendFrame({\n op: WS_OP.PUBLISH,\n topic,\n data,\n ...(options?.messageId === undefined ? {} : { messageId: options.messageId }),\n ...(options?.timestamp === undefined ? {} : { timestamp: options.timestamp })\n });\n }\n\n /** Publish many items for one topic as a single wire frame. One-item\n * batches delegate to `publish` so the legacy single-publication frame\n * shape (including binary framing) is preserved. */\n publishBatch(topic: string, items: ReadonlyArray<DataBusPublicationItem>): MaybePromise<void> {\n if (items.length === 0) return;\n if (items.length === 1) {\n const single = items[0]!;\n return this.publish(topic, single.data, {\n ...(single.messageId === undefined ? {} : { messageId: single.messageId }),\n ...(single.timestamp === undefined ? {} : { timestamp: single.timestamp })\n });\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publishBatch\" frame.'));\n return;\n }\n // Binary payloads are embedded as byte arrays so the whole batch stays in\n // one JSON frame; the server re-fans them out as individual publications.\n this.socket.send(JSON.stringify({\n op: WS_OP.PUBLISH_BATCH,\n topic,\n items: items.map(item => ({\n data: item.data instanceof ArrayBuffer ? Array.from(new Uint8Array(item.data)) : item.data,\n ...(item.messageId === undefined ? {} : { messageId: item.messageId }),\n ...(item.timestamp === undefined ? {} : { timestamp: item.timestamp })\n }))\n }));\n }\n\n /** Close the socket and drop all state. Safe to call multiple times. */\n stop(): MaybePromise<void> {\n const socket = this.socket;\n this.socket = null;\n this.handlers = null;\n this.subscribedTopics.clear();\n socket?.close();\n }\n\n /** Send one JSON frame. Frames are dropped with an `onError` report when\n * the socket is not open \u2014 subscribe frames are re-sent on open, so the\n * only real loss is a publish during a disconnect window. */\n private sendFrame(payload: { op: string; topic: string; data?: unknown; messageId?: string; timestamp?: number }): void {\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error(`WebSocket is not open; dropped \"${payload.op}\" frame.`));\n return;\n }\n this.socket.send(JSON.stringify(payload));\n }\n\n private sendBinaryFrame(topic: string, data: ArrayBuffer, messageId?: string, timestamp?: number): void {\n if (messageId !== undefined || timestamp !== undefined) {\n // Binary frames retain their compact legacy shape; metadata is sent as a\n // JSON envelope so IDs are never silently lost.\n this.sendFrame({\n op: WS_OP.PUBLISH,\n topic,\n data: Array.from(new Uint8Array(data)),\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n });\n return;\n }\n if (this.socket?.readyState !== WS_OPEN) {\n this.handlers?.onError(new Error('WebSocket is not open; dropped \"publish\" frame.'));\n return;\n }\n const topicBytes = new TextEncoder().encode(topic);\n if (topicBytes.length > 0xffff) {\n this.handlers?.onError(new Error('WebSocket topic is too long for a binary frame.'));\n return;\n }\n const frame = new Uint8Array(3 + topicBytes.length + data.byteLength);\n frame[0] = 0xc7;\n new DataView(frame.buffer).setUint16(1, topicBytes.length);\n frame.set(topicBytes, 3);\n frame.set(new Uint8Array(data), 3 + topicBytes.length);\n this.socket.send(frame.buffer);\n }\n\n /** Parse a server frame. Only objects carrying a string `topic` are\n * publications; malformed JSON and unknown shapes are ignored so a chatty\n * server cannot crash the message path. */\n private async handleMessage(raw: unknown): Promise<void> {\n // Browser WebSockets may deliver binary frames as Blob unless\n // `binaryType = 'arraybuffer'` is explicitly configured by the host.\n // Normalize Blob asynchronously and reuse the exact ArrayBuffer parser.\n if (typeof Blob !== 'undefined' && raw instanceof Blob) {\n try {\n await this.handleMessage(await raw.arrayBuffer());\n } catch (error) {\n this.handlers?.onError(error);\n }\n return;\n }\n let parsed: unknown;\n if (raw instanceof ArrayBuffer) {\n const bytes = new Uint8Array(raw);\n if (bytes[0] !== 0xc7 || bytes.length < 3) return;\n const topicLength = new DataView(raw).getUint16(1);\n if (bytes.length < 3 + topicLength) return;\n const topic = new TextDecoder().decode(bytes.subarray(3, 3 + topicLength));\n const data = bytes.slice(3 + topicLength).buffer;\n this.handlers?.onMessage({ topic, data: data as TData });\n return;\n }\n if (typeof raw !== 'string') return;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.handlers?.onError(new Error('WebSocket server sent a non-JSON frame.'));\n return;\n }\n if (!parsed || typeof parsed !== 'object') return;\n const publication = parseDataBusPublication<TData>(parsed);\n if (publication) this.handlers?.onMessage(publication as DataBusMessage<TData>);\n }\n}\n\n/** Resolve the platform WebSocket, or null in runtimes without one (SSR/Node). */\nfunction defaultWebSocketFactory(url: string, protocols?: string | string[]): WebSocketLike {\n if (typeof WebSocket === 'undefined') {\n throw new Error('WebSocketTransport requires a WebSocket implementation.');\n }\n return new WebSocket(url, protocols) as unknown as WebSocketLike;\n}\n\n/** Create a CrossTabDataBus backed by a plain WebSocket transport.\n * Cross-tab clustering (owner dedup, sticky routes, failover) works identically\n * to the Centrifuge backend \u2014 only the transport I/O differs. */\nexport function createWebSocketDataBus<TData = unknown>(\n options: CreateWebSocketDataBusOptions<TData>\n): CrossTabDataBus<WebSocketDataBusConfig, TData> {\n const { clusterKey, connection, ...dataBusOptions } = options;\n return new CrossTabDataBus({\n ...dataBusOptions,\n autoStart: true,\n clusterKey: clusterKey ?? connection.url,\n initialConfig: connection,\n transport: new WebSocketTransport<TData>(connection)\n });\n}\n\n/** Re-export for convenience: the status type used by the transport. */\nexport type { WorkerStatus };\n"],
5
+ "mappings": "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",
6
+ "names": ["pending"]
7
7
  }