bunqueue 1.1.3 → 1.2.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.
Files changed (111) hide show
  1. package/README.md +37 -0
  2. package/dist/application/dlqManager.d.ts.map +1 -1
  3. package/dist/application/dlqManager.js +11 -3
  4. package/dist/application/dlqManager.js.map +1 -1
  5. package/dist/application/eventsManager.d.ts +10 -1
  6. package/dist/application/eventsManager.d.ts.map +1 -1
  7. package/dist/application/eventsManager.js +91 -21
  8. package/dist/application/eventsManager.js.map +1 -1
  9. package/dist/application/operations/ack.d.ts +16 -1
  10. package/dist/application/operations/ack.d.ts.map +1 -1
  11. package/dist/application/operations/ack.js +269 -5
  12. package/dist/application/operations/ack.js.map +1 -1
  13. package/dist/application/operations/jobManagement.d.ts.map +1 -1
  14. package/dist/application/operations/jobManagement.js +17 -3
  15. package/dist/application/operations/jobManagement.js.map +1 -1
  16. package/dist/application/operations/pull.d.ts +1 -0
  17. package/dist/application/operations/pull.d.ts.map +1 -1
  18. package/dist/application/operations/pull.js +106 -8
  19. package/dist/application/operations/pull.js.map +1 -1
  20. package/dist/application/operations/push.d.ts.map +1 -1
  21. package/dist/application/operations/push.js +9 -0
  22. package/dist/application/operations/push.js.map +1 -1
  23. package/dist/application/operations/queueControl.d.ts +4 -1
  24. package/dist/application/operations/queueControl.d.ts.map +1 -1
  25. package/dist/application/operations/queueControl.js +16 -13
  26. package/dist/application/operations/queueControl.js.map +1 -1
  27. package/dist/application/queueManager.d.ts +28 -2
  28. package/dist/application/queueManager.d.ts.map +1 -1
  29. package/dist/application/queueManager.js +132 -34
  30. package/dist/application/queueManager.js.map +1 -1
  31. package/dist/application/webhookManager.d.ts +7 -1
  32. package/dist/application/webhookManager.d.ts.map +1 -1
  33. package/dist/application/webhookManager.js +38 -6
  34. package/dist/application/webhookManager.js.map +1 -1
  35. package/dist/application/workerManager.d.ts +5 -1
  36. package/dist/application/workerManager.d.ts.map +1 -1
  37. package/dist/application/workerManager.js +35 -10
  38. package/dist/application/workerManager.js.map +1 -1
  39. package/dist/bunqueue +0 -0
  40. package/dist/cli/commands/backup.d.ts +20 -0
  41. package/dist/cli/commands/backup.d.ts.map +1 -0
  42. package/dist/cli/commands/backup.js +143 -0
  43. package/dist/cli/commands/backup.js.map +1 -0
  44. package/dist/cli/help.d.ts.map +1 -1
  45. package/dist/cli/help.js +6 -0
  46. package/dist/cli/help.js.map +1 -1
  47. package/dist/cli/index.d.ts.map +1 -1
  48. package/dist/cli/index.js +27 -0
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/domain/queue/priorityQueue.d.ts +15 -0
  51. package/dist/domain/queue/priorityQueue.d.ts.map +1 -1
  52. package/dist/domain/queue/priorityQueue.js +40 -0
  53. package/dist/domain/queue/priorityQueue.js.map +1 -1
  54. package/dist/domain/queue/shard.d.ts +67 -0
  55. package/dist/domain/queue/shard.d.ts.map +1 -1
  56. package/dist/domain/queue/shard.js +198 -0
  57. package/dist/domain/queue/shard.js.map +1 -1
  58. package/dist/infrastructure/backup/index.d.ts +5 -0
  59. package/dist/infrastructure/backup/index.d.ts.map +1 -0
  60. package/dist/infrastructure/backup/index.js +5 -0
  61. package/dist/infrastructure/backup/index.js.map +1 -0
  62. package/dist/infrastructure/backup/s3Backup.d.ts +101 -0
  63. package/dist/infrastructure/backup/s3Backup.d.ts.map +1 -0
  64. package/dist/infrastructure/backup/s3Backup.js +313 -0
  65. package/dist/infrastructure/backup/s3Backup.js.map +1 -0
  66. package/dist/infrastructure/persistence/sqlite.d.ts +3 -1
  67. package/dist/infrastructure/persistence/sqlite.d.ts.map +1 -1
  68. package/dist/infrastructure/persistence/sqlite.js +25 -4
  69. package/dist/infrastructure/persistence/sqlite.js.map +1 -1
  70. package/dist/infrastructure/scheduler/cronScheduler.d.ts +7 -0
  71. package/dist/infrastructure/scheduler/cronScheduler.d.ts.map +1 -1
  72. package/dist/infrastructure/scheduler/cronScheduler.js +49 -18
  73. package/dist/infrastructure/scheduler/cronScheduler.js.map +1 -1
  74. package/dist/infrastructure/server/handlers/advanced.d.ts +1 -1
  75. package/dist/infrastructure/server/handlers/advanced.d.ts.map +1 -1
  76. package/dist/infrastructure/server/handlers/advanced.js +14 -11
  77. package/dist/infrastructure/server/handlers/advanced.js.map +1 -1
  78. package/dist/infrastructure/server/handlers/core.d.ts +2 -2
  79. package/dist/infrastructure/server/handlers/core.d.ts.map +1 -1
  80. package/dist/infrastructure/server/handlers/core.js +6 -12
  81. package/dist/infrastructure/server/handlers/core.js.map +1 -1
  82. package/dist/infrastructure/server/http.d.ts.map +1 -1
  83. package/dist/infrastructure/server/http.js +22 -27
  84. package/dist/infrastructure/server/http.js.map +1 -1
  85. package/dist/infrastructure/server/rateLimiter.d.ts +4 -3
  86. package/dist/infrastructure/server/rateLimiter.d.ts.map +1 -1
  87. package/dist/infrastructure/server/rateLimiter.js +71 -19
  88. package/dist/infrastructure/server/rateLimiter.js.map +1 -1
  89. package/dist/main.js +18 -0
  90. package/dist/main.js.map +1 -1
  91. package/dist/shared/hash.d.ts +4 -4
  92. package/dist/shared/hash.d.ts.map +1 -1
  93. package/dist/shared/hash.js +20 -4
  94. package/dist/shared/hash.js.map +1 -1
  95. package/dist/shared/lock.d.ts +7 -1
  96. package/dist/shared/lock.d.ts.map +1 -1
  97. package/dist/shared/lock.js +75 -40
  98. package/dist/shared/lock.js.map +1 -1
  99. package/dist/shared/logger.d.ts +1 -0
  100. package/dist/shared/logger.d.ts.map +1 -1
  101. package/dist/shared/logger.js +1 -0
  102. package/dist/shared/logger.js.map +1 -1
  103. package/dist/shared/lru.d.ts +55 -0
  104. package/dist/shared/lru.d.ts.map +1 -1
  105. package/dist/shared/lru.js +165 -2
  106. package/dist/shared/lru.js.map +1 -1
  107. package/dist/shared/minHeap.d.ts +35 -0
  108. package/dist/shared/minHeap.d.ts.map +1 -0
  109. package/dist/shared/minHeap.js +116 -0
  110. package/dist/shared/minHeap.js.map +1 -0
  111. package/package.json +1 -1
package/README.md CHANGED
@@ -59,6 +59,8 @@ bunqueue
59
59
  docker run -p 6789:6789 -p 6790:6790 ghcr.io/egeominotti/bunqueue
60
60
  ```
61
61
 
62
+ <img src=".github/terminal.png" alt="bunqueue server running" width="600" />
63
+
62
64
  ### Production Setup
63
65
 
64
66
  For production, enable **persistence** and **authentication**:
@@ -672,6 +674,41 @@ curl -X POST http://localhost:6790/cron \
672
674
  | `DATA_PATH` | - | SQLite database path (in-memory if not set) |
673
675
  | `CORS_ALLOW_ORIGIN` | * | Allowed CORS origins |
674
676
 
677
+ ### S3 Backup Configuration
678
+
679
+ | Variable | Default | Description |
680
+ |----------|---------|-------------|
681
+ | `S3_BACKUP_ENABLED` | 0 | Enable automated S3 backups (1/true) |
682
+ | `S3_ACCESS_KEY_ID` | - | S3 access key |
683
+ | `S3_SECRET_ACCESS_KEY` | - | S3 secret key |
684
+ | `S3_BUCKET` | - | S3 bucket name |
685
+ | `S3_REGION` | us-east-1 | S3 region |
686
+ | `S3_ENDPOINT` | - | Custom endpoint for S3-compatible services |
687
+ | `S3_BACKUP_INTERVAL` | 21600000 | Backup interval in ms (default: 6 hours) |
688
+ | `S3_BACKUP_RETENTION` | 7 | Number of backups to retain |
689
+ | `S3_BACKUP_PREFIX` | backups/ | Prefix for backup files |
690
+
691
+ **Supported S3 Providers:**
692
+ - AWS S3
693
+ - Cloudflare R2: `S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com`
694
+ - MinIO: `S3_ENDPOINT=http://localhost:9000`
695
+ - DigitalOcean Spaces: `S3_ENDPOINT=https://<region>.digitaloceanspaces.com`
696
+
697
+ **CLI Commands:**
698
+ ```bash
699
+ # Create backup immediately
700
+ bunqueue backup now
701
+
702
+ # List available backups
703
+ bunqueue backup list
704
+
705
+ # Restore from backup (requires --force)
706
+ bunqueue backup restore backups/bunqueue-2024-01-15.db --force
707
+
708
+ # Show backup status
709
+ bunqueue backup status
710
+ ```
711
+
675
712
  ### Authentication
676
713
 
677
714
  Enable authentication by setting `AUTH_TOKENS`:
@@ -1 +1 @@
1
- {"version":3,"file":"dlqManager.d.ts","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;CACnC;AAED,wBAAwB;AACxB,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAIhF;AAED,0BAA0B;AAC1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CA6BlF;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAGnE"}
1
+ {"version":3,"file":"dlqManager.d.ts","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;CACnC;AAED,wBAAwB;AACxB,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAIhF;AAED,0BAA0B;AAC1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAqClF;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAGnE"}
@@ -13,25 +13,33 @@ export function getDlqJobs(queue, ctx, count) {
13
13
  export function retryDlqJobs(queue, ctx, jobId) {
14
14
  const idx = shardIndex(queue);
15
15
  const shard = ctx.shards[idx];
16
+ const now = Date.now();
16
17
  if (jobId) {
18
+ // removeFromDlq already decrements dlq counter
17
19
  const job = shard.removeFromDlq(queue, jobId);
18
20
  if (job) {
19
21
  job.attempts = 0;
20
- job.runAt = Date.now();
22
+ job.runAt = now;
21
23
  shard.getQueue(queue).push(job);
24
+ // Update running counters for O(1) stats and temporal index
25
+ const isDelayed = job.runAt > now;
26
+ shard.incrementQueued(job.id, isDelayed, job.createdAt, queue);
22
27
  ctx.jobIndex.set(job.id, { type: 'queue', shardIdx: idx, queueName: queue });
23
28
  return 1;
24
29
  }
25
30
  return 0;
26
31
  }
27
- // Retry all
32
+ // Retry all - clearDlq already decrements dlq counter
28
33
  const jobs = shard.getDlq(queue);
29
34
  const count = jobs.length;
30
35
  shard.clearDlq(queue);
31
36
  for (const job of jobs) {
32
37
  job.attempts = 0;
33
- job.runAt = Date.now();
38
+ job.runAt = now;
34
39
  shard.getQueue(queue).push(job);
40
+ // Update running counters for O(1) stats and temporal index
41
+ const isDelayed = job.runAt > now;
42
+ shard.incrementQueued(job.id, isDelayed, job.createdAt, queue);
35
43
  ctx.jobIndex.set(job.id, { type: 'queue', shardIdx: idx, queueName: queue });
36
44
  }
37
45
  return count;
@@ -1 +1 @@
1
- {"version":3,"file":"dlqManager.js","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAQ5C,wBAAwB;AACxB,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,GAAe,EAAE,KAAc;IACvE,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,GAAe,EAAE,KAAa;IACxE,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAE9B,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,GAAG,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC9C,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;YACjB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7E,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,YAAY;IACZ,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;IAC1B,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEtB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;QACjB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,GAAe;IACzD,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"dlqManager.js","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"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"}
@@ -11,12 +11,21 @@ export type EventSubscriber = (event: JobEvent) => void;
11
11
  export declare class EventsManager {
12
12
  private readonly webhookManager;
13
13
  private readonly subscribers;
14
+ /** Waiters for specific job completions - for efficient WaitJob implementation */
15
+ private readonly completionWaiters;
14
16
  constructor(webhookManager: WebhookManager);
15
17
  /** Subscribe to job events */
16
18
  subscribe(callback: EventSubscriber): () => void;
17
19
  /** Clear all subscribers (for shutdown) */
18
20
  clear(): void;
19
- /** Broadcast event to all subscribers */
21
+ /**
22
+ * Wait for a specific job to complete - event-driven, no polling
23
+ * Returns true if job completed, false if timeout
24
+ */
25
+ waitForJobCompletion(jobId: JobId, timeoutMs: number): Promise<boolean>;
26
+ /** Check if broadcast has any listeners - for batch optimizations */
27
+ needsBroadcast(): boolean;
28
+ /** Broadcast event to all subscribers - optimized to skip work when no listeners */
20
29
  broadcast(event: Partial<JobEvent> & {
21
30
  eventType: EventType;
22
31
  queue: string;
@@ -1 +1 @@
1
- {"version":3,"file":"eventsManager.d.ts","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIvD,gCAAgC;AAChC,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAExD,2BAA2B;AAC3B,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,cAAc;IAF3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;gBAExB,cAAc,EAAE,cAAc;IAE3D,8BAA8B;IAC9B,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IAQhD,2CAA2C;IAC3C,KAAK,IAAI,IAAI;IAIb,yCAAyC;IACzC,SAAS,CACP,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;QACzB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,IAAI;IA6BP,+CAA+C;IAC/C,OAAO,CAAC,iBAAiB;CAgB1B"}
1
+ {"version":3,"file":"eventsManager.d.ts","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIvD,gCAAgC;AAChC,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAExD,2BAA2B;AAC3B,qBAAa,aAAa;IAKZ,OAAO,CAAC,QAAQ,CAAC,cAAc;IAJ3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwC;gBAE7C,cAAc,EAAE,cAAc;IAE3D,8BAA8B;IAC9B,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IAQhD,2CAA2C;IAC3C,KAAK,IAAI,IAAI;IAWb;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BvE,qEAAqE;IACrE,cAAc,IAAI,OAAO;IAQzB,oFAAoF;IACpF,SAAS,CACP,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;QACzB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,IAAI;IAuDP,+CAA+C;IAC/C,OAAO,CAAC,iBAAiB;CAgB1B"}
@@ -7,6 +7,8 @@ import { webhookLog } from '../shared/logger';
7
7
  export class EventsManager {
8
8
  webhookManager;
9
9
  subscribers = [];
10
+ /** Waiters for specific job completions - for efficient WaitJob implementation */
11
+ completionWaiters = new Map();
10
12
  constructor(webhookManager) {
11
13
  this.webhookManager = webhookManager;
12
14
  }
@@ -22,34 +24,102 @@ export class EventsManager {
22
24
  /** Clear all subscribers (for shutdown) */
23
25
  clear() {
24
26
  this.subscribers.length = 0;
27
+ // Clear all waiters
28
+ for (const waiters of this.completionWaiters.values()) {
29
+ for (const resolve of waiters) {
30
+ resolve();
31
+ }
32
+ }
33
+ this.completionWaiters.clear();
34
+ }
35
+ /**
36
+ * Wait for a specific job to complete - event-driven, no polling
37
+ * Returns true if job completed, false if timeout
38
+ */
39
+ waitForJobCompletion(jobId, timeoutMs) {
40
+ const jobKey = String(jobId);
41
+ return new Promise((resolve) => {
42
+ const timer = setTimeout(() => {
43
+ // Timeout - remove from waiters
44
+ const waiters = this.completionWaiters.get(jobKey);
45
+ if (waiters) {
46
+ const idx = waiters.indexOf(resolveWaiter);
47
+ if (idx !== -1)
48
+ waiters.splice(idx, 1);
49
+ if (waiters.length === 0)
50
+ this.completionWaiters.delete(jobKey);
51
+ }
52
+ resolve(false);
53
+ }, timeoutMs);
54
+ const resolveWaiter = () => {
55
+ clearTimeout(timer);
56
+ resolve(true);
57
+ };
58
+ // Add to waiters
59
+ let waiters = this.completionWaiters.get(jobKey);
60
+ if (!waiters) {
61
+ waiters = [];
62
+ this.completionWaiters.set(jobKey, waiters);
63
+ }
64
+ waiters.push(resolveWaiter);
65
+ });
25
66
  }
26
- /** Broadcast event to all subscribers */
67
+ /** Check if broadcast has any listeners - for batch optimizations */
68
+ needsBroadcast() {
69
+ return (this.subscribers.length > 0 ||
70
+ this.webhookManager.hasEnabledWebhooks() ||
71
+ this.completionWaiters.size > 0);
72
+ }
73
+ /** Broadcast event to all subscribers - optimized to skip work when no listeners */
27
74
  broadcast(event) {
75
+ const hasSubscribers = this.subscribers.length > 0;
76
+ const hasWebhooks = this.webhookManager.hasEnabledWebhooks();
77
+ const isCompletion = event.eventType === "completed" /* EventType.Completed */;
78
+ const hasWaiters = isCompletion && this.completionWaiters.size > 0;
79
+ // Fast path: nothing to notify
80
+ if (!hasSubscribers && !hasWebhooks && !hasWaiters) {
81
+ return;
82
+ }
28
83
  // Notify subscribers
29
- for (const sub of this.subscribers) {
30
- try {
31
- sub(event);
84
+ if (hasSubscribers) {
85
+ for (const sub of this.subscribers) {
86
+ try {
87
+ sub(event);
88
+ }
89
+ catch {
90
+ // Ignore subscriber errors
91
+ }
32
92
  }
33
- catch {
34
- // Ignore subscriber errors
93
+ }
94
+ // Notify completion waiters for WaitJob - O(1) lookup
95
+ if (hasWaiters) {
96
+ const jobKey = String(event.jobId);
97
+ const waiters = this.completionWaiters.get(jobKey);
98
+ if (waiters) {
99
+ this.completionWaiters.delete(jobKey);
100
+ for (const resolve of waiters) {
101
+ resolve();
102
+ }
35
103
  }
36
104
  }
37
- // Trigger webhooks
38
- const webhookEvent = this.mapEventToWebhook(event.eventType);
39
- if (webhookEvent) {
40
- this.webhookManager
41
- .trigger(webhookEvent, String(event.jobId), event.queue, {
42
- data: event.data,
43
- error: event.error,
44
- })
45
- .catch((err) => {
46
- webhookLog.error('Webhook trigger failed', {
47
- event: webhookEvent,
48
- jobId: String(event.jobId),
49
- queue: event.queue,
50
- error: String(err),
105
+ // Trigger webhooks - only if there are enabled webhooks
106
+ if (hasWebhooks) {
107
+ const webhookEvent = this.mapEventToWebhook(event.eventType);
108
+ if (webhookEvent) {
109
+ this.webhookManager
110
+ .trigger(webhookEvent, String(event.jobId), event.queue, {
111
+ data: event.data,
112
+ error: event.error,
113
+ })
114
+ .catch((err) => {
115
+ webhookLog.error('Webhook trigger failed', {
116
+ event: webhookEvent,
117
+ jobId: String(event.jobId),
118
+ queue: event.queue,
119
+ error: String(err),
120
+ });
51
121
  });
52
- });
122
+ }
53
123
  }
54
124
  }
55
125
  /** Map internal event type to webhook event */
@@ -1 +1 @@
1
- {"version":3,"file":"eventsManager.js","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAK9C,2BAA2B;AAC3B,MAAM,OAAO,aAAa;IAGK;IAFZ,WAAW,GAAsB,EAAE,CAAC;IAErD,YAA6B,cAA8B;QAA9B,mBAAc,GAAd,cAAc,CAAgB;IAAG,CAAC;IAE/D,8BAA8B;IAC9B,SAAS,CAAC,QAAyB;QACjC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC;IACJ,CAAC;IAED,2CAA2C;IAC3C,KAAK;QACH,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,yCAAyC;IACzC,SAAS,CACP,KAMC;QAED,qBAAqB;QACrB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,GAAG,CAAC,KAAiB,CAAC,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;QAED,mBAAmB;QACnB,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7D,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,CAAC,cAAc;iBAChB,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE;gBACvD,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB,CAAC;iBACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACtB,UAAU,CAAC,KAAK,CAAC,wBAAwB,EAAE;oBACzC,KAAK,EAAE,YAAY;oBACnB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;oBAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;iBACnB,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;IAED,+CAA+C;IACvC,iBAAiB,CAAC,SAAoB;QAC5C,QAAQ,SAAS,EAAE,CAAC;YAClB;gBACE,OAAO,YAAY,CAAC;YACtB;gBACE,OAAO,aAAa,CAAC;YACvB;gBACE,OAAO,eAAe,CAAC;YACzB;gBACE,OAAO,YAAY,CAAC;YACtB,yCAAwB;YACxB;gBACE,2CAA2C;gBAC3C,OAAO,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"eventsManager.js","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,12 @@ export interface AckContext {
33
33
  error?: string;
34
34
  }) => void;
35
35
  onJobCompleted: (jobId: JobId) => void;
36
+ /** Batch notify completions - more efficient than per-job calls */
37
+ onJobsCompleted?: (jobIds: JobId[]) => void;
38
+ /** Fast check if broadcast is needed - avoids function call overhead */
39
+ needsBroadcast?: () => boolean;
40
+ /** Check if any jobs are waiting for dependencies */
41
+ hasPendingDeps?: () => boolean;
36
42
  }
37
43
  /**
38
44
  * Acknowledge job completion
@@ -43,7 +49,16 @@ export declare function ackJob(jobId: JobId, result: unknown, ctx: AckContext):
43
49
  */
44
50
  export declare function failJob(jobId: JobId, error: string | undefined, ctx: AckContext): Promise<void>;
45
51
  /**
46
- * Acknowledge multiple jobs
52
+ * Acknowledge multiple jobs - optimized batch processing
53
+ * Groups jobs by shard to minimize lock acquisitions: O(shards) instead of O(n)
47
54
  */
48
55
  export declare function ackJobBatch(jobIds: JobId[], ctx: AckContext): Promise<void>;
56
+ /**
57
+ * Acknowledge multiple jobs with individual results - optimized batch processing
58
+ * Same as ackJobBatch but supports passing result data for each job
59
+ */
60
+ export declare function ackJobBatchWithResults(items: Array<{
61
+ id: JobId;
62
+ result: unknown;
63
+ }>, ctx: AckContext): Promise<void>;
49
64
  //# sourceMappingURL=ack.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ack.d.ts","sourceRoot":"","sources":["../../../src/application/operations/ack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,KAAK,EAA8B,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEzD,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAClC,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD1F;AAED;;GAEG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF"}
1
+ {"version":3,"file":"ack.d.ts","sourceRoot":"","sources":["../../../src/application/operations/ack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,KAAK,EAA8B,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEzD,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAClC,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACvC,mEAAmE;IACnE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IAC5C,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;IAC/B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;CAChC;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD1F;AAED;;GAEG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAuDf;AAKD;;;GAGG;AAEH,wBAAsB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8JjF;AAED;;;GAGG;AAEH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,EAC5C,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CA2If"}
@@ -76,8 +76,11 @@ export async function failJob(jobId, error, ctx) {
76
76
  shard.releaseJobResources(job.queue, job.uniqueKey, job.groupId);
77
77
  if (canRetry(job)) {
78
78
  // Retry with exponential backoff
79
- job.runAt = Date.now() + calculateBackoff(job);
79
+ const now = Date.now();
80
+ job.runAt = now + calculateBackoff(job);
80
81
  shard.getQueue(job.queue).push(job);
82
+ // Update running counters for O(1) stats (retry jobs are always delayed)
83
+ shard.incrementQueued(jobId, true, job.createdAt, job.queue);
81
84
  ctx.jobIndex.set(jobId, { type: 'queue', shardIdx: idx, queueName: job.queue });
82
85
  ctx.storage?.updateForRetry(job);
83
86
  }
@@ -88,7 +91,7 @@ export async function failJob(jobId, error, ctx) {
88
91
  ctx.totalFailed.value++;
89
92
  }
90
93
  else {
91
- // Move to DLQ
94
+ // Move to DLQ (addToDlq already updates dlq counter)
92
95
  shard.addToDlq(job);
93
96
  ctx.jobIndex.set(jobId, { type: 'dlq', queueName: job.queue });
94
97
  ctx.storage?.markFailed(job, error ?? null);
@@ -104,12 +107,273 @@ export async function failJob(jobId, error, ctx) {
104
107
  error,
105
108
  });
106
109
  }
110
+ /** Debug flag for ackBatch timing */
111
+ const DEBUG_ACK_BATCH = process.env.DEBUG_ACK_BATCH === '1';
107
112
  /**
108
- * Acknowledge multiple jobs
113
+ * Acknowledge multiple jobs - optimized batch processing
114
+ * Groups jobs by shard to minimize lock acquisitions: O(shards) instead of O(n)
109
115
  */
116
+ // eslint-disable-next-line complexity
110
117
  export async function ackJobBatch(jobIds, ctx) {
111
- for (const id of jobIds) {
112
- await ackJob(id, undefined, ctx);
118
+ if (jobIds.length === 0)
119
+ return;
120
+ // Small batches - use parallel individual acks (overhead of grouping not worth it)
121
+ if (jobIds.length <= 4) {
122
+ await Promise.all(jobIds.map((id) => ackJob(id, undefined, ctx)));
123
+ return;
124
+ }
125
+ const t0 = DEBUG_ACK_BATCH ? performance.now() : 0;
126
+ // Step 1: Group jobs by processing shard
127
+ const byProcShard = new Map();
128
+ for (const jobId of jobIds) {
129
+ const procIdx = processingShardIndex(jobId);
130
+ let group = byProcShard.get(procIdx);
131
+ if (!group) {
132
+ group = [];
133
+ byProcShard.set(procIdx, group);
134
+ }
135
+ group.push(jobId);
136
+ }
137
+ const t1 = DEBUG_ACK_BATCH ? performance.now() : 0;
138
+ // Step 2: Extract all jobs from processing shards (one lock per shard)
139
+ // Use arrays instead of Map for faster iteration in step 5
140
+ const extractedJobs = [];
141
+ await Promise.all(Array.from(byProcShard.entries()).map(async ([procIdx, ids]) => {
142
+ await withWriteLock(ctx.processingLocks[procIdx], () => {
143
+ for (const jobId of ids) {
144
+ const job = ctx.processingShards[procIdx].get(jobId);
145
+ if (job) {
146
+ ctx.processingShards[procIdx].delete(jobId);
147
+ extractedJobs.push({ id: jobId, job });
148
+ }
149
+ }
150
+ });
151
+ }));
152
+ const t2 = DEBUG_ACK_BATCH ? performance.now() : 0;
153
+ // Step 3: Group extracted jobs by queue shard
154
+ const byQueueShard = new Map();
155
+ for (let i = 0; i < extractedJobs.length; i++) {
156
+ const job = extractedJobs[i].job;
157
+ const idx = shardIndex(job.queue);
158
+ let group = byQueueShard.get(idx);
159
+ if (!group) {
160
+ group = [];
161
+ byQueueShard.set(idx, group);
162
+ }
163
+ group.push(job);
164
+ }
165
+ const t3 = DEBUG_ACK_BATCH ? performance.now() : 0;
166
+ // Step 4: Release resources for each queue shard (one lock per shard)
167
+ await Promise.all(Array.from(byQueueShard.entries()).map(async ([idx, jobs]) => {
168
+ await withWriteLock(ctx.shardLocks[idx], () => {
169
+ const shard = ctx.shards[idx];
170
+ for (const job of jobs) {
171
+ shard.releaseJobResources(job.queue, job.uniqueKey, job.groupId);
172
+ }
173
+ });
174
+ }));
175
+ const t4 = DEBUG_ACK_BATCH ? performance.now() : 0;
176
+ // Step 5: Update indexes and metrics (no locks needed - single-threaded)
177
+ // Optimized: use indexed for loop (faster than for-of), minimize function calls
178
+ const now = Date.now();
179
+ const completedLocation = { type: 'completed' };
180
+ const storage = ctx.storage;
181
+ const hasStorage = storage !== null;
182
+ const jobCount = extractedJobs.length;
183
+ // Pre-check flags (check once, not per job)
184
+ const needsBroadcast = ctx.needsBroadcast?.() ?? true;
185
+ const hasPendingDeps = ctx.hasPendingDeps?.() ?? true;
186
+ const t5a = DEBUG_ACK_BATCH ? performance.now() : 0;
187
+ // Batch counter update (single BigInt operation instead of N increments)
188
+ ctx.totalCompleted.value += BigInt(jobCount);
189
+ const t5b = DEBUG_ACK_BATCH ? performance.now() : 0;
190
+ // Main loop - indexed for loop is faster than for-of on arrays
191
+ for (let i = 0; i < jobCount; i++) {
192
+ const { id: jobId, job } = extractedJobs[i];
193
+ if (!job.removeOnComplete) {
194
+ ctx.completedJobs.add(jobId);
195
+ ctx.jobIndex.set(jobId, completedLocation);
196
+ if (hasStorage)
197
+ storage.markCompleted(jobId, now);
198
+ }
199
+ else {
200
+ ctx.jobIndex.delete(jobId);
201
+ if (hasStorage)
202
+ storage.deleteJob(jobId);
203
+ }
204
+ }
205
+ const t5c = DEBUG_ACK_BATCH ? performance.now() : 0;
206
+ // Broadcast events (only if listeners exist)
207
+ if (needsBroadcast) {
208
+ const event = {
209
+ eventType: 'completed',
210
+ queue: '',
211
+ jobId: '',
212
+ timestamp: now,
213
+ };
214
+ for (let i = 0; i < jobCount; i++) {
215
+ const { id: jobId, job } = extractedJobs[i];
216
+ event.queue = job.queue;
217
+ event.jobId = jobId;
218
+ ctx.broadcast(event);
219
+ }
220
+ }
221
+ const t5d = DEBUG_ACK_BATCH ? performance.now() : 0;
222
+ // Batch notify completions (for dependency checking)
223
+ if (hasPendingDeps && ctx.onJobsCompleted) {
224
+ const completedIds = extractedJobs.map((e) => e.id);
225
+ ctx.onJobsCompleted(completedIds);
226
+ }
227
+ else if (hasPendingDeps) {
228
+ for (let i = 0; i < jobCount; i++) {
229
+ ctx.onJobCompleted(extractedJobs[i].id);
230
+ }
231
+ }
232
+ const t5 = DEBUG_ACK_BATCH ? performance.now() : 0;
233
+ if (DEBUG_ACK_BATCH) {
234
+ console.log(` Step 5 details:`);
235
+ console.log(` 5a checks: ${(t5b - t5a).toFixed(2)}ms`);
236
+ console.log(` 5b main loop: ${(t5c - t5b).toFixed(2)}ms`);
237
+ console.log(` 5c broadcast: ${(t5d - t5c).toFixed(2)}ms (needsBroadcast=${needsBroadcast})`);
238
+ console.log(` 5d deps: ${(t5 - t5d).toFixed(2)}ms (hasPendingDeps=${hasPendingDeps})`);
239
+ }
240
+ if (DEBUG_ACK_BATCH) {
241
+ console.log(`ackBatch timing (${jobIds.length} jobs):`);
242
+ console.log(` Step 1 (group proc): ${(t1 - t0).toFixed(2)}ms`);
243
+ console.log(` Step 2 (extract): ${(t2 - t1).toFixed(2)}ms`);
244
+ console.log(` Step 3 (group queue): ${(t3 - t2).toFixed(2)}ms`);
245
+ console.log(` Step 4 (release): ${(t4 - t3).toFixed(2)}ms`);
246
+ console.log(` Step 5 (update idx): ${(t5 - t4).toFixed(2)}ms`);
247
+ console.log(` Total: ${(t5 - t0).toFixed(2)}ms`);
248
+ }
249
+ }
250
+ /**
251
+ * Acknowledge multiple jobs with individual results - optimized batch processing
252
+ * Same as ackJobBatch but supports passing result data for each job
253
+ */
254
+ // eslint-disable-next-line complexity
255
+ export async function ackJobBatchWithResults(items, ctx) {
256
+ if (items.length === 0)
257
+ return;
258
+ // Small batches - use parallel individual acks
259
+ if (items.length <= 4) {
260
+ await Promise.all(items.map((item) => ackJob(item.id, item.result, ctx)));
261
+ return;
262
+ }
263
+ const t0 = DEBUG_ACK_BATCH ? performance.now() : 0;
264
+ // Step 1: Group jobs by processing shard
265
+ const byProcShard = new Map();
266
+ for (const item of items) {
267
+ const procIdx = processingShardIndex(item.id);
268
+ let group = byProcShard.get(procIdx);
269
+ if (!group) {
270
+ group = [];
271
+ byProcShard.set(procIdx, group);
272
+ }
273
+ group.push(item);
274
+ }
275
+ const t1 = DEBUG_ACK_BATCH ? performance.now() : 0;
276
+ // Step 2: Extract all jobs from processing shards (one lock per shard)
277
+ // Keep result with each extracted job
278
+ const extractedJobs = [];
279
+ await Promise.all(Array.from(byProcShard.entries()).map(async ([procIdx, itemsInShard]) => {
280
+ await withWriteLock(ctx.processingLocks[procIdx], () => {
281
+ for (const item of itemsInShard) {
282
+ const job = ctx.processingShards[procIdx].get(item.id);
283
+ if (job) {
284
+ ctx.processingShards[procIdx].delete(item.id);
285
+ extractedJobs.push({ id: item.id, job, result: item.result });
286
+ }
287
+ }
288
+ });
289
+ }));
290
+ const t2 = DEBUG_ACK_BATCH ? performance.now() : 0;
291
+ // Step 3: Group extracted jobs by queue shard
292
+ const byQueueShard = new Map();
293
+ for (let i = 0; i < extractedJobs.length; i++) {
294
+ const job = extractedJobs[i].job;
295
+ const idx = shardIndex(job.queue);
296
+ let group = byQueueShard.get(idx);
297
+ if (!group) {
298
+ group = [];
299
+ byQueueShard.set(idx, group);
300
+ }
301
+ group.push(job);
302
+ }
303
+ const t3 = DEBUG_ACK_BATCH ? performance.now() : 0;
304
+ // Step 4: Release resources for each queue shard (one lock per shard)
305
+ await Promise.all(Array.from(byQueueShard.entries()).map(async ([idx, jobs]) => {
306
+ await withWriteLock(ctx.shardLocks[idx], () => {
307
+ const shard = ctx.shards[idx];
308
+ for (const job of jobs) {
309
+ shard.releaseJobResources(job.queue, job.uniqueKey, job.groupId);
310
+ }
311
+ });
312
+ }));
313
+ const t4 = DEBUG_ACK_BATCH ? performance.now() : 0;
314
+ // Step 5: Update indexes, store results, and metrics
315
+ const now = Date.now();
316
+ const completedLocation = { type: 'completed' };
317
+ const storage = ctx.storage;
318
+ const hasStorage = storage !== null;
319
+ const jobCount = extractedJobs.length;
320
+ // Pre-check flags
321
+ const needsBroadcast = ctx.needsBroadcast?.() ?? true;
322
+ const hasPendingDeps = ctx.hasPendingDeps?.() ?? true;
323
+ // Batch counter update
324
+ ctx.totalCompleted.value += BigInt(jobCount);
325
+ // Main loop with result storage
326
+ for (let i = 0; i < jobCount; i++) {
327
+ const { id: jobId, job, result } = extractedJobs[i];
328
+ if (!job.removeOnComplete) {
329
+ ctx.completedJobs.add(jobId);
330
+ if (result !== undefined) {
331
+ ctx.jobResults.set(jobId, result);
332
+ if (hasStorage)
333
+ storage.storeResult(jobId, result);
334
+ }
335
+ ctx.jobIndex.set(jobId, completedLocation);
336
+ if (hasStorage)
337
+ storage.markCompleted(jobId, now);
338
+ }
339
+ else {
340
+ ctx.jobIndex.delete(jobId);
341
+ if (hasStorage)
342
+ storage.deleteJob(jobId);
343
+ }
344
+ }
345
+ // Broadcast events with results (only if listeners exist)
346
+ if (needsBroadcast) {
347
+ for (let i = 0; i < jobCount; i++) {
348
+ const { id: jobId, job, result } = extractedJobs[i];
349
+ ctx.broadcast({
350
+ eventType: 'completed',
351
+ queue: job.queue,
352
+ jobId,
353
+ timestamp: now,
354
+ data: result,
355
+ });
356
+ }
357
+ }
358
+ // Batch notify completions (for dependency checking)
359
+ if (hasPendingDeps && ctx.onJobsCompleted) {
360
+ const completedIds = extractedJobs.map((e) => e.id);
361
+ ctx.onJobsCompleted(completedIds);
362
+ }
363
+ else if (hasPendingDeps) {
364
+ for (let i = 0; i < jobCount; i++) {
365
+ ctx.onJobCompleted(extractedJobs[i].id);
366
+ }
367
+ }
368
+ const t5 = DEBUG_ACK_BATCH ? performance.now() : 0;
369
+ if (DEBUG_ACK_BATCH) {
370
+ console.log(`ackBatchWithResults timing (${items.length} jobs):`);
371
+ console.log(` Step 1 (group proc): ${(t1 - t0).toFixed(2)}ms`);
372
+ console.log(` Step 2 (extract): ${(t2 - t1).toFixed(2)}ms`);
373
+ console.log(` Step 3 (group queue): ${(t3 - t2).toFixed(2)}ms`);
374
+ console.log(` Step 4 (release): ${(t4 - t3).toFixed(2)}ms`);
375
+ console.log(` Step 5 (update+store): ${(t5 - t4).toFixed(2)}ms`);
376
+ console.log(` Total: ${(t5 - t0).toFixed(2)}ms`);
113
377
  }
114
378
  }
115
379
  //# sourceMappingURL=ack.js.map