@trigger.dev/redis-worker 4.0.0-v4-beta.21 → 4.0.0-v4-beta.22

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.d.cts CHANGED
@@ -62,6 +62,7 @@ declare class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
62
62
  size({ includeFuture }?: {
63
63
  includeFuture?: boolean;
64
64
  }): Promise<number>;
65
+ getJob(id: string): Promise<QueueItem<TMessageCatalog> | null>;
65
66
  moveToDeadLetterQueue(id: string, errorMessage: string): Promise<void>;
66
67
  sizeOfDeadLetterQueue(): Promise<number>;
67
68
  redriveFromDeadLetterQueue(id: string): Promise<void>;
@@ -75,26 +76,44 @@ declare module "@internal/redis" {
75
76
  redriveFromDeadLetterQueue(queue: string, items: string, dlq: string, dlqItems: string, id: string, callback?: Callback<number>): Result<number, Context>;
76
77
  moveToDeadLetterQueue(queue: string, items: string, dlq: string, dlqItems: string, id: string, errorMessage: string, callback?: Callback<number>): Result<number, Context>;
77
78
  enqueueItemOnce(queue: string, items: string, id: string, score: number, serializedItem: string, callback?: Callback<number>): Result<number, Context>;
79
+ getJob(queue: string, items: string, id: string, callback?: Callback<[string, string, string] | null>): Result<[string, string, string] | null, Context>;
78
80
  }
79
81
  }
80
82
 
83
+ declare const CronSchema: z.ZodObject<{
84
+ cron: z.ZodString;
85
+ lastTimestamp: z.ZodOptional<z.ZodNumber>;
86
+ timestamp: z.ZodNumber;
87
+ }, "strip", z.ZodTypeAny, {
88
+ timestamp: number;
89
+ cron: string;
90
+ lastTimestamp?: number | undefined;
91
+ }, {
92
+ timestamp: number;
93
+ cron: string;
94
+ lastTimestamp?: number | undefined;
95
+ }>;
96
+ type CronSchema = z.infer<typeof CronSchema>;
81
97
  type WorkerCatalog = {
82
98
  [key: string]: {
83
99
  schema: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>;
84
100
  visibilityTimeoutMs: number;
85
101
  retry?: RetryOptions;
102
+ cron?: string;
103
+ jitterInMs?: number;
86
104
  };
87
105
  };
88
106
  type QueueCatalogFromWorkerCatalog<Catalog extends WorkerCatalog> = {
89
107
  [K in keyof Catalog]: Catalog[K]["schema"];
90
108
  };
91
- type JobHandler<Catalog extends WorkerCatalog, K extends keyof Catalog> = (params: {
109
+ type JobHandlerParams<Catalog extends WorkerCatalog, K extends keyof Catalog> = {
92
110
  id: string;
93
111
  payload: z.infer<Catalog[K]["schema"]>;
94
112
  visibilityTimeoutMs: number;
95
113
  attempt: number;
96
114
  deduplicationKey?: string;
97
- }) => Promise<void>;
115
+ };
116
+ type JobHandler<Catalog extends WorkerCatalog, K extends keyof Catalog> = (params: JobHandlerParams<Catalog, K>) => Promise<void>;
98
117
  type WorkerConcurrencyOptions = {
99
118
  workers?: number;
100
119
  tasksPerWorker?: number;
@@ -129,7 +148,7 @@ declare class Worker<TCatalog extends WorkerCatalog> {
129
148
  private isShuttingDown;
130
149
  private concurrency;
131
150
  private shutdownTimeoutMs;
132
- private limiter;
151
+ private limiters;
133
152
  constructor(options: WorkerOptions<TCatalog>);
134
153
  start(): this;
135
154
  /**
@@ -172,6 +191,7 @@ declare class Worker<TCatalog extends WorkerCatalog> {
172
191
  */
173
192
  reschedule(id: string, availableAt: Date): Promise<void>;
174
193
  ack(id: string): Promise<void>;
194
+ getJob(id: string): Promise<QueueItem<QueueCatalogFromWorkerCatalog<TCatalog>> | null>;
175
195
  /**
176
196
  * The main loop that each worker runs. It repeatedly polls for items,
177
197
  * processes them, and then waits before the next iteration.
@@ -183,6 +203,11 @@ declare class Worker<TCatalog extends WorkerCatalog> {
183
203
  private processItem;
184
204
  private withHistogram;
185
205
  private static delay;
206
+ private setupCron;
207
+ private enqueueCronJob;
208
+ private rescheduleCronJob;
209
+ private calculateNextScheduledAt;
210
+ private timestampIdentifier;
186
211
  private setupSubscriber;
187
212
  private handleRedriveMessage;
188
213
  private setupShutdownHandlers;
@@ -190,4 +215,4 @@ declare class Worker<TCatalog extends WorkerCatalog> {
190
215
  stop(): Promise<void>;
191
216
  }
192
217
 
193
- export { type AnyMessageCatalog, type AnyQueueItem, type MessageCatalogKey, type MessageCatalogSchema, type MessageCatalogValue, type QueueItem, SimpleQueue, Worker, type WorkerCatalog, type WorkerConcurrencyOptions };
218
+ export { type AnyMessageCatalog, type AnyQueueItem, CronSchema, type JobHandler, type JobHandlerParams, type MessageCatalogKey, type MessageCatalogSchema, type MessageCatalogValue, type QueueItem, SimpleQueue, Worker, type WorkerCatalog, type WorkerConcurrencyOptions };
package/dist/index.d.ts CHANGED
@@ -62,6 +62,7 @@ declare class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
62
62
  size({ includeFuture }?: {
63
63
  includeFuture?: boolean;
64
64
  }): Promise<number>;
65
+ getJob(id: string): Promise<QueueItem<TMessageCatalog> | null>;
65
66
  moveToDeadLetterQueue(id: string, errorMessage: string): Promise<void>;
66
67
  sizeOfDeadLetterQueue(): Promise<number>;
67
68
  redriveFromDeadLetterQueue(id: string): Promise<void>;
@@ -75,26 +76,44 @@ declare module "@internal/redis" {
75
76
  redriveFromDeadLetterQueue(queue: string, items: string, dlq: string, dlqItems: string, id: string, callback?: Callback<number>): Result<number, Context>;
76
77
  moveToDeadLetterQueue(queue: string, items: string, dlq: string, dlqItems: string, id: string, errorMessage: string, callback?: Callback<number>): Result<number, Context>;
77
78
  enqueueItemOnce(queue: string, items: string, id: string, score: number, serializedItem: string, callback?: Callback<number>): Result<number, Context>;
79
+ getJob(queue: string, items: string, id: string, callback?: Callback<[string, string, string] | null>): Result<[string, string, string] | null, Context>;
78
80
  }
79
81
  }
80
82
 
83
+ declare const CronSchema: z.ZodObject<{
84
+ cron: z.ZodString;
85
+ lastTimestamp: z.ZodOptional<z.ZodNumber>;
86
+ timestamp: z.ZodNumber;
87
+ }, "strip", z.ZodTypeAny, {
88
+ timestamp: number;
89
+ cron: string;
90
+ lastTimestamp?: number | undefined;
91
+ }, {
92
+ timestamp: number;
93
+ cron: string;
94
+ lastTimestamp?: number | undefined;
95
+ }>;
96
+ type CronSchema = z.infer<typeof CronSchema>;
81
97
  type WorkerCatalog = {
82
98
  [key: string]: {
83
99
  schema: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>;
84
100
  visibilityTimeoutMs: number;
85
101
  retry?: RetryOptions;
102
+ cron?: string;
103
+ jitterInMs?: number;
86
104
  };
87
105
  };
88
106
  type QueueCatalogFromWorkerCatalog<Catalog extends WorkerCatalog> = {
89
107
  [K in keyof Catalog]: Catalog[K]["schema"];
90
108
  };
91
- type JobHandler<Catalog extends WorkerCatalog, K extends keyof Catalog> = (params: {
109
+ type JobHandlerParams<Catalog extends WorkerCatalog, K extends keyof Catalog> = {
92
110
  id: string;
93
111
  payload: z.infer<Catalog[K]["schema"]>;
94
112
  visibilityTimeoutMs: number;
95
113
  attempt: number;
96
114
  deduplicationKey?: string;
97
- }) => Promise<void>;
115
+ };
116
+ type JobHandler<Catalog extends WorkerCatalog, K extends keyof Catalog> = (params: JobHandlerParams<Catalog, K>) => Promise<void>;
98
117
  type WorkerConcurrencyOptions = {
99
118
  workers?: number;
100
119
  tasksPerWorker?: number;
@@ -129,7 +148,7 @@ declare class Worker<TCatalog extends WorkerCatalog> {
129
148
  private isShuttingDown;
130
149
  private concurrency;
131
150
  private shutdownTimeoutMs;
132
- private limiter;
151
+ private limiters;
133
152
  constructor(options: WorkerOptions<TCatalog>);
134
153
  start(): this;
135
154
  /**
@@ -172,6 +191,7 @@ declare class Worker<TCatalog extends WorkerCatalog> {
172
191
  */
173
192
  reschedule(id: string, availableAt: Date): Promise<void>;
174
193
  ack(id: string): Promise<void>;
194
+ getJob(id: string): Promise<QueueItem<QueueCatalogFromWorkerCatalog<TCatalog>> | null>;
175
195
  /**
176
196
  * The main loop that each worker runs. It repeatedly polls for items,
177
197
  * processes them, and then waits before the next iteration.
@@ -183,6 +203,11 @@ declare class Worker<TCatalog extends WorkerCatalog> {
183
203
  private processItem;
184
204
  private withHistogram;
185
205
  private static delay;
206
+ private setupCron;
207
+ private enqueueCronJob;
208
+ private rescheduleCronJob;
209
+ private calculateNextScheduledAt;
210
+ private timestampIdentifier;
186
211
  private setupSubscriber;
187
212
  private handleRedriveMessage;
188
213
  private setupShutdownHandlers;
@@ -190,4 +215,4 @@ declare class Worker<TCatalog extends WorkerCatalog> {
190
215
  stop(): Promise<void>;
191
216
  }
192
217
 
193
- export { type AnyMessageCatalog, type AnyQueueItem, type MessageCatalogKey, type MessageCatalogSchema, type MessageCatalogValue, type QueueItem, SimpleQueue, Worker, type WorkerCatalog, type WorkerConcurrencyOptions };
218
+ export { type AnyMessageCatalog, type AnyQueueItem, CronSchema, type JobHandler, type JobHandlerParams, type MessageCatalogKey, type MessageCatalogSchema, type MessageCatalogValue, type QueueItem, SimpleQueue, Worker, type WorkerCatalog, type WorkerConcurrencyOptions };
package/dist/index.js CHANGED
@@ -7,6 +7,8 @@ import { webcrypto } from 'node:crypto';
7
7
  import '@trigger.dev/core/v3/utils/flattenAttributes';
8
8
  import { calculateNextRetryDelay } from '@trigger.dev/core/v3';
9
9
  import { shutdownManager } from '@trigger.dev/core/v3/serverOnly';
10
+ import { z } from 'zod';
11
+ import { parseExpression } from 'cron-parser';
10
12
 
11
13
  const require = createRequire(import.meta.url || process.cwd() + '/index.js');
12
14
  var __create = Object.create;
@@ -8736,7 +8738,7 @@ var require_Redis = __commonJS({
8736
8738
  var lodash_1 = require_lodash3();
8737
8739
  var Deque = require_denque();
8738
8740
  var debug = (0, utils_1.Debug)("redis");
8739
- var Redis3 = class _Redis extends Commander_1.default {
8741
+ var Redis4 = class _Redis extends Commander_1.default {
8740
8742
  constructor(arg1, arg2, arg3) {
8741
8743
  super();
8742
8744
  this.status = "wait";
@@ -9299,12 +9301,12 @@ var require_Redis = __commonJS({
9299
9301
  }).catch(lodash_1.noop);
9300
9302
  }
9301
9303
  };
9302
- Redis3.Cluster = cluster_1.default;
9303
- Redis3.Command = Command_1.default;
9304
- Redis3.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
9305
- (0, applyMixin_1.default)(Redis3, events_1.EventEmitter);
9306
- (0, transaction_1.addTransactionSupport)(Redis3.prototype);
9307
- exports.default = Redis3;
9304
+ Redis4.Cluster = cluster_1.default;
9305
+ Redis4.Command = Command_1.default;
9306
+ Redis4.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
9307
+ (0, applyMixin_1.default)(Redis4, events_1.EventEmitter);
9308
+ (0, transaction_1.addTransactionSupport)(Redis4.prototype);
9309
+ exports.default = Redis4;
9308
9310
  }
9309
9311
  });
9310
9312
 
@@ -9557,7 +9559,8 @@ var SimpleQueue = class {
9557
9559
  id,
9558
9560
  item: parsedItem,
9559
9561
  job: parsedItem.job,
9560
- timestamp
9562
+ timestamp,
9563
+ availableJobs: Object.keys(this.schema)
9561
9564
  });
9562
9565
  continue;
9563
9566
  }
@@ -9638,8 +9641,30 @@ var SimpleQueue = class {
9638
9641
  throw e;
9639
9642
  }
9640
9643
  }
9644
+ async getJob(id) {
9645
+ const result = await this.redis.getJob(`queue`, `items`, id);
9646
+ if (!result) {
9647
+ return null;
9648
+ }
9649
+ const [_, score, serializedItem] = result;
9650
+ const item = JSON.parse(serializedItem);
9651
+ return {
9652
+ id,
9653
+ job: item.job,
9654
+ item: item.item,
9655
+ visibilityTimeoutMs: item.visibilityTimeoutMs,
9656
+ attempt: item.attempt ?? 0,
9657
+ timestamp: new Date(Number(score)),
9658
+ deduplicationKey: item.deduplicationKey ?? void 0
9659
+ };
9660
+ }
9641
9661
  async moveToDeadLetterQueue(id, errorMessage) {
9642
9662
  try {
9663
+ this.logger.debug(`SimpleQueue ${this.name}.moveToDeadLetterQueue(): moving item to DLQ`, {
9664
+ queue: this.name,
9665
+ id,
9666
+ errorMessage
9667
+ });
9643
9668
  const result = await this.redis.moveToDeadLetterQueue(
9644
9669
  `queue`,
9645
9670
  `items`,
@@ -9760,6 +9785,25 @@ var SimpleQueue = class {
9760
9785
  return dequeued
9761
9786
  `
9762
9787
  });
9788
+ this.redis.defineCommand("getJob", {
9789
+ numberOfKeys: 2,
9790
+ lua: `
9791
+ local queue = KEYS[1]
9792
+ local items = KEYS[2]
9793
+ local jobId = ARGV[1]
9794
+
9795
+ local serializedItem = redis.call('HGET', items, jobId)
9796
+
9797
+ if serializedItem == false then
9798
+ return nil
9799
+ end
9800
+
9801
+ -- get the score from the queue sorted set
9802
+ local score = redis.call('ZSCORE', queue, jobId)
9803
+
9804
+ return { jobId, score, serializedItem }
9805
+ `
9806
+ });
9763
9807
  this.redis.defineCommand("ackItem", {
9764
9808
  numberOfKeys: 2,
9765
9809
  lua: `
@@ -11001,6 +11045,11 @@ function validateConcurrency(concurrency) {
11001
11045
  throw new TypeError("Expected `concurrency` to be a number from 1 and up");
11002
11046
  }
11003
11047
  }
11048
+ var CronSchema = z.object({
11049
+ cron: z.string(),
11050
+ lastTimestamp: z.number().optional(),
11051
+ timestamp: z.number()
11052
+ });
11004
11053
  var defaultRetrySettings = {
11005
11054
  maxAttempts: 12,
11006
11055
  factor: 2,
@@ -11029,7 +11078,6 @@ var Worker = class _Worker {
11029
11078
  this.jobs = options.jobs;
11030
11079
  const { workers = 1, tasksPerWorker = 1, limit = 10 } = options.concurrency ?? {};
11031
11080
  this.concurrency = { workers, tasksPerWorker, limit };
11032
- this.limiter = pLimit(this.concurrency.limit);
11033
11081
  const masterQueueObservableGauge = this.meter.createObservableGauge("redis_worker.queue.size", {
11034
11082
  description: "The number of items in the queue",
11035
11083
  unit: "items",
@@ -11080,7 +11128,7 @@ var Worker = class _Worker {
11080
11128
  concurrency;
11081
11129
  shutdownTimeoutMs;
11082
11130
  // The p-limit limiter to control overall concurrency.
11083
- limiter;
11131
+ limiters = {};
11084
11132
  async #updateQueueSizeMetric(observableResult) {
11085
11133
  const queueSize = await this.queue.size();
11086
11134
  observableResult.observe(queueSize, {
@@ -11094,19 +11142,30 @@ var Worker = class _Worker {
11094
11142
  });
11095
11143
  }
11096
11144
  async #updateConcurrencyLimitActiveMetric(observableResult) {
11097
- observableResult.observe(this.limiter.activeCount, {
11098
- worker_name: this.options.name
11099
- });
11145
+ for (const [workerId, limiter] of Object.entries(this.limiters)) {
11146
+ observableResult.observe(limiter.activeCount, {
11147
+ worker_name: this.options.name,
11148
+ worker_id: workerId
11149
+ });
11150
+ }
11100
11151
  }
11101
11152
  async #updateConcurrencyLimitPendingMetric(observableResult) {
11102
- observableResult.observe(this.limiter.pendingCount, {
11103
- worker_name: this.options.name
11104
- });
11153
+ for (const [workerId, limiter] of Object.entries(this.limiters)) {
11154
+ observableResult.observe(limiter.pendingCount, {
11155
+ worker_name: this.options.name,
11156
+ worker_id: workerId
11157
+ });
11158
+ }
11105
11159
  }
11106
11160
  start() {
11107
11161
  const { workers, tasksPerWorker } = this.concurrency;
11162
+ this.logger.info("Starting worker", {
11163
+ workers,
11164
+ tasksPerWorker,
11165
+ concurrency: this.concurrency
11166
+ });
11108
11167
  for (let i = 0; i < workers; i++) {
11109
- this.workerLoops.push(this.runWorkerLoop(`worker-${nanoid(12)}`, tasksPerWorker));
11168
+ this.workerLoops.push(this.runWorkerLoop(`worker-${nanoid(12)}`, tasksPerWorker, i, workers));
11110
11169
  }
11111
11170
  this.setupShutdownHandlers();
11112
11171
  this.subscriber = createRedisClient(this.options.redisOptions, {
@@ -11118,6 +11177,7 @@ var Worker = class _Worker {
11118
11177
  }
11119
11178
  });
11120
11179
  this.setupSubscriber();
11180
+ this.setupCron();
11121
11181
  return this;
11122
11182
  }
11123
11183
  /**
@@ -11256,37 +11316,78 @@ var Worker = class _Worker {
11256
11316
  }
11257
11317
  );
11258
11318
  }
11319
+ async getJob(id) {
11320
+ return this.queue.getJob(id);
11321
+ }
11259
11322
  /**
11260
11323
  * The main loop that each worker runs. It repeatedly polls for items,
11261
11324
  * processes them, and then waits before the next iteration.
11262
11325
  */
11263
- async runWorkerLoop(workerId, taskCount) {
11326
+ async runWorkerLoop(workerId, taskCount, workerIndex, totalWorkers) {
11327
+ const limiter = pLimit(this.concurrency.limit);
11328
+ this.limiters[workerId] = limiter;
11264
11329
  const pollIntervalMs = this.options.pollIntervalMs ?? 1e3;
11265
11330
  const immediatePollIntervalMs = this.options.immediatePollIntervalMs ?? 100;
11331
+ const delayBetweenWorkers = this.options.pollIntervalMs ?? 1e3;
11332
+ const delay = delayBetweenWorkers * (totalWorkers - workerIndex);
11333
+ await _Worker.delay(delay);
11334
+ this.logger.info("Starting worker loop", {
11335
+ workerIndex,
11336
+ totalWorkers,
11337
+ delay,
11338
+ workerId,
11339
+ taskCount,
11340
+ pollIntervalMs,
11341
+ immediatePollIntervalMs,
11342
+ concurrencyOptions: this.concurrency
11343
+ });
11266
11344
  while (!this.isShuttingDown) {
11267
- if (this.limiter.activeCount + this.limiter.pendingCount >= this.concurrency.limit) {
11345
+ if (limiter.activeCount + limiter.pendingCount >= this.concurrency.limit) {
11346
+ this.logger.debug("Worker at capacity, waiting", {
11347
+ workerId,
11348
+ concurrencyOptions: this.concurrency,
11349
+ activeCount: limiter.activeCount,
11350
+ pendingCount: limiter.pendingCount
11351
+ });
11268
11352
  await _Worker.delay(pollIntervalMs);
11269
11353
  continue;
11270
11354
  }
11355
+ const $taskCount = Math.min(
11356
+ taskCount,
11357
+ this.concurrency.limit - limiter.activeCount - limiter.pendingCount
11358
+ );
11271
11359
  try {
11272
11360
  const items = await this.withHistogram(
11273
11361
  this.metrics.dequeueDuration,
11274
- this.queue.dequeue(taskCount),
11362
+ this.queue.dequeue($taskCount),
11275
11363
  {
11276
11364
  worker_id: workerId,
11277
- task_count: taskCount
11365
+ task_count: $taskCount
11278
11366
  }
11279
11367
  );
11280
11368
  if (items.length === 0) {
11369
+ this.logger.debug("No items to dequeue", {
11370
+ workerId,
11371
+ concurrencyOptions: this.concurrency,
11372
+ activeCount: limiter.activeCount,
11373
+ pendingCount: limiter.pendingCount
11374
+ });
11281
11375
  await _Worker.delay(pollIntervalMs);
11282
11376
  continue;
11283
11377
  }
11378
+ this.logger.debug("Dequeued items", {
11379
+ workerId,
11380
+ itemCount: items.length,
11381
+ concurrencyOptions: this.concurrency,
11382
+ activeCount: limiter.activeCount,
11383
+ pendingCount: limiter.pendingCount
11384
+ });
11284
11385
  for (const item of items) {
11285
- this.limiter(() => this.processItem(item, items.length, workerId)).catch(
11286
- (err) => {
11287
- this.logger.error("Unhandled error in processItem:", { error: err, workerId, item });
11288
- }
11289
- );
11386
+ limiter(
11387
+ () => this.processItem(item, items.length, workerId, limiter)
11388
+ ).catch((err) => {
11389
+ this.logger.error("Unhandled error in processItem:", { error: err, workerId, item });
11390
+ });
11290
11391
  }
11291
11392
  } catch (error) {
11292
11393
  this.logger.error("Error dequeuing items:", { name: this.options.name, error });
@@ -11295,17 +11396,22 @@ var Worker = class _Worker {
11295
11396
  }
11296
11397
  await _Worker.delay(immediatePollIntervalMs);
11297
11398
  }
11399
+ this.logger.info("Worker loop finished", { workerId });
11298
11400
  }
11299
11401
  /**
11300
11402
  * Processes a single item.
11301
11403
  */
11302
- async processItem({ id, job, item, visibilityTimeoutMs, attempt, timestamp, deduplicationKey }, batchSize, workerId) {
11404
+ async processItem({ id, job, item, visibilityTimeoutMs, attempt, timestamp, deduplicationKey }, batchSize, workerId, limiter) {
11303
11405
  const catalogItem = this.options.catalog[job];
11304
11406
  const handler = this.jobs[job];
11305
11407
  if (!handler) {
11306
11408
  this.logger.error(`No handler found for job type: ${job}`);
11307
11409
  return;
11308
11410
  }
11411
+ if (!catalogItem) {
11412
+ this.logger.error(`No catalog item found for job type: ${job}`);
11413
+ return;
11414
+ }
11309
11415
  await startSpan(
11310
11416
  this.tracer,
11311
11417
  "processItem",
@@ -11321,6 +11427,9 @@ var Worker = class _Worker {
11321
11427
  }
11322
11428
  );
11323
11429
  await this.queue.ack(id, deduplicationKey);
11430
+ if (catalogItem.cron) {
11431
+ await this.rescheduleCronJob(job, catalogItem, item);
11432
+ }
11324
11433
  },
11325
11434
  {
11326
11435
  kind: SpanKind.CONSUMER,
@@ -11331,9 +11440,9 @@ var Worker = class _Worker {
11331
11440
  job_timestamp: timestamp.getTime(),
11332
11441
  job_age_in_ms: Date.now() - timestamp.getTime(),
11333
11442
  worker_id: workerId,
11334
- worker_limit_concurrency: this.limiter.concurrency,
11335
- worker_limit_active: this.limiter.activeCount,
11336
- worker_limit_pending: this.limiter.pendingCount,
11443
+ worker_limit_concurrency: limiter.concurrency,
11444
+ worker_limit_active: limiter.activeCount,
11445
+ worker_limit_pending: limiter.pendingCount,
11337
11446
  worker_name: this.options.name,
11338
11447
  batch_size: batchSize
11339
11448
  }
@@ -11367,6 +11476,9 @@ var Worker = class _Worker {
11367
11476
  errorMessage
11368
11477
  });
11369
11478
  await this.queue.moveToDeadLetterQueue(id, errorMessage);
11479
+ if (catalogItem.cron) {
11480
+ await this.rescheduleCronJob(job, catalogItem, item);
11481
+ }
11370
11482
  return;
11371
11483
  }
11372
11484
  const retryDate = new Date(Date.now() + retryDelay);
@@ -11419,6 +11531,93 @@ var Worker = class _Worker {
11419
11531
  static delay(ms) {
11420
11532
  return new Promise((resolve) => setTimeout(resolve, ms));
11421
11533
  }
11534
+ setupCron() {
11535
+ const cronJobs = Object.entries(this.options.catalog).filter(([_, value]) => value.cron);
11536
+ if (cronJobs.length === 0) {
11537
+ return;
11538
+ }
11539
+ this.logger.info("Setting up cron jobs", {
11540
+ cronJobs: cronJobs.map(([job, value]) => ({
11541
+ job,
11542
+ cron: value.cron,
11543
+ jitterInMs: value.jitterInMs
11544
+ }))
11545
+ });
11546
+ const enqueuePromises = cronJobs.map(
11547
+ ([job, value]) => this.enqueueCronJob(value.cron, job, value.jitterInMs)
11548
+ );
11549
+ Promise.allSettled(enqueuePromises).then((results) => {
11550
+ results.forEach((result) => {
11551
+ if (result.status === "fulfilled") {
11552
+ this.logger.info("Enqueued cron job", { result: result.value });
11553
+ } else {
11554
+ this.logger.error("Failed to enqueue cron job", { reason: result.reason });
11555
+ }
11556
+ });
11557
+ });
11558
+ }
11559
+ async enqueueCronJob(cron, job, jitter, lastTimestamp) {
11560
+ const scheduledAt = this.calculateNextScheduledAt(cron, lastTimestamp);
11561
+ const identifier = [job, this.timestampIdentifier(scheduledAt)].join(":");
11562
+ const appliedJitter = typeof jitter === "number" ? Math.random() * jitter - jitter / 2 : 0;
11563
+ const availableAt = new Date(scheduledAt.getTime() + appliedJitter);
11564
+ const enqueued = await this.enqueueOnce({
11565
+ id: identifier,
11566
+ job,
11567
+ payload: {
11568
+ timestamp: scheduledAt.getTime(),
11569
+ lastTimestamp: lastTimestamp?.getTime(),
11570
+ cron
11571
+ },
11572
+ availableAt
11573
+ });
11574
+ this.logger.info("Enqueued cron job", {
11575
+ identifier,
11576
+ cron,
11577
+ job,
11578
+ scheduledAt,
11579
+ enqueued,
11580
+ availableAt,
11581
+ appliedJitter,
11582
+ jitter
11583
+ });
11584
+ return {
11585
+ identifier,
11586
+ cron,
11587
+ job,
11588
+ scheduledAt,
11589
+ enqueued
11590
+ };
11591
+ }
11592
+ async rescheduleCronJob(job, catalogItem, item) {
11593
+ if (!catalogItem.cron) {
11594
+ return;
11595
+ }
11596
+ return this.enqueueCronJob(
11597
+ catalogItem.cron,
11598
+ job,
11599
+ catalogItem.jitterInMs,
11600
+ new Date(item.timestamp)
11601
+ );
11602
+ }
11603
+ calculateNextScheduledAt(cron, lastTimestamp) {
11604
+ const scheduledAt = parseExpression(cron, {
11605
+ currentDate: lastTimestamp
11606
+ }).next().toDate();
11607
+ if (scheduledAt < /* @__PURE__ */ new Date()) {
11608
+ return this.calculateNextScheduledAt(cron);
11609
+ }
11610
+ return scheduledAt;
11611
+ }
11612
+ timestampIdentifier(timestamp) {
11613
+ const year = timestamp.getUTCFullYear();
11614
+ const month = timestamp.getUTCMonth();
11615
+ const day = timestamp.getUTCDate();
11616
+ const hour = timestamp.getUTCHours();
11617
+ const minute = timestamp.getUTCMinutes();
11618
+ const second = timestamp.getUTCSeconds();
11619
+ return `${year}-${month}-${day}-${hour}-${minute}-${second}`;
11620
+ }
11422
11621
  setupSubscriber() {
11423
11622
  const channel = `${this.options.name}:redrive`;
11424
11623
  this.subscriber?.subscribe(channel, (err) => {
@@ -11475,6 +11674,6 @@ var Worker = class _Worker {
11475
11674
  }
11476
11675
  };
11477
11676
 
11478
- export { SimpleQueue, Worker };
11677
+ export { CronSchema, SimpleQueue, Worker };
11479
11678
  //# sourceMappingURL=index.js.map
11480
11679
  //# sourceMappingURL=index.js.map