@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.cjs +230 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -4
- package/dist/index.d.ts +29 -4
- package/dist/index.js +230 -31
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
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
|
|
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
|
-
}
|
|
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
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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
|
|
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
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
(0, applyMixin_1.default)(
|
|
9306
|
-
(0, transaction_1.addTransactionSupport)(
|
|
9307
|
-
exports.default =
|
|
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
|
-
|
|
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
|
-
|
|
11098
|
-
|
|
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
|
-
|
|
11103
|
-
|
|
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 (
|
|
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
|
-
|
|
11286
|
-
(
|
|
11287
|
-
|
|
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:
|
|
11335
|
-
worker_limit_active:
|
|
11336
|
-
worker_limit_pending:
|
|
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
|