cross-tab-worker-databus 0.20.71 → 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 +204 -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-D2SIT473.js → chunk-PW63EWIK.js} +1090 -494
  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 +1403 -637
  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 +1453 -730
  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 -69
  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 +2 -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 +228 -162
  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 +74 -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 +86 -5
  94. package/package.json +39 -16
  95. package/dist/chunk-D2SIT473.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-D2SIT473.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,153 +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] });
123
186
  },
124
187
  appendBatch(messages) {
125
188
  if (messages.length === 0) return Promise.resolve();
126
- return serializeMutation(async () => {
127
- const db = await open();
128
- await new Promise((resolve, reject) => {
129
- let transaction;
130
- try {
131
- transaction = db.transaction(storeName, "readwrite");
132
- } catch (error) {
133
- invalidate(db);
134
- reject(error);
135
- return;
136
- }
137
- const store = transaction.objectStore(storeName);
138
- const grouped = /* @__PURE__ */ new Map();
139
- for (const message of messages) grouped.set(message.topic, [...grouped.get(message.topic) ?? [], message]);
140
- for (const [topic, topicMessages] of grouped) {
141
- const request = store.get(topic);
142
- request.onsuccess = () => {
143
- let history = (request.result?.messages ?? []).concat(topicMessages);
144
- if (pruneStrategy !== "count" && retentionMs !== void 0) {
145
- const cutoff = Date.now() - retentionMs;
146
- history = history.filter((item) => item.timestamp === void 0 || item.timestamp >= cutoff);
147
- }
148
- if (pruneStrategy !== "age") history = history.slice(-maxPerTopic);
149
- store.put({ topic, messages: history });
150
- };
151
- request.onerror = () => {
152
- invalidate(db);
153
- reject(request.error ?? new Error("Failed to read replay history."));
154
- };
155
- }
156
- transaction.oncomplete = () => resolve();
157
- transaction.onerror = () => {
158
- invalidate(db);
159
- reject(transaction.error ?? new Error("Failed to persist replay history batch."));
160
- };
161
- });
162
- });
189
+ return enqueue({ kind: "batch", messages });
163
190
  },
164
191
  clear() {
165
- return serializeMutation(async () => {
166
- const db = await open();
167
- await new Promise((resolve, reject) => {
168
- let transaction;
169
- try {
170
- transaction = db.transaction(storeName, "readwrite");
171
- } catch (error) {
172
- invalidate(db);
173
- reject(error);
174
- return;
175
- }
176
- transaction.objectStore(storeName).clear();
177
- transaction.oncomplete = () => resolve();
178
- transaction.onerror = () => {
179
- invalidate(db);
180
- reject(transaction.error ?? new Error("Failed to clear replay history."));
181
- };
182
- });
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
+ })()
183
213
  });
184
214
  },
185
215
  clearTopic(topic) {
186
- return serializeMutation(async () => {
187
- const db = await open();
188
- await new Promise((resolve, reject) => {
189
- let transaction;
190
- try {
191
- transaction = db.transaction(storeName, "readwrite");
192
- } catch (error) {
193
- invalidate(db);
194
- reject(error);
195
- return;
196
- }
197
- transaction.objectStore(storeName).delete(topic);
198
- transaction.oncomplete = () => resolve();
199
- transaction.onerror = () => {
200
- invalidate(db);
201
- reject(transaction.error ?? new Error("Failed to clear topic replay history."));
202
- };
203
- });
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
+ })()
204
237
  });
205
238
  },
206
239
  clearBefore(timestamp) {
207
- return serializeMutation(async () => {
208
- const db = await open();
209
- await new Promise((resolve, reject) => {
210
- let transaction;
211
- try {
212
- transaction = db.transaction(storeName, "readwrite");
213
- } catch (error) {
214
- invalidate(db);
215
- reject(error);
216
- return;
217
- }
218
- const store = transaction.objectStore(storeName);
219
- const request = store.getAll();
220
- request.onsuccess = () => {
221
- for (const record of request.result) {
222
- const messages = record.messages.filter((message) => message.timestamp === void 0 || message.timestamp >= timestamp);
223
- if (messages.length === 0) store.delete(record.topic);
224
- 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;
225
252
  }
226
- };
227
- request.onerror = () => {
228
- invalidate(db);
229
- reject(request.error ?? new Error("Failed to read replay history."));
230
- };
231
- transaction.oncomplete = () => resolve();
232
- transaction.onerror = () => {
233
- invalidate(db);
234
- reject(transaction.error ?? new Error("Failed to prune replay history."));
235
- };
236
- });
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
+ })()
237
273
  });
