@nicnocquee/dataqueue 1.35.0-beta.20260224112317 → 1.35.0-beta.20260224120854
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/package.json +3 -1
- package/src/backends/redis.test.ts +65 -0
- package/src/index.test.ts +65 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nicnocquee/dataqueue",
|
|
3
|
-
"version": "1.35.0-beta.
|
|
3
|
+
"version": "1.35.0-beta.20260224120854",
|
|
4
4
|
"description": "PostgreSQL or Redis-backed job queue for Node.js applications with support for serverless environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"ci": "npm run build && npm run check-format && npm run check-exports && npm run lint && npm run test",
|
|
24
24
|
"lint": "tsc",
|
|
25
25
|
"test": "vitest run --reporter=verbose",
|
|
26
|
+
"test:coverage": "vitest run --reporter=verbose --coverage",
|
|
26
27
|
"format": "prettier --write .",
|
|
27
28
|
"check-format": "prettier --check .",
|
|
28
29
|
"check-exports": "attw --pack .",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
57
58
|
"@types/node": "^24.0.4",
|
|
58
59
|
"@types/pg": "^8.15.4",
|
|
60
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
59
61
|
"ioredis": "^5.9.3",
|
|
60
62
|
"node-pg-migrate": "^8.0.3",
|
|
61
63
|
"pnpm": "^9.0.0",
|
|
@@ -544,6 +544,71 @@ describe('Redis backend integration', () => {
|
|
|
544
544
|
expect(job?.status).toBe('pending');
|
|
545
545
|
});
|
|
546
546
|
|
|
547
|
+
it('reclaims an in-flight job via supervisor and allows reprocessing', async () => {
|
|
548
|
+
let firstAttempt = true;
|
|
549
|
+
const handler = vi.fn(async () => {
|
|
550
|
+
if (firstAttempt) {
|
|
551
|
+
firstAttempt = false;
|
|
552
|
+
await new Promise<void>(() => {});
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const jobId = await jobQueue.addJob({
|
|
557
|
+
jobType: 'test',
|
|
558
|
+
payload: { foo: 'redis-reclaim-live-loop' },
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const firstProcessor = jobQueue.createProcessor(
|
|
562
|
+
{
|
|
563
|
+
email: vi.fn(async () => {}),
|
|
564
|
+
sms: vi.fn(async () => {}),
|
|
565
|
+
test: handler,
|
|
566
|
+
},
|
|
567
|
+
{ pollInterval: 25, batchSize: 1, concurrency: 1 },
|
|
568
|
+
);
|
|
569
|
+
firstProcessor.startInBackground();
|
|
570
|
+
|
|
571
|
+
let processingJob = await jobQueue.getJob(jobId);
|
|
572
|
+
for (let i = 0; i < 50; i++) {
|
|
573
|
+
if (processingJob?.status === 'processing') {
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
577
|
+
processingJob = await jobQueue.getJob(jobId);
|
|
578
|
+
}
|
|
579
|
+
expect(processingJob?.status).toBe('processing');
|
|
580
|
+
|
|
581
|
+
await firstProcessor.stopAndDrain(25);
|
|
582
|
+
|
|
583
|
+
const supervisor = jobQueue.createSupervisor({
|
|
584
|
+
stuckJobsTimeoutMinutes: 0,
|
|
585
|
+
cleanupJobsDaysToKeep: 0,
|
|
586
|
+
cleanupEventsDaysToKeep: 0,
|
|
587
|
+
expireTimedOutTokens: false,
|
|
588
|
+
});
|
|
589
|
+
const maintenance = await supervisor.start();
|
|
590
|
+
expect(maintenance.reclaimedJobs).toBe(1);
|
|
591
|
+
|
|
592
|
+
const reclaimedJob = await jobQueue.getJob(jobId);
|
|
593
|
+
expect(reclaimedJob?.status).toBe('pending');
|
|
594
|
+
expect(reclaimedJob?.lockedAt).toBeNull();
|
|
595
|
+
expect(reclaimedJob?.lockedBy).toBeNull();
|
|
596
|
+
|
|
597
|
+
const secondProcessor = jobQueue.createProcessor(
|
|
598
|
+
{
|
|
599
|
+
email: vi.fn(async () => {}),
|
|
600
|
+
sms: vi.fn(async () => {}),
|
|
601
|
+
test: handler,
|
|
602
|
+
},
|
|
603
|
+
{ batchSize: 1, concurrency: 1 },
|
|
604
|
+
);
|
|
605
|
+
await secondProcessor.start();
|
|
606
|
+
|
|
607
|
+
const completedJob = await jobQueue.getJob(jobId);
|
|
608
|
+
expect(completedJob?.status).toBe('completed');
|
|
609
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
610
|
+
});
|
|
611
|
+
|
|
547
612
|
it('getPool should throw for Redis backend', () => {
|
|
548
613
|
expect(() => jobQueue.getPool()).toThrow(
|
|
549
614
|
'getPool() is only available with the PostgreSQL backend',
|
package/src/index.test.ts
CHANGED
|
@@ -120,6 +120,71 @@ describe('index integration', () => {
|
|
|
120
120
|
expect(job).toBeNull();
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
it('reclaims an in-flight job via supervisor and allows reprocessing', async () => {
|
|
124
|
+
let firstAttempt = true;
|
|
125
|
+
const handler = vi.fn(async () => {
|
|
126
|
+
if (firstAttempt) {
|
|
127
|
+
firstAttempt = false;
|
|
128
|
+
await new Promise<void>(() => {});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const jobId = await jobQueue.addJob({
|
|
133
|
+
jobType: 'test',
|
|
134
|
+
payload: { foo: 'reclaim-live-loop' },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const firstProcessor = jobQueue.createProcessor(
|
|
138
|
+
{
|
|
139
|
+
email: vi.fn(async () => {}),
|
|
140
|
+
sms: vi.fn(async () => {}),
|
|
141
|
+
test: handler,
|
|
142
|
+
},
|
|
143
|
+
{ pollInterval: 25, batchSize: 1, concurrency: 1 },
|
|
144
|
+
);
|
|
145
|
+
firstProcessor.startInBackground();
|
|
146
|
+
|
|
147
|
+
let processingJob = await jobQueue.getJob(jobId);
|
|
148
|
+
for (let i = 0; i < 50; i++) {
|
|
149
|
+
if (processingJob?.status === 'processing') {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
153
|
+
processingJob = await jobQueue.getJob(jobId);
|
|
154
|
+
}
|
|
155
|
+
expect(processingJob?.status).toBe('processing');
|
|
156
|
+
|
|
157
|
+
await firstProcessor.stopAndDrain(25);
|
|
158
|
+
|
|
159
|
+
const supervisor = jobQueue.createSupervisor({
|
|
160
|
+
stuckJobsTimeoutMinutes: 0,
|
|
161
|
+
cleanupJobsDaysToKeep: 0,
|
|
162
|
+
cleanupEventsDaysToKeep: 0,
|
|
163
|
+
expireTimedOutTokens: false,
|
|
164
|
+
});
|
|
165
|
+
const maintenance = await supervisor.start();
|
|
166
|
+
expect(maintenance.reclaimedJobs).toBe(1);
|
|
167
|
+
|
|
168
|
+
const reclaimedJob = await jobQueue.getJob(jobId);
|
|
169
|
+
expect(reclaimedJob?.status).toBe('pending');
|
|
170
|
+
expect(reclaimedJob?.lockedAt).toBeNull();
|
|
171
|
+
expect(reclaimedJob?.lockedBy).toBeNull();
|
|
172
|
+
|
|
173
|
+
const secondProcessor = jobQueue.createProcessor(
|
|
174
|
+
{
|
|
175
|
+
email: vi.fn(async () => {}),
|
|
176
|
+
sms: vi.fn(async () => {}),
|
|
177
|
+
test: handler,
|
|
178
|
+
},
|
|
179
|
+
{ batchSize: 1, concurrency: 1 },
|
|
180
|
+
);
|
|
181
|
+
await secondProcessor.start();
|
|
182
|
+
|
|
183
|
+
const completedJob = await jobQueue.getJob(jobId);
|
|
184
|
+
expect(completedJob?.status).toBe('completed');
|
|
185
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
186
|
+
});
|
|
187
|
+
|
|
123
188
|
it('getPool should return the underlying pool', () => {
|
|
124
189
|
expect(jobQueue.getPool()).toBeInstanceOf(Pool);
|
|
125
190
|
});
|