@nicnocquee/dataqueue 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +486 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -2
- package/dist/index.d.ts +151 -2
- package/dist/index.js +485 -30
- package/dist/index.js.map +1 -1
- package/migrations/1765809419000_add_force_kill_on_timeout_to_job_queue.sql +6 -0
- package/package.json +1 -1
- package/src/handler-validation.test.ts +414 -0
- package/src/handler-validation.ts +168 -0
- package/src/index.test.ts +224 -0
- package/src/index.ts +33 -0
- package/src/processor.test.ts +55 -0
- package/src/processor.ts +261 -17
- package/src/queue.test.ts +522 -0
- package/src/queue.ts +286 -9
- package/src/types.ts +102 -0
package/src/queue.test.ts
CHANGED
|
@@ -160,6 +160,528 @@ describe('queue integration', () => {
|
|
|
160
160
|
expect(completedJob?.status).toBe('completed');
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it('should edit a pending job with all fields', async () => {
|
|
164
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
165
|
+
jobType: 'email',
|
|
166
|
+
payload: { to: 'original@example.com' },
|
|
167
|
+
priority: 0,
|
|
168
|
+
maxAttempts: 3,
|
|
169
|
+
timeoutMs: 10000,
|
|
170
|
+
tags: ['original'],
|
|
171
|
+
});
|
|
172
|
+
const originalJob = await queue.getJob(pool, jobId);
|
|
173
|
+
const originalUpdatedAt = originalJob?.updatedAt;
|
|
174
|
+
|
|
175
|
+
// Wait a bit to ensure updated_at changes
|
|
176
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
177
|
+
|
|
178
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
179
|
+
payload: { to: 'updated@example.com' },
|
|
180
|
+
priority: 10,
|
|
181
|
+
maxAttempts: 5,
|
|
182
|
+
runAt: new Date(Date.now() + 60000),
|
|
183
|
+
timeoutMs: 20000,
|
|
184
|
+
tags: ['updated', 'priority'],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const updatedJob = await queue.getJob(pool, jobId);
|
|
188
|
+
expect(updatedJob?.payload).toEqual({ to: 'updated@example.com' });
|
|
189
|
+
expect(updatedJob?.priority).toBe(10);
|
|
190
|
+
expect(updatedJob?.maxAttempts).toBe(5);
|
|
191
|
+
expect(updatedJob?.timeoutMs).toBe(20000);
|
|
192
|
+
expect(updatedJob?.tags).toEqual(['updated', 'priority']);
|
|
193
|
+
expect(updatedJob?.status).toBe('pending');
|
|
194
|
+
expect(updatedJob?.updatedAt.getTime()).toBeGreaterThan(
|
|
195
|
+
originalUpdatedAt?.getTime() || 0,
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should edit a pending job with partial fields', async () => {
|
|
200
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
201
|
+
jobType: 'email',
|
|
202
|
+
payload: { to: 'original@example.com' },
|
|
203
|
+
priority: 0,
|
|
204
|
+
maxAttempts: 3,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Only update payload
|
|
208
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
209
|
+
payload: { to: 'updated@example.com' },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const updatedJob = await queue.getJob(pool, jobId);
|
|
213
|
+
expect(updatedJob?.payload).toEqual({ to: 'updated@example.com' });
|
|
214
|
+
expect(updatedJob?.priority).toBe(0); // Unchanged
|
|
215
|
+
expect(updatedJob?.maxAttempts).toBe(3); // Unchanged
|
|
216
|
+
|
|
217
|
+
// Only update priority
|
|
218
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
219
|
+
priority: 5,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const updatedJob2 = await queue.getJob(pool, jobId);
|
|
223
|
+
expect(updatedJob2?.payload).toEqual({ to: 'updated@example.com' }); // Still updated
|
|
224
|
+
expect(updatedJob2?.priority).toBe(5); // Now updated
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should silently fail when editing a non-pending job', async () => {
|
|
228
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
229
|
+
jobType: 'email',
|
|
230
|
+
payload: { to: 'original@example.com' },
|
|
231
|
+
});
|
|
232
|
+
await queue.completeJob(pool, jobId);
|
|
233
|
+
|
|
234
|
+
// Try to edit a completed job - should silently fail
|
|
235
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
236
|
+
payload: { to: 'updated@example.com' },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const job = await queue.getJob(pool, jobId);
|
|
240
|
+
expect(job?.status).toBe('completed');
|
|
241
|
+
expect(job?.payload).toEqual({ to: 'original@example.com' }); // Unchanged
|
|
242
|
+
|
|
243
|
+
// Try to edit a processing job
|
|
244
|
+
const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
245
|
+
pool,
|
|
246
|
+
{
|
|
247
|
+
jobType: 'email',
|
|
248
|
+
payload: { to: 'processing@example.com' },
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
await queue.getNextBatch(pool, 'worker-edit', 1);
|
|
252
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId2, {
|
|
253
|
+
payload: { to: 'updated@example.com' },
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const job2 = await queue.getJob(pool, jobId2);
|
|
257
|
+
expect(job2?.status).toBe('processing');
|
|
258
|
+
expect(job2?.payload).toEqual({ to: 'processing@example.com' }); // Unchanged
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should record edited event when editing a job', async () => {
|
|
262
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
263
|
+
jobType: 'email',
|
|
264
|
+
payload: { to: 'original@example.com' },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
268
|
+
payload: { to: 'updated@example.com' },
|
|
269
|
+
priority: 10,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const res = await pool.query(
|
|
273
|
+
'SELECT * FROM job_events WHERE job_id = $1 ORDER BY created_at ASC',
|
|
274
|
+
[jobId],
|
|
275
|
+
);
|
|
276
|
+
const events = res.rows.map(
|
|
277
|
+
(row) => objectKeysToCamelCase(row) as JobEvent,
|
|
278
|
+
);
|
|
279
|
+
const editEvent = events.find((e) => e.eventType === JobEventType.Edited);
|
|
280
|
+
expect(editEvent).not.toBeUndefined();
|
|
281
|
+
expect(editEvent?.metadata).toMatchObject({
|
|
282
|
+
payload: { to: 'updated@example.com' },
|
|
283
|
+
priority: 10,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should update updated_at timestamp when editing', async () => {
|
|
288
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
289
|
+
jobType: 'email',
|
|
290
|
+
payload: { to: 'original@example.com' },
|
|
291
|
+
});
|
|
292
|
+
const originalJob = await queue.getJob(pool, jobId);
|
|
293
|
+
const originalUpdatedAt = originalJob?.updatedAt;
|
|
294
|
+
|
|
295
|
+
// Wait a bit to ensure timestamp difference
|
|
296
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
297
|
+
|
|
298
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
299
|
+
priority: 5,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const updatedJob = await queue.getJob(pool, jobId);
|
|
303
|
+
expect(updatedJob?.updatedAt.getTime()).toBeGreaterThan(
|
|
304
|
+
originalUpdatedAt?.getTime() || 0,
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should handle editing with null values', async () => {
|
|
309
|
+
const futureDate = new Date(Date.now() + 60000);
|
|
310
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
311
|
+
jobType: 'email',
|
|
312
|
+
payload: { to: 'original@example.com' },
|
|
313
|
+
runAt: futureDate,
|
|
314
|
+
timeoutMs: 10000,
|
|
315
|
+
tags: ['original'],
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {
|
|
319
|
+
runAt: null,
|
|
320
|
+
timeoutMs: null,
|
|
321
|
+
tags: null,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const updatedJob = await queue.getJob(pool, jobId);
|
|
325
|
+
expect(updatedJob?.runAt).not.toBeNull(); // runAt null means use default (now)
|
|
326
|
+
expect(updatedJob?.timeoutMs).toBeNull();
|
|
327
|
+
expect(updatedJob?.tags).toBeNull();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should do nothing when editing with no fields', async () => {
|
|
331
|
+
const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
332
|
+
jobType: 'email',
|
|
333
|
+
payload: { to: 'original@example.com' },
|
|
334
|
+
});
|
|
335
|
+
const originalJob = await queue.getJob(pool, jobId);
|
|
336
|
+
|
|
337
|
+
await queue.editJob<{ email: { to: string } }, 'email'>(pool, jobId, {});
|
|
338
|
+
|
|
339
|
+
const job = await queue.getJob(pool, jobId);
|
|
340
|
+
expect(job?.payload).toEqual(originalJob?.payload);
|
|
341
|
+
expect(job?.priority).toBe(originalJob?.priority);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should edit all pending jobs without filters', async () => {
|
|
345
|
+
// Add three pending jobs
|
|
346
|
+
const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
347
|
+
pool,
|
|
348
|
+
{
|
|
349
|
+
jobType: 'email',
|
|
350
|
+
payload: { to: 'batch1@example.com' },
|
|
351
|
+
priority: 0,
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
355
|
+
pool,
|
|
356
|
+
{
|
|
357
|
+
jobType: 'email',
|
|
358
|
+
payload: { to: 'batch2@example.com' },
|
|
359
|
+
priority: 0,
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
const jobId3 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
363
|
+
pool,
|
|
364
|
+
{
|
|
365
|
+
jobType: 'email',
|
|
366
|
+
payload: { to: 'batch3@example.com' },
|
|
367
|
+
priority: 0,
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
// Add a completed job
|
|
371
|
+
const jobId4 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
372
|
+
pool,
|
|
373
|
+
{
|
|
374
|
+
jobType: 'email',
|
|
375
|
+
payload: { to: 'done@example.com' },
|
|
376
|
+
},
|
|
377
|
+
);
|
|
378
|
+
await queue.completeJob(pool, jobId4);
|
|
379
|
+
|
|
380
|
+
// Edit all pending jobs
|
|
381
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
382
|
+
{ email: { to: string } },
|
|
383
|
+
'email'
|
|
384
|
+
>(pool, undefined, {
|
|
385
|
+
priority: 10,
|
|
386
|
+
});
|
|
387
|
+
expect(editedCount).toBeGreaterThanOrEqual(3);
|
|
388
|
+
|
|
389
|
+
// Check that all pending jobs are updated
|
|
390
|
+
const job1 = await queue.getJob(pool, jobId1);
|
|
391
|
+
const job2 = await queue.getJob(pool, jobId2);
|
|
392
|
+
const job3 = await queue.getJob(pool, jobId3);
|
|
393
|
+
expect(job1?.priority).toBe(10);
|
|
394
|
+
expect(job2?.priority).toBe(10);
|
|
395
|
+
expect(job3?.priority).toBe(10);
|
|
396
|
+
|
|
397
|
+
// Completed job should remain unchanged
|
|
398
|
+
const completedJob = await queue.getJob(pool, jobId4);
|
|
399
|
+
expect(completedJob?.priority).toBe(0);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should edit pending jobs filtered by jobType', async () => {
|
|
403
|
+
const emailJobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
404
|
+
pool,
|
|
405
|
+
{
|
|
406
|
+
jobType: 'email',
|
|
407
|
+
payload: { to: 'email1@example.com' },
|
|
408
|
+
priority: 0,
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
const emailJobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
412
|
+
pool,
|
|
413
|
+
{
|
|
414
|
+
jobType: 'email',
|
|
415
|
+
payload: { to: 'email2@example.com' },
|
|
416
|
+
priority: 0,
|
|
417
|
+
},
|
|
418
|
+
);
|
|
419
|
+
const smsJobId = await queue.addJob<{ sms: { to: string } }, 'sms'>(pool, {
|
|
420
|
+
jobType: 'sms',
|
|
421
|
+
payload: { to: 'sms@example.com' },
|
|
422
|
+
priority: 0,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Edit only email jobs
|
|
426
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
427
|
+
{ email: { to: string }; sms: { to: string } },
|
|
428
|
+
'email'
|
|
429
|
+
>(
|
|
430
|
+
pool,
|
|
431
|
+
{ jobType: 'email' },
|
|
432
|
+
{
|
|
433
|
+
priority: 5,
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
expect(editedCount).toBeGreaterThanOrEqual(2);
|
|
437
|
+
|
|
438
|
+
const emailJob1 = await queue.getJob(pool, emailJobId1);
|
|
439
|
+
const emailJob2 = await queue.getJob(pool, emailJobId2);
|
|
440
|
+
const smsJob = await queue.getJob(pool, smsJobId);
|
|
441
|
+
expect(emailJob1?.priority).toBe(5);
|
|
442
|
+
expect(emailJob2?.priority).toBe(5);
|
|
443
|
+
expect(smsJob?.priority).toBe(0);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should edit pending jobs filtered by priority', async () => {
|
|
447
|
+
const lowPriorityJobId1 = await queue.addJob<
|
|
448
|
+
{ email: { to: string } },
|
|
449
|
+
'email'
|
|
450
|
+
>(pool, {
|
|
451
|
+
jobType: 'email',
|
|
452
|
+
payload: { to: 'low1@example.com' },
|
|
453
|
+
priority: 1,
|
|
454
|
+
});
|
|
455
|
+
const lowPriorityJobId2 = await queue.addJob<
|
|
456
|
+
{ email: { to: string } },
|
|
457
|
+
'email'
|
|
458
|
+
>(pool, {
|
|
459
|
+
jobType: 'email',
|
|
460
|
+
payload: { to: 'low2@example.com' },
|
|
461
|
+
priority: 1,
|
|
462
|
+
});
|
|
463
|
+
const highPriorityJobId = await queue.addJob<
|
|
464
|
+
{ email: { to: string } },
|
|
465
|
+
'email'
|
|
466
|
+
>(pool, {
|
|
467
|
+
jobType: 'email',
|
|
468
|
+
payload: { to: 'high@example.com' },
|
|
469
|
+
priority: 10,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Edit only low priority jobs
|
|
473
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
474
|
+
{ email: { to: string } },
|
|
475
|
+
'email'
|
|
476
|
+
>(
|
|
477
|
+
pool,
|
|
478
|
+
{ priority: 1 },
|
|
479
|
+
{
|
|
480
|
+
priority: 5,
|
|
481
|
+
},
|
|
482
|
+
);
|
|
483
|
+
expect(editedCount).toBeGreaterThanOrEqual(2);
|
|
484
|
+
|
|
485
|
+
const lowJob1 = await queue.getJob(pool, lowPriorityJobId1);
|
|
486
|
+
const lowJob2 = await queue.getJob(pool, lowPriorityJobId2);
|
|
487
|
+
const highJob = await queue.getJob(pool, highPriorityJobId);
|
|
488
|
+
expect(lowJob1?.priority).toBe(5);
|
|
489
|
+
expect(lowJob2?.priority).toBe(5);
|
|
490
|
+
expect(highJob?.priority).toBe(10);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should edit pending jobs filtered by tags', async () => {
|
|
494
|
+
const taggedJobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
495
|
+
pool,
|
|
496
|
+
{
|
|
497
|
+
jobType: 'email',
|
|
498
|
+
payload: { to: 'tagged1@example.com' },
|
|
499
|
+
tags: ['urgent', 'priority'],
|
|
500
|
+
},
|
|
501
|
+
);
|
|
502
|
+
const taggedJobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
503
|
+
pool,
|
|
504
|
+
{
|
|
505
|
+
jobType: 'email',
|
|
506
|
+
payload: { to: 'tagged2@example.com' },
|
|
507
|
+
tags: ['urgent', 'priority'],
|
|
508
|
+
},
|
|
509
|
+
);
|
|
510
|
+
const untaggedJobId = await queue.addJob<
|
|
511
|
+
{ email: { to: string } },
|
|
512
|
+
'email'
|
|
513
|
+
>(pool, {
|
|
514
|
+
jobType: 'email',
|
|
515
|
+
payload: { to: 'untagged@example.com' },
|
|
516
|
+
tags: ['other'],
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Edit only jobs with 'urgent' tag
|
|
520
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
521
|
+
{ email: { to: string } },
|
|
522
|
+
'email'
|
|
523
|
+
>(
|
|
524
|
+
pool,
|
|
525
|
+
{ tags: { values: ['urgent'], mode: 'any' } },
|
|
526
|
+
{
|
|
527
|
+
priority: 10,
|
|
528
|
+
},
|
|
529
|
+
);
|
|
530
|
+
expect(editedCount).toBeGreaterThanOrEqual(2);
|
|
531
|
+
|
|
532
|
+
const taggedJob1 = await queue.getJob(pool, taggedJobId1);
|
|
533
|
+
const taggedJob2 = await queue.getJob(pool, taggedJobId2);
|
|
534
|
+
const untaggedJob = await queue.getJob(pool, untaggedJobId);
|
|
535
|
+
expect(taggedJob1?.priority).toBe(10);
|
|
536
|
+
expect(taggedJob2?.priority).toBe(10);
|
|
537
|
+
expect(untaggedJob?.priority).toBe(0);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should edit pending jobs filtered by runAt', async () => {
|
|
541
|
+
const futureDate = new Date(Date.now() + 60000);
|
|
542
|
+
const pastDate = new Date(Date.now() - 60000);
|
|
543
|
+
|
|
544
|
+
const futureJobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
545
|
+
pool,
|
|
546
|
+
{
|
|
547
|
+
jobType: 'email',
|
|
548
|
+
payload: { to: 'future1@example.com' },
|
|
549
|
+
runAt: futureDate,
|
|
550
|
+
},
|
|
551
|
+
);
|
|
552
|
+
const futureJobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
553
|
+
pool,
|
|
554
|
+
{
|
|
555
|
+
jobType: 'email',
|
|
556
|
+
payload: { to: 'future2@example.com' },
|
|
557
|
+
runAt: futureDate,
|
|
558
|
+
},
|
|
559
|
+
);
|
|
560
|
+
const pastJobId = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
561
|
+
pool,
|
|
562
|
+
{
|
|
563
|
+
jobType: 'email',
|
|
564
|
+
payload: { to: 'past@example.com' },
|
|
565
|
+
runAt: pastDate,
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
// Edit only jobs scheduled in the future
|
|
570
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
571
|
+
{ email: { to: string } },
|
|
572
|
+
'email'
|
|
573
|
+
>(
|
|
574
|
+
pool,
|
|
575
|
+
{ runAt: { gte: new Date() } },
|
|
576
|
+
{
|
|
577
|
+
priority: 10,
|
|
578
|
+
},
|
|
579
|
+
);
|
|
580
|
+
expect(editedCount).toBeGreaterThanOrEqual(2);
|
|
581
|
+
|
|
582
|
+
const futureJob1 = await queue.getJob(pool, futureJobId1);
|
|
583
|
+
const futureJob2 = await queue.getJob(pool, futureJobId2);
|
|
584
|
+
const pastJob = await queue.getJob(pool, pastJobId);
|
|
585
|
+
expect(futureJob1?.priority).toBe(10);
|
|
586
|
+
expect(futureJob2?.priority).toBe(10);
|
|
587
|
+
expect(pastJob?.priority).toBe(0);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should not edit non-pending jobs', async () => {
|
|
591
|
+
// Create processingJobId first so it gets picked up by getNextBatch
|
|
592
|
+
const processingJobId = await queue.addJob<
|
|
593
|
+
{ email: { to: string } },
|
|
594
|
+
'email'
|
|
595
|
+
>(pool, {
|
|
596
|
+
jobType: 'email',
|
|
597
|
+
payload: { to: 'processing@example.com' },
|
|
598
|
+
priority: 0,
|
|
599
|
+
});
|
|
600
|
+
const pendingJobId = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
601
|
+
pool,
|
|
602
|
+
{
|
|
603
|
+
jobType: 'email',
|
|
604
|
+
payload: { to: 'pending@example.com' },
|
|
605
|
+
priority: 0,
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
// Mark as processing (this will pick up processingJobId since it was created first)
|
|
609
|
+
await queue.getNextBatch(pool, 'worker-batch', 1);
|
|
610
|
+
const completedJobId = await queue.addJob<
|
|
611
|
+
{ email: { to: string } },
|
|
612
|
+
'email'
|
|
613
|
+
>(pool, {
|
|
614
|
+
jobType: 'email',
|
|
615
|
+
payload: { to: 'completed@example.com' },
|
|
616
|
+
priority: 0,
|
|
617
|
+
});
|
|
618
|
+
await queue.completeJob(pool, completedJobId);
|
|
619
|
+
|
|
620
|
+
// Edit all pending jobs
|
|
621
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
622
|
+
{ email: { to: string } },
|
|
623
|
+
'email'
|
|
624
|
+
>(pool, undefined, {
|
|
625
|
+
priority: 10,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const pendingJob = await queue.getJob(pool, pendingJobId);
|
|
629
|
+
const processingJob = await queue.getJob(pool, processingJobId);
|
|
630
|
+
const completedJob = await queue.getJob(pool, completedJobId);
|
|
631
|
+
expect(pendingJob?.priority).toBe(10);
|
|
632
|
+
expect(processingJob?.priority).toBe(0);
|
|
633
|
+
expect(completedJob?.priority).toBe(0);
|
|
634
|
+
expect(editedCount).toBeGreaterThanOrEqual(1);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('should record edit events for each edited job', async () => {
|
|
638
|
+
const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
639
|
+
pool,
|
|
640
|
+
{
|
|
641
|
+
jobType: 'email',
|
|
642
|
+
payload: { to: 'event1@example.com' },
|
|
643
|
+
},
|
|
644
|
+
);
|
|
645
|
+
const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
|
|
646
|
+
pool,
|
|
647
|
+
{
|
|
648
|
+
jobType: 'email',
|
|
649
|
+
payload: { to: 'event2@example.com' },
|
|
650
|
+
},
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
await queue.editAllPendingJobs<{ email: { to: string } }, 'email'>(
|
|
654
|
+
pool,
|
|
655
|
+
undefined,
|
|
656
|
+
{
|
|
657
|
+
priority: 10,
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const events1 = await queue.getJobEvents(pool, jobId1);
|
|
662
|
+
const events2 = await queue.getJobEvents(pool, jobId2);
|
|
663
|
+
const editEvent1 = events1.find((e) => e.eventType === JobEventType.Edited);
|
|
664
|
+
const editEvent2 = events2.find((e) => e.eventType === JobEventType.Edited);
|
|
665
|
+
expect(editEvent1).not.toBeUndefined();
|
|
666
|
+
expect(editEvent2).not.toBeUndefined();
|
|
667
|
+
expect(editEvent1?.metadata).toMatchObject({ priority: 10 });
|
|
668
|
+
expect(editEvent2?.metadata).toMatchObject({ priority: 10 });
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('should return 0 when no fields to update', async () => {
|
|
672
|
+
await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
|
|
673
|
+
jobType: 'email',
|
|
674
|
+
payload: { to: 'empty@example.com' },
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const editedCount = await queue.editAllPendingJobs<
|
|
678
|
+
{ email: { to: string } },
|
|
679
|
+
'email'
|
|
680
|
+
>(pool, undefined, {});
|
|
681
|
+
|
|
682
|
+
expect(editedCount).toBe(0);
|
|
683
|
+
});
|
|
684
|
+
|
|
163
685
|
it('should cancel all upcoming jobs', async () => {
|
|
164
686
|
// Add three pending jobs
|
|
165
687
|
const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
|