@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
|
@@ -2,570 +2,568 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@igniter-js/core');
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
var
|
|
7
|
-
constructor(options) {
|
|
8
|
-
super({
|
|
9
|
-
code: options.code,
|
|
10
|
-
message: options.message,
|
|
11
|
-
statusCode: options.statusCode ?? 500,
|
|
12
|
-
causer: "@igniter-js/jobs",
|
|
13
|
-
cause: options.cause,
|
|
14
|
-
details: options.details,
|
|
15
|
-
logger: options.logger
|
|
16
|
-
});
|
|
17
|
-
this.code = options.code;
|
|
18
|
-
this.details = options.details;
|
|
19
|
-
this.name = "IgniterJobsError";
|
|
20
|
-
if (Error.captureStackTrace) {
|
|
21
|
-
Error.captureStackTrace(this, _IgniterJobsError);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
5
|
+
// src/utils/id-generator.ts
|
|
6
|
+
var IgniterJobsIdGenerator = class {
|
|
24
7
|
/**
|
|
25
|
-
*
|
|
8
|
+
* Generates a unique identifier with a prefix.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const jobId = IgniterJobsIdGenerator.generate('job')
|
|
13
|
+
* ```
|
|
26
14
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
static generate(prefix) {
|
|
16
|
+
const now = Date.now().toString(36);
|
|
17
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
18
|
+
return `${prefix}_${now}_${random}`;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var IgniterJobsError = class extends core.IgniterError {
|
|
22
|
+
constructor(options) {
|
|
23
|
+
super(options);
|
|
36
24
|
}
|
|
37
25
|
};
|
|
38
26
|
|
|
39
27
|
// src/adapters/memory.adapter.ts
|
|
40
|
-
var
|
|
28
|
+
var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
|
|
41
29
|
constructor() {
|
|
42
|
-
this.
|
|
43
|
-
|
|
30
|
+
this.client = {
|
|
31
|
+
type: "memory"
|
|
32
|
+
};
|
|
33
|
+
this.jobsById = /* @__PURE__ */ new Map();
|
|
34
|
+
this.jobsByQueue = /* @__PURE__ */ new Map();
|
|
35
|
+
this.registeredJobs = /* @__PURE__ */ new Map();
|
|
36
|
+
this.registeredCrons = /* @__PURE__ */ new Map();
|
|
44
37
|
this.workers = /* @__PURE__ */ new Map();
|
|
45
|
-
this.
|
|
38
|
+
this.subscribers = /* @__PURE__ */ new Map();
|
|
39
|
+
this.queues = {
|
|
40
|
+
list: async () => this.listQueues(),
|
|
41
|
+
get: async (name) => this.getQueueInfo(name),
|
|
42
|
+
getJobCounts: async (name) => this.getQueueJobCounts(name),
|
|
43
|
+
getJobs: async (name, filter) => {
|
|
44
|
+
const statuses = filter?.status;
|
|
45
|
+
const limit = filter?.limit ?? 100;
|
|
46
|
+
const offset = filter?.offset ?? 0;
|
|
47
|
+
const results = await this.searchJobs({
|
|
48
|
+
queue: name,
|
|
49
|
+
status: statuses,
|
|
50
|
+
limit,
|
|
51
|
+
offset
|
|
52
|
+
});
|
|
53
|
+
return results;
|
|
54
|
+
},
|
|
55
|
+
pause: async (name) => this.pauseQueue(name),
|
|
56
|
+
resume: async (name) => this.resumeQueue(name),
|
|
57
|
+
isPaused: async (name) => {
|
|
58
|
+
const info = await this.getQueueInfo(name);
|
|
59
|
+
return info?.isPaused ?? false;
|
|
60
|
+
},
|
|
61
|
+
drain: async (name) => this.drainQueue(name),
|
|
62
|
+
clean: async (name, options) => this.cleanQueue(name, options),
|
|
63
|
+
obliterate: async (name, options) => this.obliterateQueue(name, options)
|
|
64
|
+
};
|
|
65
|
+
this.pausedQueues = /* @__PURE__ */ new Set();
|
|
46
66
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Create a new memory adapter.
|
|
49
|
-
*/
|
|
50
67
|
static create() {
|
|
51
|
-
return new
|
|
68
|
+
return new _IgniterJobsMemoryAdapter();
|
|
52
69
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get or create a queue.
|
|
61
|
-
*/
|
|
62
|
-
getOrCreateQueue(name) {
|
|
63
|
-
if (!this.queues.has(name)) {
|
|
64
|
-
this.queues.set(name, {
|
|
65
|
-
name,
|
|
66
|
-
isPaused: false,
|
|
67
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
68
|
-
pausedJobTypes: /* @__PURE__ */ new Set()
|
|
70
|
+
registerJob(queueName, jobName, definition) {
|
|
71
|
+
const queueJobs = this.registeredJobs.get(queueName) ?? /* @__PURE__ */ new Map();
|
|
72
|
+
if (queueJobs.has(jobName)) {
|
|
73
|
+
throw new IgniterJobsError({
|
|
74
|
+
code: "JOBS_DUPLICATE_JOB",
|
|
75
|
+
message: `Job "${jobName}" already registered for queue "${queueName}".`
|
|
69
76
|
});
|
|
70
77
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate a unique job ID.
|
|
75
|
-
*/
|
|
76
|
-
generateJobId() {
|
|
77
|
-
return `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
78
|
+
queueJobs.set(jobName, definition);
|
|
79
|
+
this.registeredJobs.set(queueName, queueJobs);
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
await handler({
|
|
88
|
-
type,
|
|
89
|
-
data,
|
|
90
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
91
|
-
});
|
|
92
|
-
} catch {
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
81
|
+
registerCron(queueName, cronName, definition) {
|
|
82
|
+
const queueCrons = this.registeredCrons.get(queueName) ?? /* @__PURE__ */ new Map();
|
|
83
|
+
if (queueCrons.has(cronName)) {
|
|
84
|
+
throw new IgniterJobsError({
|
|
85
|
+
code: "JOBS_INVALID_CRON",
|
|
86
|
+
message: `Cron "${cronName}" already registered for queue "${queueName}".`
|
|
87
|
+
});
|
|
96
88
|
}
|
|
89
|
+
queueCrons.set(cronName, definition);
|
|
90
|
+
this.registeredCrons.set(queueName, queueCrons);
|
|
97
91
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Check if a pattern matches an event type.
|
|
100
|
-
*/
|
|
101
|
-
matchesPattern(pattern, eventType) {
|
|
102
|
-
if (pattern === "*") return true;
|
|
103
|
-
const regex = new RegExp(
|
|
104
|
-
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
105
|
-
);
|
|
106
|
-
return regex.test(eventType);
|
|
107
|
-
}
|
|
108
|
-
// ==========================================
|
|
109
|
-
// JOB OPERATIONS
|
|
110
|
-
// ==========================================
|
|
111
92
|
async dispatch(params) {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
93
|
+
const jobId = params.jobId ?? IgniterJobsIdGenerator.generate("job");
|
|
94
|
+
const maxAttempts = params.attempts ?? 1;
|
|
95
|
+
const metadata = params.metadata ?? {};
|
|
114
96
|
const job = {
|
|
115
97
|
id: jobId,
|
|
116
|
-
name: params.
|
|
98
|
+
name: params.jobName,
|
|
117
99
|
queue: params.queue,
|
|
118
|
-
|
|
119
|
-
|
|
100
|
+
input: params.input,
|
|
101
|
+
status: this.pausedQueues.has(params.queue) ? "paused" : params.delay && params.delay > 0 ? "delayed" : "waiting",
|
|
120
102
|
progress: 0,
|
|
121
|
-
|
|
122
|
-
maxAttempts
|
|
123
|
-
timestamp: Date.now(),
|
|
124
|
-
delay: params.delay,
|
|
103
|
+
attemptsMade: 0,
|
|
104
|
+
maxAttempts,
|
|
125
105
|
priority: params.priority ?? 0,
|
|
106
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
107
|
+
metadata,
|
|
126
108
|
scope: params.scope,
|
|
127
|
-
|
|
128
|
-
logs: [],
|
|
129
|
-
scheduledAt: params.delay ? Date.now() + params.delay : void 0
|
|
109
|
+
logs: []
|
|
130
110
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
111
|
+
this.jobsById.set(jobId, job);
|
|
112
|
+
const queueList = this.jobsByQueue.get(params.queue) ?? [];
|
|
113
|
+
queueList.push(jobId);
|
|
114
|
+
this.jobsByQueue.set(params.queue, queueList);
|
|
115
|
+
if (params.delay && params.delay > 0) {
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
const stored = this.jobsById.get(jobId);
|
|
118
|
+
if (!stored) return;
|
|
119
|
+
if (!this.pausedQueues.has(params.queue)) stored.status = "waiting";
|
|
120
|
+
void this.kickWorkers(params.queue);
|
|
121
|
+
}, params.delay);
|
|
122
|
+
return jobId;
|
|
123
|
+
}
|
|
124
|
+
void this.kickWorkers(params.queue);
|
|
138
125
|
return jobId;
|
|
139
126
|
}
|
|
140
127
|
async schedule(params) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
state: job.state,
|
|
156
|
-
data: job.data,
|
|
157
|
-
result: job.result,
|
|
158
|
-
error: job.error,
|
|
159
|
-
progress: job.progress,
|
|
160
|
-
attempts: job.attempts,
|
|
161
|
-
timestamp: job.timestamp,
|
|
162
|
-
processedOn: job.processedOn,
|
|
163
|
-
finishedOn: job.finishedOn,
|
|
164
|
-
delay: job.delay,
|
|
165
|
-
priority: job.priority,
|
|
166
|
-
scope: job.scope,
|
|
167
|
-
actor: job.actor
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
async getJobState(queue, jobId) {
|
|
171
|
-
const q = this.getOrCreateQueue(queue);
|
|
172
|
-
const job = q.jobs.get(jobId);
|
|
173
|
-
return job?.state ?? null;
|
|
174
|
-
}
|
|
175
|
-
async getJobProgress(queue, jobId) {
|
|
176
|
-
const q = this.getOrCreateQueue(queue);
|
|
177
|
-
const job = q.jobs.get(jobId);
|
|
178
|
-
return job?.progress ?? 0;
|
|
128
|
+
if (params.at) {
|
|
129
|
+
const delay = params.at.getTime() - Date.now();
|
|
130
|
+
if (delay <= 0) {
|
|
131
|
+
throw new IgniterJobsError({
|
|
132
|
+
code: "JOBS_INVALID_SCHEDULE",
|
|
133
|
+
message: "Scheduled time must be in the future."
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return this.dispatch({ ...params, delay });
|
|
137
|
+
}
|
|
138
|
+
if (params.cron || params.every) {
|
|
139
|
+
return this.dispatch({ ...params, delay: params.delay ?? 0 });
|
|
140
|
+
}
|
|
141
|
+
return this.dispatch(params);
|
|
179
142
|
}
|
|
180
|
-
async
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
143
|
+
async getJob(jobId, queue) {
|
|
144
|
+
const job = this.jobsById.get(jobId);
|
|
145
|
+
if (!job) return null;
|
|
146
|
+
if (queue && job.queue !== queue) return null;
|
|
147
|
+
return this.toSearchResult(job);
|
|
184
148
|
}
|
|
185
|
-
async
|
|
186
|
-
const
|
|
187
|
-
|
|
149
|
+
async getJobState(jobId, queue) {
|
|
150
|
+
const job = this.jobsById.get(jobId);
|
|
151
|
+
if (!job) return null;
|
|
152
|
+
if (queue && job.queue !== queue) return null;
|
|
153
|
+
return job.status;
|
|
154
|
+
}
|
|
155
|
+
async getJobLogs(jobId, queue) {
|
|
156
|
+
const job = this.jobsById.get(jobId);
|
|
157
|
+
if (!job) return [];
|
|
158
|
+
if (queue && job.queue !== queue) return [];
|
|
159
|
+
return job.logs;
|
|
160
|
+
}
|
|
161
|
+
async getJobProgress(jobId, queue) {
|
|
162
|
+
const job = this.jobsById.get(jobId);
|
|
163
|
+
if (!job) return 0;
|
|
164
|
+
if (queue && job.queue !== queue) return 0;
|
|
165
|
+
return job.progress;
|
|
166
|
+
}
|
|
167
|
+
async retryJob(jobId, queue) {
|
|
168
|
+
const job = this.jobsById.get(jobId);
|
|
188
169
|
if (!job) {
|
|
189
|
-
throw new IgniterJobsError({
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
});
|
|
170
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
|
|
171
|
+
}
|
|
172
|
+
if (queue && job.queue !== queue) {
|
|
173
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
|
|
194
174
|
}
|
|
195
|
-
job.
|
|
175
|
+
job.status = "waiting";
|
|
196
176
|
job.error = void 0;
|
|
197
|
-
job.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
177
|
+
job.completedAt = void 0;
|
|
178
|
+
job.progress = 0;
|
|
179
|
+
void this.kickWorkers(job.queue);
|
|
180
|
+
}
|
|
181
|
+
async removeJob(jobId, queue) {
|
|
182
|
+
const job = this.jobsById.get(jobId);
|
|
183
|
+
if (!job) return;
|
|
184
|
+
if (queue && job.queue !== queue) return;
|
|
185
|
+
this.jobsById.delete(jobId);
|
|
186
|
+
const list = this.jobsByQueue.get(job.queue);
|
|
187
|
+
if (list) this.jobsByQueue.set(job.queue, list.filter((id) => id !== jobId));
|
|
188
|
+
}
|
|
189
|
+
async promoteJob(jobId, queue) {
|
|
190
|
+
const job = this.jobsById.get(jobId);
|
|
206
191
|
if (!job) {
|
|
207
|
-
throw new IgniterJobsError({
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
192
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
|
|
193
|
+
}
|
|
194
|
+
if (queue && job.queue !== queue) {
|
|
195
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
|
|
212
196
|
}
|
|
213
|
-
if (job.
|
|
214
|
-
job.
|
|
215
|
-
job.
|
|
216
|
-
job.delay = void 0;
|
|
197
|
+
if (job.status === "delayed" || job.status === "paused") {
|
|
198
|
+
job.status = this.pausedQueues.has(job.queue) ? "paused" : "waiting";
|
|
199
|
+
void this.kickWorkers(job.queue);
|
|
217
200
|
}
|
|
218
201
|
}
|
|
219
|
-
async
|
|
220
|
-
const
|
|
221
|
-
const job = q.jobs.get(jobId);
|
|
202
|
+
async moveJobToFailed(jobId, reason, queue) {
|
|
203
|
+
const job = this.jobsById.get(jobId);
|
|
222
204
|
if (!job) {
|
|
223
|
-
throw new IgniterJobsError({
|
|
224
|
-
code: "JOBS_JOB_NOT_FOUND",
|
|
225
|
-
message: `Job "${jobId}" not found`,
|
|
226
|
-
statusCode: 404
|
|
227
|
-
});
|
|
205
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
|
|
228
206
|
}
|
|
229
|
-
job.
|
|
230
|
-
|
|
231
|
-
if (state === "failed") {
|
|
232
|
-
job.error = reason;
|
|
233
|
-
} else {
|
|
234
|
-
job.result = reason;
|
|
207
|
+
if (queue && job.queue !== queue) {
|
|
208
|
+
throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
|
|
235
209
|
}
|
|
210
|
+
job.status = "failed";
|
|
211
|
+
job.error = reason;
|
|
212
|
+
job.completedAt = /* @__PURE__ */ new Date();
|
|
236
213
|
}
|
|
237
|
-
async
|
|
238
|
-
await Promise.all(jobIds.map((id) => this.retryJob(
|
|
214
|
+
async retryManyJobs(jobIds, queue) {
|
|
215
|
+
await Promise.all(jobIds.map((id) => this.retryJob(id, queue)));
|
|
239
216
|
}
|
|
240
|
-
async
|
|
241
|
-
await Promise.all(jobIds.map((id) => this.removeJob(
|
|
217
|
+
async removeManyJobs(jobIds, queue) {
|
|
218
|
+
await Promise.all(jobIds.map((id) => this.removeJob(id, queue)));
|
|
242
219
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// ==========================================
|
|
246
|
-
async getQueue(queue) {
|
|
247
|
-
const q = this.getOrCreateQueue(queue);
|
|
248
|
-
const counts = await this.getJobCounts(queue);
|
|
220
|
+
async getQueueInfo(queue) {
|
|
221
|
+
const counts = await this.getQueueJobCounts(queue);
|
|
249
222
|
return {
|
|
250
223
|
name: queue,
|
|
251
|
-
isPaused:
|
|
224
|
+
isPaused: this.pausedQueues.has(queue),
|
|
252
225
|
jobCounts: counts
|
|
253
226
|
};
|
|
254
227
|
}
|
|
228
|
+
async getQueueJobCounts(queue) {
|
|
229
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
230
|
+
const counts = {
|
|
231
|
+
waiting: 0,
|
|
232
|
+
active: 0,
|
|
233
|
+
completed: 0,
|
|
234
|
+
failed: 0,
|
|
235
|
+
delayed: 0,
|
|
236
|
+
paused: 0
|
|
237
|
+
};
|
|
238
|
+
for (const id of jobIds) {
|
|
239
|
+
const job = this.jobsById.get(id);
|
|
240
|
+
if (!job) continue;
|
|
241
|
+
if (job.status in counts) {
|
|
242
|
+
counts[job.status]++;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return counts;
|
|
246
|
+
}
|
|
247
|
+
async listQueues() {
|
|
248
|
+
const queues = Array.from(/* @__PURE__ */ new Set([...this.jobsByQueue.keys(), ...this.registeredJobs.keys(), ...this.registeredCrons.keys()]));
|
|
249
|
+
const result = [];
|
|
250
|
+
for (const q of queues) {
|
|
251
|
+
result.push(await this.getQueueInfo(q));
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
255
|
async pauseQueue(queue) {
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
this.pausedQueues.add(queue);
|
|
257
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
258
|
+
for (const id of jobIds) {
|
|
259
|
+
const job = this.jobsById.get(id);
|
|
260
|
+
if (!job) continue;
|
|
261
|
+
if (job.status === "waiting") job.status = "paused";
|
|
262
|
+
}
|
|
258
263
|
}
|
|
259
264
|
async resumeQueue(queue) {
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
this.pausedQueues.delete(queue);
|
|
266
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
267
|
+
for (const id of jobIds) {
|
|
268
|
+
const job = this.jobsById.get(id);
|
|
269
|
+
if (!job) continue;
|
|
270
|
+
if (job.status === "paused") job.status = "waiting";
|
|
271
|
+
}
|
|
272
|
+
void this.kickWorkers(queue);
|
|
262
273
|
}
|
|
263
274
|
async drainQueue(queue) {
|
|
264
|
-
const
|
|
265
|
-
let
|
|
266
|
-
for (const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
276
|
+
let removed = 0;
|
|
277
|
+
for (const id of jobIds) {
|
|
278
|
+
const job = this.jobsById.get(id);
|
|
279
|
+
if (!job) continue;
|
|
280
|
+
if (job.status === "waiting" || job.status === "paused") {
|
|
281
|
+
this.jobsById.delete(id);
|
|
282
|
+
removed++;
|
|
270
283
|
}
|
|
271
284
|
}
|
|
272
|
-
|
|
285
|
+
this.jobsByQueue.set(queue, jobIds.filter((id) => this.jobsById.has(id)));
|
|
286
|
+
return removed;
|
|
273
287
|
}
|
|
274
288
|
async cleanQueue(queue, options) {
|
|
275
|
-
const q = this.getOrCreateQueue(queue);
|
|
276
289
|
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
290
|
+
const olderThan = options.olderThan ?? 0;
|
|
291
|
+
const limit = options.limit ?? Number.POSITIVE_INFINITY;
|
|
292
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
277
293
|
const now = Date.now();
|
|
278
|
-
let
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
}
|
|
294
|
+
let cleaned = 0;
|
|
295
|
+
for (const id of [...jobIds]) {
|
|
296
|
+
if (cleaned >= limit) break;
|
|
297
|
+
const job = this.jobsById.get(id);
|
|
298
|
+
if (!job) continue;
|
|
299
|
+
if (!statuses.includes(job.status)) continue;
|
|
300
|
+
const ageMs = now - job.createdAt.getTime();
|
|
301
|
+
if (ageMs < olderThan) continue;
|
|
302
|
+
this.jobsById.delete(id);
|
|
303
|
+
cleaned++;
|
|
290
304
|
}
|
|
291
|
-
|
|
305
|
+
this.jobsByQueue.set(queue, jobIds.filter((id) => this.jobsById.has(id)));
|
|
306
|
+
return cleaned;
|
|
292
307
|
}
|
|
293
308
|
async obliterateQueue(queue, options) {
|
|
294
|
-
this.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
309
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
310
|
+
for (const id of jobIds) this.jobsById.delete(id);
|
|
311
|
+
this.jobsByQueue.delete(queue);
|
|
312
|
+
this.registeredJobs.delete(queue);
|
|
313
|
+
this.registeredCrons.delete(queue);
|
|
314
|
+
this.pausedQueues.delete(queue);
|
|
315
|
+
}
|
|
316
|
+
async retryAllInQueue(queue) {
|
|
317
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
318
|
+
let retried = 0;
|
|
319
|
+
for (const id of jobIds) {
|
|
320
|
+
const job = this.jobsById.get(id);
|
|
321
|
+
if (!job) continue;
|
|
322
|
+
if (job.status === "failed") {
|
|
323
|
+
await this.retryJob(id, queue);
|
|
324
|
+
retried++;
|
|
305
325
|
}
|
|
306
326
|
}
|
|
307
|
-
return
|
|
327
|
+
return retried;
|
|
308
328
|
}
|
|
309
|
-
async
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
failed: 0,
|
|
316
|
-
delayed: 0,
|
|
317
|
-
paused: 0
|
|
318
|
-
};
|
|
319
|
-
for (const job of q.jobs.values()) {
|
|
320
|
-
counts[job.state]++;
|
|
329
|
+
async pauseJobType(queue, jobName) {
|
|
330
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
331
|
+
for (const id of jobIds) {
|
|
332
|
+
const job = this.jobsById.get(id);
|
|
333
|
+
if (!job) continue;
|
|
334
|
+
if (job.name === jobName && job.status === "waiting") job.status = "paused";
|
|
321
335
|
}
|
|
322
|
-
return counts;
|
|
323
336
|
}
|
|
324
|
-
async
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
results.push({
|
|
331
|
-
id: job.id,
|
|
332
|
-
name: job.name,
|
|
333
|
-
queue: job.queue,
|
|
334
|
-
state: job.state,
|
|
335
|
-
data: job.data,
|
|
336
|
-
result: job.result,
|
|
337
|
-
error: job.error,
|
|
338
|
-
progress: job.progress,
|
|
339
|
-
attempts: job.attempts,
|
|
340
|
-
timestamp: job.timestamp,
|
|
341
|
-
processedOn: job.processedOn,
|
|
342
|
-
finishedOn: job.finishedOn,
|
|
343
|
-
scope: job.scope,
|
|
344
|
-
actor: job.actor
|
|
345
|
-
});
|
|
346
|
-
}
|
|
337
|
+
async resumeJobType(queue, jobName) {
|
|
338
|
+
const jobIds = this.jobsByQueue.get(queue) ?? [];
|
|
339
|
+
for (const id of jobIds) {
|
|
340
|
+
const job = this.jobsById.get(id);
|
|
341
|
+
if (!job) continue;
|
|
342
|
+
if (job.name === jobName && job.status === "paused") job.status = "waiting";
|
|
347
343
|
}
|
|
348
|
-
|
|
349
|
-
const end = options?.end ?? results.length;
|
|
350
|
-
return results.slice(start, end);
|
|
344
|
+
void this.kickWorkers(queue);
|
|
351
345
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
346
|
+
async searchJobs(filter) {
|
|
347
|
+
const queue = filter?.queue;
|
|
348
|
+
const statuses = filter?.status;
|
|
349
|
+
const limit = filter?.limit ?? 100;
|
|
350
|
+
const offset = filter?.offset ?? 0;
|
|
351
|
+
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());
|
|
352
|
+
return all.slice(offset, offset + limit).map((j) => this.toSearchResult(j));
|
|
358
353
|
}
|
|
359
|
-
async
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
354
|
+
async searchQueues(filter) {
|
|
355
|
+
const name = filter?.name;
|
|
356
|
+
const isPaused = filter?.isPaused;
|
|
357
|
+
const all = await this.listQueues();
|
|
358
|
+
return all.filter((q) => name ? q.name.includes(name) : true).filter((q) => typeof isPaused === "boolean" ? q.isPaused === isPaused : true);
|
|
359
|
+
}
|
|
360
|
+
async searchWorkers(filter) {
|
|
361
|
+
const queue = filter?.queue;
|
|
362
|
+
const isRunning = filter?.isRunning;
|
|
363
|
+
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));
|
|
364
|
+
}
|
|
365
|
+
async createWorker(config) {
|
|
366
|
+
const workerId = IgniterJobsIdGenerator.generate("worker");
|
|
367
|
+
const state = {
|
|
368
|
+
id: workerId,
|
|
369
|
+
queues: config.queues ?? [],
|
|
370
|
+
concurrency: config.concurrency ?? 1,
|
|
371
|
+
paused: false,
|
|
372
|
+
closed: false,
|
|
373
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
374
|
+
metrics: { processed: 0, failed: 0, totalDuration: 0 },
|
|
375
|
+
handlers: config.handlers
|
|
376
|
+
};
|
|
377
|
+
this.workers.set(workerId, state);
|
|
378
|
+
for (const q of state.queues) void this.kickWorkers(q);
|
|
379
|
+
return this.toWorkerHandle(state);
|
|
380
|
+
}
|
|
381
|
+
getWorkers() {
|
|
382
|
+
const out = /* @__PURE__ */ new Map();
|
|
383
|
+
for (const [id, state] of this.workers) out.set(id, this.toWorkerHandle(state));
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
async publishEvent(channel, payload) {
|
|
387
|
+
const handlers = this.subscribers.get(channel);
|
|
388
|
+
if (!handlers) return;
|
|
389
|
+
await Promise.all(Array.from(handlers).map(async (h) => h(payload)));
|
|
390
|
+
}
|
|
391
|
+
async subscribeEvent(channel, handler) {
|
|
392
|
+
const set = this.subscribers.get(channel) ?? /* @__PURE__ */ new Set();
|
|
393
|
+
set.add(handler);
|
|
394
|
+
this.subscribers.set(channel, set);
|
|
371
395
|
return async () => {
|
|
372
|
-
this.
|
|
396
|
+
const current = this.subscribers.get(channel);
|
|
397
|
+
if (!current) return;
|
|
398
|
+
current.delete(handler);
|
|
399
|
+
if (current.size === 0) this.subscribers.delete(channel);
|
|
373
400
|
};
|
|
374
401
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
this.workers.set(workerId, {
|
|
381
|
-
handler,
|
|
382
|
-
config,
|
|
383
|
-
running: true,
|
|
384
|
-
paused: false
|
|
385
|
-
});
|
|
386
|
-
const startTime = Date.now();
|
|
387
|
-
let processed = 0;
|
|
388
|
-
let failed = 0;
|
|
389
|
-
let completed = 0;
|
|
402
|
+
async shutdown() {
|
|
403
|
+
this.workers.clear();
|
|
404
|
+
this.subscribers.clear();
|
|
405
|
+
}
|
|
406
|
+
toSearchResult(job) {
|
|
390
407
|
return {
|
|
391
|
-
id:
|
|
408
|
+
id: job.id,
|
|
409
|
+
name: job.name,
|
|
410
|
+
queue: job.queue,
|
|
411
|
+
status: job.status,
|
|
412
|
+
input: job.input,
|
|
413
|
+
result: job.result,
|
|
414
|
+
error: job.error,
|
|
415
|
+
progress: job.progress,
|
|
416
|
+
attemptsMade: job.attemptsMade,
|
|
417
|
+
priority: job.priority,
|
|
418
|
+
createdAt: job.createdAt,
|
|
419
|
+
startedAt: job.startedAt,
|
|
420
|
+
completedAt: job.completedAt,
|
|
421
|
+
metadata: job.metadata,
|
|
422
|
+
scope: job.scope
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
toWorkerHandle(worker) {
|
|
426
|
+
return {
|
|
427
|
+
id: worker.id,
|
|
428
|
+
queues: worker.queues,
|
|
392
429
|
pause: async () => {
|
|
393
|
-
|
|
394
|
-
if (worker) worker.paused = true;
|
|
430
|
+
worker.paused = true;
|
|
395
431
|
},
|
|
396
432
|
resume: async () => {
|
|
397
|
-
|
|
398
|
-
|
|
433
|
+
worker.paused = false;
|
|
434
|
+
for (const q of worker.queues) void this.kickWorkers(q);
|
|
399
435
|
},
|
|
400
436
|
close: async () => {
|
|
401
|
-
|
|
437
|
+
worker.closed = true;
|
|
402
438
|
},
|
|
403
|
-
isRunning: () =>
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
isPaused: () => {
|
|
408
|
-
return this.workers.get(workerId)?.paused || false;
|
|
409
|
-
},
|
|
410
|
-
getMetrics: async () => ({
|
|
411
|
-
processed,
|
|
412
|
-
failed,
|
|
413
|
-
completed,
|
|
414
|
-
active: 0,
|
|
415
|
-
uptime: Date.now() - startTime
|
|
416
|
-
})
|
|
439
|
+
isRunning: () => !worker.closed && !worker.paused,
|
|
440
|
+
isPaused: () => worker.paused,
|
|
441
|
+
isClosed: () => worker.closed,
|
|
442
|
+
getMetrics: async () => this.toWorkerMetrics(worker)
|
|
417
443
|
};
|
|
418
444
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
445
|
+
toWorkerMetrics(worker) {
|
|
446
|
+
const uptime = Date.now() - worker.startedAt.getTime();
|
|
447
|
+
const processed = worker.metrics.processed;
|
|
448
|
+
return {
|
|
449
|
+
processed,
|
|
450
|
+
failed: worker.metrics.failed,
|
|
451
|
+
avgDuration: processed > 0 ? worker.metrics.totalDuration / processed : 0,
|
|
452
|
+
concurrency: worker.concurrency,
|
|
453
|
+
uptime
|
|
454
|
+
};
|
|
427
455
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
for (const
|
|
433
|
-
|
|
434
|
-
for (const queueName of worker.config.queues) {
|
|
435
|
-
const queue = this.queues.get(queueName);
|
|
436
|
-
if (!queue || queue.isPaused) continue;
|
|
437
|
-
for (const job of queue.jobs.values()) {
|
|
438
|
-
if (job.state !== "waiting") continue;
|
|
439
|
-
if (queue.pausedJobTypes.has(job.name)) continue;
|
|
440
|
-
if (job.scheduledAt && job.scheduledAt > Date.now()) continue;
|
|
441
|
-
job.state = "active";
|
|
442
|
-
job.processedOn = Date.now();
|
|
443
|
-
job.attempts++;
|
|
444
|
-
try {
|
|
445
|
-
const result = await worker.handler({
|
|
446
|
-
id: job.id,
|
|
447
|
-
name: job.name,
|
|
448
|
-
queue: job.queue,
|
|
449
|
-
data: job.data,
|
|
450
|
-
attempt: job.attempts,
|
|
451
|
-
timestamp: job.timestamp,
|
|
452
|
-
scope: job.scope,
|
|
453
|
-
actor: job.actor,
|
|
454
|
-
log: async (level, message) => {
|
|
455
|
-
job.logs.push({
|
|
456
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
457
|
-
message,
|
|
458
|
-
level
|
|
459
|
-
});
|
|
460
|
-
},
|
|
461
|
-
updateProgress: async (progress) => {
|
|
462
|
-
job.progress = progress;
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
job.state = "completed";
|
|
466
|
-
job.result = result;
|
|
467
|
-
job.finishedOn = Date.now();
|
|
468
|
-
await this.emitEvent(`${job.queue}:${job.name}:completed`, {
|
|
469
|
-
jobId: job.id,
|
|
470
|
-
result
|
|
471
|
-
});
|
|
472
|
-
} catch (error) {
|
|
473
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
474
|
-
if (job.attempts < job.maxAttempts) {
|
|
475
|
-
job.state = "waiting";
|
|
476
|
-
await this.emitEvent(`${job.queue}:${job.name}:retrying`, {
|
|
477
|
-
jobId: job.id,
|
|
478
|
-
error: errorMessage,
|
|
479
|
-
attempt: job.attempts
|
|
480
|
-
});
|
|
481
|
-
} else {
|
|
482
|
-
job.state = "failed";
|
|
483
|
-
job.error = errorMessage;
|
|
484
|
-
job.finishedOn = Date.now();
|
|
485
|
-
await this.emitEvent(`${job.queue}:${job.name}:failed`, {
|
|
486
|
-
jobId: job.id,
|
|
487
|
-
error: errorMessage
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
456
|
+
async kickWorkers(queue) {
|
|
457
|
+
if (this.pausedQueues.has(queue)) return;
|
|
458
|
+
const relevant = Array.from(this.workers.values()).filter((w) => !w.closed && !w.paused && (w.queues.length === 0 || w.queues.includes(queue)));
|
|
459
|
+
if (relevant.length === 0) return;
|
|
460
|
+
for (const w of relevant) {
|
|
461
|
+
void this.processLoop(w, queue);
|
|
493
462
|
}
|
|
494
463
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
464
|
+
async processLoop(worker, queue) {
|
|
465
|
+
if (worker.closed || worker.paused) return;
|
|
466
|
+
const concurrency = Math.max(1, worker.concurrency);
|
|
467
|
+
const running = worker.__running;
|
|
468
|
+
const currentRunning = running ?? 0;
|
|
469
|
+
if (currentRunning >= concurrency) return;
|
|
470
|
+
worker.__running = currentRunning + 1;
|
|
471
|
+
try {
|
|
472
|
+
const next = this.nextJob(queue);
|
|
473
|
+
if (!next) return;
|
|
474
|
+
await this.processJob(worker, next);
|
|
475
|
+
} finally {
|
|
476
|
+
worker.__running = worker.__running - 1;
|
|
477
|
+
if (this.nextJob(queue)) void this.processLoop(worker, queue);
|
|
478
|
+
else if (worker.handlers?.onIdle) await worker.handlers.onIdle();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
nextJob(queue) {
|
|
482
|
+
const ids = this.jobsByQueue.get(queue) ?? [];
|
|
483
|
+
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());
|
|
484
|
+
return candidates[0] ?? null;
|
|
485
|
+
}
|
|
486
|
+
async processJob(worker, job) {
|
|
487
|
+
if (this.pausedQueues.has(job.queue)) {
|
|
488
|
+
job.status = "paused";
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
job.status = "active";
|
|
492
|
+
job.startedAt = /* @__PURE__ */ new Date();
|
|
493
|
+
job.attemptsMade += 1;
|
|
494
|
+
job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "info", message: "Job started" });
|
|
495
|
+
if (worker.handlers?.onActive) await worker.handlers.onActive({ job: this.toSearchResult(job) });
|
|
496
|
+
const start = Date.now();
|
|
497
|
+
try {
|
|
498
|
+
const definition = this.registeredJobs.get(job.queue)?.get(job.name);
|
|
499
|
+
if (!definition) {
|
|
500
|
+
throw new IgniterJobsError({
|
|
501
|
+
code: "JOBS_NOT_REGISTERED",
|
|
502
|
+
message: `Job "${job.name}" is not registered for queue "${job.queue}".`
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
if (definition.onStart) {
|
|
506
|
+
await definition.onStart({
|
|
507
|
+
input: job.input,
|
|
508
|
+
context: {},
|
|
509
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
524
510
|
scope: job.scope,
|
|
525
|
-
|
|
511
|
+
startedAt: job.startedAt
|
|
526
512
|
});
|
|
527
513
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const bVal = field === "createdAt" ? b.timestamp : b[field];
|
|
534
|
-
return direction === "asc" ? aVal - bVal : bVal - aVal;
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
const offset = filter.offset ?? 0;
|
|
538
|
-
const limit = filter.limit ?? 100;
|
|
539
|
-
return results.slice(offset, offset + limit);
|
|
540
|
-
}
|
|
541
|
-
async searchQueues(filter) {
|
|
542
|
-
const results = [];
|
|
543
|
-
for (const [queueName, queue] of this.queues) {
|
|
544
|
-
if (filter.name && !queueName.includes(filter.name)) continue;
|
|
545
|
-
if (filter.isPaused !== void 0 && queue.isPaused !== filter.isPaused) continue;
|
|
546
|
-
const counts = await this.getJobCounts(queueName);
|
|
547
|
-
results.push({
|
|
548
|
-
name: queueName,
|
|
549
|
-
isPaused: queue.isPaused,
|
|
550
|
-
jobCounts: counts
|
|
514
|
+
const result = await definition.handler({
|
|
515
|
+
input: job.input,
|
|
516
|
+
context: {},
|
|
517
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
518
|
+
scope: job.scope
|
|
551
519
|
});
|
|
520
|
+
const duration = Date.now() - start;
|
|
521
|
+
job.status = "completed";
|
|
522
|
+
job.completedAt = /* @__PURE__ */ new Date();
|
|
523
|
+
job.result = result;
|
|
524
|
+
job.progress = 100;
|
|
525
|
+
job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "info", message: `Job completed in ${duration}ms` });
|
|
526
|
+
worker.metrics.processed += 1;
|
|
527
|
+
worker.metrics.totalDuration += duration;
|
|
528
|
+
if (definition.onSuccess) {
|
|
529
|
+
await definition.onSuccess({
|
|
530
|
+
input: job.input,
|
|
531
|
+
context: {},
|
|
532
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
533
|
+
scope: job.scope,
|
|
534
|
+
result,
|
|
535
|
+
duration
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
if (worker.handlers?.onSuccess) await worker.handlers.onSuccess({ job: this.toSearchResult(job), result });
|
|
539
|
+
} catch (error) {
|
|
540
|
+
job.error = error?.message ?? String(error);
|
|
541
|
+
job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "error", message: job.error ?? "Unknown error" });
|
|
542
|
+
const isFinalAttempt = job.attemptsMade >= job.maxAttempts;
|
|
543
|
+
if (isFinalAttempt) {
|
|
544
|
+
job.status = "failed";
|
|
545
|
+
job.completedAt = /* @__PURE__ */ new Date();
|
|
546
|
+
worker.metrics.failed += 1;
|
|
547
|
+
const definition = this.registeredJobs.get(job.queue)?.get(job.name);
|
|
548
|
+
if (definition?.onFailure) {
|
|
549
|
+
await definition.onFailure({
|
|
550
|
+
input: job.input,
|
|
551
|
+
context: {},
|
|
552
|
+
job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
|
|
553
|
+
scope: job.scope,
|
|
554
|
+
error,
|
|
555
|
+
isFinalAttempt: true
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (worker.handlers?.onFailure) await worker.handlers.onFailure({ job: this.toSearchResult(job), error });
|
|
559
|
+
} else {
|
|
560
|
+
job.status = "waiting";
|
|
561
|
+
void this.kickWorkers(job.queue);
|
|
562
|
+
}
|
|
552
563
|
}
|
|
553
|
-
return results;
|
|
554
|
-
}
|
|
555
|
-
// ==========================================
|
|
556
|
-
// LIFECYCLE
|
|
557
|
-
// ==========================================
|
|
558
|
-
async shutdown() {
|
|
559
|
-
if (this.processingInterval) {
|
|
560
|
-
clearInterval(this.processingInterval);
|
|
561
|
-
this.processingInterval = null;
|
|
562
|
-
}
|
|
563
|
-
this.workers.clear();
|
|
564
|
-
this.queues.clear();
|
|
565
|
-
this.eventHandlers.clear();
|
|
566
564
|
}
|
|
567
565
|
};
|
|
568
566
|
|
|
569
|
-
exports.
|
|
567
|
+
exports.IgniterJobsMemoryAdapter = IgniterJobsMemoryAdapter;
|
|
570
568
|
//# sourceMappingURL=memory.adapter.js.map
|
|
571
569
|
//# sourceMappingURL=memory.adapter.js.map
|