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.
@@ -1199,6 +1199,9 @@ var DataBusTraceReporter = class {
1199
1199
  metricsIntervalMs;
1200
1200
  sink;
1201
1201
  now;
1202
+ asyncSink;
1203
+ pendingEvents = [];
1204
+ sinkFlushScheduled = false;
1202
1205
  intervalHandle = null;
1203
1206
  intervalStartedAt = 0;
1204
1207
  // A reporter may be flushed explicitly before start(), but once stop() is
@@ -1220,6 +1223,7 @@ var DataBusTraceReporter = class {
1220
1223
  this.mode = options?.mode ?? "all";
1221
1224
  this.metricsIntervalMs = normalizeInterval(options?.metricsIntervalMs);
1222
1225
  this.sink = options?.sink ?? (() => void 0);
1226
+ this.asyncSink = options?.asyncSink ?? false;
1223
1227
  this.now = now;
1224
1228
  }
1225
1229
  /** Start the periodic metrics flush interval. No-op when mode is 'events'
@@ -1346,6 +1350,22 @@ var DataBusTraceReporter = class {
1346
1350
  this.dedupSuppressed = 0;
1347
1351
  }
1348
1352
  emit(event) {
1353
+ if (this.asyncSink) {
1354
+ this.pendingEvents.push(event);
1355
+ if (!this.sinkFlushScheduled) {
1356
+ this.sinkFlushScheduled = true;
1357
+ queueMicrotask(() => {
1358
+ this.sinkFlushScheduled = false;
1359
+ const events = this.pendingEvents;
1360
+ this.pendingEvents = [];
1361
+ for (const queued of events) this.emitSync(queued);
1362
+ });
1363
+ }
1364
+ return;
1365
+ }
1366
+ this.emitSync(event);
1367
+ }
1368
+ emitSync(event) {
1349
1369
  try {
1350
1370
  this.sink(event);
1351
1371
  } catch (error) {
@@ -1401,6 +1421,7 @@ var CrossTabDataBus = class {
1401
1421
  replayMaxPerTopic;
1402
1422
  replayPersistence;
1403
1423
  replayRetentionMs;
1424
+ replayPruneStrategy;
1404
1425
  replayRetentionSweepMs;
1405
1426
  persistenceRetryMaxAttempts;
1406
1427
  persistenceRetryBackoffMs;
@@ -1416,6 +1437,10 @@ var CrossTabDataBus = class {
1416
1437
  trace;
1417
1438
  dedupMaxEntries;
1418
1439
  dedupTtlMs;
1440
+ dedupAdaptiveBounds;
1441
+ dedupWindowStartedAt = 0;
1442
+ dedupWindowAccepted = 0;
1443
+ dedupAdaptiveWindowMs = 5e3;
1419
1444
  dedupSweepMs;
1420
1445
  dedupSweepTimer = null;
1421
1446
  dedupEnabled;
@@ -1474,6 +1499,8 @@ var CrossTabDataBus = class {
1474
1499
  this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
1475
1500
  this.replayPersistence = replay?.persistence ?? null;
1476
1501
  this.replayRetentionMs = replay?.retentionMs;
1502
+ this.replayPruneStrategy = replay?.pruneStrategy ?? "count";
1503
+ if (!["count", "age", "both"].includes(this.replayPruneStrategy)) throw new TypeError("replay.pruneStrategy must be count, age, or both.");
1477
1504
  if (this.replayRetentionMs !== void 0 && (!Number.isFinite(this.replayRetentionMs) || this.replayRetentionMs <= 0)) {
1478
1505
  throw new TypeError("replay.retentionMs must be a positive finite number.");
1479
1506
  }
@@ -1506,6 +1533,9 @@ var CrossTabDataBus = class {
1506
1533
  this.trace = new DataBusTraceReporter(trace);
1507
1534
  this.dedupMaxEntries = dedup?.maxEntries ?? 1e3;
1508
1535
  this.dedupTtlMs = dedup?.ttlMs ?? 6e4;
1536
+ this.dedupAdaptiveBounds = dedup?.adaptiveTtl;
1537
+ if (this.dedupAdaptiveBounds && (this.dedupAdaptiveBounds.minMs <= 0 || this.dedupAdaptiveBounds.maxMs < this.dedupAdaptiveBounds.minMs)) throw new TypeError("dedup.adaptiveTtl bounds are invalid.");
1538
+ this.dedupWindowStartedAt = this.now();
1509
1539
  this.dedupSweepMs = dedup?.sweepMs;
1510
1540
  this.dedupEnabled = dedup !== void 0;
1511
1541
  if (!Number.isSafeInteger(this.dedupMaxEntries) || this.dedupMaxEntries <= 0) {
@@ -1779,7 +1809,8 @@ var CrossTabDataBus = class {
1779
1809
  enabled: this.dedupEnabled,
1780
1810
  tracked: this.seenMessageIds.size,
1781
1811
  suppressed: this.dedupSuppressed,
1782
- accepted: this.dedupAccepted
1812
+ accepted: this.dedupAccepted,
1813
+ ...this.dedupAdaptiveBounds ? { ttlMs: this.currentDedupTtl() } : {}
1783
1814
  };
1784
1815
  }
1785
1816
  /** Drop all remembered IDs and reset dedup counters. */
@@ -1951,6 +1982,7 @@ var CrossTabDataBus = class {
1951
1982
  }
1952
1983
  this.seenMessageIds.set(message.messageId, now);
1953
1984
  this.dedupAccepted += 1;
1985
+ this.dedupWindowAccepted += 1;
1954
1986
  this.trace.recordDedupAccepted();
1955
1987
  while (this.seenMessageIds.size > this.dedupMaxEntries) {
1956
1988
  const oldest = this.seenMessageIds.keys().next().value;
@@ -1968,11 +2000,24 @@ var CrossTabDataBus = class {
1968
2000
  this.dedupSweepTimer = null;
1969
2001
  }
1970
2002
  pruneExpiredDedup() {
1971
- const cutoff = this.now() - this.dedupTtlMs;
2003
+ const cutoff = this.now() - this.currentDedupTtl();
1972
2004
  for (const [id, timestamp] of this.seenMessageIds) {
1973
2005
  if (timestamp < cutoff) this.seenMessageIds.delete(id);
1974
2006
  }
1975
2007
  }
2008
+ currentDedupTtl() {
2009
+ if (!this.dedupAdaptiveBounds) return this.dedupTtlMs;
2010
+ const now = this.now();
2011
+ const elapsed = now - this.dedupWindowStartedAt;
2012
+ if (elapsed >= this.dedupAdaptiveWindowMs) {
2013
+ this.dedupWindowStartedAt = now;
2014
+ this.dedupWindowAccepted = 0;
2015
+ return this.dedupAdaptiveBounds.maxMs;
2016
+ }
2017
+ const rate = this.dedupWindowAccepted / Math.max(1, elapsed);
2018
+ const factor = Math.min(1, rate / 0.01);
2019
+ return this.dedupAdaptiveBounds.maxMs - (this.dedupAdaptiveBounds.maxMs - this.dedupAdaptiveBounds.minMs) * factor;
2020
+ }
1976
2021
  /** Deliver a message to every local handler registered for its topic,
1977
2022
  * plus every handler registered with a wildcard subscription that matches
1978
2023
  * (e.g. a handler subscribed to "chat.*" receives "chat.room.1"). */
@@ -1997,7 +2042,17 @@ var CrossTabDataBus = class {
1997
2042
  }
1998
2043
  const storedMessage = message;
1999
2044
  buffer.push(storedMessage);
2000
- if (buffer.length > this.replayMaxPerTopic) buffer.shift();
2045
+ if (this.replayPruneStrategy !== "age") {
2046
+ while (buffer.length > this.replayMaxPerTopic) buffer.shift();
2047
+ }
2048
+ if (this.replayPruneStrategy !== "count" && this.replayRetentionMs !== void 0) {
2049
+ const cutoff = this.now() - this.replayRetentionMs;
2050
+ while (buffer.length > 0) {
2051
+ const first = buffer[0];
2052
+ if (!first || first.timestamp === void 0 || first.timestamp >= cutoff) break;
2053
+ buffer.shift();
2054
+ }
2055
+ }
2001
2056
  if (this.replayPersistence) {
2002
2057
  void this.withPersistenceRetry("append", () => this.replayPersistence.append(storedMessage)).catch((error) => this.reportPersistenceError(error));
2003
2058
  if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {