@trigger.dev/redis-worker 0.0.0-prerelease-20250503193704 → 0.0.0-prerelease-20250703115146

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.cjs CHANGED
@@ -8,7 +8,8 @@ var crypto = require('crypto');
8
8
  require('@trigger.dev/core/v3/utils/flattenAttributes');
9
9
  var v3 = require('@trigger.dev/core/v3');
10
10
  var serverOnly = require('@trigger.dev/core/v3/serverOnly');
11
- var promClient = require('prom-client');
11
+ var zod = require('zod');
12
+ var cronParser = require('cron-parser');
12
13
 
13
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
15
 
@@ -8743,7 +8744,7 @@ var require_Redis = __commonJS({
8743
8744
  var lodash_1 = require_lodash3();
8744
8745
  var Deque = require_denque();
8745
8746
  var debug = (0, utils_1.Debug)("redis");
8746
- var Redis3 = class _Redis extends Commander_1.default {
8747
+ var Redis4 = class _Redis extends Commander_1.default {
8747
8748
  constructor(arg1, arg2, arg3) {
8748
8749
  super();
8749
8750
  this.status = "wait";
@@ -9306,12 +9307,12 @@ var require_Redis = __commonJS({
9306
9307
  }).catch(lodash_1.noop);
9307
9308
  }
9308
9309
  };
9309
- Redis3.Cluster = cluster_1.default;
9310
- Redis3.Command = Command_1.default;
9311
- Redis3.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
9312
- (0, applyMixin_1.default)(Redis3, events_1.EventEmitter);
9313
- (0, transaction_1.addTransactionSupport)(Redis3.prototype);
9314
- exports.default = Redis3;
9310
+ Redis4.Cluster = cluster_1.default;
9311
+ Redis4.Command = Command_1.default;
9312
+ Redis4.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
9313
+ (0, applyMixin_1.default)(Redis4, events_1.EventEmitter);
9314
+ (0, transaction_1.addTransactionSupport)(Redis4.prototype);
9315
+ exports.default = Redis4;
9315
9316
  }
9316
9317
  });
9317
9318
 
@@ -9509,6 +9510,39 @@ var SimpleQueue = class {
9509
9510
  throw e;
9510
9511
  }
9511
9512
  }
9513
+ async enqueueOnce({
9514
+ id,
9515
+ job,
9516
+ item,
9517
+ attempt,
9518
+ availableAt,
9519
+ visibilityTimeoutMs
9520
+ }) {
9521
+ if (!id) {
9522
+ throw new Error("enqueueOnce requires an id");
9523
+ }
9524
+ try {
9525
+ const score = availableAt ? availableAt.getTime() : Date.now();
9526
+ const deduplicationKey = nanoid();
9527
+ const serializedItem = JSON.stringify({
9528
+ job,
9529
+ item,
9530
+ visibilityTimeoutMs,
9531
+ attempt,
9532
+ deduplicationKey
9533
+ });
9534
+ const result = await this.redis.enqueueItemOnce(`queue`, `items`, id, score, serializedItem);
9535
+ return result === 1;
9536
+ } catch (e) {
9537
+ this.logger.error(`SimpleQueue ${this.name}.enqueueOnce(): error enqueuing`, {
9538
+ queue: this.name,
9539
+ error: e,
9540
+ id,
9541
+ item
9542
+ });
9543
+ throw e;
9544
+ }
9545
+ }
9512
9546
  async dequeue(count = 1) {
9513
9547
  const now = Date.now();
9514
9548
  try {
@@ -9531,7 +9565,8 @@ var SimpleQueue = class {
9531
9565
  id,
9532
9566
  item: parsedItem,
9533
9567
  job: parsedItem.job,
9534
- timestamp
9568
+ timestamp,
9569
+ availableJobs: Object.keys(this.schema)
9535
9570
  });
9536
9571
  continue;
9537
9572
  }
@@ -9612,8 +9647,30 @@ var SimpleQueue = class {
9612
9647
  throw e;
9613
9648
  }
9614
9649
  }
