@igniter-js/jobs 0.1.0 → 0.1.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/AGENTS.md +115 -541
- package/CHANGELOG.md +5 -0
- package/README.md +118 -356
- package/dist/adapter-PiDCQWQd.d.mts +529 -0
- package/dist/adapter-PiDCQWQd.d.ts +529 -0
- package/dist/adapters/bullmq.adapter.d.mts +55 -110
- package/dist/adapters/bullmq.adapter.d.ts +55 -110
- package/dist/adapters/bullmq.adapter.js +431 -529
- package/dist/adapters/bullmq.adapter.js.map +1 -1
- package/dist/adapters/bullmq.adapter.mjs +431 -529
- package/dist/adapters/bullmq.adapter.mjs.map +1 -1
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.d.ts +3 -3
- package/dist/adapters/index.js +889 -960
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +888 -959
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/adapters/memory.adapter.d.mts +52 -98
- package/dist/adapters/memory.adapter.d.ts +52 -98
- package/dist/adapters/memory.adapter.js +471 -473
- package/dist/adapters/memory.adapter.js.map +1 -1
- package/dist/adapters/memory.adapter.mjs +471 -473
- package/dist/adapters/memory.adapter.mjs.map +1 -1
- package/dist/index.d.mts +485 -958
- package/dist/index.d.ts +485 -958
- package/dist/index.js +2053 -917
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2051 -917
- package/dist/index.mjs.map +1 -1
- package/package.json +34 -16
- package/dist/adapter-CcQCatSa.d.mts +0 -1411
- package/dist/adapter-CcQCatSa.d.ts +0 -1411
package/dist/adapters/index.js
CHANGED
|
@@ -1,1129 +1,1058 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var adapterBullmq = require('@igniter-js/adapter-bullmq');
|
|
3
4
|
var core = require('@igniter-js/core');
|
|
4
5
|
|
|
5
|
-
// src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (Error.captureStackTrace) {
|
|
21
|
-
Error.captureStackTrace(this, _IgniterJobsError);
|
|
22
|
-
}
|
|
6
|
+
// src/adapters/bullmq.adapter.ts
|
|
7
|
+
|
|
8
|
+
// src/utils/prefix.ts
|
|
9
|
+
var _IgniterJobsPrefix = class _IgniterJobsPrefix {
|
|
10
|
+
/**
|
|
11
|
+
* Builds a normalized queue name using the global prefix and queue id.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const name = IgniterJobsPrefix.buildQueueName('email')
|
|
16
|
+
* // -> igniter:jobs:email
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
static buildQueueName(queue) {
|
|
20
|
+
return `${_IgniterJobsPrefix.BASE_PREFIX}:${queue}`;
|
|
23
21
|
}
|
|
24
22
|
/**
|
|
25
|
-
*
|
|
23
|
+
* Builds the event channel used for pub/sub.
|
|
24
|
+
*
|
|
25
|
+
* Unscoped events are published to a global channel per service/environment.
|
|
26
|
+
* Scoped events are also published to an additional channel for that scope.
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
static buildEventsChannel(params) {
|
|
29
|
+
const base = `${_IgniterJobsPrefix.BASE_PREFIX}:events:${params.environment}:${params.service}`;
|
|
30
|
+
if (!params.scope) return base;
|
|
31
|
+
return `${base}:scope:${params.scope.type}:${params.scope.id}`;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
_IgniterJobsPrefix.BASE_PREFIX = "igniter:jobs";
|
|
35
|
+
var IgniterJobsPrefix = _IgniterJobsPrefix;
|
|
36
|
+
var IgniterJobsError = class extends core.IgniterError {
|
|
37
|
+
constructor(options) {
|
|
38
|
+
super(options);
|
|
36
39
|
}
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
// src/adapters/bullmq.adapter.ts
|
|
40
|
-
|
|
43
|
+
function toDateArray(values) {
|
|
44
|
+
if (!values) return void 0;
|
|
45
|
+
return values.map((v) => v instanceof Date ? v : new Date(v));
|
|
46
|
+
}
|
|
47
|
+
var IgniterJobsBullMQAdapter = class _IgniterJobsBullMQAdapter {
|
|
41
48
|
constructor(options) {
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
49
|
+
this.subscribers = /* @__PURE__ */ new Map();
|
|
50
|
+
this.coreAdapter = null;
|
|
51
|
+
this.coreExecutor = null;
|
|
52
|
+
this.executorDirty = true;
|
|
53
|
+
this.jobsByQueue = /* @__PURE__ */ new Map();
|
|
54
|
+
this.cronsByQueue = /* @__PURE__ */ new Map();
|
|
46
55
|
this.redis = options.redis;
|
|
56
|
+
this.publisher = this.redis;
|
|
57
|
+
this.subscriber = this.redis.duplicate();
|
|
58
|
+
this.client = { redis: this.redis };
|
|
59
|
+
this.subscriber.on("message", (channel, message) => {
|
|
60
|
+
const set = this.subscribers.get(channel);
|
|
61
|
+
if (!set || set.size === 0) return;
|
|
62
|
+
let payload = message;
|
|
63
|
+
try {
|
|
64
|
+
payload = JSON.parse(message);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
for (const handler of set) handler(payload);
|
|
68
|
+
});
|
|
69
|
+
this.queues = {
|
|
70
|
+
list: async () => this.listQueues(),
|
|
71
|
+
get: async (name) => this.getQueueInfo(name),
|
|
72
|
+
getJobCounts: async (name) => this.getQueueJobCounts(name),
|
|
73
|
+
getJobs: async (name, filter) => {
|
|
74
|
+
const full = this.toCoreQueueName(name);
|
|
75
|
+
return this.core().queues.getJobs(full, filter);
|
|
76
|
+
},
|
|
77
|
+
pause: async (name) => this.pauseQueue(name),
|
|
78
|
+
resume: async (name) => this.resumeQueue(name),
|
|
79
|
+
isPaused: async (name) => {
|
|
80
|
+
const full = this.toCoreQueueName(name);
|
|
81
|
+
return this.core().queues.isPaused(full);
|
|
82
|
+
},
|
|
83
|
+
drain: async (name) => this.drainQueue(name),
|
|
84
|
+
clean: async (name, options2) => this.cleanQueue(name, options2),
|
|
85
|
+
obliterate: async (name, options2) => this.obliterateQueue(name, options2)
|
|
86
|
+
};
|
|
47
87
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Create a new BullMQ adapter.
|
|
50
|
-
*
|
|
51
|
-
* @param options - Adapter options with Redis connection
|
|
52
|
-
* @returns A new BullMQAdapter instance
|
|
53
|
-
*/
|
|
54
88
|
static create(options) {
|
|
55
|
-
return new
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Get the underlying Redis client.
|
|
59
|
-
*/
|
|
60
|
-
get client() {
|
|
61
|
-
return this.redis;
|
|
89
|
+
return new _IgniterJobsBullMQAdapter(options);
|
|
62
90
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
return this.BullMQ;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Get or create a queue instance.
|
|
74
|
-
*/
|
|
75
|
-
async getOrCreateQueue(name) {
|
|
76
|
-
if (!this.queues.has(name)) {
|
|
77
|
-
const { Queue } = await this.getBullMQ();
|
|
78
|
-
const queue = new Queue(name, {
|
|
79
|
-
connection: this.redis
|
|
91
|
+
registerJob(queueName, jobName, definition) {
|
|
92
|
+
const map = this.jobsByQueue.get(queueName) ?? /* @__PURE__ */ new Map();
|
|
93
|
+
if (map.has(jobName)) {
|
|
94
|
+
throw new IgniterJobsError({
|
|
95
|
+
code: "JOBS_DUPLICATE_JOB",
|
|
96
|
+
message: `Job "${jobName}" is already registered in queue "${queueName}".`
|
|
80
97
|
});
|
|
81
|
-
this.queues.set(name, queue);
|
|
82
98
|
}
|
|
83
|
-
|
|
99
|
+
map.set(jobName, definition);
|
|
100
|
+
this.jobsByQueue.set(queueName, map);
|
|
101
|
+
this.executorDirty = true;
|
|
84
102
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const events = new QueueEvents(name, {
|
|
92
|
-
connection: this.redis
|
|
103
|
+
registerCron(queueName, cronName, definition) {
|
|
104
|
+
const map = this.cronsByQueue.get(queueName) ?? /* @__PURE__ */ new Map();
|
|
105
|
+
if (map.has(cronName)) {
|
|
106
|
+
throw new IgniterJobsError({
|
|
107
|
+
code: "JOBS_INVALID_CRON",
|
|
108
|
+
message: `Cron "${cronName}" is already registered in queue "${queueName}".`
|
|
93
109
|
});
|
|
94
|
-
this.queueEvents.set(name, events);
|
|
95
110
|
}
|
|
96
|
-
|
|
111
|
+
map.set(cronName, definition);
|
|
112
|
+
this.cronsByQueue.set(queueName, map);
|
|
113
|
+
this.executorDirty = true;
|
|
97
114
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Convert BullMQ job state to IgniterJobStatus.
|
|
100
|
-
*/
|
|
101
|
-
mapJobState(state) {
|
|
102
|
-
const stateMap = {
|
|
103
|
-
waiting: "waiting",
|
|
104
|
-
active: "active",
|
|
105
|
-
completed: "completed",
|
|
106
|
-
failed: "failed",
|
|
107
|
-
delayed: "delayed",
|
|
108
|
-
paused: "paused",
|
|
109
|
-
"waiting-children": "waiting"
|
|
110
|
-
};
|
|
111
|
-
return stateMap[state] || "waiting";
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Convert BullMQ job to IgniterJobInfo.
|
|
115
|
-
*/
|
|
116
|
-
async mapJobToInfo(job) {
|
|
117
|
-
const data = job.data;
|
|
118
|
-
const state = await job.getState();
|
|
119
|
-
return {
|
|
120
|
-
id: job.id,
|
|
121
|
-
name: job.name,
|
|
122
|
-
queue: job.queueName,
|
|
123
|
-
state: this.mapJobState(state || "waiting"),
|
|
124
|
-
data: data.input ?? data,
|
|
125
|
-
result: job.returnvalue,
|
|
126
|
-
error: job.failedReason,
|
|
127
|
-
progress: typeof job.progress === "number" ? job.progress : 0,
|
|
128
|
-
attempts: job.attemptsMade,
|
|
129
|
-
timestamp: job.timestamp,
|
|
130
|
-
processedOn: job.processedOn,
|
|
131
|
-
finishedOn: job.finishedOn,
|
|
132
|
-
delay: job.delay,
|
|
133
|
-
priority: job.opts?.priority,
|
|
134
|
-
scope: data.scope,
|
|
135
|
-
actor: data.actor,
|
|
136
|
-
metadata: data.metadata
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
// ==========================================
|
|
140
|
-
// JOB OPERATIONS
|
|
141
|
-
// ==========================================
|
|
142
115
|
async dispatch(params) {
|
|
143
|
-
const
|
|
144
|
-
const
|
|
116
|
+
const executor = await this.executor();
|
|
117
|
+
const namespace = executor[params.queue];
|
|
118
|
+
if (!namespace) {
|
|
119
|
+
throw new IgniterJobsError({
|
|
120
|
+
code: "JOBS_QUEUE_NOT_FOUND",
|
|
121
|
+
message: `Queue "${params.queue}" is not registered in the adapter.`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return namespace.enqueue({
|
|
125
|
+
task: params.jobName,
|
|
126
|
+
input: params.input,
|
|
145
127
|
jobId: params.jobId,
|
|
146
128
|
delay: params.delay,
|
|
147
129
|
priority: params.priority,
|
|
148
|
-
attempts: params.attempts
|
|
149
|
-
|
|
150
|
-
type: params.backoff.type === "custom" ? "fixed" : params.backoff.type,
|
|
151
|
-
delay: params.backoff.type === "custom" ? params.backoff.delays[0] : params.backoff.delay
|
|
152
|
-
} : void 0,
|
|
130
|
+
attempts: params.attempts,
|
|
131
|
+
metadata: params.metadata,
|
|
153
132
|
removeOnComplete: params.removeOnComplete,
|
|
154
|
-
removeOnFail: params.removeOnFail
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
input: params.data,
|
|
158
|
-
scope: params.scope,
|
|
159
|
-
actor: params.actor
|
|
160
|
-
};
|
|
161
|
-
const job = await queue.add(params.name, jobData, jobOptions);
|
|
162
|
-
return job.id;
|
|
133
|
+
removeOnFail: params.removeOnFail,
|
|
134
|
+
limiter: params.limiter
|
|
135
|
+
});
|
|
163
136
|
}
|
|
164
137
|
async schedule(params) {
|
|
165
|
-
const
|
|
166
|
-
const
|
|
138
|
+
const executor = await this.executor();
|
|
139
|
+
const namespace = executor[params.queue];
|
|
140
|
+
if (!namespace) {
|
|
141
|
+
throw new IgniterJobsError({
|
|
142
|
+
code: "JOBS_QUEUE_NOT_FOUND",
|
|
143
|
+
message: `Queue "${params.queue}" is not registered in the adapter.`
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const schedule = {
|
|
167
147
|
jobId: params.jobId,
|
|
168
|
-
delay: params.
|
|
148
|
+
delay: params.delay,
|
|
169
149
|
priority: params.priority,
|
|
170
|
-
attempts: params.attempts
|
|
171
|
-
|
|
172
|
-
type: params.backoff.type === "custom" ? "fixed" : params.backoff.type,
|
|
173
|
-
delay: params.backoff.type === "custom" ? params.backoff.delays[0] : params.backoff.delay
|
|
174
|
-
} : void 0,
|
|
150
|
+
attempts: params.attempts,
|
|
151
|
+
metadata: params.metadata,
|
|
175
152
|
removeOnComplete: params.removeOnComplete,
|
|
176
153
|
removeOnFail: params.removeOnFail,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
every: params.every
|
|
154
|
+
limiter: params.limiter,
|
|
155
|
+
at: params.at,
|
|
156
|
+
repeat: params.cron || params.every || params.maxExecutions || params.skipWeekends || params.onlyBusinessHours || params.businessHours || params.onlyWeekdays || params.skipDates ? {
|
|
157
|
+
cron: params.cron,
|
|
158
|
+
every: params.every,
|
|
159
|
+
times: params.maxExecutions,
|
|
160
|
+
skipWeekends: params.skipWeekends,
|
|
161
|
+
onlyBusinessHours: params.onlyBusinessHours,
|
|
162
|
+
businessHours: params.businessHours,
|
|
163
|
+
onlyWeekdays: params.onlyWeekdays,
|
|
164
|
+
skipDates: toDateArray(params.skipDates)
|
|
182
165
|
} : void 0
|
|
183
166
|
};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
-
const job = await queue.add(params.name, jobData, jobOptions);
|
|
190
|
-
return job.id;
|
|
167
|
+
return namespace.schedule({
|
|
168
|
+
task: params.jobName,
|
|
169
|
+
input: params.input,
|
|
170
|
+
...schedule
|
|
171
|
+
});
|
|
191
172
|
}
|
|
192
|
-
async getJob(
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
if (!job) return null;
|
|
196
|
-
return await this.mapJobToInfo(job);
|
|
173
|
+
async getJob(jobId, queue) {
|
|
174
|
+
const result = await this.core().job.get(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
175
|
+
return result ? this.mapJob(result, queue) : null;
|
|
197
176
|
}
|
|
198
|
-
async getJobState(
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
if (!job) return null;
|
|
202
|
-
const state = await job.getState();
|
|
203
|
-
return this.mapJobState(state);
|
|
177
|
+
async getJobState(jobId, queue) {
|
|
178
|
+
const state = await this.core().job.getState(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
179
|
+
return state;
|
|
204
180
|
}
|
|
205
|
-
async
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
if (!job) return 0;
|
|
209
|
-
return typeof job.progress === "number" ? job.progress : 0;
|
|
181
|
+
async getJobLogs(jobId, queue) {
|
|
182
|
+
const logs = await this.core().job.getLogs(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
183
|
+
return logs;
|
|
210
184
|
}
|
|
211
|
-
async
|
|
212
|
-
|
|
213
|
-
const job = await q.getJob(jobId);
|
|
214
|
-
if (!job) return [];
|
|
215
|
-
const { logs } = await q.getJobLogs(jobId);
|
|
216
|
-
return logs.map((log, index) => ({
|
|
217
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
218
|
-
message: log,
|
|
219
|
-
level: "info"
|
|
220
|
-
}));
|
|
221
|
-
}
|
|
222
|
-
async retryJob(queue, jobId) {
|
|
223
|
-
const q = await this.getOrCreateQueue(queue);
|
|
224
|
-
const job = await q.getJob(jobId);
|
|
225
|
-
if (!job) {
|
|
226
|
-
throw new IgniterJobsError({
|
|
227
|
-
code: "JOBS_JOB_NOT_FOUND",
|
|
228
|
-
message: `Job "${jobId}" not found in queue "${queue}"`,
|
|
229
|
-
statusCode: 404
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
await job.retry();
|
|
185
|
+
async getJobProgress(jobId, queue) {
|
|
186
|
+
return this.core().job.getProgress(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
233
187
|
}
|
|
234
|
-
async
|
|
235
|
-
|
|
236
|
-
const job = await q.getJob(jobId);
|
|
237
|
-
if (!job) return;
|
|
238
|
-
await job.remove();
|
|
188
|
+
async retryJob(jobId, queue) {
|
|
189
|
+
await this.core().job.retry(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
239
190
|
}
|
|
240
|
-
async
|
|
241
|
-
|
|
242
|
-
const job = await q.getJob(jobId);
|
|
243
|
-
if (!job) {
|
|
244
|
-
throw new IgniterJobsError({
|
|
245
|
-
code: "JOBS_JOB_NOT_FOUND",
|
|
246
|
-
message: `Job "${jobId}" not found in queue "${queue}"`,
|
|
247
|
-
statusCode: 404
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
await job.promote();
|
|
191
|
+
async removeJob(jobId, queue) {
|
|
192
|
+
await this.core().job.remove(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
251
193
|
}
|
|
252
|
-
async
|
|
253
|
-
|
|
254
|
-
const job = await q.getJob(jobId);
|
|
255
|
-
if (!job) {
|
|
256
|
-
throw new IgniterJobsError({
|
|
257
|
-
code: "JOBS_JOB_NOT_FOUND",
|
|
258
|
-
message: `Job "${jobId}" not found in queue "${queue}"`,
|
|
259
|
-
statusCode: 404
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
if (state === "failed") {
|
|
263
|
-
await job.moveToFailed(new Error(reason || "Manually moved to failed"), "manual");
|
|
264
|
-
} else {
|
|
265
|
-
await job.moveToCompleted(reason || "Manually completed", "manual");
|
|
266
|
-
}
|
|
194
|
+
async promoteJob(jobId, queue) {
|
|
195
|
+
await this.core().job.promote(jobId, queue ? this.toCoreQueueName(queue) : void 0);
|
|
267
196
|
}
|
|
268
|
-
async
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
async
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
async getQueue(queue) {
|
|
290
|
-
const q = await this.getOrCreateQueue(queue);
|
|
291
|
-
const isPaused = await q.isPaused();
|
|
292
|
-
const counts = await q.getJobCounts();
|
|
293
|
-
return {
|
|
294
|
-
name: queue,
|
|
295
|
-
isPaused,
|
|
296
|
-
jobCounts: {
|
|
297
|
-
waiting: counts.waiting || 0,
|
|
298
|
-
active: counts.active || 0,
|
|
299
|
-
completed: counts.completed || 0,
|
|
300
|
-
failed: counts.failed || 0,
|
|
301
|
-
delayed: counts.delayed || 0,
|
|
302
|
-
paused: counts.paused || 0
|
|
303
|
-
}
|
|
304
|
-
};
|
|
197
|
+
async moveJobToFailed(jobId, reason, queue) {
|
|
198
|
+
await this.core().job.moveToFailed(jobId, reason, queue ? this.toCoreQueueName(queue) : void 0);
|
|
199
|
+
}
|
|
200
|
+
async retryManyJobs(jobIds, queue) {
|
|
201
|
+
await this.core().job.retryMany(jobIds, queue ? this.toCoreQueueName(queue) : void 0);
|
|
202
|
+
}
|
|
203
|
+
async removeManyJobs(jobIds, queue) {
|
|
204
|
+
await this.core().job.removeMany(jobIds, queue ? this.toCoreQueueName(queue) : void 0);
|
|
205
|
+
}
|
|
206
|
+
async getQueueInfo(queue) {
|
|
207
|
+
const info = await this.core().queues.get(this.toCoreQueueName(queue));
|
|
208
|
+
if (!info) return null;
|
|
209
|
+
return this.mapQueueInfo(info);
|
|
210
|
+
}
|
|
211
|
+
async getQueueJobCounts(queue) {
|
|
212
|
+
const counts = await this.core().queues.getJobCounts(this.toCoreQueueName(queue));
|
|
213
|
+
return counts;
|
|
214
|
+
}
|
|
215
|
+
async listQueues() {
|
|
216
|
+
const list = await this.core().queues.list();
|
|
217
|
+
return list.map((q) => this.mapQueueInfo(q));
|
|
305
218
|
}
|
|
306
219
|
async pauseQueue(queue) {
|
|
307
|
-
|
|
308
|
-
await q.pause();
|
|
220
|
+
await this.core().queues.pause(this.toCoreQueueName(queue));
|
|
309
221
|
}
|
|
310
222
|
async resumeQueue(queue) {
|
|
311
|
-
|
|
312
|
-
await q.resume();
|
|
223
|
+
await this.core().queues.resume(this.toCoreQueueName(queue));
|
|
313
224
|
}
|
|
314
225
|
async drainQueue(queue) {
|
|
315
|
-
|
|
316
|
-
await q.drain();
|
|
317
|
-
return 0;
|
|
226
|
+
return this.core().queues.drain(this.toCoreQueueName(queue));
|
|
318
227
|
}
|
|
319
228
|
async cleanQueue(queue, options) {
|
|
320
|
-
|
|
321
|
-
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
322
|
-
let total = 0;
|
|
323
|
-
for (const status of statuses) {
|
|
324
|
-
const cleaned = await q.clean(
|
|
325
|
-
options.olderThan ?? 0,
|
|
326
|
-
options.limit ?? 1e3,
|
|
327
|
-
status
|
|
328
|
-
);
|
|
329
|
-
total += cleaned.length;
|
|
330
|
-
}
|
|
331
|
-
return total;
|
|
229
|
+
return this.core().queues.clean(this.toCoreQueueName(queue), options);
|
|
332
230
|
}
|
|
333
231
|
async obliterateQueue(queue, options) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
await Promise.all(failed.map((job) => job.retry()));
|
|
341
|
-
return failed.length;
|
|
342
|
-
}
|
|
343
|
-
async getJobCounts(queue) {
|
|
344
|
-
const q = await this.getOrCreateQueue(queue);
|
|
345
|
-
const counts = await q.getJobCounts();
|
|
346
|
-
return {
|
|
347
|
-
waiting: counts.waiting || 0,
|
|
348
|
-
active: counts.active || 0,
|
|
349
|
-
completed: counts.completed || 0,
|
|
350
|
-
failed: counts.failed || 0,
|
|
351
|
-
delayed: counts.delayed || 0,
|
|
352
|
-
paused: counts.paused || 0
|
|
353
|
-
};
|
|
232
|
+
await this.core().queues.obliterate(this.toCoreQueueName(queue), options);
|
|
233
|
+
}
|
|
234
|
+
async retryAllInQueue(queue) {
|
|
235
|
+
const jobs = await this.core().queues.getJobs(this.toCoreQueueName(queue), { status: ["failed"], limit: 1e3 });
|
|
236
|
+
await Promise.all(jobs.map((j) => this.core().job.retry(j.id, this.toCoreQueueName(queue))));
|
|
237
|
+
return jobs.length;
|
|
354
238
|
}
|
|
355
|
-
async listJobs(queue, options) {
|
|
356
|
-
const q = await this.getOrCreateQueue(queue);
|
|
357
|
-
const statuses = options?.status || ["waiting", "active", "completed", "failed", "delayed"];
|
|
358
|
-
const jobs = [];
|
|
359
|
-
for (const status of statuses) {
|
|
360
|
-
const statusJobs = await q.getJobs([status], options?.start, options?.end);
|
|
361
|
-
jobs.push(...statusJobs);
|
|
362
|
-
}
|
|
363
|
-
return Promise.all(jobs.map(async (job) => {
|
|
364
|
-
const data = job.data;
|
|
365
|
-
const state = await job.getState();
|
|
366
|
-
return {
|
|
367
|
-
id: job.id,
|
|
368
|
-
name: job.name,
|
|
369
|
-
queue: job.queueName,
|
|
370
|
-
state: this.mapJobState(state || "waiting"),
|
|
371
|
-
data: data.input ?? data,
|
|
372
|
-
result: job.returnvalue,
|
|
373
|
-
error: job.failedReason,
|
|
374
|
-
progress: typeof job.progress === "number" ? job.progress : 0,
|
|
375
|
-
attempts: job.attemptsMade,
|
|
376
|
-
timestamp: job.timestamp,
|
|
377
|
-
processedOn: job.processedOn,
|
|
378
|
-
finishedOn: job.finishedOn,
|
|
379
|
-
scope: data.scope,
|
|
380
|
-
actor: data.actor
|
|
381
|
-
};
|
|
382
|
-
}));
|
|
383
|
-
}
|
|
384
|
-
// ==========================================
|
|
385
|
-
// PAUSE/RESUME JOB TYPES
|
|
386
|
-
// ==========================================
|
|
387
239
|
async pauseJobType(queue, jobName) {
|
|
388
|
-
|
|
240
|
+
throw new IgniterJobsError({
|
|
241
|
+
code: "JOBS_QUEUE_OPERATION_FAILED",
|
|
242
|
+
message: "BullMQ backend does not support pausing a single job type; pause the queue or adjust worker filters."
|
|
243
|
+
});
|
|
389
244
|
}
|
|
390
245
|
async resumeJobType(queue, jobName) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
async
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
246
|
+
throw new IgniterJobsError({
|
|
247
|
+
code: "JOBS_QUEUE_OPERATION_FAILED",
|
|
248
|
+
message: "BullMQ backend does not support resuming a single job type; resume the queue or adjust worker filters."
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async searchJobs(filter) {
|
|
252
|
+
const queue = filter?.queue;
|
|
253
|
+
const status = filter?.status;
|
|
254
|
+
const limit = filter?.limit ?? 100;
|
|
255
|
+
const offset = filter?.offset ?? 0;
|
|
256
|
+
if (queue) {
|
|
257
|
+
const jobs = await this.core().queues.getJobs(this.toCoreQueueName(queue), { status, limit, offset });
|
|
258
|
+
return jobs.map((j) => this.mapJob(j, queue));
|
|
259
|
+
}
|
|
260
|
+
const queues = await this.listQueues();
|
|
261
|
+
const results = [];
|
|
262
|
+
for (const q of queues) {
|
|
263
|
+
const jobs = await this.core().queues.getJobs(this.toCoreQueueName(q.name), { status, limit, offset });
|
|
264
|
+
results.push(...jobs.map((j) => this.mapJob(j, q.name)));
|
|
265
|
+
if (results.length >= limit) break;
|
|
266
|
+
}
|
|
267
|
+
return results.slice(0, limit);
|
|
268
|
+
}
|
|
269
|
+
async searchQueues(filter) {
|
|
270
|
+
const all = await this.listQueues();
|
|
271
|
+
const name = filter?.name;
|
|
272
|
+
const isPaused = filter?.isPaused;
|
|
273
|
+
return all.filter((q) => name ? q.name.includes(name) : true).filter((q) => typeof isPaused === "boolean" ? q.isPaused === isPaused : true);
|
|
274
|
+
}
|
|
275
|
+
async searchWorkers(filter) {
|
|
276
|
+
const queue = filter?.queue;
|
|
277
|
+
const isRunning = filter?.isRunning;
|
|
278
|
+
const all = Array.from(this.core().getWorkers().values());
|
|
279
|
+
return all.filter((w) => {
|
|
280
|
+
if (!queue) return true;
|
|
281
|
+
const coreQueue = this.toCoreQueueName(queue);
|
|
282
|
+
const queues = w.config?.queues ?? [w.queueName];
|
|
283
|
+
return Array.isArray(queues) ? queues.includes(coreQueue) : false;
|
|
284
|
+
}).filter((w) => typeof isRunning === "boolean" ? isRunning ? w.isRunning() : !w.isRunning() : true).map((w) => this.mapWorker(w));
|
|
285
|
+
}
|
|
286
|
+
async createWorker(config) {
|
|
287
|
+
await this.executor();
|
|
288
|
+
const queuesSource = config.queues?.length ? config.queues : Array.from(/* @__PURE__ */ new Set([...this.jobsByQueue.keys(), ...this.cronsByQueue.keys()]));
|
|
289
|
+
const queues = queuesSource.map((q) => this.toCoreQueueName(q));
|
|
290
|
+
const coreConfig = {
|
|
291
|
+
queues,
|
|
292
|
+
concurrency: config.concurrency ?? 1,
|
|
293
|
+
limiter: config.limiter,
|
|
294
|
+
onActive: config.handlers?.onActive,
|
|
295
|
+
onSuccess: config.handlers?.onSuccess,
|
|
296
|
+
onFailure: config.handlers?.onFailure,
|
|
297
|
+
onIdle: config.handlers?.onIdle
|
|
298
|
+
};
|
|
299
|
+
const handle = await this.core().worker(coreConfig);
|
|
300
|
+
return this.mapWorker(handle);
|
|
301
|
+
}
|
|
302
|
+
getWorkers() {
|
|
303
|
+
const out = /* @__PURE__ */ new Map();
|
|
304
|
+
for (const [id, handle] of this.core().getWorkers()) out.set(id, this.mapWorker(handle));
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
async publishEvent(channel, payload) {
|
|
308
|
+
await this.publisher.publish(channel, JSON.stringify(payload));
|
|
309
|
+
}
|
|
310
|
+
async subscribeEvent(channel, handler) {
|
|
311
|
+
const set = this.subscribers.get(channel) ?? /* @__PURE__ */ new Set();
|
|
312
|
+
const wrapped = (payload) => void handler(payload);
|
|
313
|
+
set.add(wrapped);
|
|
314
|
+
this.subscribers.set(channel, set);
|
|
315
|
+
if (set.size === 1) {
|
|
316
|
+
await this.subscriber.subscribe(channel);
|
|
424
317
|
}
|
|
425
318
|
return async () => {
|
|
426
|
-
|
|
319
|
+
const current = this.subscribers.get(channel);
|
|
320
|
+
if (!current) return;
|
|
321
|
+
current.delete(wrapped);
|
|
322
|
+
if (current.size === 0) {
|
|
323
|
+
this.subscribers.delete(channel);
|
|
324
|
+
await this.subscriber.unsubscribe(channel);
|
|
325
|
+
}
|
|
427
326
|
};
|
|
428
327
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// ==========================================
|
|
437
|
-
// WORKERS
|
|
438
|
-
// ==========================================
|
|
439
|
-
async createWorker(config, handler) {
|
|
440
|
-
const { Worker } = await this.getBullMQ();
|
|
441
|
-
const workerId = `worker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
442
|
-
const workers = [];
|
|
443
|
-
for (const queueName of config.queues) {
|
|
444
|
-
const workerOptions = {
|
|
445
|
-
connection: this.redis,
|
|
446
|
-
concurrency: config.concurrency,
|
|
447
|
-
lockDuration: config.lockDuration,
|
|
448
|
-
limiter: config.limiter
|
|
449
|
-
};
|
|
450
|
-
const worker = new Worker(
|
|
451
|
-
queueName,
|
|
452
|
-
async (job) => {
|
|
453
|
-
const data = job.data;
|
|
454
|
-
return handler({
|
|
455
|
-
id: job.id,
|
|
456
|
-
name: job.name,
|
|
457
|
-
queue: job.queueName,
|
|
458
|
-
data: data.input ?? data,
|
|
459
|
-
attempt: job.attemptsMade + 1,
|
|
460
|
-
timestamp: job.timestamp,
|
|
461
|
-
scope: data.scope,
|
|
462
|
-
actor: data.actor,
|
|
463
|
-
log: async (level, message) => {
|
|
464
|
-
await job.log(`[${level.toUpperCase()}] ${message}`);
|
|
465
|
-
},
|
|
466
|
-
updateProgress: async (progress) => {
|
|
467
|
-
await job.updateProgress(progress);
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
},
|
|
471
|
-
workerOptions
|
|
472
|
-
);
|
|
473
|
-
if (config.onIdle) {
|
|
474
|
-
worker.on("drained", config.onIdle);
|
|
475
|
-
}
|
|
476
|
-
workers.push(worker);
|
|
477
|
-
this.workers.set(`${workerId}-${queueName}`, worker);
|
|
478
|
-
}
|
|
479
|
-
let isPaused = false;
|
|
480
|
-
const startTime = Date.now();
|
|
481
|
-
let processed = 0;
|
|
482
|
-
let failed = 0;
|
|
483
|
-
let completed = 0;
|
|
484
|
-
for (const worker of workers) {
|
|
485
|
-
worker.on("completed", () => {
|
|
486
|
-
processed++;
|
|
487
|
-
completed++;
|
|
488
|
-
});
|
|
489
|
-
worker.on("failed", () => {
|
|
490
|
-
processed++;
|
|
491
|
-
failed++;
|
|
328
|
+
async shutdown() {
|
|
329
|
+
await this.subscriber.quit();
|
|
330
|
+
}
|
|
331
|
+
core() {
|
|
332
|
+
if (!this.coreAdapter) {
|
|
333
|
+
this.coreAdapter = adapterBullmq.createBullMQAdapter({
|
|
334
|
+
store: { client: this.redis }
|
|
492
335
|
});
|
|
493
336
|
}
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
)?.[0];
|
|
510
|
-
if (key) this.workers.delete(key);
|
|
337
|
+
return this.coreAdapter;
|
|
338
|
+
}
|
|
339
|
+
async executor() {
|
|
340
|
+
if (!this.executorDirty && this.coreExecutor) return this.coreExecutor;
|
|
341
|
+
const routers = {};
|
|
342
|
+
const flattened = {};
|
|
343
|
+
const allQueues = /* @__PURE__ */ new Set([...this.jobsByQueue.keys(), ...this.cronsByQueue.keys()]);
|
|
344
|
+
for (const queueName of allQueues) {
|
|
345
|
+
const coreJobs = {};
|
|
346
|
+
const jobs = this.jobsByQueue.get(queueName);
|
|
347
|
+
if (jobs) {
|
|
348
|
+
for (const [jobName, def] of jobs.entries()) {
|
|
349
|
+
const queue = def.queue ? `${queueName}.${def.queue}` : queueName;
|
|
350
|
+
const fullQueue = IgniterJobsPrefix.buildQueueName(queue);
|
|
351
|
+
coreJobs[jobName] = this.toCoreJobDefinition(queueName, jobName, def, fullQueue);
|
|
511
352
|
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
// ==========================================
|
|
525
|
-
// SEARCH
|
|
526
|
-
// ==========================================
|
|
527
|
-
async searchJobs(filter) {
|
|
528
|
-
const results = [];
|
|
529
|
-
const queuesToSearch = filter.queue ? [filter.queue] : Array.from(this.queues.keys());
|
|
530
|
-
for (const queueName of queuesToSearch) {
|
|
531
|
-
const jobs = await this.listJobs(queueName, {
|
|
532
|
-
status: filter.status,
|
|
533
|
-
start: filter.offset,
|
|
534
|
-
end: filter.limit ? (filter.offset || 0) + filter.limit : void 0
|
|
353
|
+
}
|
|
354
|
+
const crons = this.cronsByQueue.get(queueName);
|
|
355
|
+
if (crons) {
|
|
356
|
+
for (const [cronName, def] of crons.entries()) {
|
|
357
|
+
const fullQueue = IgniterJobsPrefix.buildQueueName(queueName);
|
|
358
|
+
coreJobs[cronName] = this.toCoreCronJobDefinition(queueName, cronName, def, fullQueue);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (Object.keys(coreJobs).length === 0) continue;
|
|
362
|
+
routers[queueName] = this.core().router({
|
|
363
|
+
jobs: coreJobs,
|
|
364
|
+
namespace: queueName
|
|
535
365
|
});
|
|
536
|
-
for (const
|
|
537
|
-
|
|
538
|
-
if (filter.scopeId && job.scope?.id !== filter.scopeId) continue;
|
|
539
|
-
if (filter.actorId && job.actor?.id !== filter.actorId) continue;
|
|
540
|
-
if (filter.dateRange?.from && job.timestamp < filter.dateRange.from.getTime()) continue;
|
|
541
|
-
if (filter.dateRange?.to && job.timestamp > filter.dateRange.to.getTime()) continue;
|
|
542
|
-
results.push(job);
|
|
366
|
+
for (const [jobName, def] of Object.entries(coreJobs)) {
|
|
367
|
+
flattened[`${queueName}.${jobName}`] = def;
|
|
543
368
|
}
|
|
544
369
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
370
|
+
await this.core().bulkRegister(flattened);
|
|
371
|
+
this.coreExecutor = this.core().merge(routers);
|
|
372
|
+
this.executorDirty = false;
|
|
373
|
+
return this.coreExecutor;
|
|
374
|
+
}
|
|
375
|
+
toCoreQueueName(queueName) {
|
|
376
|
+
return IgniterJobsPrefix.buildQueueName(queueName);
|
|
377
|
+
}
|
|
378
|
+
mapQueueInfo(info) {
|
|
379
|
+
return {
|
|
380
|
+
name: this.fromCoreQueueName(info.name),
|
|
381
|
+
isPaused: info.isPaused,
|
|
382
|
+
jobCounts: info.jobCounts
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
fromCoreQueueName(full) {
|
|
386
|
+
const prefix = `${IgniterJobsPrefix.BASE_PREFIX}:`;
|
|
387
|
+
return full.startsWith(prefix) ? full.slice(prefix.length) : full;
|
|
388
|
+
}
|
|
389
|
+
mapJob(job, queue) {
|
|
390
|
+
const q = queue ?? this.fromCoreQueueName(job.metadata?.queue ?? job.queueName ?? "");
|
|
391
|
+
const scope = job.metadata?.__igniter_jobs_scope;
|
|
392
|
+
return {
|
|
393
|
+
id: job.id,
|
|
394
|
+
name: job.name,
|
|
395
|
+
queue: q,
|
|
396
|
+
status: job.status,
|
|
397
|
+
input: job.payload,
|
|
398
|
+
result: job.result,
|
|
399
|
+
error: job.error,
|
|
400
|
+
progress: 0,
|
|
401
|
+
attemptsMade: job.attemptsMade ?? 0,
|
|
402
|
+
priority: job.priority ?? 0,
|
|
403
|
+
createdAt: job.createdAt,
|
|
404
|
+
startedAt: job.processedAt,
|
|
405
|
+
completedAt: job.completedAt,
|
|
406
|
+
metadata: job.metadata,
|
|
407
|
+
scope
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
mapWorker(handle) {
|
|
411
|
+
const queues = handle.config?.queues ?? [handle.queueName];
|
|
412
|
+
return {
|
|
413
|
+
id: handle.id,
|
|
414
|
+
queues: queues.map((q) => this.fromCoreQueueName(q)),
|
|
415
|
+
pause: () => handle.pause(),
|
|
416
|
+
resume: () => handle.resume(),
|
|
417
|
+
close: () => handle.close(),
|
|
418
|
+
isRunning: () => handle.isRunning(),
|
|
419
|
+
isPaused: () => handle.isPaused(),
|
|
420
|
+
isClosed: () => handle.isClosed(),
|
|
421
|
+
getMetrics: async () => handle.getMetrics()
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
toCoreJobDefinition(queueName, jobName, def, fullQueueName) {
|
|
425
|
+
const handler = async (ctx) => {
|
|
426
|
+
return def.handler({
|
|
427
|
+
input: ctx.input,
|
|
428
|
+
context: ctx.context,
|
|
429
|
+
job: {
|
|
430
|
+
id: ctx.job.id,
|
|
431
|
+
name: jobName,
|
|
432
|
+
queue: queueName,
|
|
433
|
+
attemptsMade: ctx.job.attemptsMade,
|
|
434
|
+
createdAt: ctx.job.createdAt,
|
|
435
|
+
metadata: ctx.job.metadata
|
|
436
|
+
},
|
|
437
|
+
scope: ctx.job.metadata?.__igniter_jobs_scope
|
|
551
438
|
});
|
|
552
|
-
}
|
|
553
|
-
return
|
|
439
|
+
};
|
|
440
|
+
return {
|
|
441
|
+
name: jobName,
|
|
442
|
+
input: def.input,
|
|
443
|
+
handler,
|
|
444
|
+
queue: { name: fullQueueName },
|
|
445
|
+
attempts: def.attempts,
|
|
446
|
+
priority: def.priority,
|
|
447
|
+
delay: def.delay,
|
|
448
|
+
removeOnComplete: def.removeOnComplete,
|
|
449
|
+
removeOnFail: def.removeOnFail,
|
|
450
|
+
metadata: def.metadata,
|
|
451
|
+
limiter: def.limiter,
|
|
452
|
+
onStart: def.onStart,
|
|
453
|
+
onSuccess: def.onSuccess,
|
|
454
|
+
onFailure: def.onFailure,
|
|
455
|
+
onProgress: def.onProgress
|
|
456
|
+
};
|
|
554
457
|
}
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
completed: counts.completed || 0,
|
|
569
|
-
failed: counts.failed || 0,
|
|
570
|
-
delayed: counts.delayed || 0,
|
|
571
|
-
paused: counts.paused || 0
|
|
572
|
-
}
|
|
458
|
+
toCoreCronJobDefinition(queueName, cronName, def, fullQueueName) {
|
|
459
|
+
const handler = async (ctx) => {
|
|
460
|
+
return def.handler({
|
|
461
|
+
context: ctx.context,
|
|
462
|
+
job: {
|
|
463
|
+
id: ctx.job.id,
|
|
464
|
+
name: cronName,
|
|
465
|
+
queue: queueName,
|
|
466
|
+
attemptsMade: ctx.job.attemptsMade,
|
|
467
|
+
createdAt: ctx.job.createdAt,
|
|
468
|
+
metadata: ctx.job.metadata
|
|
469
|
+
},
|
|
470
|
+
scope: ctx.job.metadata?.__igniter_jobs_scope
|
|
573
471
|
});
|
|
574
|
-
}
|
|
575
|
-
return
|
|
472
|
+
};
|
|
473
|
+
return {
|
|
474
|
+
name: cronName,
|
|
475
|
+
handler,
|
|
476
|
+
queue: { name: fullQueueName },
|
|
477
|
+
repeat: {
|
|
478
|
+
cron: def.cron,
|
|
479
|
+
tz: def.tz,
|
|
480
|
+
limit: def.maxExecutions,
|
|
481
|
+
startDate: def.startDate,
|
|
482
|
+
endDate: def.endDate
|
|
483
|
+
},
|
|
484
|
+
metadata: def.onlyBusinessHours || def.skipWeekends || def.businessHours || def.onlyWeekdays || def.skipDates || def.startDate && def.endDate ? {
|
|
485
|
+
advancedScheduling: {
|
|
486
|
+
onlyBusinessHours: def.onlyBusinessHours,
|
|
487
|
+
skipWeekends: def.skipWeekends,
|
|
488
|
+
businessHours: def.businessHours,
|
|
489
|
+
skipDates: toDateArray(def.skipDates),
|
|
490
|
+
onlyWeekdays: def.onlyWeekdays,
|
|
491
|
+
between: def.startDate && def.endDate ? [def.startDate, def.endDate] : void 0
|
|
492
|
+
}
|
|
493
|
+
} : void 0
|
|
494
|
+
};
|
|
576
495
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/utils/id-generator.ts
|
|
499
|
+
var IgniterJobsIdGenerator = class {
|
|
500
|
+
/**
|
|
501
|
+
* Generates a unique identifier with a prefix.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```typescript
|
|
505
|
+
* const jobId = IgniterJobsIdGenerator.generate('job')
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
static generate(prefix) {
|
|
509
|
+
const now = Date.now().toString(36);
|
|
510
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
511
|
+
return `${prefix}_${now}_${random}`;
|
|
593
512
|
}
|
|
594
513
|
};
|
|
595
514
|
|
|
596
515
|
// src/adapters/memory.adapter.ts
|
|
597
|
-
var
|
|
516
|
+
var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
|
|
598
517
|
constructor() {
|
|
599
|
-
this.
|
|
600
|
-
|
|
518
|
+
this.client = {
|
|
519
|
+
type: "memory"
|
|
520
|
+
};
|
|
521
|
+
this.jobsById = /* @__PURE__ */ new Map();
|
|
522
|
+
this.jobsByQueue = /* @__PURE__ */ new Map();
|
|
523
|
+
this.registeredJobs = /* @__PURE__ */ new Map();
|
|
524
|
+
this.registeredCrons = /* @__PURE__ */ new Map();
|
|
601
525
|
this.workers = /* @__PURE__ */ new Map();
|
|
602
|
-
this.
|
|
526
|
+
this.subscribers = /* @__PURE__ */ new Map();
|
|
527
|
+
this.queues = {
|
|
528
|
+
list: async () => this.listQueues(),
|
|
529
|
+
get: async (name) => this.getQueueInfo(name),
|
|
530
|
+
getJobCounts: async (name) => this.getQueueJobCounts(name),
|
|
531
|
+
getJobs: async (name, filter) => {
|
|
532
|
+
const statuses = filter?.status;
|
|
533
|
+
const limit = filter?.limit ?? 100;
|
|
534
|
+
const offset = filter?.offset ?? 0;
|
|
535
|
+
const results = await this.searchJobs({
|
|
536
|
+
queue: name,
|
|
537
|
+
status: statuses,
|
|
538
|
+
limit,
|
|
539
|
+
offset
|
|
540
|
+
});
|
|
541
|
+
return results;
|
|
542
|
+
},
|
|
543
|
+
pause: async (name) => this.pauseQueue(name),
|
|
544
|
+
resume: async (name) => this.resumeQueue(name),
|
|
545
|
+
isPaused: async (name) => {
|
|
546
|
+
const info = await this.getQueueInfo(name);
|
|
547
|
+
return info?.isPaused ?? false;
|
|
548
|
+
},
|
|
549
|
+
drain: async (name) => this.drainQueue(name),
|
|
550
|
+
clean: async (name, options) => this.cleanQueue(name, options),
|
|
551
|
+
obliterate: async (name, options) => this.obliterateQueue(name, options)
|
|
552
|
+
};
|
|
553
|
+
this.pausedQueues = /* @__PURE__ */ new Set();
|
|
603
554
|
}
|
|
604
|
-
/**
|
|
605
|
-
* Create a new memory adapter.
|
|
606
|
-
*/
|
|
607
555
|
static create() {
|
|
608
|
-
return new
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Get the underlying client (null for memory adapter).
|
|
612
|
-
*/
|
|
613
|
-
get client() {
|
|
614
|
-
return null;
|
|
556
|
+
return new _IgniterJobsMemoryAdapter();
|
|
615
557
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
name,
|
|
623
|
-
isPaused: false,
|
|
624
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
625
|
-
pausedJobTypes: /* @__PURE__ */ new Set()
|
|
558
|
+
registerJob(queueName, jobName, definition) {
|
|
559
|
+
const queueJobs = this.registeredJobs.get(queueName) ?? /* @__PURE__ */ new Map();
|
|
560
|
+
if (queueJobs.has(jobName)) {
|
|
561
|
+
throw new IgniterJobsError({
|
|
562
|
+
code: "JOBS_DUPLICATE_JOB",
|
|
563
|
+
message: `Job "${jobName}" already registered for queue "${queueName}".`
|
|
626
564
|
});
|
|
627
565
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
/**
|
|
631
|
-
* Generate a unique job ID.
|
|
632
|
-
*/
|
|
633
|
-
generateJobId() {
|
|
634
|
-
return `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
566
|
+
queueJobs.set(jobName, definition);
|
|
567
|
+
this.registeredJobs.set(queueName, queueJobs);
|
|
635
568
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
try {
|
|
644
|
-
await handler({
|
|
645
|
-
type,
|
|
646
|
-
data,
|
|
647
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
648
|
-
});
|
|
649
|
-
} catch {
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
569
|
+
registerCron(queueName, cronName, definition) {
|
|
570
|
+
const queueCrons = this.registeredCrons.get(queueName) ?? /* @__PURE__ */ new Map();
|
|
571
|
+
if (queueCrons.has(cronName)) {
|
|
572
|
+
throw new IgniterJobsError({
|
|
573
|
+
code: "JOBS_INVALID_CRON",
|
|
574
|
+
message: `Cron "${cronName}" already registered for queue "${queueName}".`
|
|
575
|
+
});
|
|
653
576
|
}
|
|
577
|
+
queueCrons.set(cronName, definition);
|
|
578
|
+
this.registeredCrons.set(queueName, queueCrons);
|
|
654
579
|
}
|
|
655
|
-
/**
|
|
656
|
-
* Check if a pattern matches an event type.
|
|
657
|
-
*/
|
|
658
|
-
matchesPattern(pattern, eventType) {
|
|
659
|
-
if (pattern === "*") return true;
|
|
660
|
-
const regex = new RegExp(
|
|
661
|
-
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
662
|
-
);
|
|
663
|
-
return regex.test(eventType);
|
|
664
|
-
}
|
|
665
|
-
// ==========================================
|
|
666
|
-
// JOB OPERATIONS
|
|
667
|
-
// ==========================================
|
|
668
580
|
async dispatch(params) {
|
|
669
|
-
const
|
|
670
|
-
const
|
|
581
|
+
const jobId = params.jobId ?? IgniterJobsIdGenerator.generate("job");
|
|
582
|
+
const maxAttempts = params.attempts ?? 1;
|
|
583
|
+
const metadata = params.metadata ?? {};
|
|
671
584
|
const job = {
|
|
672
585
|
id: jobId,
|
|
673
|
-
name: params.
|
|
586
|
+
name: params.jobName,
|
|
674
587
|
queue: params.queue,
|
|
675
|
-
|
|
676
|
-
|
|
588
|
+
input: params.input,
|
|
589
|
+
status: this.pausedQueues.has(params.queue) ? "paused" : params.delay && params.delay > 0 ? "delayed" : "waiting",
|
|
677
590
|
progress: 0,
|
|
678
|
-
|
|
679
|
-
maxAttempts
|
|
680
|
-
timestamp: Date.now(),
|
|
681
|
-
delay: params.delay,
|
|
591
|
+
attemptsMade: 0,
|
|
592
|
+
maxAttempts,
|
|
682
593
|
priority: params.priority ?? 0,
|
|
594
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
595
|
+
metadata,
|
|
683
596
|
scope: params.scope,
|
|
684
|
-
|
|
685
|
-
logs: [],
|
|
686
|
-
scheduledAt: params.delay ? Date.now() + params.delay : void 0
|
|
597
|
+
logs: []
|
|
687
598
|
};
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
599
|
+
this.jobsById.set(jobId, job);
|
|
600
|
+
const queueList = this.jobsByQueue.get(params.queue) ?? [];
|
|
601
|
+
queueList.push(jobId);
|
|
602
|
+
this.jobsByQueue.set(params.queue, queueList);
|
|
603
|
+
if (params.delay && params.delay > 0) {
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
const stored = this.jobsById.get(jobId);
|
|
606
|
+
if (!stored) return;
|
|
607
|
+
if (!this.pausedQueues.has(params.queue)) stored.status = "waiting";
|
|
608
|
+
void this.kickWorkers(params.queue);
|
|
609
|
+
}, params.delay);
|
|
610
|
+
return jobId;
|
|
611
|
+
}
|
|
612
|
+
void this.kickWorkers(params.queue);
|
|
695
613
|
return jobId;
|
|
696
614
|
}
|
|
697
615
|
async schedule(params) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
616
|
+
if (params.at) {
|
|
617
|
+
const delay = params.at.getTime() - Date.now();
|
|
618
|
+
if (delay <= 0) {
|
|
619
|
+
throw new IgniterJobsError({
|
|
620
|
+
code: "JOBS_INVALID_SCHEDULE",
|
|
621
|
+
message: "Scheduled time must be in the future."
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return this.dispatch({ ...params, delay });
|
|
625
|
+
}
|
|
626
|
+
if (params.cron || params.every) {
|
|
627
|
+
return this.dispatch({ ...params, delay: params.delay ?? 0 });
|
|
628
|
+
}
|
|
629
|
+
return this.dispatch(params);
|
|
703
630
|
}
|
|
704
|
-
async getJob(
|
|
705
|
-
const
|
|
706
|
-
const job = q.jobs.get(jobId);
|
|
631
|
+
async getJob(jobId, queue) {
|
|
632
|
+
const job = this.jobsById.get(jobId);
|
|
707
633
|
if (!job) return null;
|
|
708
|
-
return
|
|
709
|
-
|
|
710
|
-
name: job.name,
|
|
711
|
-
queue: job.queue,
|
|
712
|
-
state: job.state,
|
|
713
|
-
data: job.data,
|
|
714
|
-
result: job.result,
|
|
715
|
-
error: job.error,
|
|
716
|
-
progress: job.progress,
|
|
717
|
-
attempts: job.attempts,
|
|
718
|
-
timestamp: job.timestamp,
|
|
719
|
-
processedOn: job.processedOn,
|
|
720
|
-
finishedOn: job.finishedOn,
|
|
721
|
-
delay: job.delay,
|
|
722
|
-
priority: job.priority,
|
|
723
|
-
scope: job.scope,
|
|
724
|
-
actor: job.actor
|
|
725
|
-
};
|
|
634
|
+
if (queue && job.queue !== queue) return null;
|
|
635
|
+
return this.toSearchResult(job);
|
|
726
636
|
}
|
|
727
|
-
async getJobState(
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
637
|
+
async getJobState(jobId, queue) {
|
|
638
|
+
const job = this.jobsById.get(jobId);
|
|
639
|
+
if (!job) return null;
|
|
640
|
+
if (queue && job.queue !== queue) return null;
|
|
641
|
+
return job.status;
|
|
731
642
|
}
|
|
732
|
-
async
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
643
|
+
async getJobLogs(jobId, queue) {
|
|
644
|
+
const job = this.jobsById.get(jobId);
|
|
645
|
+
if (!job) return [];
|
|
646
|
+
if (queue && job.queue !== queue) return [];
|
|
647
|
+
return job.logs;
|
|
736
648
|
}
|
|
737
|
-
async
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
649
|
+
async getJobProgress(jobId, queue) {
|
|
650
|
+
const job = this.jobsById.get(jobId);
|
|
651
|
+
if (!job) return 0;
|
|
652
|
+
if (queue && job.queue !== queue) return 0;
|
|
653
|
+
return job.progress;
|
|
741
654
|
}
|
|
742
|
-
async retryJob(
|
|
743
|
-
const
|
|
744
|
-
const job = q.jobs.get(jobId);
|
|
655
|
+
async retryJob(jobId, queue) {
|
|
656
|
+
const job = this.jobsById.get(jobId);
|
|
745
657
|
if (!job) {
|
|
746
|
-
throw new IgniterJobsError({
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
});
|
|
658
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
|
|
659
|
+
}
|
|
660
|
+
if (queue && job.queue !== queue) {
|
|
661
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
|
|
751
662
|
}
|
|
752
|
-
job.
|
|
663
|
+
job.status = "waiting";
|
|
753
664
|
job.error = void 0;
|
|
754
|
-
job.
|
|
665
|
+
job.completedAt = void 0;
|
|
666
|
+
job.progress = 0;
|
|
667
|
+
void this.kickWorkers(job.queue);
|
|
755
668
|
}
|
|
756
|
-
async removeJob(
|
|
757
|
-
const
|
|
758
|
-
|
|
669
|
+
async removeJob(jobId, queue) {
|
|
670
|
+
const job = this.jobsById.get(jobId);
|
|
671
|
+
if (!job) return;
|
|
672
|
+
if (queue && job.queue !== queue) return;
|
|
673
|
+
this.jobsById.delete(jobId);
|
|
674
|
+
const list = this.jobsByQueue.get(job.queue);
|
|
675
|
+
if (list) this.jobsByQueue.set(job.queue, list.filter((id) => id !== jobId));
|
|
759
676
|
}
|
|
760
|
-
async promoteJob(
|
|
761
|
-
const
|
|
762
|
-
const job = q.jobs.get(jobId);
|
|
677
|
+
async promoteJob(jobId, queue) {
|
|
678
|
+
const job = this.jobsById.get(jobId);
|
|
763
679
|
if (!job) {
|
|
764
|
-
throw new IgniterJobsError({
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
});
|
|
680
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
|
|
681
|
+
}
|
|
682
|
+
if (queue && job.queue !== queue) {
|
|
683
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
|
|
769
684
|
}
|
|
770
|
-
if (job.
|
|
771
|
-
job.
|
|
772
|
-
job.
|
|
773
|
-
job.delay = void 0;
|
|
685
|
+
if (job.status === "delayed" || job.status === "paused") {
|
|
686
|
+
job.status = this.pausedQueues.has(job.queue) ? "paused" : "waiting";
|
|
687
|
+
void this.kickWorkers(job.queue);
|
|
774
688
|
}
|
|
775
689
|
}
|
|
776
|
-
async
|
|
777
|
-
const
|
|
778
|
-
const job = q.jobs.get(jobId);
|
|
690
|
+
async moveJobToFailed(jobId, reason, queue) {
|
|
691
|
+
const job = this.jobsById.get(jobId);
|
|
779
692
|
if (!job) {
|
|
780
|
-
throw new IgniterJobsError({
|
|
781
|
-
code: "JOBS_JOB_NOT_FOUND",
|
|
782
|
-
message: `Job "${jobId}" not found`,
|
|
783
|
-
statusCode: 404
|
|
784
|
-
});
|
|
693
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
|
|
785
694
|
}
|
|
786
|
-
job.
|
|
787
|
-
|
|
788
|
-
if (state === "failed") {
|
|
789
|
-
job.error = reason;
|
|
790
|
-
} else {
|
|
791
|
-
job.result = reason;
|
|
695
|
+
if (queue && job.queue !== queue) {
|
|
696
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
|
|
792
697
|
}
|
|
698
|
+
job.status = "failed";
|
|
699
|
+
job.error = reason;
|
|
700
|
+
job.completedAt = /* @__PURE__ */ new Date();
|
|
793
701
|
}
|
|
794
|
-
async
|
|
795
|
-
await Promise.all(jobIds.map((id) => this.retryJob(
|
|
702
|
+
async retryManyJobs(jobIds, queue) {
|
|
703
|
+
await Promise.all(jobIds.map((id) => this.retryJob(id, queue)));
|
|
796
704
|
}
|
|
797
|
-
async
|
|
798
|
-
await Promise.all(jobIds.map((id) => this.removeJob(
|
|
705
|
+
async removeManyJobs(jobIds, queue) {
|
|
706
|
+
await Promise.all(jobIds.map((id) => this.removeJob(id, queue)));
|
|
799
707
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
// ==========================================
|
|
803
|
-
async getQueue(queue) {
|
|
804
|
-
const q = this.getOrCreateQueue(queue);
|
|
805
|
-
const counts = await this.getJobCounts(queue);
|
|
708
|
+
async getQueueInfo(queue) {
|
|
709
|
+
const counts = await this.getQueueJobCounts(queue);
|
|
806
710
|
return {
|
|
807
711
|
name: queue,
|
|
808
|
-
isPaused:
|
|
712
|
+
isPaused: this.pausedQueues.has(queue),
|
|
809
713
|
jobCounts: counts
|
|
810
714
|
};
|
|
811
715
|
}
|
|
716
|
+
async getQueueJobCounts(queue) {
|
|
717
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
718
|
+
const counts = {
|
|
719
|
+
waiting: 0,
|
|
720
|
+
active: 0,
|
|
721
|
+
completed: 0,
|
|
722
|
+
failed: 0,
|
|
723
|
+
delayed: 0,
|
|
724
|
+
paused: 0
|
|
725
|
+
};
|
|
726
|
+
for (const id of jobIds) {
|
|
727
|
+
const job = this.jobsById.get(id);
|
|
728
|
+
if (!job) continue;
|
|
729
|
+
if (job.status in counts) {
|
|
730
|
+
counts[job.status]++;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return counts;
|
|
734
|
+
}
|
|
735
|
+
async listQueues() {
|
|
736
|
+
const queues = Array.from(/* @__PURE__ */ new Set([...this.jobsByQueue.keys(), ...this.registeredJobs.keys(), ...this.registeredCrons.keys()]));
|
|
737
|
+
const result = [];
|
|
738
|
+
for (const q of queues) {
|
|
739
|
+
result.push(await this.getQueueInfo(q));
|
|
740
|
+
}
|
|
741
|
+
return result;
|
|
742
|
+
}
|
|
812
743
|
async pauseQueue(queue) {
|
|
813
|
-
|
|
814
|
-
|
|
744
|
+
this.pausedQueues.add(queue);
|
|
745
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
746
|
+
for (const id of jobIds) {
|
|
747
|
+
const job = this.jobsById.get(id);
|
|
748
|
+
if (!job) continue;
|
|
749
|
+
if (job.status === "waiting") job.status = "paused";
|
|
750
|
+
}
|
|
815
751
|
}
|
|
816
752
|
async resumeQueue(queue) {
|
|
817
|
-
|
|
818
|
-
|
|
753
|
+
this.pausedQueues.delete(queue);
|
|
754
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
755
|
+
for (const id of jobIds) {
|
|
756
|
+
const job = this.jobsById.get(id);
|
|
757
|
+
if (!job) continue;
|
|
758
|
+
if (job.status === "paused") job.status = "waiting";
|
|
759
|
+
}
|
|
760
|
+
void this.kickWorkers(queue);
|
|
819
761
|
}
|
|
820
762
|
async drainQueue(queue) {
|
|
821
|
-
const
|
|
822
|
-
let
|
|
823
|
-
for (const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
763
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
764
|
+
let removed = 0;
|
|
765
|
+
for (const id of jobIds) {
|
|
766
|
+
const job = this.jobsById.get(id);
|
|
767
|
+
if (!job) continue;
|
|
768
|
+
if (job.status === "waiting" || job.status === "paused") {
|
|
769
|
+
this.jobsById.delete(id);
|
|
770
|
+
removed++;
|
|
827
771
|
}
|
|
828
772
|
}
|
|
829
|
-
|
|
773
|
+
this.jobsByQueue.set(queue, jobIds.filter((id) => this.jobsById.has(id)));
|
|
774
|
+
return removed;
|
|
830
775
|
}
|
|
831
776
|
async cleanQueue(queue, options) {
|
|
832
|
-
const q = this.getOrCreateQueue(queue);
|
|
833
777
|
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
778
|
+
const olderThan = options.olderThan ?? 0;
|
|
779
|
+
const limit = options.limit ?? Number.POSITIVE_INFINITY;
|
|
780
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
834
781
|
const now = Date.now();
|
|
835
|
-
let
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
}
|
|
846
|
-
}
|
|
782
|
+
let cleaned = 0;
|
|
783
|
+
for (const id of [...jobIds]) {
|
|
784
|
+
if (cleaned >= limit) break;
|
|
785
|
+
const job = this.jobsById.get(id);
|
|
786
|
+
if (!job) continue;
|
|
787
|
+
if (!statuses.includes(job.status)) continue;
|
|
788
|
+
const ageMs = now - job.createdAt.getTime();
|
|
789
|
+
if (ageMs < olderThan) continue;
|
|
790
|
+
this.jobsById.delete(id);
|
|
791
|
+
cleaned++;
|
|
847
792
|
}
|
|
848
|
-
|
|
793
|
+
this.jobsByQueue.set(queue, jobIds.filter((id) => this.jobsById.has(id)));
|
|
794
|
+
return cleaned;
|
|
849
795
|
}
|
|
850
796
|
async obliterateQueue(queue, options) {
|
|
851
|
-
this.
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
797
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
798
|
+
for (const id of jobIds) this.jobsById.delete(id);
|
|
799
|
+
this.jobsByQueue.delete(queue);
|
|
800
|
+
this.registeredJobs.delete(queue);
|
|
801
|
+
this.registeredCrons.delete(queue);
|
|
802
|
+
this.pausedQueues.delete(queue);
|
|
803
|
+
}
|
|
804
|
+
async retryAllInQueue(queue) {
|
|
805
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
806
|
+
let retried = 0;
|
|
807
|
+
for (const id of jobIds) {
|
|
808
|
+
const job = this.jobsById.get(id);
|
|
809
|
+
if (!job) continue;
|
|
810
|
+
if (job.status === "failed") {
|
|
811
|
+
await this.retryJob(id, queue);
|
|
812
|
+
retried++;
|
|
862
813
|
}
|
|
863
814
|
}
|
|
864
|
-
return
|
|
815
|
+
return retried;
|
|
865
816
|
}
|
|
866
|
-
async
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
failed: 0,
|
|
873
|
-
delayed: 0,
|
|
874
|
-
paused: 0
|
|
875
|
-
};
|
|
876
|
-
for (const job of q.jobs.values()) {
|
|
877
|
-
counts[job.state]++;
|
|
817
|
+
async pauseJobType(queue, jobName) {
|
|
818
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
819
|
+
for (const id of jobIds) {
|
|
820
|
+
const job = this.jobsById.get(id);
|
|
821
|
+
if (!job) continue;
|
|
822
|
+
if (job.name === jobName && job.status === "waiting") job.status = "paused";
|
|
878
823
|
}
|
|
879
|
-
return counts;
|
|
880
824
|
}
|
|
881
|
-
async
|
|
882
|
-
const
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (
|
|
887
|
-
results.push({
|
|
888
|
-
id: job.id,
|
|
889
|
-
name: job.name,
|
|
890
|
-
queue: job.queue,
|
|
891
|
-
state: job.state,
|
|
892
|
-
data: job.data,
|
|
893
|
-
result: job.result,
|
|
894
|
-
error: job.error,
|
|
895
|
-
progress: job.progress,
|
|
896
|
-
attempts: job.attempts,
|
|
897
|
-
timestamp: job.timestamp,
|
|
898
|
-
processedOn: job.processedOn,
|
|
899
|
-
finishedOn: job.finishedOn,
|
|
900
|
-
scope: job.scope,
|
|
901
|
-
actor: job.actor
|
|
902
|
-
});
|
|
903
|
-
}
|
|
825
|
+
async resumeJobType(queue, jobName) {
|
|
826
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
827
|
+
for (const id of jobIds) {
|
|
828
|
+
const job = this.jobsById.get(id);
|
|
829
|
+
if (!job) continue;
|
|
830
|
+
if (job.name === jobName && job.status === "paused") job.status = "waiting";
|
|
904
831
|
}
|
|
905
|
-
|
|
906
|
-
const end = options?.end ?? results.length;
|
|
907
|
-
return results.slice(start, end);
|
|
832
|
+
void this.kickWorkers(queue);
|
|
908
833
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
const
|
|
914
|
-
|
|
834
|
+
async searchJobs(filter) {
|
|
835
|
+
const queue = filter?.queue;
|
|
836
|
+
const statuses = filter?.status;
|
|
837
|
+
const limit = filter?.limit ?? 100;
|
|
838
|
+
const offset = filter?.offset ?? 0;
|
|
839
|
+
const all = Array.from(this.jobsById.values()).filter((j) => queue ? j.queue === queue : true).filter((j) => statuses ? statuses.includes(j.status) : true).sort((a, b) => b.priority - a.priority || a.createdAt.getTime() - b.createdAt.getTime());
|
|
840
|
+
return all.slice(offset, offset + limit).map((j) => this.toSearchResult(j));
|
|
915
841
|
}
|
|
916
|
-
async
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
842
|
+
async searchQueues(filter) {
|
|
843
|
+
const name = filter?.name;
|
|
844
|
+
const isPaused = filter?.isPaused;
|
|
845
|
+
const all = await this.listQueues();
|
|
846
|
+
return all.filter((q) => name ? q.name.includes(name) : true).filter((q) => typeof isPaused === "boolean" ? q.isPaused === isPaused : true);
|
|
847
|
+
}
|
|
848
|
+
async searchWorkers(filter) {
|
|
849
|
+
const queue = filter?.queue;
|
|
850
|
+
const isRunning = filter?.isRunning;
|
|
851
|
+
return Array.from(this.workers.values()).filter((w) => queue ? w.queues.includes(queue) : true).filter((w) => typeof isRunning === "boolean" ? isRunning ? !w.closed : w.closed : true).map((w) => this.toWorkerHandle(w));
|
|
852
|
+
}
|
|
853
|
+
async createWorker(config) {
|
|
854
|
+
const workerId = IgniterJobsIdGenerator.generate("worker");
|
|
855
|
+
const state = {
|
|
856
|
+
id: workerId,
|
|
857
|
+
queues: config.queues ?? [],
|
|
858
|
+
concurrency: config.concurrency ?? 1,
|
|
859
|
+
paused: false,
|
|
860
|
+
closed: false,
|
|
861
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
862
|
+
metrics: { processed: 0, failed: 0, totalDuration: 0 },
|
|
863
|
+
handlers: config.handlers
|
|
864
|
+
};
|
|
865
|
+
this.workers.set(workerId, state);
|
|
866
|
+
for (const q of state.queues) void this.kickWorkers(q);
|
|
867
|
+
return this.toWorkerHandle(state);
|
|
868
|
+
}
|
|
869
|
+
getWorkers() {
|
|
870
|
+
const out = /* @__PURE__ */ new Map();
|
|
871
|
+
for (const [id, state] of this.workers) out.set(id, this.toWorkerHandle(state));
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
async publishEvent(channel, payload) {
|
|
875
|
+
const handlers = this.subscribers.get(channel);
|
|
876
|
+
if (!handlers) return;
|
|
877
|
+
await Promise.all(Array.from(handlers).map(async (h) => h(payload)));
|
|
878
|
+
}
|
|
879
|
+
async subscribeEvent(channel, handler) {
|
|
880
|
+
const set = this.subscribers.get(channel) ?? /* @__PURE__ */ new Set();
|
|
881
|
+
set.add(handler);
|
|
882
|
+
this.subscribers.set(channel, set);
|
|
928
883
|
return async () => {
|
|
929
|
-
this.
|
|
884
|
+
const current = this.subscribers.get(channel);
|
|
885
|
+
if (!current) return;
|
|
886
|
+
current.delete(handler);
|
|
887
|
+
if (current.size === 0) this.subscribers.delete(channel);
|
|
930
888
|
};
|
|
931
889
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
this.workers.set(workerId, {
|
|
938
|
-
handler,
|
|
939
|
-
config,
|
|
940
|
-
running: true,
|
|
941
|
-
paused: false
|
|
942
|
-
});
|
|
943
|
-
const startTime = Date.now();
|
|
944
|
-
let processed = 0;
|
|
945
|
-
let failed = 0;
|
|
946
|
-
let completed = 0;
|
|
890
|
+
async shutdown() {
|
|
891
|
+
this.workers.clear();
|
|
892
|
+
this.subscribers.clear();
|
|
893
|
+
}
|
|
894
|
+
toSearchResult(job) {
|
|
947
895
|
return {
|
|
948
|
-
id:
|
|
896
|
+
id: job.id,
|
|
897
|
+
name: job.name,
|
|
898
|
+
queue: job.queue,
|
|
899
|
+
status: job.status,
|
|
900
|
+
input: job.input,
|
|
901
|
+
result: job.result,
|
|
902
|
+
error: job.error,
|
|
903
|
+
progress: job.progress,
|
|
904
|
+
attemptsMade: job.attemptsMade,
|
|
905
|
+
priority: job.priority,
|
|
906
|
+
createdAt: job.createdAt,
|
|
907
|
+
startedAt: job.startedAt,
|
|
908
|
+
completedAt: job.completedAt,
|
|
909
|
+
metadata: job.metadata,
|
|
910
|
+
scope: job.scope
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
toWorkerHandle(worker) {
|
|
914
|
+
return {
|
|
915
|
+
id: worker.id,
|
|
916
|
+
queues: worker.queues,
|
|
949
917
|
pause: async () => {
|
|
950
|
-
|
|
951
|
-
if (worker) worker.paused = true;
|
|
918
|
+
worker.paused = true;
|
|
952
919
|
},
|
|
953
920
|
resume: async () => {
|
|
954
|
-
|
|
955
|
-
|
|
921
|
+
worker.paused = false;
|
|
922
|
+
for (const q of worker.queues) void this.kickWorkers(q);
|
|
956
923
|
},
|
|
957
924
|
close: async () => {
|
|
958
|
-
|
|
959
|
-
},
|
|
960
|
-
isRunning: () => {
|
|
961
|
-
const worker = this.workers.get(workerId);
|
|
962
|
-
return worker?.running && !worker?.paused || false;
|
|
963
|
-
},
|
|
964
|
-
isPaused: () => {
|
|
965
|
-
return this.workers.get(workerId)?.paused || false;
|
|
925
|
+
worker.closed = true;
|
|
966
926
|
},
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
active: 0,
|
|
972
|
-
uptime: Date.now() - startTime
|
|
973
|
-
})
|
|
927
|
+
isRunning: () => !worker.closed && !worker.paused,
|
|
928
|
+
isPaused: () => worker.paused,
|
|
929
|
+
isClosed: () => worker.closed,
|
|
930
|
+
getMetrics: async () => this.toWorkerMetrics(worker)
|
|
974
931
|
};
|
|
975
932
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
933
|
+
toWorkerMetrics(worker) {
|
|
934
|
+
const uptime = Date.now() - worker.startedAt.getTime();
|
|
935
|
+
const processed = worker.metrics.processed;
|
|
936
|
+
return {
|
|
937
|
+
processed,
|
|
938
|
+
failed: worker.metrics.failed,
|
|
939
|
+
avgDuration: processed > 0 ? worker.metrics.totalDuration / processed : 0,
|
|
940
|
+
concurrency: worker.concurrency,
|
|
941
|
+
uptime
|
|
942
|
+
};
|
|
984
943
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
for (const
|
|
990
|
-
|
|
991
|
-
for (const queueName of worker.config.queues) {
|
|
992
|
-
const queue = this.queues.get(queueName);
|
|
993
|
-
if (!queue || queue.isPaused) continue;
|
|
994
|
-
for (const job of queue.jobs.values()) {
|
|
995
|
-
if (job.state !== "waiting") continue;
|
|
996
|
-
if (queue.pausedJobTypes.has(job.name)) continue;
|
|
997
|
-
if (job.scheduledAt && job.scheduledAt > Date.now()) continue;
|
|
998
|
-
job.state = "active";
|
|
999
|
-
job.processedOn = Date.now();
|
|
1000
|
-
job.attempts++;
|
|
1001
|
-
try {
|
|
1002
|
-
const result = await worker.handler({
|
|
1003
|
-
id: job.id,
|
|
1004
|
-
name: job.name,
|
|
1005
|
-
queue: job.queue,
|
|
1006
|
-
data: job.data,
|
|
1007
|
-
attempt: job.attempts,
|
|
1008
|
-
timestamp: job.timestamp,
|
|
1009
|
-
scope: job.scope,
|
|
1010
|
-
actor: job.actor,
|
|
1011
|
-
log: async (level, message) => {
|
|
1012
|
-
job.logs.push({
|
|
1013
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
1014
|
-
message,
|
|
1015
|
-
level
|
|
1016
|
-
});
|
|
1017
|
-
},
|
|
1018
|
-
updateProgress: async (progress) => {
|
|
1019
|
-
job.progress = progress;
|
|
1020
|
-
}
|
|
1021
|
-
});
|
|
1022
|
-
job.state = "completed";
|
|
1023
|
-
job.result = result;
|
|
1024
|
-
job.finishedOn = Date.now();
|
|
1025
|
-
await this.emitEvent(`${job.queue}:${job.name}:completed`, {
|
|
1026
|
-
jobId: job.id,
|
|
1027
|
-
result
|
|
1028
|
-
});
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1031
|
-
if (job.attempts < job.maxAttempts) {
|
|
1032
|
-
job.state = "waiting";
|
|
1033
|
-
await this.emitEvent(`${job.queue}:${job.name}:retrying`, {
|
|
1034
|
-
jobId: job.id,
|
|
1035
|
-
error: errorMessage,
|
|
1036
|
-
attempt: job.attempts
|
|
1037
|
-
});
|
|
1038
|
-
} else {
|
|
1039
|
-
job.state = "failed";
|
|
1040
|
-
job.error = errorMessage;
|
|
1041
|
-
job.finishedOn = Date.now();
|
|
1042
|
-
await this.emitEvent(`${job.queue}:${job.name}:failed`, {
|
|
1043
|
-
jobId: job.id,
|
|
1044
|
-
error: errorMessage
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
944
|
+
async kickWorkers(queue) {
|
|
945
|
+
if (this.pausedQueues.has(queue)) return;
|
|
946
|
+
const relevant = Array.from(this.workers.values()).filter((w) => !w.closed && !w.paused && (w.queues.length === 0 || w.queues.includes(queue)));
|
|
947
|
+
if (relevant.length === 0) return;
|
|
948
|
+
for (const w of relevant) {
|
|
949
|
+
void this.processLoop(w, queue);
|
|
1050
950
|
}
|
|
1051
951
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
952
|
+
async processLoop(worker, queue) {
|
|
953
|
+
if (worker.closed || worker.paused) return;
|
|
954
|
+
const concurrency = Math.max(1, worker.concurrency);
|
|
955
|
+
const running = worker.__running;
|
|
956
|
+
const currentRunning = running ?? 0;
|
|
957
|
+
if (currentRunning >= concurrency) return;
|
|
958
|
+
worker.__running = currentRunning + 1;
|
|
959
|
+
try {
|
|
960
|
+
const next = this.nextJob(queue);
|
|
961
|
+
if (!next) return;
|
|
962
|
+
await this.processJob(worker, next);
|
|
963
|
+
} finally {
|
|
964
|
+
worker.__running = worker.__running - 1;
|
|
965
|
+
if (this.nextJob(queue)) void this.processLoop(worker, queue);
|
|
966
|
+
else if (worker.handlers?.onIdle) await worker.handlers.onIdle();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
nextJob(queue) {
|
|
970
|
+
const ids = this.jobsByQueue.get(queue) ?? [];
|
|
971
|
+
const candidates = ids.map((id) => this.jobsById.get(id)).filter((j) => Boolean(j)).filter((j) => j.status === "waiting").sort((a, b) => b.priority - a.priority || a.createdAt.getTime() - b.createdAt.getTime());
|
|
972
|
+
return candidates[0] ?? null;
|
|
973
|
+
}
|
|
974
|
+
async processJob(worker, job) {
|
|
975
|
+
if (this.pausedQueues.has(job.queue)) {
|
|
976
|
+
job.status = "paused";
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
job.status = "active";
|
|
980
|
+
job.startedAt = /* @__PURE__ */ new Date();
|
|
981
|
+
job.attemptsMade += 1;
|
|
982
|
+
job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "info", message: "Job started" });
|
|
983
|
+
if (worker.handlers?.onActive) await worker.handlers.onActive({ job: this.toSearchResult(job) });
|
|
984
|
+
const start = Date.now();
|
|
985
|
+
try {
|
|
986
|
+
const definition = this.registeredJobs.get(job.queue)?.get(job.name);
|
|
987
|
+
if (!definition) {
|
|
988
|
+
throw new IgniterJobsError({
|
|
989
|
+
code: "JOBS_NOT_REGISTERED",
|
|
990
|
+
message: `Job "${job.name}" is not registered for queue "${job.queue}".`
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
if (definition.onStart) {
|
|
994
|
+
await definition.onStart({
|
|
995
|
+
input: job.input,
|
|
996
|
+
context: {},
|
|
997
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
1081
998
|
scope: job.scope,
|
|
1082
|
-
|
|
999
|
+
startedAt: job.startedAt
|
|
1083
1000
|
});
|
|
1084
1001
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
const bVal = field === "createdAt" ? b.timestamp : b[field];
|
|
1091
|
-
return direction === "asc" ? aVal - bVal : bVal - aVal;
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
const offset = filter.offset ?? 0;
|
|
1095
|
-
const limit = filter.limit ?? 100;
|
|
1096
|
-
return results.slice(offset, offset + limit);
|
|
1097
|
-
}
|
|
1098
|
-
async searchQueues(filter) {
|
|
1099
|
-
const results = [];
|
|
1100
|
-
for (const [queueName, queue] of this.queues) {
|
|
1101
|
-
if (filter.name && !queueName.includes(filter.name)) continue;
|
|
1102
|
-
if (filter.isPaused !== void 0 && queue.isPaused !== filter.isPaused) continue;
|
|
1103
|
-
const counts = await this.getJobCounts(queueName);
|
|
1104
|
-
results.push({
|
|
1105
|
-
name: queueName,
|
|
1106
|
-
isPaused: queue.isPaused,
|
|
1107
|
-
jobCounts: counts
|
|
1002
|
+
const result = await definition.handler({
|
|
1003
|
+
input: job.input,
|
|
1004
|
+
context: {},
|
|
1005
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
1006
|
+
scope: job.scope
|
|
1108
1007
|
});
|
|
1008
|
+
const duration = Date.now() - start;
|
|
1009
|
+
job.status = "completed";
|
|
1010
|
+
job.completedAt = /* @__PURE__ */ new Date();
|
|
1011
|
+
job.result = result;
|
|
1012
|
+
job.progress = 100;
|
|
1013
|
+
job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "info", message: `Job completed in ${duration}ms` });
|
|
1014
|
+
worker.metrics.processed += 1;
|
|
1015
|
+
worker.metrics.totalDuration += duration;
|
|
1016
|
+
if (definition.onSuccess) {
|
|
1017
|
+
await definition.onSuccess({
|
|
1018
|
+
input: job.input,
|
|
1019
|
+
context: {},
|
|
1020
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
1021
|
+
scope: job.scope,
|
|
1022
|
+
result,
|
|
1023
|
+
duration
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
if (worker.handlers?.onSuccess) await worker.handlers.onSuccess({ job: this.toSearchResult(job), result });
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
job.error = error?.message ?? String(error);
|
|
1029
|
+
job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "error", message: job.error ?? "Unknown error" });
|
|
1030
|
+
const isFinalAttempt = job.attemptsMade >= job.maxAttempts;
|
|
1031
|
+
if (isFinalAttempt) {
|
|
1032
|
+
job.status = "failed";
|
|
1033
|
+
job.completedAt = /* @__PURE__ */ new Date();
|
|
1034
|
+
worker.metrics.failed += 1;
|
|
1035
|
+
const definition = this.registeredJobs.get(job.queue)?.get(job.name);
|
|
1036
|
+
if (definition?.onFailure) {
|
|
1037
|
+
await definition.onFailure({
|
|
1038
|
+
input: job.input,
|
|
1039
|
+
context: {},
|
|
1040
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
1041
|
+
scope: job.scope,
|
|
1042
|
+
error,
|
|
1043
|
+
isFinalAttempt: true
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
if (worker.handlers?.onFailure) await worker.handlers.onFailure({ job: this.toSearchResult(job), error });
|
|
1047
|
+
} else {
|
|
1048
|
+
job.status = "waiting";
|
|
1049
|
+
void this.kickWorkers(job.queue);
|
|
1050
|
+
}
|
|
1109
1051
|
}
|
|
1110
|
-
return results;
|
|
1111
|
-
}
|
|
1112
|
-
// ==========================================
|
|
1113
|
-
// LIFECYCLE
|
|
1114
|
-
// ==========================================
|
|
1115
|
-
async shutdown() {
|
|
1116
|
-
if (this.processingInterval) {
|
|
1117
|
-
clearInterval(this.processingInterval);
|
|
1118
|
-
this.processingInterval = null;
|
|
1119
|
-
}
|
|
1120
|
-
this.workers.clear();
|
|
1121
|
-
this.queues.clear();
|
|
1122
|
-
this.eventHandlers.clear();
|
|
1123
1052
|
}
|
|
1124
1053
|
};
|
|
1125
1054
|
|
|
1126
|
-
exports.
|
|
1127
|
-
exports.
|
|
1055
|
+
exports.IgniterJobsBullMQAdapter = IgniterJobsBullMQAdapter;
|
|
1056
|
+
exports.IgniterJobsMemoryAdapter = IgniterJobsMemoryAdapter;
|
|
1128
1057
|
//# sourceMappingURL=index.js.map
|
|
1129
1058
|
//# sourceMappingURL=index.js.map
|