238
274
  }
239
275
  };
@@ -245,6 +281,7 @@ var WebSocketTransport = class {
245
281
  constructor(connection) {
246
282
  this.connection = connection;
247
283
  }
284
+ connection;
248
285
  diagnosticsName = "websocket";
249
286
  diagnosticsBackend = "native-websocket";
250
287
  socket = null;
@@ -261,22 +298,22 @@ var WebSocketTransport = class {
261
298
  try {
262
299
  socket = factory(config.url, protocols);
263
300
  } catch (error) {
264
- handlers.onStatus("error");
301
+ handlers.onStatus(WORKER_STATUS.ERROR);
265
302
  handlers.onError(error);
266
303
  return;
267
304
  }
268
305
  socket.onopen = () => {
269
306
  if (this.socket !== socket || this.handlers !== handlers) return;
270
307
  for (const topic of this.subscribedTopics) {
271
- this.sendFrame({ op: "subscribe", topic });
308
+ this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
272
309
  }
273
- handlers.onStatus("connected");
310
+ handlers.onStatus(WORKER_STATUS.CONNECTED);
274
311
  };
275
312
  socket.onclose = () => {
276
- if (this.socket === socket && this.handlers === handlers) handlers.onStatus("disconnected");
313
+ if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.DISCONNECTED);
277
314
  };
278
315
  socket.onerror = () => {
279
- if (this.socket === socket && this.handlers === handlers) handlers.onStatus("error");
316
+ if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.ERROR);
280
317
  };
281
318
  socket.onmessage = (event) => {
282
319
  if (this.socket === socket && this.handlers === handlers) void this.handleMessage(event.data);
@@ -287,12 +324,12 @@ var WebSocketTransport = class {
287
324
  * not duplicate the local tracking entry. */
288
325
  subscribe(topic) {
289
326
  this.subscribedTopics.add(topic);
290
- this.sendFrame({ op: "subscribe", topic });
327
+ this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
291
328
  }
292
329
  /** Idempotent: unsubscribing an unknown topic is a no-op. */
293
330
  unsubscribe(topic) {
294
331
  this.subscribedTopics.delete(topic);
295
- this.sendFrame({ op: "unsubscribe", topic });
332
+ this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });
296
333
  }
297
334
  /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */
298
335
  publish(topic, data, options) {
@@ -301,13 +338,39 @@ var WebSocketTransport = class {
301
338
  return;
302
339
  }
303
340
  this.sendFrame({
304
- op: "publish",
341
+ op: WS_OP.PUBLISH,
305
342
  topic,
306
343
  data,
307
344
  ...options?.messageId === void 0 ? {} : { messageId: options.messageId },
308
345
  ...options?.timestamp === void 0 ? {} : { timestamp: options.timestamp }
309
346
  });
310
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
+ }
311
374
  /** Close the socket and drop all state. Safe to call multiple times. */
312
375
  stop() {
313
376
  const socket = this.socket;
@@ -329,7 +392,7 @@ var WebSocketTransport = class {
329
392
  sendBinaryFrame(topic, data, messageId, timestamp) {
330
393
  if (messageId !== void 0 || timestamp !== void 0) {
331
394
  this.sendFrame({
332
- op: "publish",
395
+ op: WS_OP.PUBLISH,
333
396
  topic,
334
397
  data: Array.from(new Uint8Array(data)),
335
398
  ...messageId === void 0 ? {} : { messageId },
@@ -409,10 +472,13 @@ export {
409
472
  DEFAULT_MAX_ACTIVE_WORKERS,
410
473
  WebSocketTransport,
411
474
  WorkerClusterRuntime,
475
+ approximatePayloadBytes,
412
476
  createBrowserEnvironment,
413
477
  createIndexedDbReplayPersistence,
414
478
  createOpaqueKey,
479
+ createStorageEventChannel,
415
480
  createWebSocketDataBus,
481
+ effectiveWorkerLoad,
416
482
  getOrCreateTabId,
417
483
  hasActiveOwner,
418
484
  isWildcardTopic,