@happyvertical/jobs 0.80.0 → 0.80.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/bull.js +343 -346
- package/dist/adapters/bull.js.map +1 -1
- package/dist/adapters/bullmq.js +351 -388
- package/dist/adapters/bullmq.js.map +1 -1
- package/dist/adapters/cloud-tasks.js +307 -333
- package/dist/adapters/cloud-tasks.js.map +1 -1
- package/dist/adapters/postgres.js +288 -328
- package/dist/adapters/postgres.js.map +1 -1
- package/dist/adapters/sqlite.js +236 -258
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/adapters/sqs.js +363 -408
- package/dist/adapters/sqs.js.map +1 -1
- package/dist/chunks/base-store-DIasEzL0.js +386 -0
- package/dist/chunks/base-store-DIasEzL0.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +219 -242
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunks/base-store-DlNksWvQ.js +0 -324
- package/dist/chunks/base-store-DlNksWvQ.js.map +0 -1
package/dist/adapters/sqs.js
CHANGED
|
@@ -1,411 +1,366 @@
|
|
|
1
|
+
import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
|
|
1
2
|
import { createId } from "@happyvertical/utils";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
const totals = {
|
|
361
|
-
pending: 0,
|
|
362
|
-
running: 0,
|
|
363
|
-
completed: 0,
|
|
364
|
-
failed: 0,
|
|
365
|
-
cancelled: 0,
|
|
366
|
-
avgDuration: null
|
|
367
|
-
};
|
|
368
|
-
for (const state of this.jobStates.values()) {
|
|
369
|
-
if (queue && state.job.queue !== queue) continue;
|
|
370
|
-
switch (state.job.status) {
|
|
371
|
-
case "pending":
|
|
372
|
-
totals.pending++;
|
|
373
|
-
break;
|
|
374
|
-
case "running":
|
|
375
|
-
totals.running++;
|
|
376
|
-
break;
|
|
377
|
-
case "completed":
|
|
378
|
-
totals.completed++;
|
|
379
|
-
break;
|
|
380
|
-
case "failed":
|
|
381
|
-
totals.failed++;
|
|
382
|
-
break;
|
|
383
|
-
case "cancelled":
|
|
384
|
-
totals.cancelled++;
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return totals;
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Close the SQS client
|
|
392
|
-
*/
|
|
393
|
-
async close() {
|
|
394
|
-
if (this.client) {
|
|
395
|
-
this.client.destroy();
|
|
396
|
-
this.client = null;
|
|
397
|
-
}
|
|
398
|
-
this.jobStates.clear();
|
|
399
|
-
this.queueUrls.clear();
|
|
400
|
-
this.initialized = false;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
3
|
+
//#region src/adapters/sqs.ts
|
|
4
|
+
/**
|
|
5
|
+
* SQS-based job store implementation
|
|
6
|
+
*
|
|
7
|
+
* Note: SQS has limitations that make some operations different:
|
|
8
|
+
* - `update()` throws error (messages are immutable)
|
|
9
|
+
* - `list()` only returns pending jobs from SQS
|
|
10
|
+
* - `cancel()` requires the job to have been dequeued first
|
|
11
|
+
* - For full job tracking, consider using DynamoDB alongside SQS
|
|
12
|
+
*/
|
|
13
|
+
var SQSJobStore = class extends BaseJobStore {
|
|
14
|
+
config;
|
|
15
|
+
client = null;
|
|
16
|
+
awsSdkModule = null;
|
|
17
|
+
queueUrls = /* @__PURE__ */ new Map();
|
|
18
|
+
jobStates = /* @__PURE__ */ new Map();
|
|
19
|
+
constructor(config) {
|
|
20
|
+
super();
|
|
21
|
+
this.config = {
|
|
22
|
+
visibilityTimeout: 300,
|
|
23
|
+
messageRetentionDays: 4,
|
|
24
|
+
useFifo: false,
|
|
25
|
+
...config
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the store - dynamically imports AWS SDK
|
|
30
|
+
*/
|
|
31
|
+
async initialize() {
|
|
32
|
+
if (this.initialized) return;
|
|
33
|
+
try {
|
|
34
|
+
this.awsSdkModule = await import("@aws-sdk/client-sqs");
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error("AWS SDK is required for SQSJobStore. Install it with: npm install @aws-sdk/client-sqs");
|
|
37
|
+
}
|
|
38
|
+
this.client = new this.awsSdkModule.SQSClient({
|
|
39
|
+
region: this.config.region,
|
|
40
|
+
credentials: this.config.credentials
|
|
41
|
+
});
|
|
42
|
+
this.initialized = true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get or create queue URL for a queue name
|
|
46
|
+
*/
|
|
47
|
+
getQueueUrl(queueName) {
|
|
48
|
+
if (!this.queueUrls.has(queueName)) {
|
|
49
|
+
const suffix = this.config.useFifo ? ".fifo" : "";
|
|
50
|
+
this.queueUrls.set(queueName, `${this.config.queueUrlPrefix}${queueName}${suffix}`);
|
|
51
|
+
}
|
|
52
|
+
return this.queueUrls.get(queueName);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convert SQS message to Job format
|
|
56
|
+
*/
|
|
57
|
+
sqsMessageToJob(message, queueName) {
|
|
58
|
+
if (!message.Body) return null;
|
|
59
|
+
try {
|
|
60
|
+
const data = JSON.parse(message.Body);
|
|
61
|
+
const now = /* @__PURE__ */ new Date();
|
|
62
|
+
const state = this.jobStates.get(data.id);
|
|
63
|
+
if (state) {
|
|
64
|
+
state.receiptHandle = message.ReceiptHandle;
|
|
65
|
+
return state.job;
|
|
66
|
+
}
|
|
67
|
+
const job = {
|
|
68
|
+
id: data.id,
|
|
69
|
+
queue: queueName,
|
|
70
|
+
payload: data.payload,
|
|
71
|
+
status: "pending",
|
|
72
|
+
priority: data.priority,
|
|
73
|
+
attempts: Number(message.Attributes?.ApproximateReceiveCount ?? 0),
|
|
74
|
+
maxAttempts: data.maxAttempts,
|
|
75
|
+
runAt: new Date(data.runAt),
|
|
76
|
+
startedAt: null,
|
|
77
|
+
completedAt: null,
|
|
78
|
+
timeout: data.timeout,
|
|
79
|
+
timeoutBehavior: data.timeoutBehavior,
|
|
80
|
+
lastError: null,
|
|
81
|
+
resultPointer: null,
|
|
82
|
+
retryStrategy: data.retryStrategy,
|
|
83
|
+
workerId: null,
|
|
84
|
+
workerHeartbeat: null,
|
|
85
|
+
createdAt: new Date(data.createdAt),
|
|
86
|
+
updatedAt: now
|
|
87
|
+
};
|
|
88
|
+
this.jobStates.set(data.id, {
|
|
89
|
+
job,
|
|
90
|
+
receiptHandle: message.ReceiptHandle
|
|
91
|
+
});
|
|
92
|
+
return job;
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Enqueue a new job
|
|
99
|
+
*/
|
|
100
|
+
async enqueue(options) {
|
|
101
|
+
if (!this.initialized || !this.client || !this.awsSdkModule) throw new Error("SQSJobStore not initialized");
|
|
102
|
+
const queueName = options.queue ?? "default";
|
|
103
|
+
const queueUrl = this.getQueueUrl(queueName);
|
|
104
|
+
const now = /* @__PURE__ */ new Date();
|
|
105
|
+
const jobId = createId();
|
|
106
|
+
const jobData = {
|
|
107
|
+
id: jobId,
|
|
108
|
+
queue: queueName,
|
|
109
|
+
payload: options.payload,
|
|
110
|
+
priority: priorityToNumber(options.priority),
|
|
111
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
112
|
+
timeout: options.timeout ?? 3e5,
|
|
113
|
+
timeoutBehavior: options.timeoutBehavior ?? "fail",
|
|
114
|
+
retryStrategy: options.retryStrategy && "toConfig" in options.retryStrategy ? options.retryStrategy.toConfig() : options.retryStrategy ?? {
|
|
115
|
+
type: "exponential",
|
|
116
|
+
config: {
|
|
117
|
+
initialDelay: 1e3,
|
|
118
|
+
multiplier: 2
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
runAt: (options.runAt ?? now).toISOString(),
|
|
122
|
+
createdAt: now.toISOString()
|
|
123
|
+
};
|
|
124
|
+
const messageParams = {
|
|
125
|
+
QueueUrl: queueUrl,
|
|
126
|
+
MessageBody: JSON.stringify(jobData),
|
|
127
|
+
DelaySeconds: options.runAt ? Math.min(900, Math.max(0, Math.floor((options.runAt.getTime() - now.getTime()) / 1e3))) : void 0
|
|
128
|
+
};
|
|
129
|
+
if (this.config.useFifo) {
|
|
130
|
+
messageParams.MessageGroupId = queueName;
|
|
131
|
+
messageParams.MessageDeduplicationId = jobId;
|
|
132
|
+
}
|
|
133
|
+
await this.client.send(new this.awsSdkModule.SendMessageCommand(messageParams));
|
|
134
|
+
const job = {
|
|
135
|
+
id: jobId,
|
|
136
|
+
queue: queueName,
|
|
137
|
+
payload: options.payload,
|
|
138
|
+
status: "pending",
|
|
139
|
+
priority: priorityToNumber(options.priority),
|
|
140
|
+
attempts: 0,
|
|
141
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
142
|
+
runAt: options.runAt ?? now,
|
|
143
|
+
startedAt: null,
|
|
144
|
+
completedAt: null,
|
|
145
|
+
timeout: options.timeout ?? 3e5,
|
|
146
|
+
timeoutBehavior: options.timeoutBehavior ?? "fail",
|
|
147
|
+
lastError: null,
|
|
148
|
+
resultPointer: null,
|
|
149
|
+
retryStrategy: jobData.retryStrategy,
|
|
150
|
+
workerId: null,
|
|
151
|
+
workerHeartbeat: null,
|
|
152
|
+
createdAt: now,
|
|
153
|
+
updatedAt: now
|
|
154
|
+
};
|
|
155
|
+
this.jobStates.set(jobId, { job });
|
|
156
|
+
await this.emitEvent("job.created", job);
|
|
157
|
+
return job;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Dequeue jobs ready for processing
|
|
161
|
+
*/
|
|
162
|
+
async dequeue(queues, limit, workerId) {
|
|
163
|
+
if (!this.initialized || !this.client || !this.awsSdkModule) throw new Error("SQSJobStore not initialized");
|
|
164
|
+
const jobs = [];
|
|
165
|
+
for (const queueName of queues) {
|
|
166
|
+
if (jobs.length >= limit) break;
|
|
167
|
+
const queueUrl = this.getQueueUrl(queueName);
|
|
168
|
+
const result = await this.client.send(new this.awsSdkModule.ReceiveMessageCommand({
|
|
169
|
+
QueueUrl: queueUrl,
|
|
170
|
+
MaxNumberOfMessages: Math.min(10, limit - jobs.length),
|
|
171
|
+
VisibilityTimeout: this.config.visibilityTimeout,
|
|
172
|
+
MessageSystemAttributeNames: ["ApproximateReceiveCount"],
|
|
173
|
+
WaitTimeSeconds: 0
|
|
174
|
+
}));
|
|
175
|
+
if (result.Messages) for (const message of result.Messages) {
|
|
176
|
+
const job = this.sqsMessageToJob(message, queueName);
|
|
177
|
+
if (job) {
|
|
178
|
+
job.status = "running";
|
|
179
|
+
job.workerId = workerId;
|
|
180
|
+
job.startedAt = /* @__PURE__ */ new Date();
|
|
181
|
+
jobs.push(job);
|
|
182
|
+
await this.emitEvent("job.started", job);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return jobs;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Update a job - NOT SUPPORTED in SQS (messages are immutable)
|
|
190
|
+
*/
|
|
191
|
+
async update(_id, _updates) {
|
|
192
|
+
throw new Error("SQS does not support updating jobs. Messages are immutable. Consider using DynamoDB alongside SQS for full job state tracking.");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get a job by ID (from in-memory state only)
|
|
196
|
+
*/
|
|
197
|
+
async get(id) {
|
|
198
|
+
return this.jobStates.get(id)?.job ?? null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* List jobs with filtering
|
|
202
|
+
* Note: SQS doesn't support efficient listing - this only returns in-memory state
|
|
203
|
+
*/
|
|
204
|
+
async list(filter) {
|
|
205
|
+
const jobs = [];
|
|
206
|
+
const limit = filter.limit ?? 100;
|
|
207
|
+
for (const state of this.jobStates.values()) {
|
|
208
|
+
const job = state.job;
|
|
209
|
+
if (filter.queue && job.queue !== filter.queue) continue;
|
|
210
|
+
if (filter.status) {
|
|
211
|
+
if (!(Array.isArray(filter.status) ? filter.status : [filter.status]).includes(job.status)) continue;
|
|
212
|
+
}
|
|
213
|
+
if (filter.objectType && job.payload.objectType !== filter.objectType) continue;
|
|
214
|
+
if (filter.method && job.payload.method !== filter.method) continue;
|
|
215
|
+
if (filter.createdAfter && job.createdAt < filter.createdAfter) continue;
|
|
216
|
+
if (filter.createdBefore && job.createdAt > filter.createdBefore) continue;
|
|
217
|
+
jobs.push(job);
|
|
218
|
+
if (jobs.length >= limit) break;
|
|
219
|
+
}
|
|
220
|
+
return jobs;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Cancel a job by deleting its message from SQS
|
|
224
|
+
*/
|
|
225
|
+
async cancel(id) {
|
|
226
|
+
if (!this.initialized || !this.client || !this.awsSdkModule) throw new Error("SQSJobStore not initialized");
|
|
227
|
+
const state = this.jobStates.get(id);
|
|
228
|
+
if (!state) throw new Error(`Job ${id} not found or not yet received from queue`);
|
|
229
|
+
if (!state.receiptHandle) throw new Error(`Job ${id} has no receipt handle - cannot delete from SQS`);
|
|
230
|
+
const queueUrl = this.getQueueUrl(state.job.queue);
|
|
231
|
+
await this.client.send(new this.awsSdkModule.DeleteMessageCommand({
|
|
232
|
+
QueueUrl: queueUrl,
|
|
233
|
+
ReceiptHandle: state.receiptHandle
|
|
234
|
+
}));
|
|
235
|
+
state.job.status = "cancelled";
|
|
236
|
+
state.job.completedAt = /* @__PURE__ */ new Date();
|
|
237
|
+
await this.emitEvent("job.cancelled", state.job);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Mark job as completed
|
|
241
|
+
*/
|
|
242
|
+
async markCompleted(id, resultPointer) {
|
|
243
|
+
if (!this.initialized || !this.client || !this.awsSdkModule) throw new Error("SQSJobStore not initialized");
|
|
244
|
+
const state = this.jobStates.get(id);
|
|
245
|
+
if (!state || !state.receiptHandle) throw new Error(`Job ${id} not found or has no receipt handle`);
|
|
246
|
+
const queueUrl = this.getQueueUrl(state.job.queue);
|
|
247
|
+
await this.client.send(new this.awsSdkModule.DeleteMessageCommand({
|
|
248
|
+
QueueUrl: queueUrl,
|
|
249
|
+
ReceiptHandle: state.receiptHandle
|
|
250
|
+
}));
|
|
251
|
+
state.job.status = "completed";
|
|
252
|
+
state.job.completedAt = /* @__PURE__ */ new Date();
|
|
253
|
+
state.job.resultPointer = resultPointer ?? null;
|
|
254
|
+
await this.emitEvent("job.completed", state.job, { resultPointer });
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Mark job as failed
|
|
258
|
+
*/
|
|
259
|
+
async markFailed(id, error) {
|
|
260
|
+
const state = this.jobStates.get(id);
|
|
261
|
+
if (!state) throw new Error(`Job ${id} not found`);
|
|
262
|
+
state.job.status = "failed";
|
|
263
|
+
state.job.completedAt = /* @__PURE__ */ new Date();
|
|
264
|
+
state.job.lastError = error;
|
|
265
|
+
await this.emitEvent("job.failed", state.job, { error });
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Clean up old jobs from in-memory state
|
|
269
|
+
* Note: SQS handles message retention automatically
|
|
270
|
+
*/
|
|
271
|
+
async cleanup(options) {
|
|
272
|
+
let cleaned = 0;
|
|
273
|
+
for (const [id, state] of this.jobStates) {
|
|
274
|
+
const { job } = state;
|
|
275
|
+
if (options.completedBefore && job.status === "completed" && job.completedAt && job.completedAt < options.completedBefore) {
|
|
276
|
+
this.jobStates.delete(id);
|
|
277
|
+
cleaned++;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (options.failedBefore && job.status === "failed" && job.completedAt && job.completedAt < options.failedBefore) {
|
|
281
|
+
this.jobStates.delete(id);
|
|
282
|
+
cleaned++;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (options.cancelledBefore && job.status === "cancelled" && job.completedAt && job.completedAt < options.cancelledBefore) {
|
|
286
|
+
this.jobStates.delete(id);
|
|
287
|
+
cleaned++;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (options.limit && cleaned >= options.limit) break;
|
|
291
|
+
}
|
|
292
|
+
return cleaned;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Update visibility timeout for a job (extends processing time)
|
|
296
|
+
*/
|
|
297
|
+
async heartbeat(jobId, _workerId) {
|
|
298
|
+
if (!this.initialized || !this.client || !this.awsSdkModule) throw new Error("SQSJobStore not initialized");
|
|
299
|
+
const state = this.jobStates.get(jobId);
|
|
300
|
+
if (!state || !state.receiptHandle) return;
|
|
301
|
+
const queueUrl = this.getQueueUrl(state.job.queue);
|
|
302
|
+
await this.client.send(new this.awsSdkModule.ChangeMessageVisibilityCommand({
|
|
303
|
+
QueueUrl: queueUrl,
|
|
304
|
+
ReceiptHandle: state.receiptHandle,
|
|
305
|
+
VisibilityTimeout: this.config.visibilityTimeout
|
|
306
|
+
}));
|
|
307
|
+
state.job.workerHeartbeat = /* @__PURE__ */ new Date();
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get queue statistics
|
|
311
|
+
*/
|
|
312
|
+
async stats(queue) {
|
|
313
|
+
if (!this.initialized || !this.client || !this.awsSdkModule) throw new Error("SQSJobStore not initialized");
|
|
314
|
+
const totals = {
|
|
315
|
+
pending: 0,
|
|
316
|
+
running: 0,
|
|
317
|
+
completed: 0,
|
|
318
|
+
failed: 0,
|
|
319
|
+
cancelled: 0,
|
|
320
|
+
avgDuration: null
|
|
321
|
+
};
|
|
322
|
+
for (const state of this.jobStates.values()) {
|
|
323
|
+
if (queue && state.job.queue !== queue) continue;
|
|
324
|
+
switch (state.job.status) {
|
|
325
|
+
case "pending":
|
|
326
|
+
totals.pending++;
|
|
327
|
+
break;
|
|
328
|
+
case "running":
|
|
329
|
+
totals.running++;
|
|
330
|
+
break;
|
|
331
|
+
case "completed":
|
|
332
|
+
totals.completed++;
|
|
333
|
+
break;
|
|
334
|
+
case "failed":
|
|
335
|
+
totals.failed++;
|
|
336
|
+
break;
|
|
337
|
+
case "cancelled":
|
|
338
|
+
totals.cancelled++;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return totals;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Close the SQS client
|
|
346
|
+
*/
|
|
347
|
+
async close() {
|
|
348
|
+
if (this.client) {
|
|
349
|
+
this.client.destroy();
|
|
350
|
+
this.client = null;
|
|
351
|
+
}
|
|
352
|
+
this.jobStates.clear();
|
|
353
|
+
this.queueUrls.clear();
|
|
354
|
+
this.initialized = false;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* Create an SQS job store instance
|
|
359
|
+
*/
|
|
403
360
|
function createSQSJobStore(config) {
|
|
404
|
-
|
|
361
|
+
return new SQSJobStore(config);
|
|
405
362
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
};
|
|
411
|
-
//# sourceMappingURL=sqs.js.map
|
|
363
|
+
//#endregion
|
|
364
|
+
export { SQSJobStore, SQSJobStore as default, createSQSJobStore };
|
|
365
|
+
|
|
366
|
+
//# sourceMappingURL=sqs.js.map
|