tina4-nodejs 3.10.76 → 3.10.84

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.
@@ -28,12 +28,15 @@
28
28
  * const queue = new Queue();
29
29
  * queue.push("emails", { to: "alice@test.com" });
30
30
  */
31
- import { mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, existsSync } from "node:fs";
32
- import { join } from "node:path";
33
- import { randomUUID } from "node:crypto";
34
31
  import { RabbitMQBackend } from "./queueBackends/rabbitmqBackend.js";
35
32
  import { KafkaBackend } from "./queueBackends/kafkaBackend.js";
36
33
  import { MongoBackend } from "./queueBackends/mongoBackend.js";
34
+ import { LiteBackend } from "./queueBackends/liteBackend.js";
35
+ import { type QueueJob, type JobData, createJob } from "./job.js";
36
+
37
+ export { LiteBackend } from "./queueBackends/liteBackend.js";
38
+
39
+ export { type QueueJob } from "./job.js";
37
40
 
38
41
  // ── Types ────────────────────────────────────────────────────
39
42
 
@@ -44,50 +47,6 @@ export interface QueueConfig {
44
47
  maxRetries?: number;
45
48
  }
46
49
 
47
- export interface QueueJob {
48
- id: string;
49
- payload: unknown;
50
- status: "pending" | "reserved" | "failed" | "dead" | "completed";
51
- createdAt: string;
52
- attempts: number;
53
- delayUntil: string | null;
54
- priority: number;
55
- topic: string;
56
- error?: string;
57
- /** Mark this job as completed. */
58
- complete(): void;
59
- /** Mark this job as failed with a reason. */
60
- fail(reason?: string): void;
61
- /** Reject this job with a reason. Alias for fail(). */
62
- reject(reason?: string): void;
63
- /** Re-queue this job with incremented attempts and optional delay. */
64
- retry(delaySeconds?: number): void;
65
- }
66
-
67
- /** Create a QueueJob with lifecycle methods bound to a Queue instance. */
68
- function createJob(data: Omit<QueueJob, "complete" | "fail" | "reject" | "retry">, queue: Queue, topic: string): QueueJob {
69
- const job: QueueJob = {
70
- ...data,
71
- topic,
72
- complete() {
73
- job.status = "completed";
74
- },
75
- fail(reason = "") {
76
- job.status = "failed";
77
- job.error = reason;
78
- job.attempts = (job.attempts || 0) + 1;
79
- queue._failJob(topic, job, reason, queue.getMaxRetries());
80
- },
81
- reject(reason = "") {
82
- job.fail(reason);
83
- },
84
- retry(delaySeconds?: number) {
85
- queue._retryJob(topic, job, delaySeconds);
86
- },
87
- };
88
- return job;
89
- }
90
-
91
50
  export interface ProcessOptions {
92
51
  pollInterval?: number;
93
52
  maxJobs?: number;
@@ -98,7 +57,7 @@ export interface QueueBackendInterface {
98
57
  push(queue: string, payload: unknown, delay?: number): string;
99
58
  pop(queue: string): QueueJob | null;
100
59
  size(queue: string): number;
101
- clear(queue: string): void;
60
+ clear(queue: string): number;
102
61
  }
103
62
 
104
63
  // ── Queue ────────────────────────────────────────────────────
@@ -108,8 +67,8 @@ export class Queue {
108
67
  private basePath: string;
109
68
  private topic: string;
110
69
  private _maxRetries: number;
111
- private seq: number = 0;
112
70
  private externalBackend: QueueBackendInterface | null = null;
71
+ private liteBackend!: LiteBackend;
113
72
 
114
73
  /**
115
74
  * Unified Queue constructor.
@@ -137,6 +96,7 @@ export class Queue {
137
96
  ?? "data/queue";
138
97
  this.topic = resolvedConfig.topic ?? "default";
139
98
  this._maxRetries = resolvedConfig.maxRetries ?? 3;
99
+ this.liteBackend = new LiteBackend(this.basePath);
140
100
 
141
101
  // Initialize external backends
142
102
  if (this.backendName === "rabbitmq") {
@@ -148,160 +108,53 @@ export class Queue {
148
108
  }
149
109
  }
150
110
 
151
- // ── Directory helpers ────────────────────────────────────────
152
-
153
- private ensureDir(queue: string): string {
154
- const dir = join(this.basePath, queue);
155
- mkdirSync(dir, { recursive: true });
156
- return dir;
157
- }
158
-
159
- private ensureFailedDir(queue: string): string {
160
- const dir = join(this.basePath, queue, "failed");
161
- mkdirSync(dir, { recursive: true });
162
- return dir;
163
- }
164
-
165
111
  // ── Unified API (topic-aware) ────────────────────────────────
166
112
 
167
113
  /**
168
114
  * Add a job to the queue. Returns job ID.
169
115
  *
170
116
  * Can be called as:
171
- * queue.push(payload) — uses constructor topic
172
- * queue.push(payload, delay) — uses constructor topic with delay
173
- * queue.push(payload, delay, priority) uses constructor topic with delay and priority
174
- * queue.push("queueName", payload) — legacy: explicit queue name
175
- * queue.push("queueName", payload, delay) — legacy with delay
117
+ * queue.push(payload) — uses constructor topic
118
+ * queue.push(payload, delay) — uses constructor topic with delay
119
+ * queue.push(payload, delay, priority) — with delay and priority
176
120
  *
177
121
  * @param priority — Higher value = higher priority. Default 0.
178
122
  */
179
- push(queueOrPayload: string | unknown, payloadOrDelay?: unknown, delay?: number, priority?: number): string {
180
- let queue: string;
181
- let payload: unknown;
182
- let actualDelay: number | undefined;
183
- let actualPriority: number;
184
-
185
- if (typeof queueOrPayload === "string" && payloadOrDelay !== undefined && typeof payloadOrDelay !== "number") {
186
- // Legacy: push("queueName", payload, delay?, priority?)
187
- queue = queueOrPayload;
188
- payload = payloadOrDelay;
189
- actualDelay = delay;
190
- actualPriority = priority ?? 0;
191
- } else {
192
- // Unified: push(payload) or push(payload, delay) or push(payload, delay, priority)
193
- queue = this.topic;
194
- payload = queueOrPayload;
195
- actualDelay = typeof payloadOrDelay === "number" ? payloadOrDelay : delay;
196
- actualPriority = typeof payloadOrDelay === "number" ? (delay ?? 0) : (priority ?? 0);
197
- }
198
-
123
+ push(payload: unknown, delay?: number, priority: number = 0): string {
199
124
  if (this.externalBackend) {
200
- return this.externalBackend.push(queue, payload, actualDelay);
125
+ return this.externalBackend.push(this.topic, payload, delay);
201
126
  }
202
- const dir = this.ensureDir(queue);
203
- const id = randomUUID();
204
- const now = new Date().toISOString();
205
- this.seq++;
206
-
207
- const job = {
208
- id,
209
- payload,
210
- status: "pending" as const,
211
- createdAt: now,
212
- attempts: 0,
213
- delayUntil: actualDelay ? new Date(Date.now() + actualDelay * 1000).toISOString() : null,
214
- priority: actualPriority,
215
- };
216
-
217
- const prefix = `${Date.now()}-${String(this.seq).padStart(6, "0")}`;
218
- writeFileSync(join(dir, `${prefix}_${id}.queue-data`), JSON.stringify(job, null, 2));
219
- return id;
127
+ return this.liteBackend.push(this.topic, payload, delay, priority);
220
128
  }
221
129
 
222
130
  /**
223
- * Atomically claim the next available job. Returns null if empty.
224
- *
225
- * Can be called as:
226
- * queue.pop() — uses constructor topic
227
- * queue.pop("queueName") — legacy: explicit queue name
131
+ * Atomically claim the next available job from this queue's topic. Returns null if empty.
228
132
  */
229
- pop(queue?: string): QueueJob | null {
230
- const q = queue ?? this.topic;
133
+ pop(): QueueJob | null {
134
+ const q = this.topic;
231
135
 
232
136
  if (this.externalBackend) {
233
137
  return this.externalBackend.pop(q);
234
138
  }
235
- const dir = this.ensureDir(q);
236
-
237
- let files: string[];
238
- try {
239
- files = readdirSync(dir).filter(f => f.endsWith(".queue-data")).sort();
240
- } catch {
241
- return null;
242
- }
243
-
244
- const now = new Date().toISOString();
245
-
246
- for (const file of files) {
247
- const filePath = join(dir, file);
248
- let job: QueueJob;
249
- try {
250
- job = JSON.parse(readFileSync(filePath, "utf-8"));
251
- } catch {
252
- continue;
253
- }
254
-
255
- if (job.status !== "pending") continue;
256
- if (job.delayUntil && job.delayUntil > now) continue;
257
-
258
- job.status = "reserved";
259
- job.topic = q;
260
- job.priority = job.priority ?? 0;
261
- writeFileSync(filePath, JSON.stringify(job, null, 2));
262
-
263
- try {
264
- unlinkSync(filePath);
265
- } catch {
266
- // Already consumed by another worker
267
- }
268
-
269
- return createJob(job as any, this, q);
270
- }
271
-
272
- return null;
139
+ return this.liteBackend.pop(q, this);
273
140
  }
274
141
 
275
142
  /**
276
143
  * Process jobs from a queue with a handler function.
277
144
  */
278
145
  process(
279
- handlerOrQueue: string | ((job: QueueJob) => Promise<void> | void),
280
- handlerOrOptions?: ((job: QueueJob) => Promise<void> | void) | ProcessOptions,
146
+ handler: (job: QueueJob) => Promise<void> | void,
281
147
  options?: ProcessOptions,
282
148
  ): void {
283
- let queue: string;
284
- let handler: (job: QueueJob) => Promise<void> | void;
285
- let opts: ProcessOptions | undefined;
286
-
287
- if (typeof handlerOrQueue === "string") {
288
- // Legacy: process("queueName", handler, options)
289
- queue = handlerOrQueue;
290
- handler = handlerOrOptions as (job: QueueJob) => Promise<void> | void;
291
- opts = options;
292
- } else {
293
- // Unified: process(handler, options?)
294
- queue = this.topic;
295
- handler = handlerOrQueue;
296
- opts = handlerOrOptions as ProcessOptions | undefined;
297
- }
149
+ const queue = this.topic;
150
+ const opts = options;
298
151
 
299
152
  const maxJobs = opts?.maxJobs ?? Infinity;
300
153
  const maxRetries = opts?.maxRetries ?? this._maxRetries;
301
154
  let processed = 0;
302
155
 
303
156
  while (processed < maxJobs) {
304
- const job = this.pop(queue);
157
+ const job = this.pop();
305
158
  if (!job) break;
306
159
  try {
307
160
  const result = handler(job);
@@ -320,294 +173,84 @@ export class Queue {
320
173
  }
321
174
 
322
175
  /**
323
- * Count jobs in a queue filtered by status.
324
- *
325
- * @param queue — Queue/topic name (defaults to constructor topic)
326
- * @param status — Job status to count: "pending" (default) or "failed"
176
+ * Count jobs filtered by status. Defaults to "pending".
327
177
  */
328
- size(queue?: string, status: string = "pending"): number {
329
- const q = queue ?? this.topic;
178
+ size(status: string = "pending"): number {
179
+ const q = this.topic;
330
180
 
331
181
  if (this.externalBackend) {
332
182
  return this.externalBackend.size(q);
333
183
  }
334
-
335
- if (status === "failed") {
336
- const failedDir = this.ensureFailedDir(q);
337
- let files: string[];
338
- try {
339
- files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data"));
340
- } catch {
341
- return 0;
342
- }
343
- return files.length;
344
- }
345
-
346
- const dir = this.ensureDir(q);
347
- let files: string[];
348
- try {
349
- files = readdirSync(dir).filter(f => f.endsWith(".queue-data"));
350
- } catch {
351
- return 0;
352
- }
353
-
354
- let count = 0;
355
- for (const file of files) {
356
- try {
357
- const job = JSON.parse(readFileSync(join(dir, file), "utf-8"));
358
- if (job.status === status) count++;
359
- } catch {
360
- // skip corrupt files
361
- }
362
- }
363
- return count;
184
+ return this.liteBackend.size(q, status);
364
185
  }
365
186
 
366
187
  /**
367
- * Remove all jobs from a queue.
188
+ * Remove all jobs from this queue's topic. Returns the number cleared.
368
189
  */
369
- clear(queue?: string): void {
370
- const q = queue ?? this.topic;
190
+ clear(): number {
191
+ const q = this.topic;
371
192
 
372
193
  if (this.externalBackend) {
373
194
  this.externalBackend.clear(q);
374
- return;
375
- }
376
- const dir = this.ensureDir(q);
377
- try {
378
- const files = readdirSync(dir).filter(f => f.endsWith(".queue-data"));
379
- for (const file of files) {
380
- unlinkSync(join(dir, file));
381
- }
382
- } catch {
383
- // directory might not exist
384
- }
385
-
386
- // Also clear failed jobs
387
- const failedDir = join(dir, "failed");
388
- try {
389
- if (existsSync(failedDir)) {
390
- const files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data"));
391
- for (const file of files) {
392
- unlinkSync(join(failedDir, file));
393
- }
394
- }
395
- } catch {
396
- // ignore
195
+ return 0;
397
196
  }
197
+ return this.liteBackend.clear(q);
398
198
  }
399
199
 
400
200
  /**
401
- * Get all failed jobs for a queue.
201
+ * Get all failed jobs for this queue's topic.
402
202
  */
403
- failed(queue?: string): QueueJob[] {
404
- const q = queue ?? this.topic;
405
- const failedDir = this.ensureFailedDir(q);
406
- const results: QueueJob[] = [];
407
-
408
- try {
409
- const files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data")).sort();
410
- for (const file of files) {
411
- try {
412
- const job: QueueJob = JSON.parse(readFileSync(join(failedDir, file), "utf-8"));
413
- results.push(job);
414
- } catch {
415
- // skip corrupt files
416
- }
417
- }
418
- } catch {
419
- // directory might not exist
420
- }
421
-
422
- return results;
203
+ failed(): QueueJob[] {
204
+ return this.liteBackend.failed(this.topic);
423
205
  }
424
206
 
425
207
  /**
426
- * Retry a failed job by moving it back to the queue.
208
+ * Retry all dead letter jobs for this queue's topic.
209
+ * Moves failed jobs that exceeded max retries back to pending.
210
+ *
211
+ * @param delaySeconds - Optional delay before jobs become available
212
+ * @returns true if at least one job was re-queued, false if none found
427
213
  */
428
- retry(jobId: string): boolean {
429
- try {
430
- const queues = readdirSync(this.basePath);
431
- for (const queue of queues) {
432
- const failedDir = join(this.basePath, queue, "failed");
433
- const filePath = join(failedDir, `${jobId}.queue-data`);
434
-
435
- if (existsSync(filePath)) {
436
- const job: QueueJob = JSON.parse(readFileSync(filePath, "utf-8"));
437
- job.status = "pending";
438
- job.attempts = (job.attempts || 0) + 1;
439
- job.error = undefined;
440
-
441
- this.seq++;
442
- const prefix = `${Date.now()}-${String(this.seq).padStart(6, "0")}`;
443
- const queueDir = join(this.basePath, queue);
444
- writeFileSync(join(queueDir, `${prefix}_${jobId}.queue-data`), JSON.stringify(job, null, 2));
445
- unlinkSync(filePath);
446
- return true;
447
- }
448
- }
449
- } catch {
450
- // ignore
214
+ retry(delaySeconds?: number): boolean {
215
+ const deadJobs = this.deadLetters();
216
+ if (deadJobs.length === 0) return false;
217
+ let retried = false;
218
+ for (const job of deadJobs) {
219
+ const ok = this.liteBackend.retry(this.topic, job.id, delaySeconds);
220
+ if (ok) retried = true;
451
221
  }
452
-
453
- return false;
222
+ return retried;
454
223
  }
455
224
 
456
225
  /**
457
226
  * Get dead letter jobs — failed jobs that exceeded max retries.
458
227
  */
459
- deadLetters(queue?: string, maxRetries?: number): QueueJob[] {
460
- const q = queue ?? this.topic;
461
- const mr = maxRetries ?? this._maxRetries;
462
- const failedDir = this.ensureFailedDir(q);
463
- const results: QueueJob[] = [];
464
-
465
- try {
466
- const files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data")).sort();
467
- for (const file of files) {
468
- try {
469
- const job: QueueJob = JSON.parse(readFileSync(join(failedDir, file), "utf-8"));
470
- if ((job.attempts || 0) >= mr) {
471
- job.status = "dead";
472
- results.push(job);
473
- }
474
- } catch {
475
- // skip corrupt files
476
- }
477
- }
478
- } catch {
479
- // directory might not exist
480
- }
481
-
482
- return results;
228
+ deadLetters(maxRetries?: number): QueueJob[] {
229
+ return this.liteBackend.deadLetters(this.topic, maxRetries ?? this._maxRetries);
483
230
  }
484
231
 
485
232
  /**
486
- * Delete messages by status.
233
+ * Delete messages by status (e.g. "completed", "failed", "dead").
487
234
  */
488
- purge(statusOrQueue: string, statusOrMaxRetries?: string | number, maxRetries?: number): number {
489
- let queue: string;
490
- let status: string;
491
- let mr: number;
492
-
493
- if (typeof statusOrMaxRetries === "string") {
494
- // Legacy: purge("queueName", "status", maxRetries?)
495
- queue = statusOrQueue;
496
- status = statusOrMaxRetries;
497
- mr = maxRetries ?? this._maxRetries;
498
- } else {
499
- // Unified: purge("status") or purge("status", maxRetries)
500
- queue = this.topic;
501
- status = statusOrQueue;
502
- mr = typeof statusOrMaxRetries === "number" ? statusOrMaxRetries : (maxRetries ?? this._maxRetries);
503
- }
504
-
505
- let count = 0;
506
-
507
- if (status === "dead") {
508
- const failedDir = this.ensureFailedDir(queue);
509
- try {
510
- const files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data"));
511
- for (const file of files) {
512
- try {
513
- const job: QueueJob = JSON.parse(readFileSync(join(failedDir, file), "utf-8"));
514
- if ((job.attempts || 0) >= mr) {
515
- unlinkSync(join(failedDir, file));
516
- count++;
517
- }
518
- } catch {
519
- // skip corrupt files
520
- }
521
- }
522
- } catch {
523
- // directory might not exist
524
- }
525
- } else if (status === "failed") {
526
- const failedDir = this.ensureFailedDir(queue);
527
- try {
528
- const files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data"));
529
- for (const file of files) {
530
- try {
531
- const job: QueueJob = JSON.parse(readFileSync(join(failedDir, file), "utf-8"));
532
- if ((job.attempts || 0) < mr) {
533
- unlinkSync(join(failedDir, file));
534
- count++;
535
- }
536
- } catch {
537
- // skip corrupt files
538
- }
539
- }
540
- } catch {
541
- // directory might not exist
542
- }
543
- } else {
544
- const dir = this.ensureDir(queue);
545
- try {
546
- const files = readdirSync(dir).filter(f => f.endsWith(".queue-data"));
547
- for (const file of files) {
548
- try {
549
- const job: QueueJob = JSON.parse(readFileSync(join(dir, file), "utf-8"));
550
- if (job.status === status) {
551
- unlinkSync(join(dir, file));
552
- count++;
553
- }
554
- } catch {
555
- // skip corrupt files
556
- }
557
- }
558
- } catch {
559
- // directory might not exist
560
- }
561
- }
562
-
563
- return count;
235
+ purge(status: string, maxRetries?: number): number {
236
+ return this.liteBackend.purge(this.topic, status, maxRetries ?? this._maxRetries);
564
237
  }
565
238
 
566
239
  /**
567
240
  * Re-queue failed jobs that haven't exceeded max retries back to pending.
568
241
  */
569
- retryFailed(queue?: string, maxRetries?: number): number {
570
- const q = queue ?? this.topic;
571
- const mr = maxRetries ?? this._maxRetries;
572
- const failedDir = this.ensureFailedDir(q);
573
- const queueDir = this.ensureDir(q);
574
- let count = 0;
575
-
576
- try {
577
- const files = readdirSync(failedDir).filter(f => f.endsWith(".queue-data"));
578
- for (const file of files) {
579
- try {
580
- const filePath = join(failedDir, file);
581
- const job: QueueJob = JSON.parse(readFileSync(filePath, "utf-8"));
582
-
583
- if ((job.attempts || 0) >= mr) {
584
- continue;
585
- }
586
-
587
- job.status = "pending";
588
- job.error = undefined;
589
-
590
- this.seq++;
591
- const prefix = `${Date.now()}-${String(this.seq).padStart(6, "0")}`;
592
- writeFileSync(join(queueDir, `${prefix}_${job.id}.queue-data`), JSON.stringify(job, null, 2));
593
- unlinkSync(filePath);
594
- count++;
595
- } catch {
596
- // skip corrupt files
597
- }
598
- }
599
- } catch {
600
- // directory might not exist
601
- }
602
-
603
- return count;
242
+ retryFailed(maxRetries?: number): number {
243
+ return this.liteBackend.retryFailed(this.topic, maxRetries ?? this._maxRetries);
604
244
  }
605
245
 
606
246
  /**
607
247
  * Produce a message onto a topic. Convenience wrapper around push().
608
248
  */
609
- produce(topic: string, payload: unknown, delay?: number): string {
610
- return this.push(topic, payload, delay);
249
+ produce(topic: string, payload: unknown, delay?: number, priority: number = 0): string {
250
+ if (this.externalBackend) {
251
+ return this.externalBackend.push(topic, payload, delay);
252
+ }
253
+ return this.liteBackend.push(topic, payload, delay, priority);
611
254
  }
612
255
 
613
256
  /**
@@ -636,17 +279,19 @@ export class Queue {
636
279
  * for await (const job of queue.consume("emails")) { ... }
637
280
  * for await (const job of queue.consume("emails", undefined, 5000)) { ... }
638
281
  */
639
- async *consume(topic?: string, id?: string, pollInterval: number = 1000): AsyncGenerator<QueueJob> {
282
+ async *consume(topic?: string, id?: string, pollInterval: number = 1000, iterations: number = 0): AsyncGenerator<QueueJob> {
640
283
  const q = topic ?? this.topic;
641
284
 
642
285
  if (id !== undefined) {
643
- const raw = this.popById(q, id);
644
- if (raw) yield createJob(raw as any, this, q);
286
+ const raw = this.popById(id);
287
+ if (raw) yield createJob(raw as any, this);
645
288
  return;
646
289
  }
647
290
 
648
291
  // pollInterval=0 → single-pass drain (returns when empty)
649
292
  // pollInterval>0 → long-running poll (sleeps when empty, never returns)
293
+ // iterations>0 → stop after consuming N jobs
294
+ let consumed = 0;
650
295
  while (true) {
651
296
  const raw = this.pop(q) as any;
652
297
  if (raw === null) {
@@ -654,41 +299,17 @@ export class Queue {
654
299
  await new Promise(resolve => setTimeout(resolve, pollInterval));
655
300
  continue;
656
301
  }
657
- yield createJob(raw, this, q);
302
+ yield createJob(raw, this);
303
+ consumed++;
304
+ if (iterations > 0 && consumed >= iterations) break;
658
305
  }
659
306
  }
660
307
 
661
308
  /**
662
- * Pop a specific job by ID from the queue.
309
+ * Pop a specific job by ID from this queue's topic.
663
310
  */
664
- popById(queue: string, id: string): QueueJob | null {
665
- const q = queue ?? this.topic;
666
- const dir = this.ensureDir(q);
667
-
668
- let files: string[];
669
- try {
670
- files = readdirSync(dir).filter(f => f.endsWith(".queue-data"));
671
- } catch {
672
- return null;
673
- }
674
-
675
- for (const file of files) {
676
- const filePath = join(dir, file);
677
- let job: QueueJob;
678
- try {
679
- job = JSON.parse(readFileSync(filePath, "utf-8"));
680
- } catch {
681
- continue;
682
- }
683
-
684
- if (job.status !== "pending") continue;
685
- if (job.id === id) {
686
- try { unlinkSync(filePath); } catch { /* already consumed */ }
687
- return job;
688
- }
689
- }
690
-
691
- return null;
311
+ popById(id: string): QueueJob | null {
312
+ return this.liteBackend.popById(this.topic, id);
692
313
  }
693
314
 
694
315
  /**
@@ -706,26 +327,13 @@ export class Queue {
706
327
  * Move a job to the failed directory.
707
328
  */
708
329
  _failJob(queue: string, job: QueueJob, error: string, maxRetries: number): void {
709
- const failedDir = this.ensureFailedDir(queue);
710
- job.status = "failed";
711
- job.attempts = (job.attempts || 0) + 1;
712
- job.error = error;
713
-
714
- writeFileSync(join(failedDir, `${job.id}.queue-data`), JSON.stringify(job, null, 2));
330
+ this.liteBackend.failJob(queue, job, error, maxRetries);
715
331
  }
716
332
 
717
333
  /**
718
334
  * Re-queue a job back to the main queue directory with incremented attempts.
719
335
  */
720
336
  _retryJob(queue: string, job: QueueJob, delaySeconds?: number): void {
721
- const dir = this.ensureDir(queue);
722
- job.status = "pending";
723
- job.attempts = (job.attempts || 0) + 1;
724
- job.error = undefined;
725
- job.delayUntil = delaySeconds ? new Date(Date.now() + delaySeconds * 1000).toISOString() : null;
726
-
727
- this.seq++;
728
- const prefix = `${Date.now()}-${String(this.seq).padStart(6, "0")}`;
729
- writeFileSync(join(dir, `${prefix}_${job.id}.queue-data`), JSON.stringify(job, null, 2));
337
+ this.liteBackend.retryJob(queue, job, delaySeconds);
730
338
  }
731
339
  }