cross-tab-worker-databus 0.20.63 → 0.20.65
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 +12 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-ZHCGYGL3.js → chunk-KKSZ5GUC.js} +59 -4
- package/dist/{chunk-ZHCGYGL3.js.map → chunk-KKSZ5GUC.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +58 -3
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +58 -3
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/data-bus.d.ts +14 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/trace.d.ts +6 -0
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/roadmap.md +9 -1
- package/docs/zh/roadmap.md +9 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.65] - 2026-09-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added opt-in adaptive dedup TTL sampling with bounded `minMs`/`maxMs` controls and diagnostics exposure.
|
|
7
|
+
- Added replay `pruneStrategy` configuration (`count`, `age`, `both`) for memory history trimming while preserving legacy defaults.
|
|
8
|
+
|
|
9
|
+
## [0.20.64] - 2026-09-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Added opt-in `asyncSink` trace mode, batching sink delivery onto a microtask while preserving event order and sink error isolation.
|
|
13
|
+
- Added regression coverage for asynchronous trace batching (339 unit tests).
|
|
14
|
+
|
|
3
15
|
## [0.20.63] - 2026-09-05
|
|
4
16
|
|
|
5
17
|
### Added
|
package/dist/centrifuge.js
CHANGED
|
@@ -1158,6 +1158,9 @@ var DataBusTraceReporter = class {
|
|
|
1158
1158
|
metricsIntervalMs;
|
|
1159
1159
|
sink;
|
|
1160
1160
|
now;
|
|
1161
|
+
asyncSink;
|
|
1162
|
+
pendingEvents = [];
|
|
1163
|
+
sinkFlushScheduled = false;
|
|
1161
1164
|
intervalHandle = null;
|
|
1162
1165
|
intervalStartedAt = 0;
|
|
1163
1166
|
// A reporter may be flushed explicitly before start(), but once stop() is
|
|
@@ -1179,6 +1182,7 @@ var DataBusTraceReporter = class {
|
|
|
1179
1182
|
this.mode = options?.mode ?? "all";
|
|
1180
1183
|
this.metricsIntervalMs = normalizeInterval(options?.metricsIntervalMs);
|
|
1181
1184
|
this.sink = options?.sink ?? (() => void 0);
|
|
1185
|
+
this.asyncSink = options?.asyncSink ?? false;
|
|
1182
1186
|
this.now = now;
|
|
1183
1187
|
}
|
|
1184
1188
|
/** Start the periodic metrics flush interval. No-op when mode is 'events'
|
|
@@ -1305,6 +1309,22 @@ var DataBusTraceReporter = class {
|
|
|
1305
1309
|
this.dedupSuppressed = 0;
|
|
1306
1310
|
}
|
|
1307
1311
|
emit(event) {
|
|
1312
|
+
if (this.asyncSink) {
|
|
1313
|
+
this.pendingEvents.push(event);
|
|
1314
|
+
if (!this.sinkFlushScheduled) {
|
|
1315
|
+
this.sinkFlushScheduled = true;
|
|
1316
|
+
queueMicrotask(() => {
|
|
1317
|
+
this.sinkFlushScheduled = false;
|
|
1318
|
+
const events = this.pendingEvents;
|
|
1319
|
+
this.pendingEvents = [];
|
|
1320
|
+
for (const queued of events) this.emitSync(queued);
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
this.emitSync(event);
|
|
1326
|
+
}
|
|
1327
|
+
emitSync(event) {
|
|
1308
1328
|
try {
|
|
1309
1329
|
this.sink(event);
|
|
1310
1330
|
} catch (error) {
|
|
@@ -1360,6 +1380,7 @@ var CrossTabDataBus = class {
|
|
|
1360
1380
|
replayMaxPerTopic;
|
|
1361
1381
|
replayPersistence;
|
|
1362
1382
|
replayRetentionMs;
|
|
1383
|
+
replayPruneStrategy;
|
|
1363
1384
|
replayRetentionSweepMs;
|
|
1364
1385
|
persistenceRetryMaxAttempts;
|
|
1365
1386
|
persistenceRetryBackoffMs;
|
|
@@ -1375,6 +1396,10 @@ var CrossTabDataBus = class {
|
|
|
1375
1396
|
trace;
|
|
1376
1397
|
dedupMaxEntries;
|
|
1377
1398
|
dedupTtlMs;
|
|
1399
|
+
dedupAdaptiveBounds;
|
|
1400
|
+
dedupWindowStartedAt = 0;
|
|
1401
|
+
dedupWindowAccepted = 0;
|
|
1402
|
+
dedupAdaptiveWindowMs = 5e3;
|
|
1378
1403
|
dedupSweepMs;
|
|
1379
1404
|
dedupSweepTimer = null;
|
|
1380
1405
|
dedupEnabled;
|
|
@@ -1433,6 +1458,8 @@ var CrossTabDataBus = class {
|
|
|
1433
1458
|
this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
|
|
1434
1459
|
this.replayPersistence = replay?.persistence ?? null;
|
|
1435
1460
|
this.replayRetentionMs = replay?.retentionMs;
|
|
1461
|
+
this.replayPruneStrategy = replay?.pruneStrategy ?? "count";
|
|
1462
|
+
if (!["count", "age", "both"].includes(this.replayPruneStrategy)) throw new TypeError("replay.pruneStrategy must be count, age, or both.");
|
|
1436
1463
|
if (this.replayRetentionMs !== void 0 && (!Number.isFinite(this.replayRetentionMs) || this.replayRetentionMs <= 0)) {
|
|
1437
1464
|
throw new TypeError("replay.retentionMs must be a positive finite number.");
|
|
1438
1465
|
}
|
|
@@ -1465,6 +1492,9 @@ var CrossTabDataBus = class {
|
|
|
1465
1492
|
this.trace = new DataBusTraceReporter(trace);
|
|
1466
1493
|
this.dedupMaxEntries = dedup?.maxEntries ?? 1e3;
|
|
1467
1494
|
this.dedupTtlMs = dedup?.ttlMs ?? 6e4;
|
|
1495
|
+
this.dedupAdaptiveBounds = dedup?.adaptiveTtl;
|
|
1496
|
+
if (this.dedupAdaptiveBounds && (this.dedupAdaptiveBounds.minMs <= 0 || this.dedupAdaptiveBounds.maxMs < this.dedupAdaptiveBounds.minMs)) throw new TypeError("dedup.adaptiveTtl bounds are invalid.");
|
|
1497
|
+
this.dedupWindowStartedAt = this.now();
|
|
1468
1498
|
this.dedupSweepMs = dedup?.sweepMs;
|
|
1469
1499
|
this.dedupEnabled = dedup !== void 0;
|
|
1470
1500
|
if (!Number.isSafeInteger(this.dedupMaxEntries) || this.dedupMaxEntries <= 0) {
|
|
@@ -1738,7 +1768,8 @@ var CrossTabDataBus = class {
|
|
|
1738
1768
|
enabled: this.dedupEnabled,
|
|
1739
1769
|
tracked: this.seenMessageIds.size,
|
|
1740
1770
|
suppressed: this.dedupSuppressed,
|
|
1741
|
-
accepted: this.dedupAccepted
|
|
1771
|
+
accepted: this.dedupAccepted,
|
|
1772
|
+
...this.dedupAdaptiveBounds ? { ttlMs: this.currentDedupTtl() } : {}
|
|
1742
1773
|
};
|
|
1743
1774
|
}
|
|
1744
1775
|
/** Drop all remembered IDs and reset dedup counters. */
|
|
@@ -1910,6 +1941,7 @@ var CrossTabDataBus = class {
|
|
|
1910
1941
|
}
|
|
1911
1942
|
this.seenMessageIds.set(message.messageId, now);
|
|
1912
1943
|
this.dedupAccepted += 1;
|
|
1944
|
+
this.dedupWindowAccepted += 1;
|
|
1913
1945
|
this.trace.recordDedupAccepted();
|
|
1914
1946
|
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1915
1947
|
const oldest = this.seenMessageIds.keys().next().value;
|
|
@@ -1927,11 +1959,24 @@ var CrossTabDataBus = class {
|
|
|
1927
1959
|
this.dedupSweepTimer = null;
|
|
1928
1960
|
}
|
|
1929
1961
|
pruneExpiredDedup() {
|
|
1930
|
-
const cutoff = this.now() - this.
|
|
1962
|
+
const cutoff = this.now() - this.currentDedupTtl();
|
|
1931
1963
|
for (const [id, timestamp] of this.seenMessageIds) {
|
|
1932
1964
|
if (timestamp < cutoff) this.seenMessageIds.delete(id);
|
|
1933
1965
|
}
|
|
1934
1966
|
}
|
|
1967
|
+
currentDedupTtl() {
|
|
1968
|
+
if (!this.dedupAdaptiveBounds) return this.dedupTtlMs;
|
|
1969
|
+
const now = this.now();
|
|
1970
|
+
const elapsed = now - this.dedupWindowStartedAt;
|
|
1971
|
+
if (elapsed >= this.dedupAdaptiveWindowMs) {
|
|
1972
|
+
this.dedupWindowStartedAt = now;
|
|
1973
|
+
this.dedupWindowAccepted = 0;
|
|
1974
|
+
return this.dedupAdaptiveBounds.maxMs;
|
|
1975
|
+
}
|
|
1976
|
+
const rate = this.dedupWindowAccepted / Math.max(1, elapsed);
|
|
1977
|
+
const factor = Math.min(1, rate / 0.01);
|
|
1978
|
+
return this.dedupAdaptiveBounds.maxMs - (this.dedupAdaptiveBounds.maxMs - this.dedupAdaptiveBounds.minMs) * factor;
|
|
1979
|
+
}
|
|
1935
1980
|
/** Deliver a message to every local handler registered for its topic,
|
|
1936
1981
|
* plus every handler registered with a wildcard subscription that matches
|
|
1937
1982
|
* (e.g. a handler subscribed to "chat.*" receives "chat.room.1"). */
|
|
@@ -1956,7 +2001,17 @@ var CrossTabDataBus = class {
|
|
|
1956
2001
|
}
|
|
1957
2002
|
const storedMessage = message;
|
|
1958
2003
|
buffer.push(storedMessage);
|
|
1959
|
-
if (
|
|
2004
|
+
if (this.replayPruneStrategy !== "age") {
|
|
2005
|
+
while (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
2006
|
+
}
|
|
2007
|
+
if (this.replayPruneStrategy !== "count" && this.replayRetentionMs !== void 0) {
|
|
2008
|
+
const cutoff = this.now() - this.replayRetentionMs;
|
|
2009
|
+
while (buffer.length > 0) {
|
|
2010
|
+
const first = buffer[0];
|
|
2011
|
+
if (!first || first.timestamp === void 0 || first.timestamp >= cutoff) break;
|
|
2012
|
+
buffer.shift();
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1960
2015
|
if (this.replayPersistence) {
|
|
1961
2016
|
void this.withPersistenceRetry("append", () => this.replayPersistence.append(storedMessage)).catch((error) => this.reportPersistenceError(error));
|
|
1962
2017
|
if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {
|
|
@@ -2309,4 +2364,4 @@ export {
|
|
|
2309
2364
|
parseDataBusPublication,
|
|
2310
2365
|
selectWorkerBackend
|
|
2311
2366
|
};
|
|
2312
|
-
//# sourceMappingURL=chunk-
|
|
2367
|
+
//# sourceMappingURL=chunk-KKSZ5GUC.js.map
|