cross-tab-worker-databus 0.20.71 → 0.20.86
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 +216 -2
- package/README.md +8 -0
- package/README.zh.md +4 -0
- package/dist/centrifuge-protocol.d.ts +59 -26
- package/dist/centrifuge-protocol.d.ts.map +1 -1
- package/dist/centrifuge-session.d.ts +9 -0
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.d.ts +12 -2
- package/dist/centrifuge.d.ts.map +1 -1
- package/dist/centrifuge.js +168 -88
- package/dist/centrifuge.js.map +3 -3
- package/dist/centrifuge.shared.worker.js +146 -38
- package/dist/centrifuge.shared.worker.js.map +3 -3
- package/dist/centrifuge.worker.js +140 -33
- package/dist/centrifuge.worker.js.map +3 -3
- package/dist/{chunk-D2SIT473.js → chunk-SDOV3UHG.js} +1304 -515
- package/dist/chunk-SDOV3UHG.js.map +7 -0
- package/dist/chunk-TZ7ZP7YD.js +175 -0
- package/dist/chunk-TZ7ZP7YD.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +1608 -650
- package/dist/cjs/centrifuge.cjs.map +4 -4
- package/dist/cjs/hooks.cjs +37 -2
- package/dist/cjs/hooks.cjs.map +3 -3
- package/dist/cjs/index.cjs +1692 -747
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/cjs/vue.cjs +45 -2
- package/dist/cjs/vue.cjs.map +3 -3
- package/dist/core/cluster.d.ts +40 -4
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +134 -77
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/dedup-manager.d.ts +98 -0
- package/dist/core/dedup-manager.d.ts.map +1 -0
- package/dist/core/environment.d.ts +43 -3
- package/dist/core/environment.d.ts.map +1 -1
- package/dist/core/replay-manager.d.ts +129 -0
- package/dist/core/replay-manager.d.ts.map +1 -0
- package/dist/core/replay-persistence.d.ts +2 -1
- package/dist/core/replay-persistence.d.ts.map +1 -1
- package/dist/core/replay-pruning.d.ts +21 -0
- package/dist/core/replay-pruning.d.ts.map +1 -0
- package/dist/core/routing.d.ts +22 -3
- package/dist/core/routing.d.ts.map +1 -1
- package/dist/core/storage-batch.d.ts +0 -4
- package/dist/core/storage-batch.d.ts.map +1 -1
- package/dist/core/trace.d.ts +55 -15
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/types.d.ts +58 -9
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/hooks.d.ts +12 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +28 -2
- package/dist/hooks.js.map +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +263 -166
- package/dist/index.js.map +3 -3
- package/dist/utils/constants.d.ts +170 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/error-utils.d.ts +28 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/metadata.d.ts +16 -0
- package/dist/utils/metadata.d.ts.map +1 -0
- package/dist/utils/storage-utils.d.ts +24 -0
- package/dist/utils/storage-utils.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +76 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/vue.d.ts +13 -1
- package/dist/vue.d.ts.map +1 -1
- package/dist/vue.js +36 -2
- package/dist/vue.js.map +2 -2
- package/dist/websocket.d.ts +6 -1
- package/dist/websocket.d.ts.map +1 -1
- package/dist/worker-mode.d.ts +6 -2
- package/dist/worker-mode.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/api.md +135 -4
- package/docs/architecture.md +92 -1
- package/docs/benchmarks.md +24 -0
- package/docs/capabilities.md +24 -3
- package/docs/configuration.md +58 -0
- package/docs/getting-started.md +35 -0
- package/docs/release-checklist.md +28 -5
- package/docs/roadmap.md +81 -5
- package/docs/zh/README.md +2 -1
- package/docs/zh/api.md +134 -4
- package/docs/zh/architecture.md +61 -1
- package/docs/zh/benchmarks.md +24 -0
- package/docs/zh/capabilities.md +21 -3
- package/docs/zh/configuration.md +53 -0
- package/docs/zh/getting-started.md +35 -0
- package/docs/zh/release-checklist.md +29 -6
- package/docs/zh/roadmap.md +93 -5
- package/package.json +39 -16
- package/dist/chunk-D2SIT473.js.map +0 -7
package/dist/index.js
CHANGED
|
@@ -2,33 +2,44 @@ 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,
|
|
10
16
|
parseDataBusPublication,
|
|
17
|
+
pruneReplayHistory,
|
|
11
18
|
selectActiveWorkers,
|
|
12
19
|
selectLeastLoadedWorker,
|
|
13
20
|
selectRebalanceTarget,
|
|
14
21
|
selectWorkerBackend,
|
|
15
22
|
topicMatchesPattern
|
|
16
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SDOV3UHG.js";
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_STORAGE_PREFIX,
|
|
26
|
+
PRUNE_STRATEGY,
|
|
27
|
+
WORKER_STATUS,
|
|
28
|
+
WS_OP
|
|
29
|
+
} from "./chunk-TZ7ZP7YD.js";
|
|
17
30
|
|
|
18
31
|
// src/core/replay-persistence.ts
|
|
19
32
|
function createIndexedDbReplayPersistence(options) {
|
|
20
33
|
const indexedDb = globalThis.indexedDB;
|
|
21
34
|
if (!indexedDb) throw new Error("IndexedDB is unavailable in this environment.");
|
|
22
|
-
const dbName = options.dbName ??
|
|
35
|
+
const dbName = options.dbName ?? DEFAULT_STORAGE_PREFIX;
|
|
23
36
|
const storeName = "replay";
|
|
24
37
|
const maxPerTopic = options.maxPerTopic;
|
|
25
|
-
const pruneStrategy = options.pruneStrategy ??
|
|
38
|
+
const pruneStrategy = options.pruneStrategy ?? PRUNE_STRATEGY.COUNT;
|
|
26
39
|
const retentionMs = options.retentionMs;
|
|
27
|
-
|
|
28
|
-
if (retentionMs !== void 0
|
|
29
|
-
|
|
30
|
-
throw new TypeError(`maxPerTopic must be a positive safe integer, got ${String(maxPerTopic)}.`);
|
|
31
|
-
}
|
|
40
|
+
assertPruneStrategy(pruneStrategy);
|
|
41
|
+
if (retentionMs !== void 0) assertPositiveFiniteNumber(retentionMs, "retentionMs");
|
|
42
|
+
assertPositiveSafeInteger(maxPerTopic, "maxPerTopic");
|
|
32
43
|
let dbPromise = null;
|
|
33
44
|
const invalidate = (db) => {
|
|
34
45
|
if (dbPromise) {
|
|
@@ -40,15 +51,94 @@ function createIndexedDbReplayPersistence(options) {
|
|
|
40
51
|
}, () => void 0);
|
|
41
52
|
}
|
|
42
53
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const pending = [];
|
|
55
|
+
let draining = false;
|
|
56
|
+
const drain = async () => {
|
|
57
|
+
if (draining) return;
|
|
58
|
+
draining = true;
|
|
59
|
+
try {
|
|
60
|
+
await Promise.resolve();
|
|
61
|
+
while (pending.length > 0) {
|
|
62
|
+
const head = pending[0].mutation;
|
|
63
|
+
if (head.kind === "batch") {
|
|
64
|
+
const merged = [];
|
|
65
|
+
const entries = [];
|
|
66
|
+
while (pending.length > 0) {
|
|
67
|
+
const next = pending[0].mutation;
|
|
68
|
+
if (next.kind !== "batch") break;
|
|
69
|
+
merged.push(...next.messages);
|
|
70
|
+
entries.push({ resolve: pending[0].resolve, reject: pending[0].reject });
|
|
71
|
+
pending.shift();
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await appendTransaction(merged);
|
|
75
|
+
for (const entry of entries) entry.resolve();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
for (const entry of entries) entry.reject(error);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
const entry = pending.shift();
|
|
81
|
+
try {
|
|
82
|
+
await head.run();
|
|
83
|
+
entry.resolve();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
entry.reject(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
draining = false;
|
|
91
|
+
}
|
|
48
92
|
};
|
|
93
|
+
const enqueue = (mutation) => new Promise((resolve, reject) => {
|
|
94
|
+
pending.push({ mutation, resolve, reject });
|
|
95
|
+
void drain();
|
|
96
|
+
});
|
|
97
|
+
const appendTransaction = (messages) => (async () => {
|
|
98
|
+
const db = await open();
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
let transaction;
|
|
101
|
+
try {
|
|
102
|
+
transaction = db.transaction(storeName, "readwrite");
|
|
103
|
+
} catch (error) {
|
|
104
|
+
invalidate(db);
|
|
105
|
+
reject(error);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const store = transaction.objectStore(storeName);
|
|
109
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const message of messages) {
|
|
111
|
+
grouped.set(message.topic, [...grouped.get(message.topic) ?? [], message]);
|
|
112
|
+
}
|
|
113
|
+
let hasError = false;
|
|
114
|
+
const fail = (error) => {
|
|
115
|
+
if (hasError) return;
|
|
116
|
+
hasError = true;
|
|
117
|
+
invalidate(db);
|
|
118
|
+
reject(error);
|
|
119
|
+
};
|
|
120
|
+
for (const [topic, topicMessages] of grouped) {
|
|
121
|
+
const request = store.get(topic);
|
|
122
|
+
request.onsuccess = () => {
|
|
123
|
+
if (hasError) return;
|
|
124
|
+
const history = pruneReplayHistory(
|
|
125
|
+
(request.result?.messages ?? []).concat(topicMessages),
|
|
126
|
+
{ maxPerTopic, pruneStrategy, retentionMs, now: Date.now() }
|
|
127
|
+
);
|
|
128
|
+
store.put({ topic, messages: history });
|
|
129
|
+
};
|
|
130
|
+
request.onerror = () => fail(request.error ?? new Error("Failed to read replay history."));
|
|
131
|
+
}
|
|
132
|
+
transaction.oncomplete = () => {
|
|
133
|
+
if (!hasError) resolve();
|
|
134
|
+
};
|
|
135
|
+
transaction.onerror = () => fail(transaction.error ?? new Error("Failed to persist replay history."));
|
|
136
|
+
transaction.onabort = () => fail(transaction.error ?? new Error("Failed to persist replay history."));
|
|
137
|
+
});
|
|
138
|
+
})();
|
|
49
139
|
const open = () => {
|
|
50
140
|
if (dbPromise) return dbPromise;
|
|
51
|
-
const
|
|
141
|
+
const pending2 = new Promise((resolve, reject) => {
|
|
52
142
|
const request = indexedDb.open(dbName, 1);
|
|
53
143
|
request.onupgradeneeded = () => request.result.createObjectStore(storeName, { keyPath: "topic" });
|
|
54
144
|
request.onsuccess = () => {
|
|
@@ -61,179 +151,156 @@ function createIndexedDbReplayPersistence(options) {
|
|
|
61
151
|
};
|
|
62
152
|
request.onerror = () => reject(request.error ?? new Error("Failed to open replay database."));
|
|
63
153
|
});
|
|
64
|
-
dbPromise =
|
|
65
|
-
void
|
|
66
|
-
if (dbPromise ===
|
|
154
|
+
dbPromise = pending2;
|
|
155
|
+
void pending2.catch(() => {
|
|
156
|
+
if (dbPromise === pending2) dbPromise = null;
|
|
67
157
|
});
|
|
68
|
-
return
|
|
158
|
+
return pending2;
|
|
69
159
|
};
|
|
70
160
|
return {
|
|
71
161
|
async load() {
|
|
72
162
|
const db = await open();
|
|
73
163
|
return new Promise((resolve, reject) => {
|
|
164
|
+
let transaction;
|
|
74
165
|
let request;
|
|
75
166
|
try {
|
|
76
|
-
|
|
167
|
+
transaction = db.transaction(storeName, "readonly");
|
|
168
|
+
request = transaction.objectStore(storeName).getAll();
|
|
77
169
|
} catch (error) {
|
|
78
170
|
invalidate(db);
|
|
79
171
|
reject(error);
|
|
80
172
|
return;
|
|
81
173
|
}
|
|
82
|
-
|
|
83
|
-
|
|
174
|
+
let settled = false;
|
|
175
|
+
const fail = (error) => {
|
|
176
|
+
if (settled) return;
|
|
177
|
+
settled = true;
|
|
84
178
|
invalidate(db);
|
|
85
|
-
reject(
|
|
179
|
+
reject(error);
|
|
86
180
|
};
|
|
181
|
+
let records = [];
|
|
182
|
+
request.onsuccess = () => {
|
|
183
|
+
records = request.result;
|
|
184
|
+
};
|
|
185
|
+
request.onerror = () => fail(request.error ?? new Error("Failed to load replay history."));
|
|
186
|
+
transaction.oncomplete = () => {
|
|
187
|
+
if (settled) return;
|
|
188
|
+
settled = true;
|
|
189
|
+
resolve(records.flatMap((record) => record.messages));
|
|
190
|
+
};
|
|
191
|
+
transaction.onabort = () => fail(transaction.error ?? new Error("Failed to load replay history."));
|
|
87
192
|
});
|
|
88
193
|
},
|
|
89
194
|
append(message) {
|
|
90
|
-
return
|
|
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
|
-
});
|
|
195
|
+
return enqueue({ kind: "batch", messages: [message] });
|
|
123
196
|
},
|
|
124
197
|
appendBatch(messages) {
|
|
125
198
|
if (messages.length === 0) return Promise.resolve();
|
|
126
|
-
return
|
|
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
|
-
});
|
|
199
|
+
return enqueue({ kind: "batch", messages });
|
|
163
200
|
},
|
|
164
201
|
clear() {
|
|
165
|
-
return
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
return enqueue({
|
|
203
|
+
kind: "run",
|
|
204
|
+
run: () => (async () => {
|
|
205
|
+
const db = await open();
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
let transaction;
|
|
208
|
+
try {
|
|
209
|
+
transaction = db.transaction(storeName, "readwrite");
|
|
210
|
+
} catch (error) {
|
|
211
|
+
invalidate(db);
|
|
212
|
+
reject(error);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let settled = false;
|
|
216
|
+
const fail = (error) => {
|
|
217
|
+
if (settled) return;
|
|
218
|
+
settled = true;
|
|
219
|
+
invalidate(db);
|
|
220
|
+
reject(error);
|
|
221
|
+
};
|
|
222
|
+
transaction.objectStore(storeName).clear();
|
|
223
|
+
transaction.oncomplete = () => {
|
|
224
|
+
settled = true;
|
|
225
|
+
resolve();
|
|
226
|
+
};
|
|
227
|
+
transaction.onerror = () => fail(transaction.error ?? new Error("Failed to clear replay history."));
|
|
228
|
+
transaction.onabort = () => fail(transaction.error ?? new Error("Failed to clear replay history."));
|
|
229
|
+
});
|
|
230
|
+
})()
|
|
183
231
|
});
|
|
184
232
|
},
|
|
185
233
|
clearTopic(topic) {
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
234
|
+
return enqueue({
|
|
235
|
+
kind: "run",
|
|
236
|
+
run: () => (async () => {
|
|
237
|
+
const db = await open();
|
|
238
|
+
return new Promise((resolve, reject) => {
|
|
239
|
+
let transaction;
|
|
240
|
+
try {
|
|
241
|
+
transaction = db.transaction(storeName, "readwrite");
|
|
242
|
+
} catch (error) {
|
|
243
|
+
invalidate(db);
|
|
244
|
+
reject(error);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
let settled = false;
|
|
248
|
+
const fail = (error) => {
|
|
249
|
+
if (settled) return;
|
|
250
|
+
settled = true;
|
|
251
|
+
invalidate(db);
|
|
252
|
+
reject(error);
|
|
253
|
+
};
|
|
254
|
+
transaction.objectStore(storeName).delete(topic);
|
|
255
|
+
transaction.oncomplete = () => {
|
|
256
|
+
settled = true;
|
|
257
|
+
resolve();
|
|
258
|
+
};
|
|
259
|
+
transaction.onerror = () => fail(transaction.error ?? new Error("Failed to clear topic replay history."));
|
|
260
|
+
transaction.onabort = () => fail(transaction.error ?? new Error("Failed to clear topic replay history."));
|
|
261
|
+
});
|
|
262
|
+
})()
|
|
204
263
|
});
|
|
205
264
|
},
|
|
206
265
|
clearBefore(timestamp) {
|
|
207
|
-
return
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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 });
|
|
266
|
+
return enqueue({
|
|
267
|
+
kind: "run",
|
|
268
|
+
run: () => (async () => {
|
|
269
|
+
const db = await open();
|
|
270
|
+
return new Promise((resolve, reject) => {
|
|
271
|
+
let transaction;
|
|
272
|
+
try {
|
|
273
|
+
transaction = db.transaction(storeName, "readwrite");
|
|
274
|
+
} catch (error) {
|
|
275
|
+
invalidate(db);
|
|
276
|
+
reject(error);
|
|
277
|
+
return;
|
|
225
278
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
279
|
+
let settled = false;
|
|
280
|
+
const fail = (error) => {
|
|
281
|
+
if (settled) return;
|
|
282
|
+
settled = true;
|
|
283
|
+
invalidate(db);
|
|
284
|
+
reject(error);
|
|
285
|
+
};
|
|
286
|
+
const store = transaction.objectStore(storeName);
|
|
287
|
+
const request = store.getAll();
|
|
288
|
+
request.onsuccess = () => {
|
|
289
|
+
for (const record of request.result) {
|
|
290
|
+
const messages = record.messages.filter((message) => message.timestamp === void 0 || message.timestamp >= timestamp);
|
|
291
|
+
if (messages.length === 0) store.delete(record.topic);
|
|
292
|
+
else if (messages.length !== record.messages.length) store.put({ topic: record.topic, messages });
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
request.onerror = () => fail(request.error ?? new Error("Failed to read replay history."));
|
|
296
|
+
transaction.oncomplete = () => {
|
|
297
|
+
settled = true;
|
|
298
|
+
resolve();
|
|
299
|
+
};
|
|
300
|
+
transaction.onerror = () => fail(transaction.error ?? new Error("Failed to prune replay history."));
|
|
301
|
+
transaction.onabort = () => fail(transaction.error ?? new Error("Failed to prune replay history."));
|
|
302
|
+
});
|
|
303
|
+
})()
|
|
237
304
|
});
|
|
238
305
|
}
|
|
239
306
|
};
|
|
@@ -245,6 +312,7 @@ var WebSocketTransport = class {
|
|
|
245
312
|
constructor(connection) {
|
|
246
313
|
this.connection = connection;
|
|
247
314
|
}
|
|
315
|
+
connection;
|
|
248
316
|
diagnosticsName = "websocket";
|
|
249
317
|
diagnosticsBackend = "native-websocket";
|
|
250
318
|
socket = null;
|
|
@@ -261,22 +329,22 @@ var WebSocketTransport = class {
|
|
|
261
329
|
try {
|
|
262
330
|
socket = factory(config.url, protocols);
|
|
263
331
|
} catch (error) {
|
|
264
|
-
handlers.onStatus(
|
|
332
|
+
handlers.onStatus(WORKER_STATUS.ERROR);
|
|
265
333
|
handlers.onError(error);
|
|
266
334
|
return;
|
|
267
335
|
}
|
|
268
336
|
socket.onopen = () => {
|
|
269
337
|
if (this.socket !== socket || this.handlers !== handlers) return;
|
|
270
338
|
for (const topic of this.subscribedTopics) {
|
|
271
|
-
this.sendFrame({ op:
|
|
339
|
+
this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
|
|
272
340
|
}
|
|
273
|
-
handlers.onStatus(
|
|
341
|
+
handlers.onStatus(WORKER_STATUS.CONNECTED);
|
|
274
342
|
};
|
|
275
343
|
socket.onclose = () => {
|
|
276
|
-
if (this.socket === socket && this.handlers === handlers) handlers.onStatus(
|
|
344
|
+
if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.DISCONNECTED);
|
|
277
345
|
};
|
|
278
346
|
socket.onerror = () => {
|
|
279
|
-
if (this.socket === socket && this.handlers === handlers) handlers.onStatus(
|
|
347
|
+
if (this.socket === socket && this.handlers === handlers) handlers.onStatus(WORKER_STATUS.ERROR);
|
|
280
348
|
};
|
|
281
349
|
socket.onmessage = (event) => {
|
|
282
350
|
if (this.socket === socket && this.handlers === handlers) void this.handleMessage(event.data);
|
|
@@ -287,12 +355,12 @@ var WebSocketTransport = class {
|
|
|
287
355
|
* not duplicate the local tracking entry. */
|
|
288
356
|
subscribe(topic) {
|
|
289
357
|
this.subscribedTopics.add(topic);
|
|
290
|
-
this.sendFrame({ op:
|
|
358
|
+
this.sendFrame({ op: WS_OP.SUBSCRIBE, topic });
|
|
291
359
|
}
|
|
292
360
|
/** Idempotent: unsubscribing an unknown topic is a no-op. */
|
|
293
361
|
unsubscribe(topic) {
|
|
294
362
|
this.subscribedTopics.delete(topic);
|
|
295
|
-
this.sendFrame({ op:
|
|
363
|
+
this.sendFrame({ op: WS_OP.UNSUBSCRIBE, topic });
|
|
296
364
|
}
|
|
297
365
|
/** Publish `data` to `topic` as a JSON frame. Requires an open socket. */
|
|
298
366
|
publish(topic, data, options) {
|
|
@@ -301,13 +369,39 @@ var WebSocketTransport = class {
|
|
|
301
369
|
return;
|
|
302
370
|
}
|
|
303
371
|
this.sendFrame({
|
|
304
|
-
op:
|
|
372
|
+
op: WS_OP.PUBLISH,
|
|
305
373
|
topic,
|
|
306
374
|
data,
|
|
307
375
|
...options?.messageId === void 0 ? {} : { messageId: options.messageId },
|
|
308
376
|
...options?.timestamp === void 0 ? {} : { timestamp: options.timestamp }
|
|
309
377
|
});
|
|
310
378
|
}
|
|
379
|
+
/** Publish many items for one topic as a single wire frame. One-item
|
|
380
|
+
* batches delegate to `publish` so the legacy single-publication frame
|
|
381
|
+
* shape (including binary framing) is preserved. */
|
|
382
|
+
publishBatch(topic, items) {
|
|
383
|
+
if (items.length === 0) return;
|
|
384
|
+
if (items.length === 1) {
|
|
385
|
+
const single = items[0];
|
|
386
|
+
return this.publish(topic, single.data, {
|
|
387
|
+
...single.messageId === void 0 ? {} : { messageId: single.messageId },
|
|
388
|
+
...single.timestamp === void 0 ? {} : { timestamp: single.timestamp }
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (this.socket?.readyState !== WS_OPEN) {
|
|
392
|
+
this.handlers?.onError(new Error('WebSocket is not open; dropped "publishBatch" frame.'));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
this.socket.send(JSON.stringify({
|
|
396
|
+
op: WS_OP.PUBLISH_BATCH,
|
|
397
|
+
topic,
|
|
398
|
+
items: items.map((item) => ({
|
|
399
|
+
data: item.data instanceof ArrayBuffer ? Array.from(new Uint8Array(item.data)) : item.data,
|
|
400
|
+
...item.messageId === void 0 ? {} : { messageId: item.messageId },
|
|
401
|
+
...item.timestamp === void 0 ? {} : { timestamp: item.timestamp }
|
|
402
|
+
}))
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
311
405
|
/** Close the socket and drop all state. Safe to call multiple times. */
|
|
312
406
|
stop() {
|
|
313
407
|
const socket = this.socket;
|
|
@@ -329,7 +423,7 @@ var WebSocketTransport = class {
|
|
|
329
423
|
sendBinaryFrame(topic, data, messageId, timestamp) {
|
|
330
424
|
if (messageId !== void 0 || timestamp !== void 0) {
|
|
331
425
|
this.sendFrame({
|
|
332
|
-
op:
|
|
426
|
+
op: WS_OP.PUBLISH,
|
|
333
427
|
topic,
|
|
334
428
|
data: Array.from(new Uint8Array(data)),
|
|
335
429
|
...messageId === void 0 ? {} : { messageId },
|
|
@@ -409,10 +503,13 @@ export {
|
|
|
409
503
|
DEFAULT_MAX_ACTIVE_WORKERS,
|
|
410
504
|
WebSocketTransport,
|
|
411
505
|
WorkerClusterRuntime,
|
|
506
|
+
approximatePayloadBytes,
|
|
412
507
|
createBrowserEnvironment,
|
|
413
508
|
createIndexedDbReplayPersistence,
|
|
414
509
|
createOpaqueKey,
|
|
510
|
+
createStorageEventChannel,
|
|
415
511
|
createWebSocketDataBus,
|
|
512
|
+
effectiveWorkerLoad,
|
|
416
513
|
getOrCreateTabId,
|
|
417
514
|
hasActiveOwner,
|
|
418
515
|
isWildcardTopic,
|