9650
+ async getJob(id) {
9651
+ const result = await this.redis.getJob(`queue`, `items`, id);
9652
+ if (!result) {
9653
+ return null;
9654
+ }
9655
+ const [_, score, serializedItem] = result;
9656
+ const item = JSON.parse(serializedItem);
9657
+ return {
9658
+ id,
9659
+ job: item.job,
9660
+ item: item.item,
9661
+ visibilityTimeoutMs: item.visibilityTimeoutMs,
9662
+ attempt: item.attempt ?? 0,
9663
+ timestamp: new Date(Number(score)),
9664
+ deduplicationKey: item.deduplicationKey ?? void 0
9665
+ };
9666
+ }
9615
9667
  async moveToDeadLetterQueue(id, errorMessage) {
9616
9668
  try {
9669
+ this.logger.debug(`SimpleQueue ${this.name}.moveToDeadLetterQueue(): moving item to DLQ`, {
9670
+ queue: this.name,
9671
+ id,
9672
+ errorMessage
9673
+ });
9617
9674
  const result = await this.redis.moveToDeadLetterQueue(
9618
9675
  `queue`,
9619
9676
  `items`,
@@ -9734,6 +9791,25 @@ var SimpleQueue = class {
9734
9791
  return dequeued
9735
9792
  `
9736
9793
  });
9794
+ this.redis.defineCommand("getJob", {
9795
+ numberOfKeys: 2,
9796
+ lua: `
9797
+ local queue = KEYS[1]
9798
+ local items = KEYS[2]
9799
+ local jobId = ARGV[1]
9800
+
9801
+ local serializedItem = redis.call('HGET', items, jobId)
9802
+
9803
+ if serializedItem == false then
9804
+ return nil
9805
+ end
9806
+
9807
+ -- get the score from the queue sorted set
9808
+ local score = redis.call('ZSCORE', queue, jobId)
9809
+
9810
+ return { jobId, score, serializedItem }
9811
+ `
9812
+ });
9737
9813
  this.redis.defineCommand("ackItem", {
9738
9814
  numberOfKeys: 2,
9739
9815
  lua: `
@@ -9823,6 +9899,25 @@ var SimpleQueue = class {
9823
9899
  return 1
9824
9900
  `
9825
9901
  });
9902
+ this.redis.defineCommand("enqueueItemOnce", {
9903
+ numberOfKeys: 2,
9904
+ lua: `
9905
+ local queue = KEYS[1]
9906
+ local items = KEYS[2]
9907
+ local id = ARGV[1]
9908
+ local score = ARGV[2]
9909
+ local serializedItem = ARGV[3]
9910
+
9911
+ -- Only add if not exists
9912
+ local added = redis.call('HSETNX', items, id, serializedItem)
9913
+ if added == 1 then
9914
+ redis.call('ZADD', queue, 'NX', score, id)
9915
+ return 1
9916
+ else
9917
+ return 0
9918
+ end
9919
+ `
9920
+ });
9826
9921
  }
9827
9922
  };
9828
9923
 
@@ -10179,6 +10274,173 @@ var BaseContext = (
10179
10274
  );
10180
10275
  var ROOT_CONTEXT = new BaseContext();
10181
10276
 
10277
+ // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/metrics/NoopMeter.js
10278
+ var __extends = /* @__PURE__ */ function() {
10279
+ var extendStatics = function(d, b) {
10280
+ extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
10281
+ d2.__proto__ = b2;
10282
+ } || function(d2, b2) {
10283
+ for (var p in b2) if (Object.prototype.hasOwnProperty.call(b2, p)) d2[p] = b2[p];
10284
+ };
10285
+ return extendStatics(d, b);
10286
+ };
10287
+ return function(d, b) {
10288
+ if (typeof b !== "function" && b !== null)
10289
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
10290
+ extendStatics(d, b);
10291
+ function __() {
10292
+ this.constructor = d;
10293
+ }
10294
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
10295
+ };
10296
+ }();
10297
+ var NoopMeter = (
10298
+ /** @class */
10299
+ function() {
10300
+ function NoopMeter2() {
10301
+ }
10302
+ NoopMeter2.prototype.createGauge = function(_name, _options) {
10303
+ return NOOP_GAUGE_METRIC;
10304
+ };
10305
+ NoopMeter2.prototype.createHistogram = function(_name, _options) {
10306
+ return NOOP_HISTOGRAM_METRIC;
10307
+ };
10308
+ NoopMeter2.prototype.createCounter = function(_name, _options) {
10309
+ return NOOP_COUNTER_METRIC;
10310
+ };
10311
+ NoopMeter2.prototype.createUpDownCounter = function(_name, _options) {
10312
+ return NOOP_UP_DOWN_COUNTER_METRIC;
10313
+ };
10314
+ NoopMeter2.prototype.createObservableGauge = function(_name, _options) {
10315
+ return NOOP_OBSERVABLE_GAUGE_METRIC;
10316
+ };
10317
+ NoopMeter2.prototype.createObservableCounter = function(_name, _options) {
10318
+ return NOOP_OBSERVABLE_COUNTER_METRIC;
10319
+ };
10320
+ NoopMeter2.prototype.createObservableUpDownCounter = function(_name, _options) {
10321
+ return NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC;
10322
+ };
10323
+ NoopMeter2.prototype.addBatchObservableCallback = function(_callback, _observables) {
10324
+ };
10325
+ NoopMeter2.prototype.removeBatchObservableCallback = function(_callback) {
10326
+ };
10327
+ return NoopMeter2;
10328
+ }()
10329
+ );
10330
+ var NoopMetric = (
10331
+ /** @class */
10332
+ /* @__PURE__ */ function() {
10333
+ function NoopMetric2() {
10334
+ }
10335
+ return NoopMetric2;
10336
+ }()
10337
+ );
10338
+ var NoopCounterMetric = (
10339
+ /** @class */
10340
+ function(_super) {
10341
+ __extends(NoopCounterMetric2, _super);
10342
+ function NoopCounterMetric2() {
10343
+ return _super !== null && _super.apply(this, arguments) || this;
10344
+ }
10345
+ NoopCounterMetric2.prototype.add = function(_value, _attributes) {
10346
+ };
10347
+ return NoopCounterMetric2;
10348
+ }(NoopMetric)
10349
+ );
10350
+ var NoopUpDownCounterMetric = (
10351
+ /** @class */
10352
+ function(_super) {
10353
+ __extends(NoopUpDownCounterMetric2, _super);
10354
+ function NoopUpDownCounterMetric2() {
10355
+ return _super !== null && _super.apply(this, arguments) || this;
10356
+ }
10357
+ NoopUpDownCounterMetric2.prototype.add = function(_value, _attributes) {
10358
+ };
10359
+ return NoopUpDownCounterMetric2;
10360
+ }(NoopMetric)
10361
+ );
10362
+ var NoopGaugeMetric = (
10363
+ /** @class */
10364
+ function(_super) {
10365
+ __extends(NoopGaugeMetric2, _super);
10366
+ function NoopGaugeMetric2() {
10367
+ return _super !== null && _super.apply(this, arguments) || this;
10368
+ }
10369
+ NoopGaugeMetric2.prototype.record = function(_value, _attributes) {
10370
+ };
10371
+ return NoopGaugeMetric2;
10372
+ }(NoopMetric)
10373
+ );
10374
+ var NoopHistogramMetric = (
10375
+ /** @class */
10376
+ function(_super) {
10377
+ __extends(NoopHistogramMetric2, _super);
10378
+ function NoopHistogramMetric2() {
10379
+ return _super !== null && _super.apply(this, arguments) || this;
10380
+ }
10381
+ NoopHistogramMetric2.prototype.record = function(_value, _attributes) {
10382
+ };
10383
+ return NoopHistogramMetric2;
10384
+ }(NoopMetric)
10385
+ );
10386
+ var NoopObservableMetric = (
10387
+ /** @class */
10388
+ function() {
10389
+ function NoopObservableMetric2() {
10390
+ }
10391
+ NoopObservableMetric2.prototype.addCallback = function(_callback) {
10392
+ };
10393
+ NoopObservableMetric2.prototype.removeCallback = function(_callback) {
10394
+ };
10395
+ return NoopObservableMetric2;
10396
+ }()
10397
+ );
10398
+ var NoopObservableCounterMetric = (
10399
+ /** @class */
10400
+ function(_super) {
10401
+ __extends(NoopObservableCounterMetric2, _super);
10402
+ function NoopObservableCounterMetric2() {
10403
+ return _super !== null && _super.apply(this, arguments) || this;
10404
+ }
10405
+ return NoopObservableCounterMetric2;
10406
+ }(NoopObservableMetric)
10407
+ );
10408
+ var NoopObservableGaugeMetric = (
10409
+ /** @class */
10410
+ function(_super) {
10411
+ __extends(NoopObservableGaugeMetric2, _super);
10412
+ function NoopObservableGaugeMetric2() {
10413
+ return _super !== null && _super.apply(this, arguments) || this;
10414
+ }
10415
+ return NoopObservableGaugeMetric2;
10416
+ }(NoopObservableMetric)
10417
+ );
10418
+ var NoopObservableUpDownCounterMetric = (
10419
+ /** @class */
10420
+ function(_super) {
10421
+ __extends(NoopObservableUpDownCounterMetric2, _super);
10422
+ function NoopObservableUpDownCounterMetric2() {
10423
+ return _super !== null && _super.apply(this, arguments) || this;
10424
+ }
10425
+ return NoopObservableUpDownCounterMetric2;
10426
+ }(NoopObservableMetric)
10427
+ );
10428
+ var NOOP_METER = new NoopMeter();
10429
+ var NOOP_COUNTER_METRIC = new NoopCounterMetric();
10430
+ var NOOP_GAUGE_METRIC = new NoopGaugeMetric();
10431
+ var NOOP_HISTOGRAM_METRIC = new NoopHistogramMetric();
10432
+ var NOOP_UP_DOWN_COUNTER_METRIC = new NoopUpDownCounterMetric();
10433
+ var NOOP_OBSERVABLE_COUNTER_METRIC = new NoopObservableCounterMetric();
10434
+ var NOOP_OBSERVABLE_GAUGE_METRIC = new NoopObservableGaugeMetric();
10435
+ var NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC = new NoopObservableUpDownCounterMetric();
10436
+
10437
+ // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/metrics/Metric.js
10438
+ var ValueType;
10439
+ (function(ValueType2) {
10440
+ ValueType2[ValueType2["INT"] = 0] = "INT";
10441
+ ValueType2[ValueType2["DOUBLE"] = 1] = "DOUBLE";
10442
+ })(ValueType || (ValueType = {}));
10443
+
10182
10444
  // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/context/NoopContextManager.js
10183
10445
  var __read3 = function(o, n) {
10184
10446
  var m = typeof Symbol === "function" && o[Symbol.iterator];
@@ -10541,8 +10803,54 @@ var SpanStatusCode;
10541
10803
  SpanStatusCode2[SpanStatusCode2["ERROR"] = 2] = "ERROR";
10542
10804
  })(SpanStatusCode || (SpanStatusCode = {}));
10543
10805
 
10806
+ // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/metrics/NoopMeterProvider.js
10807
+ var NoopMeterProvider = (
10808
+ /** @class */
10809
+ function() {
10810
+ function NoopMeterProvider2() {
10811
+ }
10812
+ NoopMeterProvider2.prototype.getMeter = function(_name, _version, _options) {
10813
+ return NOOP_METER;
10814
+ };
10815
+ return NoopMeterProvider2;
10816
+ }()
10817
+ );
10818
+ var NOOP_METER_PROVIDER = new NoopMeterProvider();
10819
+
10820
+ // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/api/metrics.js
10821
+ var API_NAME3 = "metrics";
10822
+ var MetricsAPI = (
10823
+ /** @class */
10824
+ function() {
10825
+ function MetricsAPI2() {
10826
+ }
10827
+ MetricsAPI2.getInstance = function() {
10828
+ if (!this._instance) {
10829
+ this._instance = new MetricsAPI2();
10830
+ }
10831
+ return this._instance;
10832
+ };
10833
+ MetricsAPI2.prototype.setGlobalMeterProvider = function(provider) {
10834
+ return registerGlobal(API_NAME3, provider, DiagAPI.instance());
10835
+ };
10836
+ MetricsAPI2.prototype.getMeterProvider = function() {
10837
+ return getGlobal(API_NAME3) || NOOP_METER_PROVIDER;
10838
+ };
10839
+ MetricsAPI2.prototype.getMeter = function(name, version, options) {
10840
+ return this.getMeterProvider().getMeter(name, version, options);
10841
+ };
10842
+ MetricsAPI2.prototype.disable = function() {
10843
+ unregisterGlobal(API_NAME3, DiagAPI.instance());
10844
+ };
10845
+ return MetricsAPI2;
10846
+ }()
10847
+ );
10848
+
10849
+ // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/metrics-api.js
10850
+ var metrics = MetricsAPI.getInstance();
10851
+
10544
10852
  // ../../node_modules/.pnpm/@opentelemetry+api@1.9.0/node_modules/@opentelemetry/api/build/esm/api/trace.js
10545
- var API_NAME3 = "trace";
10853
+ var API_NAME4 = "trace";
10546
10854
  var TraceAPI = (
10547
10855
  /** @class */
10548
10856
  function() {
@@ -10564,20 +10872,20 @@ var TraceAPI = (
10564
10872
  return this._instance;
10565
10873
  };
10566
10874
  TraceAPI2.prototype.setGlobalTracerProvider = function(provider) {
10567
- var success = registerGlobal(API_NAME3, this._proxyTracerProvider, DiagAPI.instance());
10875
+ var success = registerGlobal(API_NAME4, this._proxyTracerProvider, DiagAPI.instance());
10568
10876
  if (success) {
10569
10877
  this._proxyTracerProvider.setDelegate(provider);
10570
10878
  }
10571
10879
  return success;
10572
10880
  };
10573
10881
  TraceAPI2.prototype.getTracerProvider = function() {
10574
- return getGlobal(API_NAME3) || this._proxyTracerProvider;
10882
+ return getGlobal(API_NAME4) || this._proxyTracerProvider;
10575
10883
  };
10576
10884
  TraceAPI2.prototype.getTracer = function(name, version) {
10577
10885
  return this.getTracerProvider().getTracer(name, version);
10578
10886
  };
10579
10887
  TraceAPI2.prototype.disable = function() {
10580
- unregisterGlobal(API_NAME3, DiagAPI.instance());
10888
+ unregisterGlobal(API_NAME4, DiagAPI.instance());
10581
10889
  this._proxyTracerProvider = new ProxyTracerProvider();
10582
10890
  };
10583
10891
  return TraceAPI2;
@@ -10743,6 +11051,11 @@ function validateConcurrency(concurrency) {
10743
11051
  throw new TypeError("Expected `concurrency` to be a number from 1 and up");
10744
11052
  }
10745
11053
  }
11054
+ var CronSchema = zod.z.object({
11055
+ cron: zod.z.string(),
11056
+ lastTimestamp: zod.z.number().optional(),
11057
+ timestamp: zod.z.number()
11058
+ });
10746
11059
  var defaultRetrySettings = {
10747
11060
  maxAttempts: 12,
10748
11061
  factor: 2,
@@ -10757,6 +11070,7 @@ var Worker = class _Worker {
10757
11070
  this.options = options;
10758
11071
  this.logger = options.logger ?? new logger$1.Logger("Worker", "debug");
10759
11072
  this.tracer = options.tracer ?? trace.getTracer(options.name);
11073
+ this.meter = options.meter ?? metrics.getMeter(options.name);
10760
11074
  this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? 6e4;
10761
11075
  const schema = Object.fromEntries(
10762
11076
  Object.entries(this.options.catalog).map(([key, value]) => [key, value.schema])
@@ -10771,56 +11085,47 @@ var Worker = class _Worker {
10771
11085
  const { workers = 1, tasksPerWorker = 1, limit = 10 } = options.concurrency ?? {};
10772
11086
  this.concurrency = { workers, tasksPerWorker, limit };
10773
11087
  this.limiter = pLimit(this.concurrency.limit);
10774
- this.metrics.register = options.metrics?.register;
10775
- if (!this.metrics.register) {
10776
- return;
10777
- }
10778
- this.metrics.enqueueDuration = new promClient.Histogram({
10779
- name: "redis_worker_enqueue_duration_seconds",
10780
- help: "The duration of enqueue operations",
10781
- labelNames: ["worker_name", "job_type", "has_available_at"],
10782
- buckets: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
10783
- registers: [this.metrics.register]
10784
- });
10785
- this.metrics.dequeueDuration = new promClient.Histogram({
10786
- name: "redis_worker_dequeue_duration_seconds",
10787
- help: "The duration of dequeue operations",
10788
- labelNames: ["worker_name", "worker_id", "task_count"],
10789
- buckets: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
10790
- registers: [this.metrics.register]
10791
- });
10792
- this.metrics.jobDuration = new promClient.Histogram({
10793
- name: "redis_worker_job_duration_seconds",
10794
- help: "The duration of job operations",
10795
- labelNames: ["worker_name", "worker_id", "batch_size", "job_type", "attempt"],
10796
- // use different buckets here as jobs can take a while to run
10797
- buckets: [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 20, 30, 45, 60],
10798
- registers: [this.metrics.register]
10799
- });
10800
- this.metrics.ackDuration = new promClient.Histogram({
10801
- name: "redis_worker_ack_duration_seconds",
10802
- help: "The duration of ack operations",
10803
- labelNames: ["worker_name"],
10804
- buckets: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
10805
- registers: [this.metrics.register]
10806
- });
10807
- this.metrics.redriveDuration = new promClient.Histogram({
10808
- name: "redis_worker_redrive_duration_seconds",
10809
- help: "The duration of redrive operations",
10810
- labelNames: ["worker_name"],
10811
- buckets: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
10812
- registers: [this.metrics.register]
10813
- });
10814
- this.metrics.rescheduleDuration = new promClient.Histogram({
10815
- name: "redis_worker_reschedule_duration_seconds",
10816
- help: "The duration of reschedule operations",
10817
- labelNames: ["worker_name"],
10818
- buckets: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
10819
- registers: [this.metrics.register]
11088
+ const masterQueueObservableGauge = this.meter.createObservableGauge("redis_worker.queue.size", {
11089
+ description: "The number of items in the queue",
11090
+ unit: "items",
11091
+ valueType: ValueType.INT
10820
11092
  });
11093
+ masterQueueObservableGauge.addCallback(this.#updateQueueSizeMetric.bind(this));
11094
+ const deadLetterQueueObservableGauge = this.meter.createObservableGauge(
11095
+ "redis_worker.queue.dead_letter_size",
11096
+ {
11097
+ description: "The number of items in the dead letter queue",
11098
+ unit: "items",
11099
+ valueType: ValueType.INT
11100
+ }
11101
+ );
11102
+ deadLetterQueueObservableGauge.addCallback(this.#updateDeadLetterQueueSizeMetric.bind(this));
11103
+ const concurrencyLimitActiveObservableGauge = this.meter.createObservableGauge(
11104
+ "redis_worker.concurrency.active",
11105
+ {
11106
+ description: "The number of active workers",
11107
+ unit: "workers",
11108
+ valueType: ValueType.INT
11109
+ }
11110
+ );
11111
+ concurrencyLimitActiveObservableGauge.addCallback(
11112
+ this.#updateConcurrencyLimitActiveMetric.bind(this)
11113
+ );
11114
+ const concurrencyLimitPendingObservableGauge = this.meter.createObservableGauge(
11115
+ "redis_worker.concurrency.pending",
11116
+ {
11117
+ description: "The number of pending workers",
11118
+ unit: "workers",
11119
+ valueType: ValueType.INT
11120
+ }
11121
+ );
11122
+ concurrencyLimitPendingObservableGauge.addCallback(
11123
+ this.#updateConcurrencyLimitPendingMetric.bind(this)
11124
+ );
10821
11125
  }
10822
11126
  subscriber;
10823
11127
  tracer;
11128
+ meter;
10824
11129
  metrics = {};
10825
11130
  queue;
10826
11131
  jobs;
@@ -10831,10 +11136,37 @@ var Worker = class _Worker {
10831
11136
  shutdownTimeoutMs;
10832
11137
  // The p-limit limiter to control overall concurrency.
10833
11138
  limiter;
11139
+ async #updateQueueSizeMetric(observableResult) {
11140
+ const queueSize = await this.queue.size();
11141
+ observableResult.observe(queueSize, {
11142
+ worker_name: this.options.name
11143
+ });
11144
+ }
11145
+ async #updateDeadLetterQueueSizeMetric(observableResult) {
11146
+ const deadLetterQueueSize = await this.queue.sizeOfDeadLetterQueue();
11147
+ observableResult.observe(deadLetterQueueSize, {
11148
+ worker_name: this.options.name
11149
+ });
11150
+ }
11151
+ async #updateConcurrencyLimitActiveMetric(observableResult) {
11152
+ observableResult.observe(this.limiter.activeCount, {
11153
+ worker_name: this.options.name
11154
+ });
11155
+ }
11156
+ async #updateConcurrencyLimitPendingMetric(observableResult) {
11157
+ observableResult.observe(this.limiter.pendingCount, {
11158
+ worker_name: this.options.name
11159
+ });
11160
+ }
10834
11161
  start() {
10835
11162
  const { workers, tasksPerWorker } = this.concurrency;
11163
+ this.logger.info("Starting worker", {
11164
+ workers,
11165
+ tasksPerWorker,
11166
+ concurrency: this.concurrency
11167
+ });
10836
11168
  for (let i = 0; i < workers; i++) {
10837
- this.workerLoops.push(this.runWorkerLoop(`worker-${nanoid(12)}`, tasksPerWorker));
11169
+ this.workerLoops.push(this.runWorkerLoop(`worker-${nanoid(12)}`, tasksPerWorker, i, workers));
10838
11170
  }
10839
11171
  this.setupShutdownHandlers();
10840
11172
  this.subscriber = createRedisClient(this.options.redisOptions, {
@@ -10846,6 +11178,7 @@ var Worker = class _Worker {
10846
11178
  }
10847
11179
  });
10848
11180
  this.setupSubscriber();
11181
+ this.setupCron();
10849
11182
  return this;
10850
11183
  }
10851
11184
  /**
@@ -10898,6 +11231,56 @@ var Worker = class _Worker {
10898
11231
  }
10899
11232
  );
10900
11233
  }
11234
+ /**
11235
+ * Enqueues a job for processing once. If the job is already in the queue, it will be ignored.
11236
+ * @param options - The enqueue options.
11237
+ * @param options.id - Required unique identifier for the job.
11238
+ * @param options.job - The job type from the worker catalog.
11239
+ * @param options.payload - The job payload that matches the schema defined in the catalog.
11240
+ * @param options.visibilityTimeoutMs - Optional visibility timeout in milliseconds. Defaults to value from catalog.
11241
+ * @param options.availableAt - Optional date when the job should become available for processing. Defaults to now.
11242
+ * @returns A promise that resolves when the job is enqueued.
11243
+ */
11244
+ enqueueOnce({
11245
+ id,
11246
+ job,
11247
+ payload,
11248
+ visibilityTimeoutMs,
11249
+ availableAt
11250
+ }) {
11251
+ return startSpan(
11252
+ this.tracer,
11253
+ "enqueueOnce",
11254
+ async (span) => {
11255
+ const timeout = visibilityTimeoutMs ?? this.options.catalog[job]?.visibilityTimeoutMs;
11256
+ if (!timeout) {
11257
+ throw new Error(`No visibility timeout found for job ${String(job)} with id ${id}`);
11258
+ }
11259
+ span.setAttribute("job_visibility_timeout_ms", timeout);
11260
+ return this.withHistogram(
11261
+ this.metrics.enqueueDuration,
11262
+ this.queue.enqueueOnce({
11263
+ id,
11264
+ job,
11265
+ item: payload,
11266
+ visibilityTimeoutMs: timeout,
11267
+ availableAt
11268
+ }),
11269
+ {
11270
+ job_type: String(job),
11271
+ has_available_at: availableAt ? "true" : "false"
11272
+ }
11273
+ );
11274
+ },
11275
+ {
11276
+ kind: SpanKind.PRODUCER,
11277
+ attributes: {
11278
+ job_type: String(job),
11279
+ job_id: id
11280
+ }
11281
+ }
11282
+ );
11283
+ }
10901
11284
  /**
10902
11285
  * Reschedules an existing job to a new available date.
10903
11286
  * If the job isn't in the queue, it will be ignored.
@@ -10934,15 +11317,37 @@ var Worker = class _Worker {
10934
11317
  }
10935
11318
  );
10936
11319
  }
11320
+ async getJob(id) {
11321
+ return this.queue.getJob(id);
11322
+ }
10937
11323
  /**
10938
11324
  * The main loop that each worker runs. It repeatedly polls for items,
10939
11325
  * processes them, and then waits before the next iteration.
10940
11326
  */
10941
- async runWorkerLoop(workerId, taskCount) {
11327
+ async runWorkerLoop(workerId, taskCount, workerIndex, totalWorkers) {
10942
11328
  const pollIntervalMs = this.options.pollIntervalMs ?? 1e3;
10943
11329
  const immediatePollIntervalMs = this.options.immediatePollIntervalMs ?? 100;
11330
+ const delayBetweenWorkers = this.options.pollIntervalMs ?? 1e3;
11331
+ const delay = delayBetweenWorkers * (totalWorkers - workerIndex);
11332
+ await _Worker.delay(delay);
11333
+ this.logger.info("Starting worker loop", {
11334
+ workerIndex,
11335
+ totalWorkers,
11336
+ delay,
11337
+ workerId,
11338
+ taskCount,
11339
+ pollIntervalMs,
11340
+ immediatePollIntervalMs,
11341
+ concurrencyOptions: this.concurrency
11342
+ });
10944
11343
  while (!this.isShuttingDown) {
10945
11344
  if (this.limiter.activeCount + this.limiter.pendingCount >= this.concurrency.limit) {
11345
+ this.logger.debug("Worker at capacity, waiting", {
11346
+ workerId,
11347
+ concurrencyOptions: this.concurrency,
11348
+ activeCount: this.limiter.activeCount,
11349
+ pendingCount: this.limiter.pendingCount
11350
+ });
10946
11351
  await _Worker.delay(pollIntervalMs);
10947
11352
  continue;
10948
11353
  }
@@ -10956,9 +11361,22 @@ var Worker = class _Worker {
10956
11361
  }
10957
11362
  );
10958
11363
  if (items.length === 0) {
11364
+ this.logger.debug("No items to dequeue", {
11365
+ workerId,
11366
+ concurrencyOptions: this.concurrency,
11367
+ activeCount: this.limiter.activeCount,
11368
+ pendingCount: this.limiter.pendingCount
11369
+ });
10959
11370
  await _Worker.delay(pollIntervalMs);
10960
11371
  continue;
10961
11372
  }
11373
+ this.logger.debug("Dequeued items", {
11374
+ workerId,
11375
+ itemCount: items.length,
11376
+ concurrencyOptions: this.concurrency,
11377
+ activeCount: this.limiter.activeCount,
11378
+ pendingCount: this.limiter.pendingCount
11379
+ });
10962
11380
  for (const item of items) {
10963
11381
  this.limiter(() => this.processItem(item, items.length, workerId)).catch(
10964
11382
  (err) => {
@@ -10973,6 +11391,7 @@ var Worker = class _Worker {
10973
11391
  }
10974
11392
  await _Worker.delay(immediatePollIntervalMs);
10975
11393
  }
11394
+ this.logger.info("Worker loop finished", { workerId });
10976
11395
  }
10977
11396
  /**
10978
11397
  * Processes a single item.
@@ -10984,6 +11403,10 @@ var Worker = class _Worker {
10984
11403
  this.logger.error(`No handler found for job type: ${job}`);
10985
11404
  return;
10986
11405
  }
11406
+ if (!catalogItem) {
11407
+ this.logger.error(`No catalog item found for job type: ${job}`);
11408
+ return;
11409
+ }
10987
11410
  await startSpan(
10988
11411
  this.tracer,
10989
11412
  "processItem",
@@ -10999,6 +11422,9 @@ var Worker = class _Worker {
10999
11422
  }
11000
11423
  );
11001
11424
  await this.queue.ack(id, deduplicationKey);
11425
+ if (catalogItem.cron) {
11426
+ await this.rescheduleCronJob(job, catalogItem, item);
11427
+ }
11002
11428
  },
11003
11429
  {
11004
11430
  kind: SpanKind.CONSUMER,
@@ -11045,6 +11471,9 @@ var Worker = class _Worker {
11045
11471
  errorMessage
11046
11472
  });
11047
11473
  await this.queue.moveToDeadLetterQueue(id, errorMessage);
11474
+ if (catalogItem.cron) {
11475
+ await this.rescheduleCronJob(job, catalogItem, item);
11476
+ }
11048
11477
  return;
11049
11478
  }
11050
11479
  const retryDate = new Date(Date.now() + retryDelay);
@@ -11082,20 +11511,108 @@ var Worker = class _Worker {
11082
11511
  });
11083
11512
  }
11084
11513
  async withHistogram(histogram, promise, labels) {
11085
- if (!histogram || !this.metrics.register) {
11514
+ if (!histogram) {
11086
11515
  return promise;
11087
11516
  }
11088
- const end = histogram.startTimer({ worker_name: this.options.name, ...labels });
11517
+ const start = Date.now();
11089
11518
  try {
11090
11519
  return await promise;
11091
11520
  } finally {
11092
- end();
11521
+ const duration = (Date.now() - start) / 1e3;
11522
+ histogram.record(duration, { worker_name: this.options.name, ...labels });
11093
11523
  }
11094
11524
  }
11095
11525
  // A simple helper to delay for a given number of milliseconds.
11096
11526
  static delay(ms) {
11097
11527
  return new Promise((resolve) => setTimeout(resolve, ms));
11098
11528
  }
11529
+ setupCron() {
11530
+ const cronJobs = Object.entries(this.options.catalog).filter(([_, value]) => value.cron);
11531
+ if (cronJobs.length === 0) {
11532
+ return;
11533
+ }
11534
+ this.logger.info("Setting up cron jobs", {
11535
+ cronJobs: cronJobs.map(([job, value]) => ({
11536
+ job,
11537
+ cron: value.cron,
11538
+ jitterInMs: value.jitterInMs
11539
+ }))
11540
+ });
11541
+ const enqueuePromises = cronJobs.map(
11542
+ ([job, value]) => this.enqueueCronJob(value.cron, job, value.jitterInMs)
11543
+ );
11544
+ Promise.allSettled(enqueuePromises).then((results) => {
11545
+ results.forEach((result) => {
11546
+ if (result.status === "fulfilled") {
11547
+ this.logger.info("Enqueued cron job", { result: result.value });
11548
+ } else {
11549
+ this.logger.error("Failed to enqueue cron job", { reason: result.reason });
11550
+ }
11551
+ });
11552
+ });
11553
+ }
11554
+ async enqueueCronJob(cron, job, jitter, lastTimestamp) {
11555
+ const scheduledAt = this.calculateNextScheduledAt(cron, lastTimestamp);
11556
+ const identifier = [job, this.timestampIdentifier(scheduledAt)].join(":");
11557
+ const appliedJitter = typeof jitter === "number" ? Math.random() * jitter - jitter / 2 : 0;
11558
+ const availableAt = new Date(scheduledAt.getTime() + appliedJitter);
11559
+ const enqueued = await this.enqueueOnce({
11560
+ id: identifier,
11561
+ job,
11562
+ payload: {
11563
+ timestamp: scheduledAt.getTime(),
11564
+ lastTimestamp: lastTimestamp?.getTime(),
11565
+ cron
11566
+ },
11567
+ availableAt
11568
+ });
11569
+ this.logger.info("Enqueued cron job", {
11570
+ identifier,
11571
+ cron,
11572
+ job,
11573
+ scheduledAt,
11574
+ enqueued,
11575
+ availableAt,
11576
+ appliedJitter,
11577
+ jitter
11578
+ });
11579
+ return {
11580
+ identifier,
11581
+ cron,
11582
+ job,
11583
+ scheduledAt,
11584
+ enqueued
11585
+ };
11586
+ }
11587
+ async rescheduleCronJob(job, catalogItem, item) {
11588
+ if (!catalogItem.cron) {
11589
+ return;
11590
+ }
11591
+ return this.enqueueCronJob(
11592
+ catalogItem.cron,
11593
+ job,
11594
+ catalogItem.jitterInMs,
11595
+ new Date(item.timestamp)
11596
+ );
11597
+ }
11598
+ calculateNextScheduledAt(cron, lastTimestamp) {
11599
+ const scheduledAt = cronParser.parseExpression(cron, {
11600
+ currentDate: lastTimestamp
11601
+ }).next().toDate();
11602
+ if (scheduledAt < /* @__PURE__ */ new Date()) {
11603
+ return this.calculateNextScheduledAt(cron);
11604
+ }
11605
+ return scheduledAt;
11606
+ }
11607
+ timestampIdentifier(timestamp) {
11608
+ const year = timestamp.getUTCFullYear();
11609
+ const month = timestamp.getUTCMonth();
11610
+ const day = timestamp.getUTCDate();
11611
+ const hour = timestamp.getUTCHours();
11612
+ const minute = timestamp.getUTCMinutes();
11613
+ const second = timestamp.getUTCSeconds();
11614
+ return `${year}-${month}-${day}-${hour}-${minute}-${second}`;
11615
+ }
11099
11616
  setupSubscriber() {
11100
11617
  const channel = `${this.options.name}:redrive`;
11101
11618
  this.subscriber?.subscribe(channel, (err) => {
@@ -11152,6 +11669,7 @@ var Worker = class _Worker {
11152
11669
  }
11153
11670
  };
11154
11671
 
11672
+ exports.CronSchema = CronSchema;
11155
11673
  exports.SimpleQueue = SimpleQueue;
11156
11674
  exports.Worker = Worker;
11157
11675
  //# sourceMappingURL=index.cjs.map