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.
@@ -1174,6 +1174,9 @@ var DataBusTraceReporter = class {
1174
1174
  metricsIntervalMs;
1175
1175
  sink;
1176
1176
  now;
1177
+ asyncSink;
1178
+ pendingEvents = [];
1179
+ sinkFlushScheduled = false;
1177
1180
  intervalHandle = null;
1178
1181
  intervalStartedAt = 0;
1179
1182
  // A reporter may be flushed explicitly before start(), but once stop() is
@@ -1195,6 +1198,7 @@ var DataBusTraceReporter = class {
1195
1198
  this.mode = options?.mode ?? "all";
1196
1199
  this.metricsIntervalMs = normalizeInterval(options?.metricsIntervalMs);
1197
1200
  this.sink = options?.sink ?? (() => void 0);
1201
+ this.asyncSink = options?.asyncSink ?? false;
1198
1202
  this.now = now;
1199
1203
  }
1200
1204
  /** Start the periodic metrics flush interval. No-op when mode is 'events'
@@ -1321,6 +1325,22 @@ var DataBusTraceReporter = class {
1321
1325
  this.dedupSuppressed = 0;
1322
1326
  }
1323
1327
  emit(event) {
1328
+ if (this.asyncSink) {
1329
+ this.pendingEvents.push(event);
1330
+ if (!this.sinkFlushScheduled) {
1331
+ this.sinkFlushScheduled = true;
1332
+ queueMicrotask(() => {
1333
+ this.sinkFlushScheduled = false;
1334
+ const events = this.pendingEvents;
1335
+ this.pendingEvents = [];
1336
+ for (const queued of events) this.emitSync(queued);
1337
+ });
1338
+ }
1339
+ return;
1340
+ }
1341
+ this.emitSync(event);
1342
+ }
1343
+ emitSync(event) {
1324
1344
  try {
1325
1345
  this.sink(event);
1326
1346
  } catch (error) {
@@ -1376,6 +1396,7 @@ var CrossTabDataBus = class {
1376
1396
  replayMaxPerTopic;
1377
1397
  replayPersistence;
1378
1398
  replayRetentionMs;
1399
+ replayPruneStrategy;
1379
1400
  replayRetentionSweepMs;
1380
1401
  persistenceRetryMaxAttempts;
1381
1402
  persistenceRetryBackoffMs;
@@ -1391,6 +1412,10 @@ var CrossTabDataBus = class {
1391
1412
  trace;
1392
1413
  dedupMaxEntries;
1393
1414
  dedupTtlMs;
1415
+ dedupAdaptiveBounds;
1416
+ dedupWindowStartedAt = 0;
1417
+ dedupWindowAccepted = 0;
1418
+ dedupAdaptiveWindowMs = 5e3;
1394
1419
  dedupSweepMs;
1395
1420
  dedupSweepTimer = null;
1396
1421
  dedupEnabled;
@@ -1449,6 +1474,8 @@ var CrossTabDataBus = class {
1449
1474
  this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
1450
1475
  this.replayPersistence = replay?.persistence ?? null;
1451
1476
  this.replayRetentionMs = replay?.retentionMs;
1477
+ this.replayPruneStrategy = replay?.pruneStrategy ?? "count";
1478
+ if (!["count", "age", "both"].includes(this.replayPruneStrategy)) throw new TypeError("replay.pruneStrategy must be count, age, or both.");
1452
1479
  if (this.replayRetentionMs !== void 0 && (!Number.isFinite(this.replayRetentionMs) || this.replayRetentionMs <= 0)) {
1453
1480
  throw new TypeError("replay.retentionMs must be a positive finite number.");
1454
1481
  }
@@ -1481,6 +1508,9 @@ var CrossTabDataBus = class {
1481
1508
  this.trace = new DataBusTraceReporter(trace);
1482
1509
  this.dedupMaxEntries = dedup?.maxEntries ?? 1e3;
1483
1510
  this.dedupTtlMs = dedup?.ttlMs ?? 6e4;
1511
+ this.dedupAdaptiveBounds = dedup?.adaptiveTtl;
1512
+ if (this.dedupAdaptiveBounds && (this.dedupAdaptiveBounds.minMs <= 0 || this.dedupAdaptiveBounds.maxMs < this.dedupAdaptiveBounds.minMs)) throw new TypeError("dedup.adaptiveTtl bounds are invalid.");
1513
+ this.dedupWindowStartedAt = this.now();
1484
1514
  this.dedupSweepMs = dedup?.sweepMs;
1485
1515
  this.dedupEnabled = dedup !== void 0;
1486
1516
  if (!Number.isSafeInteger(this.dedupMaxEntries) || this.dedupMaxEntries <= 0) {
@@ -1754,7 +1784,8 @@ var CrossTabDataBus = class {
1754
1784
  enabled: this.dedupEnabled,
1755
1785
  tracked: this.seenMessageIds.size,
1756
1786
  suppressed: this.dedupSuppressed,
1757
- accepted: this.dedupAccepted
1787
+ accepted: this.dedupAccepted,
1788
+ ...this.dedupAdaptiveBounds ? { ttlMs: this.currentDedupTtl() } : {}
1758
1789
  };
1759
1790
  }
1760
1791
  /** Drop all remembered IDs and reset dedup counters. */
@@ -1926,6 +1957,7 @@ var CrossTabDataBus = class {
1926
1957
  }
1927
1958
  this.seenMessageIds.set(message.messageId, now);
1928
1959
  this.dedupAccepted += 1;
1960
+ this.dedupWindowAccepted += 1;
1929
1961
  this.trace.recordDedupAccepted();
1930
1962
  while (this.seenMessageIds.size > this.dedupMaxEntries) {
1931
1963
  const oldest = this.seenMessageIds.keys().next().value;
@@ -1943,11 +1975,24 @@ var CrossTabDataBus = class {
1943
1975
  this.dedupSweepTimer = null;
1944
1976
  }
1945
1977
  pruneExpiredDedup() {
1946
- const cutoff = this.now() - this.dedupTtlMs;
1978
+ const cutoff = this.now() - this.currentDedupTtl();
1947
1979
  for (const [id, timestamp] of this.seenMessageIds) {
1948
1980
  if (timestamp < cutoff) this.seenMessageIds.delete(id);
1949
1981
  }
1950
1982
  }
1983
+ currentDedupTtl() {
1984
+ if (!this.dedupAdaptiveBounds) return this.dedupTtlMs;
1985
+ const now = this.now();
1986
+ const elapsed = now - this.dedupWindowStartedAt;
1987
+ if (elapsed >= this.dedupAdaptiveWindowMs) {
1988
+ this.dedupWindowStartedAt = now;
1989
+ this.dedupWindowAccepted = 0;
1990
+ return this.dedupAdaptiveBounds.maxMs;
1991
+ }
1992
+ const rate = this.dedupWindowAccepted / Math.max(1, elapsed);
1993
+ const factor = Math.min(1, rate / 0.01);
1994
+ return this.dedupAdaptiveBounds.maxMs - (this.dedupAdaptiveBounds.maxMs - this.dedupAdaptiveBounds.minMs) * factor;
1995
+ }
1951
1996
  /** Deliver a message to every local handler registered for its topic,
1952
1997
  * plus every handler registered with a wildcard subscription that matches
1953
1998
  * (e.g. a handler subscribed to "chat.*" receives "chat.room.1"). */
@@ -1972,7 +2017,17 @@ var CrossTabDataBus = class {
1972
2017
  }
1973
2018
  const storedMessage = message;
1974
2019
  buffer.push(storedMessage);
1975
- if (buffer.length > this.replayMaxPerTopic) buffer.shift();
2020
+ if (this.replayPruneStrategy !== "age") {
2021
+ while (buffer.length > this.replayMaxPerTopic) buffer.shift();
2022
+ }
2023
+ if (this.replayPruneStrategy !== "count" && this.replayRetentionMs !== void 0) {
2024
+ const cutoff = this.now() - this.replayRetentionMs;
2025
+ while (buffer.length > 0) {
2026
+ const first = buffer[0];
2027
+ if (!first || first.timestamp === void 0 || first.timestamp >= cutoff) break;
2028
+ buffer.shift();
2029
+ }
2030
+ }
1976
2031
  if (this.replayPersistence) {
1977
2032
  void this.withPersistenceRetry("append", () => this.replayPersistence.append(storedMessage)).catch((error) => this.reportPersistenceError(error));
1978
2033
  if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {