@trigger.dev/redis-worker 4.5.0-rc.7 → 4.5.1

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/dist/index.js CHANGED
@@ -9053,7 +9053,7 @@ var require_Redis = __commonJS({
9053
9053
  var lodash_1 = require_lodash3();
9054
9054
  var Deque = require_denque();
9055
9055
  var debug = (0, utils_1.Debug)("redis");
9056
- var Redis4 = class _Redis extends Commander_1.default {
9056
+ var Redis3 = class _Redis extends Commander_1.default {
9057
9057
  constructor(arg1, arg2, arg3) {
9058
9058
  super();
9059
9059
  this.status = "wait";
@@ -9632,12 +9632,12 @@ var require_Redis = __commonJS({
9632
9632
  }).catch(lodash_1.noop);
9633
9633
  }
9634
9634
  };
9635
- Redis4.Cluster = cluster_1.default;
9636
- Redis4.Command = Command_1.default;
9637
- Redis4.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
9638
- (0, applyMixin_1.default)(Redis4, events_1.EventEmitter);
9639
- (0, transaction_1.addTransactionSupport)(Redis4.prototype);
9640
- exports$1.default = Redis4;
9635
+ Redis3.Cluster = cluster_1.default;
9636
+ Redis3.Command = Command_1.default;
9637
+ Redis3.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
9638
+ (0, applyMixin_1.default)(Redis3, events_1.EventEmitter);
9639
+ (0, transaction_1.addTransactionSupport)(Redis3.prototype);
9640
+ exports$1.default = Redis3;
9641
9641
  }
9642
9642
  });
9643
9643
 
@@ -9985,6 +9985,37 @@ var SimpleQueue = class {
9985
9985
  throw e;
9986
9986
  }
9987
9987
  }
9988
+ /**
9989
+ * Age (in ms) of the oldest *overdue* message — the oldest item whose scheduled
9990
+ * time has already passed (score <= now). Returns 0 when the queue is empty or
9991
+ * only holds future/delayed or in-flight (future-scored) items.
9992
+ *
9993
+ * Resolves the candidate against the `items` hash so orphaned `queue` entries
9994
+ * (a member whose payload is missing — the same stale state `dequeueItems`
9995
+ * cleans up) don't report a phantom stall for work that can't be dequeued. The
9996
+ * Lua scans due items oldest-first and returns the first score whose payload
9997
+ * still exists.
9998
+ *
9999
+ * This is the generic stall signal: it stays at 0 while a queue drains healthily
10000
+ * and rises only when due work sits undrained (poison block, dead consumer,
10001
+ * backpressure).
10002
+ */
10003
+ async oldestMessageAge() {
10004
+ try {
10005
+ const now = Date.now();
10006
+ const score = Number(await this.redis.getOldestDueScore(`queue`, `items`, now));
10007
+ if (!Number.isFinite(score) || score < 0) {
10008
+ return 0;
10009
+ }
10010
+ return Math.max(0, now - score);
10011
+ } catch (e) {
10012
+ this.logger.error(`SimpleQueue ${this.name}.oldestMessageAge(): error getting oldest age`, {
10013
+ queue: this.name,
10014
+ error: e
10015
+ });
10016
+ return 0;
10017
+ }
10018
+ }
9988
10019
  async getJob(id) {
9989
10020
  const result = await this.redis.getJob(`queue`, `items`, id);
9990
10021
  if (!result) {
@@ -10151,6 +10182,29 @@ var SimpleQueue = class {
10151
10182
  return dequeued
10152
10183
  `
10153
10184
  });
10185
+ this.redis.defineCommand("getOldestDueScore", {
10186
+ numberOfKeys: 2,
10187
+ lua: `
10188
+ local queue = KEYS[1]
10189
+ local items = KEYS[2]
10190
+ local now = tonumber(ARGV[1])
10191
+
10192
+ -- Oldest-first scan of due items, bounded so a long prefix of orphans can't
10193
+ -- make this O(n). Orphans are rare (dequeueItems removes them), so in the
10194
+ -- common case this returns on the first iteration. Read-only: unlike
10195
+ -- dequeueItems we don't ZREM orphans here \u2014 a metric probe must not mutate.
10196
+ local result = redis.call('ZRANGEBYSCORE', queue, '-inf', now, 'WITHSCORES', 'LIMIT', 0, 100)
10197
+
10198
+ for i = 1, #result, 2 do
10199
+ local id = result[i]
10200
+ if redis.call('HEXISTS', items, id) == 1 then
10201
+ return result[i + 1]
10202
+ end
10203
+ end
10204
+
10205
+ return -1
10206
+ `
10207
+ });
10154
10208
  this.redis.defineCommand("getJob", {
10155
10209
  numberOfKeys: 2,
10156
10210
  lua: `
@@ -11395,6 +11449,15 @@ var Worker = class _Worker {
11395
11449
  concurrencyLimitPendingObservableGauge.addCallback(
11396
11450
  this.#updateConcurrencyLimitPendingMetric.bind(this)
11397
11451
  );
11452
+ const oldestMessageAgeObservableGauge = this.meter.createObservableGauge(
11453
+ "redis_worker.queue.oldest_message_age",
11454
+ {
11455
+ description: "Age of the oldest overdue message in the queue",
11456
+ unit: "ms",
11457
+ valueType: ValueType.INT
11458
+ }
11459
+ );
11460
+ oldestMessageAgeObservableGauge.addCallback(this.#updateOldestMessageAgeMetric.bind(this));
11398
11461
  }
11399
11462
  subscriber;
11400
11463
  tracer;
@@ -11423,6 +11486,12 @@ var Worker = class _Worker {
11423
11486
  worker_name: this.options.name
11424
11487
  });
11425
11488
  }
11489
+ async #updateOldestMessageAgeMetric(observableResult) {
11490
+ const oldestMessageAge = await this.queue.oldestMessageAge();
11491
+ observableResult.observe(oldestMessageAge, {
11492
+ worker_name: this.options.name
11493
+ });
11494
+ }
11426
11495
  async #updateConcurrencyLimitActiveMetric(observableResult) {
11427
11496
  for (const [workerId, limiter] of Object.entries(this.limiters)) {
11428
11497
  observableResult.observe(limiter.activeCount, {
@@ -11716,15 +11785,15 @@ var Worker = class _Worker {
11716
11785
  await this.flushBatch(queueItem.job, workerId, limiter);
11717
11786
  }
11718
11787
  } else {
11719
- limiter(
11720
- () => this.processItem(queueItem, items.length, workerId, limiter)
11721
- ).catch((err) => {
11722
- this.logger.error("Unhandled error in processItem:", {
11723
- error: err,
11724
- workerId,
11725
- item
11726
- });
11727
- });
11788
+ limiter(() => this.processItem(queueItem, items.length, workerId, limiter)).catch(
11789
+ (err) => {
11790
+ this.logger.error("Unhandled error in processItem:", {
11791
+ error: err,
11792
+ workerId,
11793
+ item
11794
+ });
11795
+ }
11796
+ );
11728
11797
  }
11729
11798
  }
11730
11799
  } catch (error) {
@@ -11874,13 +11943,25 @@ var Worker = class _Worker {
11874
11943
  attempt: newAttempt
11875
11944
  };
11876
11945
  if (!shouldLogError) {
11877
- this.logger.info(`Worker batch item reached max attempts. Moving to DLQ.`, dlqLogAttributes);
11946
+ this.logger.info(
11947
+ `Worker batch item reached max attempts. Moving to DLQ.`,
11948
+ dlqLogAttributes
11949
+ );
11878
11950
  } else if (errorLogLevel === "warn") {
11879
- this.logger.warn(`Worker batch item reached max attempts. Moving to DLQ.`, dlqLogAttributes);
11951
+ this.logger.warn(
11952
+ `Worker batch item reached max attempts. Moving to DLQ.`,
11953
+ dlqLogAttributes
11954
+ );
11880
11955
  } else if (errorLogLevel === "info") {
11881
- this.logger.info(`Worker batch item reached max attempts. Moving to DLQ.`, dlqLogAttributes);
11956
+ this.logger.info(
11957
+ `Worker batch item reached max attempts. Moving to DLQ.`,
11958
+ dlqLogAttributes
11959
+ );
11882
11960
  } else {
11883
- this.logger.error(`Worker batch item reached max attempts. Moving to DLQ.`, dlqLogAttributes);
11961
+ this.logger.error(
11962
+ `Worker batch item reached max attempts. Moving to DLQ.`,
11963
+ dlqLogAttributes
11964
+ );
11884
11965
  }
11885
11966
  await this.queue.moveToDeadLetterQueue(item.id, errorMessage);
11886
11967
  continue;
@@ -12637,135 +12718,6 @@ return 1
12637
12718
  });
12638
12719
  }
12639
12720
  };
12640
- var TenantDispatch = class {
12641
- constructor(options) {
12642
- this.options = options;
12643
- this.redis = createRedisClient(options.redis);
12644
- this.keys = options.keys;
12645
- this.shardCount = Math.max(1, options.shardCount);
12646
- }
12647
- redis;
12648
- keys;
12649
- shardCount;
12650
- /**
12651
- * Get the dispatch shard ID for a tenant.
12652
- * Uses jump consistent hash on the tenant ID so each tenant
12653
- * always maps to exactly one dispatch shard.
12654
- */
12655
- getShardForTenant(tenantId) {
12656
- return jumpHash(tenantId, this.shardCount);
12657
- }
12658
- /**
12659
- * Get eligible tenants from a dispatch shard (Level 1).
12660
- * Returns tenants ordered by oldest message (lowest score first).
12661
- */
12662
- async getTenantsFromShard(shardId, limit = 1e3, maxScore) {
12663
- const dispatchKey = this.keys.dispatchKey(shardId);
12664
- const score = maxScore ?? Date.now();
12665
- const results = await this.redis.zrangebyscore(
12666
- dispatchKey,
12667
- "-inf",
12668
- score,
12669
- "WITHSCORES",
12670
- "LIMIT",
12671
- 0,
12672
- limit
12673
- );
12674
- const tenants = [];
12675
- for (let i = 0; i < results.length; i += 2) {
12676
- const tenantId = results[i];
12677
- const scoreStr = results[i + 1];
12678
- if (tenantId && scoreStr) {
12679
- tenants.push({
12680
- tenantId,
12681
- score: parseFloat(scoreStr)
12682
- });
12683
- }
12684
- }
12685
- return tenants;
12686
- }
12687
- /**
12688
- * Get queues for a specific tenant (Level 2).
12689
- * Returns queues ordered by oldest message (lowest score first).
12690
- */
12691
- async getQueuesForTenant(tenantId, limit = 1e3, maxScore) {
12692
- const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
12693
- const score = maxScore ?? Date.now();
12694
- const results = await this.redis.zrangebyscore(
12695
- tenantQueueKey,
12696
- "-inf",
12697
- score,
12698
- "WITHSCORES",
12699
- "LIMIT",
12700
- 0,
12701
- limit
12702
- );
12703
- const queues = [];
12704
- for (let i = 0; i < results.length; i += 2) {
12705
- const queueId = results[i];
12706
- const scoreStr = results[i + 1];
12707
- if (queueId && scoreStr) {
12708
- queues.push({
12709
- queueId,
12710
- score: parseFloat(scoreStr),
12711
- tenantId
12712
- });
12713
- }
12714
- }
12715
- return queues;
12716
- }
12717
- /**
12718
- * Get the number of tenants in a dispatch shard.
12719
- */
12720
- async getShardTenantCount(shardId) {
12721
- const dispatchKey = this.keys.dispatchKey(shardId);
12722
- return await this.redis.zcard(dispatchKey);
12723
- }
12724
- /**
12725
- * Get total tenant count across all dispatch shards.
12726
- * Note: tenants may appear in multiple shards, so this may overcount.
12727
- */
12728
- async getTotalTenantCount() {
12729
- const counts = await Promise.all(
12730
- Array.from({ length: this.shardCount }, (_, i) => this.getShardTenantCount(i))
12731
- );
12732
- return counts.reduce((sum, count) => sum + count, 0);
12733
- }
12734
- /**
12735
- * Get the number of queues for a tenant.
12736
- */
12737
- async getTenantQueueCount(tenantId) {
12738
- const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
12739
- return await this.redis.zcard(tenantQueueKey);
12740
- }
12741
- /**
12742
- * Remove a tenant from a specific dispatch shard.
12743
- */
12744
- async removeTenantFromShard(shardId, tenantId) {
12745
- const dispatchKey = this.keys.dispatchKey(shardId);
12746
- await this.redis.zrem(dispatchKey, tenantId);
12747
- }
12748
- /**
12749
- * Add a tenant to a dispatch shard with the given score.
12750
- */
12751
- async addTenantToShard(shardId, tenantId, score) {
12752
- const dispatchKey = this.keys.dispatchKey(shardId);
12753
- await this.redis.zadd(dispatchKey, score, tenantId);
12754
- }
12755
- /**
12756
- * Remove a queue from a tenant's queue index.
12757
- */
12758
- async removeQueueFromTenant(tenantId, queueId) {
12759
- const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
12760
- await this.redis.zrem(tenantQueueKey, queueId);
12761
- }
12762
- /**
12763
- * Close the Redis connection.
12764
- */
12765
- async close() {
12766
- await this.redis.quit();
12767
- }
12768
- };
12769
12721
 
12770
12722
  // src/fair-queue/telemetry.ts
12771
12723
  var FairQueueAttributes = {
@@ -13245,41 +13197,170 @@ var BatchedSpanManager = class {
13245
13197
  }
13246
13198
  }
13247
13199
  /**
13248
- * Clean up state for a loop when it's stopped.
13200
+ * Clean up state for a loop when it's stopped.
13201
+ */
13202
+ cleanup(loopId) {
13203
+ this.endCurrentSpan(loopId);
13204
+ this.loopStates.delete(loopId);
13205
+ }
13206
+ /**
13207
+ * Clean up all loop states.
13208
+ */
13209
+ cleanupAll() {
13210
+ for (const loopId of this.loopStates.keys()) {
13211
+ this.cleanup(loopId);
13212
+ }
13213
+ }
13214
+ };
13215
+ var noopSpan = {
13216
+ spanContext: () => ({
13217
+ traceId: "",
13218
+ spanId: "",
13219
+ traceFlags: 0
13220
+ }),
13221
+ setAttribute: () => noopSpan,
13222
+ setAttributes: () => noopSpan,
13223
+ addEvent: () => noopSpan,
13224
+ addLink: () => noopSpan,
13225
+ addLinks: () => noopSpan,
13226
+ setStatus: () => noopSpan,
13227
+ updateName: () => noopSpan,
13228
+ end: () => {
13229
+ },
13230
+ isRecording: () => false,
13231
+ recordException: () => {
13232
+ }
13233
+ };
13234
+ var noopTelemetry = new FairQueueTelemetry({});
13235
+ var TenantDispatch = class {
13236
+ constructor(options) {
13237
+ this.options = options;
13238
+ this.redis = createRedisClient(options.redis);
13239
+ this.keys = options.keys;
13240
+ this.shardCount = Math.max(1, options.shardCount);
13241
+ }
13242
+ redis;
13243
+ keys;
13244
+ shardCount;
13245
+ /**
13246
+ * Get the dispatch shard ID for a tenant.
13247
+ * Uses jump consistent hash on the tenant ID so each tenant
13248
+ * always maps to exactly one dispatch shard.
13249
+ */
13250
+ getShardForTenant(tenantId) {
13251
+ return jumpHash(tenantId, this.shardCount);
13252
+ }
13253
+ /**
13254
+ * Get eligible tenants from a dispatch shard (Level 1).
13255
+ * Returns tenants ordered by oldest message (lowest score first).
13256
+ */
13257
+ async getTenantsFromShard(shardId, limit = 1e3, maxScore) {
13258
+ const dispatchKey = this.keys.dispatchKey(shardId);
13259
+ const score = maxScore ?? Date.now();
13260
+ const results = await this.redis.zrangebyscore(
13261
+ dispatchKey,
13262
+ "-inf",
13263
+ score,
13264
+ "WITHSCORES",
13265
+ "LIMIT",
13266
+ 0,
13267
+ limit
13268
+ );
13269
+ const tenants = [];
13270
+ for (let i = 0; i < results.length; i += 2) {
13271
+ const tenantId = results[i];
13272
+ const scoreStr = results[i + 1];
13273
+ if (tenantId && scoreStr) {
13274
+ tenants.push({
13275
+ tenantId,
13276
+ score: parseFloat(scoreStr)
13277
+ });
13278
+ }
13279
+ }
13280
+ return tenants;
13281
+ }
13282
+ /**
13283
+ * Get queues for a specific tenant (Level 2).
13284
+ * Returns queues ordered by oldest message (lowest score first).
13285
+ */
13286
+ async getQueuesForTenant(tenantId, limit = 1e3, maxScore) {
13287
+ const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
13288
+ const score = maxScore ?? Date.now();
13289
+ const results = await this.redis.zrangebyscore(
13290
+ tenantQueueKey,
13291
+ "-inf",
13292
+ score,
13293
+ "WITHSCORES",
13294
+ "LIMIT",
13295
+ 0,
13296
+ limit
13297
+ );
13298
+ const queues = [];
13299
+ for (let i = 0; i < results.length; i += 2) {
13300
+ const queueId = results[i];
13301
+ const scoreStr = results[i + 1];
13302
+ if (queueId && scoreStr) {
13303
+ queues.push({
13304
+ queueId,
13305
+ score: parseFloat(scoreStr),
13306
+ tenantId
13307
+ });
13308
+ }
13309
+ }
13310
+ return queues;
13311
+ }
13312
+ /**
13313
+ * Get the number of tenants in a dispatch shard.
13314
+ */
13315
+ async getShardTenantCount(shardId) {
13316
+ const dispatchKey = this.keys.dispatchKey(shardId);
13317
+ return await this.redis.zcard(dispatchKey);
13318
+ }
13319
+ /**
13320
+ * Get total tenant count across all dispatch shards.
13321
+ * Note: tenants may appear in multiple shards, so this may overcount.
13322
+ */
13323
+ async getTotalTenantCount() {
13324
+ const counts = await Promise.all(
13325
+ Array.from({ length: this.shardCount }, (_, i) => this.getShardTenantCount(i))
13326
+ );
13327
+ return counts.reduce((sum, count) => sum + count, 0);
13328
+ }
13329
+ /**
13330
+ * Get the number of queues for a tenant.
13331
+ */
13332
+ async getTenantQueueCount(tenantId) {
13333
+ const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
13334
+ return await this.redis.zcard(tenantQueueKey);
13335
+ }
13336
+ /**
13337
+ * Remove a tenant from a specific dispatch shard.
13338
+ */
13339
+ async removeTenantFromShard(shardId, tenantId) {
13340
+ const dispatchKey = this.keys.dispatchKey(shardId);
13341
+ await this.redis.zrem(dispatchKey, tenantId);
13342
+ }
13343
+ /**
13344
+ * Add a tenant to a dispatch shard with the given score.
13249
13345
  */
13250
- cleanup(loopId) {
13251
- this.endCurrentSpan(loopId);
13252
- this.loopStates.delete(loopId);
13346
+ async addTenantToShard(shardId, tenantId, score) {
13347
+ const dispatchKey = this.keys.dispatchKey(shardId);
13348
+ await this.redis.zadd(dispatchKey, score, tenantId);
13253
13349
  }
13254
13350
  /**
13255
- * Clean up all loop states.
13351
+ * Remove a queue from a tenant's queue index.
13256
13352
  */
13257
- cleanupAll() {
13258
- for (const loopId of this.loopStates.keys()) {
13259
- this.cleanup(loopId);
13260
- }
13353
+ async removeQueueFromTenant(tenantId, queueId) {
13354
+ const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
13355
+ await this.redis.zrem(tenantQueueKey, queueId);
13261
13356
  }
13262
- };
13263
- var noopSpan = {
13264
- spanContext: () => ({
13265
- traceId: "",
13266
- spanId: "",
13267
- traceFlags: 0
13268
- }),
13269
- setAttribute: () => noopSpan,
13270
- setAttributes: () => noopSpan,
13271
- addEvent: () => noopSpan,
13272
- addLink: () => noopSpan,
13273
- addLinks: () => noopSpan,
13274
- setStatus: () => noopSpan,
13275
- updateName: () => noopSpan,
13276
- end: () => {
13277
- },
13278
- isRecording: () => false,
13279
- recordException: () => {
13357
+ /**
13358
+ * Close the Redis connection.
13359
+ */
13360
+ async close() {
13361
+ await this.redis.quit();
13280
13362
  }
13281
13363
  };
13282
- var noopTelemetry = new FairQueueTelemetry({});
13283
13364
  var VisibilityManager = class {
13284
13365
  constructor(options) {
13285
13366
  this.options = options;
@@ -14261,6 +14342,101 @@ var CallbackFairQueueKeyProducer = class extends DefaultFairQueueKeyProducer {
14261
14342
  return this.groupExtractor(groupName, queueId);
14262
14343
  }
14263
14344
  };
14345
+ var ExponentialBackoffRetry = class {
14346
+ maxAttempts;
14347
+ options;
14348
+ constructor(options) {
14349
+ this.options = {
14350
+ maxAttempts: options?.maxAttempts ?? 12,
14351
+ factor: options?.factor ?? 2,
14352
+ minTimeoutInMs: options?.minTimeoutInMs ?? 1e3,
14353
+ maxTimeoutInMs: options?.maxTimeoutInMs ?? 36e5,
14354
+ // 1 hour
14355
+ randomize: options?.randomize ?? true
14356
+ };
14357
+ this.maxAttempts = this.options.maxAttempts ?? 12;
14358
+ }
14359
+ getNextDelay(attempt, _error) {
14360
+ if (attempt >= this.maxAttempts) {
14361
+ return null;
14362
+ }
14363
+ const delay = calculateNextRetryDelay(this.options, attempt);
14364
+ return delay ?? null;
14365
+ }
14366
+ };
14367
+ var FixedDelayRetry = class {
14368
+ maxAttempts;
14369
+ delayMs;
14370
+ constructor(options) {
14371
+ this.maxAttempts = options.maxAttempts;
14372
+ this.delayMs = options.delayMs;
14373
+ }
14374
+ getNextDelay(attempt, _error) {
14375
+ if (attempt >= this.maxAttempts) {
14376
+ return null;
14377
+ }
14378
+ return this.delayMs;
14379
+ }
14380
+ };
14381
+ var LinearBackoffRetry = class {
14382
+ maxAttempts;
14383
+ baseDelayMs;
14384
+ maxDelayMs;
14385
+ constructor(options) {
14386
+ this.maxAttempts = options.maxAttempts;
14387
+ this.baseDelayMs = options.baseDelayMs;
14388
+ this.maxDelayMs = options.maxDelayMs ?? options.baseDelayMs * options.maxAttempts;
14389
+ }
14390
+ getNextDelay(attempt, _error) {
14391
+ if (attempt >= this.maxAttempts) {
14392
+ return null;
14393
+ }
14394
+ const delay = this.baseDelayMs * attempt;
14395
+ return Math.min(delay, this.maxDelayMs);
14396
+ }
14397
+ };
14398
+ var NoRetry = class {
14399
+ maxAttempts = 1;
14400
+ getNextDelay(_attempt, _error) {
14401
+ return null;
14402
+ }
14403
+ };
14404
+ var ImmediateRetry = class {
14405
+ maxAttempts;
14406
+ constructor(maxAttempts) {
14407
+ this.maxAttempts = maxAttempts;
14408
+ }
14409
+ getNextDelay(attempt, _error) {
14410
+ if (attempt >= this.maxAttempts) {
14411
+ return null;
14412
+ }
14413
+ return 0;
14414
+ }
14415
+ };
14416
+ var CustomRetry = class {
14417
+ maxAttempts;
14418
+ calculateDelay;
14419
+ constructor(options) {
14420
+ this.maxAttempts = options.maxAttempts;
14421
+ this.calculateDelay = options.calculateDelay;
14422
+ }
14423
+ getNextDelay(attempt, error) {
14424
+ if (attempt >= this.maxAttempts) {
14425
+ return null;
14426
+ }
14427
+ return this.calculateDelay(attempt, error);
14428
+ }
14429
+ };
14430
+ var defaultRetryOptions = {
14431
+ maxAttempts: 12,
14432
+ factor: 2,
14433
+ minTimeoutInMs: 1e3,
14434
+ maxTimeoutInMs: 36e5,
14435
+ randomize: true
14436
+ };
14437
+ function createDefaultRetryStrategy() {
14438
+ return new ExponentialBackoffRetry(defaultRetryOptions);
14439
+ }
14264
14440
 
14265
14441
  // src/fair-queue/scheduler.ts
14266
14442
  var BaseScheduler = class {
@@ -14389,9 +14565,7 @@ var DRRScheduler = class extends BaseScheduler {
14389
14565
  };
14390
14566
  })
14391
14567
  );
14392
- const eligibleTenants = tenantData.filter(
14393
- (t) => !t.isAtCapacity && t.deficit >= 1
14394
- );
14568
+ const eligibleTenants = tenantData.filter((t) => !t.isAtCapacity && t.deficit >= 1);
14395
14569
  const blockedTenants = tenantData.filter((t) => t.isAtCapacity);
14396
14570
  if (blockedTenants.length > 0) {
14397
14571
  this.logger.debug("DRR: tenants blocked by concurrency", {
@@ -14684,11 +14858,7 @@ var WeightedScheduler = class extends BaseScheduler {
14684
14858
  // FairScheduler Implementation
14685
14859
  // ============================================================================
14686
14860
  async selectQueues(masterQueueShard, consumerId, context2) {
14687
- const snapshot = await this.#getOrCreateSnapshot(
14688
- masterQueueShard,
14689
- consumerId,
14690
- context2
14691
- );
14861
+ const snapshot = await this.#getOrCreateSnapshot(masterQueueShard, consumerId, context2);
14692
14862
  if (snapshot.queues.length === 0) {
14693
14863
  return [];
14694
14864
  }
@@ -15039,101 +15209,6 @@ var RoundRobinScheduler = class extends BaseScheduler {
15039
15209
  return [...array.slice(normalizedIndex), ...array.slice(0, normalizedIndex)];
15040
15210
  }
15041
15211
  };
15042
- var ExponentialBackoffRetry = class {
15043
- maxAttempts;
15044
- options;
15045
- constructor(options) {
15046
- this.options = {
15047
- maxAttempts: options?.maxAttempts ?? 12,
15048
- factor: options?.factor ?? 2,
15049
- minTimeoutInMs: options?.minTimeoutInMs ?? 1e3,
15050
- maxTimeoutInMs: options?.maxTimeoutInMs ?? 36e5,
15051
- // 1 hour
15052
- randomize: options?.randomize ?? true
15053
- };
15054
- this.maxAttempts = this.options.maxAttempts ?? 12;
15055
- }
15056
- getNextDelay(attempt, _error) {
15057
- if (attempt >= this.maxAttempts) {
15058
- return null;
15059
- }
15060
- const delay = calculateNextRetryDelay(this.options, attempt);
15061
- return delay ?? null;
15062
- }
15063
- };
15064
- var FixedDelayRetry = class {
15065
- maxAttempts;
15066
- delayMs;
15067
- constructor(options) {
15068
- this.maxAttempts = options.maxAttempts;
15069
- this.delayMs = options.delayMs;
15070
- }
15071
- getNextDelay(attempt, _error) {
15072
- if (attempt >= this.maxAttempts) {
15073
- return null;
15074
- }
15075
- return this.delayMs;
15076
- }
15077
- };
15078
- var LinearBackoffRetry = class {
15079
- maxAttempts;
15080
- baseDelayMs;
15081
- maxDelayMs;
15082
- constructor(options) {
15083
- this.maxAttempts = options.maxAttempts;
15084
- this.baseDelayMs = options.baseDelayMs;
15085
- this.maxDelayMs = options.maxDelayMs ?? options.baseDelayMs * options.maxAttempts;
15086
- }
15087
- getNextDelay(attempt, _error) {
15088
- if (attempt >= this.maxAttempts) {
15089
- return null;
15090
- }
15091
- const delay = this.baseDelayMs * attempt;
15092
- return Math.min(delay, this.maxDelayMs);
15093
- }
15094
- };
15095
- var NoRetry = class {
15096
- maxAttempts = 1;
15097
- getNextDelay(_attempt, _error) {
15098
- return null;
15099
- }
15100
- };
15101
- var ImmediateRetry = class {
15102
- maxAttempts;
15103
- constructor(maxAttempts) {
15104
- this.maxAttempts = maxAttempts;
15105
- }
15106
- getNextDelay(attempt, _error) {
15107
- if (attempt >= this.maxAttempts) {
15108
- return null;
15109
- }
15110
- return 0;
15111
- }
15112
- };
15113
- var CustomRetry = class {
15114
- maxAttempts;
15115
- calculateDelay;
15116
- constructor(options) {
15117
- this.maxAttempts = options.maxAttempts;
15118
- this.calculateDelay = options.calculateDelay;
15119
- }
15120
- getNextDelay(attempt, error) {
15121
- if (attempt >= this.maxAttempts) {
15122
- return null;
15123
- }
15124
- return this.calculateDelay(attempt, error);
15125
- }
15126
- };
15127
- var defaultRetryOptions = {
15128
- maxAttempts: 12,
15129
- factor: 2,
15130
- minTimeoutInMs: 1e3,
15131
- maxTimeoutInMs: 36e5,
15132
- randomize: true
15133
- };
15134
- function createDefaultRetryStrategy() {
15135
- return new ExponentialBackoffRetry(defaultRetryOptions);
15136
- }
15137
15212
 
15138
15213
  // src/fair-queue/index.ts
15139
15214
  var FairQueue = class {
@@ -16061,10 +16136,7 @@ var FairQueue = class {
16061
16136
  if (this.concurrencyManager && storedMessage) {
16062
16137
  await this.concurrencyManager.release(descriptor, messageId);
16063
16138
  }
16064
- const { queueEmpty } = await this.#updateAllIndexesAfterDequeue(
16065
- queueId,
16066
- descriptor.tenantId
16067
- );
16139
+ const { queueEmpty } = await this.#updateAllIndexesAfterDequeue(queueId, descriptor.tenantId);
16068
16140
  if (queueEmpty) {
16069
16141
  this.queueDescriptorCache.delete(queueId);
16070
16142
  this.queueCooloffStates.delete(queueId);
@@ -16321,38 +16393,6 @@ var FairQueue = class {
16321
16393
  }
16322
16394
  return false;
16323
16395
  }
16324
- #incrementCooloff(queueId) {
16325
- if (this.queueCooloffStates.size >= this.maxCooloffStatesSize) {
16326
- this.logger.warn("Cooloff states cache hit size cap, clearing all entries", {
16327
- size: this.queueCooloffStates.size,
16328
- cap: this.maxCooloffStatesSize
16329
- });
16330
- this.queueCooloffStates.clear();
16331
- }
16332
- const state = this.queueCooloffStates.get(queueId) ?? {
16333
- tag: "normal",
16334
- consecutiveFailures: 0
16335
- };
16336
- if (state.tag === "normal") {
16337
- const newFailures = state.consecutiveFailures + 1;
16338
- if (newFailures >= this.cooloffThreshold) {
16339
- this.queueCooloffStates.set(queueId, {
16340
- tag: "cooloff",
16341
- expiresAt: Date.now() + this.cooloffPeriodMs
16342
- });
16343
- this.logger.debug("Queue entered cooloff", {
16344
- queueId,
16345
- cooloffPeriodMs: this.cooloffPeriodMs,
16346
- consecutiveFailures: newFailures
16347
- });
16348
- } else {
16349
- this.queueCooloffStates.set(queueId, {
16350
- tag: "normal",
16351
- consecutiveFailures: newFailures
16352
- });
16353
- }
16354
- }
16355
- }
16356
16396
  #resetCooloff(queueId) {
16357
16397
  this.queueCooloffStates.delete(queueId);
16358
16398
  }
@@ -16620,7 +16660,10 @@ var stringToError = z.string().transform((v, ctx) => {
16620
16660
  try {
16621
16661
  return BufferEntryError.parse(JSON.parse(v));
16622
16662
  } catch {
16623
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "expected JSON-encoded BufferEntryError" });
16663
+ ctx.addIssue({
16664
+ code: z.ZodIssueCode.custom,
16665
+ message: "expected JSON-encoded BufferEntryError"
16666
+ });
16624
16667
  return z.NEVER;
16625
16668
  }
16626
16669
  });
@@ -16818,11 +16861,7 @@ var MollifierBuffer = class {
16818
16861
  // not an error.
16819
16862
  async listEntriesForEnv(envId, maxCount) {
16820
16863
  if (maxCount <= 0) return [];
16821
- const runIds = await this.redis.lrange(
16822
- `mollifier:queue:${envId}`,
16823
- 0,
16824
- maxCount - 1
16825
- );
16864
+ const runIds = await this.redis.lrange(`mollifier:queue:${envId}`, 0, maxCount - 1);
16826
16865
  if (runIds.length === 0) return [];
16827
16866
  const pipeline = this.redis.pipeline();
16828
16867
  for (const runId of runIds) {
@@ -16959,10 +16998,7 @@ var MollifierBuffer = class {
16959
16998
  // never wiped by a slow predecessor.
16960
16999
  async releaseClaim(input) {
16961
17000
  const claimKey = makeIdempotencyClaimKey(input);
16962
- await this.redis.releaseMollifierClaim(
16963
- claimKey,
16964
- `${PENDING_PREFIX}${input.token}`
16965
- );
17001
+ await this.redis.releaseMollifierClaim(claimKey, `${PENDING_PREFIX}${input.token}`);
16966
17002
  }
16967
17003
  // Read the current claim value, used by the wait/poll loop on losers
16968
17004
  // to detect "pending" → "resolved" transitions and timeouts.
@@ -17647,9 +17683,7 @@ var MollifierDrainer = class {
17647
17683
  const orgs = await this.buffer.listOrgs();
17648
17684
  if (orgs.length === 0) return { drained: 0, failed: 0 };
17649
17685
  const orgSlice = this.takeOrgSlice(orgs);
17650
- const envsByOrg = await Promise.all(
17651
- orgSlice.map((orgId) => this.buffer.listEnvsForOrg(orgId))
17652
- );
17686
+ const envsByOrg = await Promise.all(orgSlice.map((orgId) => this.buffer.listEnvsForOrg(orgId)));
17653
17687
  const targets = [];
17654
17688
  for (let i = 0; i < orgSlice.length; i++) {
17655
17689
  const orgId = orgSlice[i];
@@ -17862,14 +17896,11 @@ var MollifierDrainer = class {
17862
17896
  } catch (writeErr) {
17863
17897
  if (this.isRetryable(writeErr)) {
17864
17898
  await this.buffer.requeue(entry.runId);
17865
- this.logger.warn(
17866
- "MollifierDrainer: terminal-failure callback retryable; requeued",
17867
- {
17868
- runId: entry.runId,
17869
- attempts: nextAttempts,
17870
- writeErr
17871
- }
17872
- );
17899
+ this.logger.warn("MollifierDrainer: terminal-failure callback retryable; requeued", {
17900
+ runId: entry.runId,
17901
+ attempts: nextAttempts,
17902
+ writeErr
17903
+ });
17873
17904
  return "failed";
17874
17905
  }
17875
17906
  this.logger.error("MollifierDrainer: terminal-failure callback failed", {