@boringnode/queue 0.5.1 → 0.6.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 (37) hide show
  1. package/README.md +103 -20
  2. package/build/chunk-6IO4P6RB.js +145 -0
  3. package/build/chunk-6IO4P6RB.js.map +1 -0
  4. package/build/{chunk-VHN3XZDC.js → chunk-AHUVTAI7.js} +278 -29
  5. package/build/chunk-AHUVTAI7.js.map +1 -0
  6. package/build/chunk-S37X3CBO.js +500 -0
  7. package/build/chunk-S37X3CBO.js.map +1 -0
  8. package/build/index.d.ts +34 -8
  9. package/build/index.js +187 -31
  10. package/build/index.js.map +1 -1
  11. package/build/{job-DImdhRFO.d.ts → job-C4oyCVxR.d.ts} +275 -15
  12. package/build/src/contracts/adapter.d.ts +1 -1
  13. package/build/src/drivers/fake_adapter.d.ts +12 -6
  14. package/build/src/drivers/fake_adapter.js +1 -1
  15. package/build/src/drivers/knex_adapter.d.ts +6 -5
  16. package/build/src/drivers/knex_adapter.js +112 -0
  17. package/build/src/drivers/knex_adapter.js.map +1 -1
  18. package/build/src/drivers/redis_adapter.d.ts +6 -5
  19. package/build/src/drivers/redis_adapter.js +166 -402
  20. package/build/src/drivers/redis_adapter.js.map +1 -1
  21. package/build/src/drivers/redis_job_storage.d.ts +17 -0
  22. package/build/src/drivers/redis_job_storage.js +14 -0
  23. package/build/src/drivers/redis_job_storage.js.map +1 -0
  24. package/build/src/drivers/redis_scripts.d.ts +87 -0
  25. package/build/src/drivers/redis_scripts.js +29 -0
  26. package/build/src/drivers/redis_scripts.js.map +1 -0
  27. package/build/src/drivers/sync_adapter.d.ts +2 -1
  28. package/build/src/drivers/sync_adapter.js +7 -1
  29. package/build/src/drivers/sync_adapter.js.map +1 -1
  30. package/build/src/otel.d.ts +2 -2
  31. package/build/src/otel.js +3 -0
  32. package/build/src/otel.js.map +1 -1
  33. package/build/src/types/index.d.ts +1 -1
  34. package/build/src/types/main.d.ts +1 -1
  35. package/build/src/types/tracing_channels.d.ts +7 -1
  36. package/package.json +18 -19
  37. package/build/chunk-VHN3XZDC.js.map +0 -1
@@ -275,6 +275,7 @@ var QueueManagerSingleton = class {
275
275
  #internalOperationWrapper;
276
276
  #executionWrapper;
277
277
  #configResolver = new QueueConfigResolver({});
278
+ #locations = [];
278
279
  #fakeState;
279
280
  /**
280
281
  * Initialize the queue system with the given configuration.
@@ -311,17 +312,43 @@ var QueueManagerSingleton = class {
311
312
  this.#internalOperationWrapper = config.internalOperationWrapper;
312
313
  this.#executionWrapper = config.executionWrapper;
313
314
  this.#configResolver = QueueConfigResolver.from(config);
314
- if (config.locations && config.locations.length > 0) {
315
- const registered = await Locator.registerFromGlob(config.locations);
316
- if (registered === 0) {
317
- this.#logger.warn(
318
- `No jobs found for locations: ${config.locations.join(", ")}. Verify your glob patterns match your job files.`
319
- );
320
- }
315
+ this.#locations = config.locations ?? [];
316
+ if (config.autoLoadJobs ?? true) {
317
+ await this.loadJobs();
321
318
  }
322
319
  this.#initialized = true;
323
320
  return this;
324
321
  }
322
+ /**
323
+ * Load and register job classes from configured or explicit locations.
324
+ *
325
+ * This low-level API is useful for framework integrations that need to
326
+ * register jobs at a precise moment in their command lifecycle.
327
+ *
328
+ * @param locations - Optional glob patterns. Defaults to the configured locations.
329
+ * @returns Number of jobs successfully registered.
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * await QueueManager.init(config)
334
+ * await QueueManager.loadJobs()
335
+ *
336
+ * // Or with explicit locations
337
+ * await QueueManager.loadJobs(['./app/jobs/**\/*.js'])
338
+ * ```
339
+ */
340
+ async loadJobs(locations = this.#locations) {
341
+ if (locations.length === 0) {
342
+ return 0;
343
+ }
344
+ const registered = await Locator.registerFromGlob(locations);
345
+ if (registered === 0) {
346
+ this.#logger.warn(
347
+ `No jobs found for locations: ${locations.join(", ")}. Verify your glob patterns match your job files.`
348
+ );
349
+ }
350
+ return registered;
351
+ }
325
352
  /**
326
353
  * Destroy any materialized adapters from the current configuration before
327
354
  * replacing it with a new one.
@@ -406,19 +433,18 @@ var QueueManagerSingleton = class {
406
433
  * Replace all adapters with a fake adapter for testing.
407
434
  *
408
435
  * The fake adapter records pushed jobs and exposes assertion helpers.
409
- * Call `restore()` to return to the previous configuration.
410
- *
411
- * @returns The fake adapter instance for assertions
412
- * @throws {E_QUEUE_NOT_INITIALIZED} If `init()` hasn't been called
436
+ * Use the `using` keyword to automatically restore the previous
437
+ * configuration when the variable goes out of scope, or call
438
+ * `restore()` manually.
413
439
  *
414
440
  * @example
415
441
  * ```typescript
416
- * const fake = QueueManager.fake()
442
+ * using fake = QueueManager.fake()
417
443
  *
418
444
  * await SendEmailJob.dispatch({ to: 'user@example.com' })
419
445
  *
420
446
  * fake.assertPushed(SendEmailJob)
421
- * QueueManager.restore()
447
+ * // Automatically restored at end of scope
422
448
  * ```
423
449
  */
424
450
  fake() {
@@ -429,6 +455,7 @@ var QueueManagerSingleton = class {
429
455
  return this.#fakeState.fakeAdapter;
430
456
  }
431
457
  const fakeAdapter = new FakeAdapter();
458
+ fakeAdapter.onDispose(() => this.restore());
432
459
  this.#fakeState = {
433
460
  defaultAdapter: this.#defaultAdapter,
434
461
  adapters: this.#adapters,
@@ -438,6 +465,7 @@ var QueueManagerSingleton = class {
438
465
  internalOperationWrapper: this.#internalOperationWrapper,
439
466
  executionWrapper: this.#executionWrapper,
440
467
  configResolver: this.#configResolver,
468
+ locations: this.#locations,
441
469
  fakeAdapter
442
470
  };
443
471
  const fakeFactory = () => fakeAdapter;
@@ -470,6 +498,7 @@ var QueueManagerSingleton = class {
470
498
  this.#internalOperationWrapper = state.internalOperationWrapper;
471
499
  this.#executionWrapper = state.executionWrapper;
472
500
  this.#configResolver = state.configResolver;
501
+ this.#locations = state.locations;
473
502
  }
474
503
  /**
475
504
  * Get the configured job factory for custom instantiation.
@@ -559,6 +588,7 @@ var QueueManagerSingleton = class {
559
588
  this.#internalOperationWrapper = void 0;
560
589
  this.#executionWrapper = void 0;
561
590
  this.#configResolver = new QueueConfigResolver({});
591
+ this.#locations = [];
562
592
  this.#fakeState = void 0;
563
593
  }
564
594
  };
@@ -573,6 +603,7 @@ var JobDispatcher = class {
573
603
  #delay;
574
604
  #priority;
575
605
  #groupId;
606
+ #dedup;
576
607
  /**
577
608
  * Create a new job dispatcher.
578
609
  *
@@ -664,6 +695,76 @@ var JobDispatcher = class {
664
695
  this.#groupId = groupId;
665
696
  return this;
666
697
  }
698
+ /**
699
+ * Configure deduplication for this job.
700
+ *
701
+ * Modes:
702
+ * - **Simple** (`{ id }`): skip duplicates while the job exists.
703
+ * - **Throttle** (`{ id, ttl }`): skip duplicates within a TTL window.
704
+ * - **Extend** (`{ id, ttl, extend: true }`): reset the TTL clock on each duplicate.
705
+ * The window length stays at the original ttl from the first dispatch.
706
+ * - **Replace** (`{ id, ttl, replace: true }`): swap the payload of the existing
707
+ * pending/delayed job on duplicate within TTL. Active jobs and retained
708
+ * completed/failed jobs return `'skipped'`. Only `payload` changes —
709
+ * priority/queue/delay/groupId are preserved.
710
+ * - **Debounce** (`{ id, ttl, replace: true, extend: true }`): replace + reset TTL.
711
+ *
712
+ * The id is automatically prefixed with the job name to prevent collisions
713
+ * between different job types.
714
+ *
715
+ * @param options.id - Unique deduplication key
716
+ * @param options.ttl - TTL as Duration ('5s', 5000). Required for extend/replace.
717
+ * @param options.extend - Reset the TTL clock on duplicate within window. Window
718
+ * length stays at the original ttl; this option's `ttl` arg is ignored on extend.
719
+ * @param options.replace - Swap payload of existing pending/delayed job within
720
+ * window. Active and retained jobs are not modified.
721
+ *
722
+ * @example
723
+ * ```typescript
724
+ * // Simple dedup
725
+ * await SendInvoiceJob.dispatch({ orderId: 123 })
726
+ * .dedup({ id: 'order-123' })
727
+ *
728
+ * // Throttle: 5 second window
729
+ * await SendEmailJob.dispatch({ to: 'x' })
730
+ * .dedup({ id: 'welcome', ttl: '5s' })
731
+ *
732
+ * // Debounce: replace payload within window
733
+ * await SaveDraftJob.dispatch({ content: 'latest' })
734
+ * .dedup({ id: 'draft-42', ttl: '2s', replace: true, extend: true })
735
+ * ```
736
+ */
737
+ dedup(options) {
738
+ if (!options.id) {
739
+ throw new Error("Dedup ID must be a non-empty string");
740
+ }
741
+ if (options.id.length > 400) {
742
+ throw new Error("Dedup ID must be 400 characters or less");
743
+ }
744
+ const prefixedLength = this.#name.length + 2 + options.id.length;
745
+ if (prefixedLength > 510) {
746
+ throw new Error(
747
+ `Dedup ID combined with job name exceeds 510 characters (got ${prefixedLength}). Shorten either the job name or the dedup id.`
748
+ );
749
+ }
750
+ if ((options.extend || options.replace) && options.ttl === void 0) {
751
+ throw new Error("dedup.ttl is required when extend or replace is set");
752
+ }
753
+ let parsedTtl;
754
+ if (options.ttl !== void 0) {
755
+ parsedTtl = parse(options.ttl);
756
+ if (!Number.isFinite(parsedTtl) || parsedTtl <= 0) {
757
+ throw new Error("dedup.ttl must be a positive duration");
758
+ }
759
+ }
760
+ this.#dedup = {
761
+ id: options.id,
762
+ ttl: parsedTtl,
763
+ extend: options.extend,
764
+ replace: options.replace
765
+ };
766
+ return this;
767
+ }
667
768
  /**
668
769
  * Use a specific adapter for this job.
669
770
  *
@@ -696,6 +797,7 @@ var JobDispatcher = class {
696
797
  */
697
798
  async run() {
698
799
  const id = randomUUID();
800
+ const dedupId = this.#dedup ? `${this.#name}::${this.#dedup.id}` : void 0;
699
801
  debug_default("dispatching job %s with id %s using payload %s", this.#name, id, this.#payload);
700
802
  const adapter = this.#getAdapterInstance();
701
803
  const wrapInternal = QueueManager.getInternalOperationWrapper();
@@ -706,16 +808,32 @@ var JobDispatcher = class {
706
808
  payload: this.#payload,
707
809
  attempts: 0,
708
810
  priority: this.#priority,
709
- groupId: this.#groupId
811
+ groupId: this.#groupId,
812
+ createdAt: Date.now(),
813
+ ...dedupId ? {
814
+ dedup: {
815
+ id: dedupId,
816
+ ttl: this.#dedup.ttl,
817
+ extend: this.#dedup.extend,
818
+ replace: this.#dedup.replace
819
+ }
820
+ } : {}
710
821
  };
711
822
  const message = { jobs: [jobData], queue: this.#queue, delay: parsedDelay };
823
+ let pushResult;
712
824
  await dispatchChannel.tracePromise(async () => {
713
- if (parsedDelay !== void 0) {
714
- await wrapInternal(() => adapter.pushLaterOn(this.#queue, jobData, parsedDelay));
715
- } else {
716
- await wrapInternal(() => adapter.pushOn(this.#queue, jobData));
825
+ const result = parsedDelay !== void 0 ? await wrapInternal(() => adapter.pushLaterOn(this.#queue, jobData, parsedDelay)) : await wrapInternal(() => adapter.pushOn(this.#queue, jobData));
826
+ if (result && typeof result === "object" && "outcome" in result) {
827
+ pushResult = { outcome: result.outcome, jobId: result.jobId };
828
+ message.dedupOutcome = result.outcome;
717
829
  }
718
830
  }, message);
831
+ if (pushResult && this.#dedup) {
832
+ return {
833
+ jobId: pushResult.jobId,
834
+ deduped: pushResult.outcome
835
+ };
836
+ }
719
837
  return { jobId: id };
720
838
  }
721
839
  /**
@@ -845,13 +963,15 @@ var JobBatchDispatcher = class {
845
963
  debug_default("dispatching %d jobs of type %s", this.#payloads.length, this.#name);
846
964
  const adapter = this.#getAdapterInstance();
847
965
  const wrapInternal = QueueManager.getInternalOperationWrapper();
966
+ const now = Date.now();
848
967
  const jobs = this.#payloads.map((payload) => ({
849
968
  id: randomUUID2(),
850
969
  name: this.#name,
851
970
  payload,
852
971
  attempts: 0,
853
972
  priority: this.#priority,
854
- groupId: this.#groupId
973
+ groupId: this.#groupId,
974
+ createdAt: now
855
975
  }));
856
976
  const message = { jobs, queue: this.#queue };
857
977
  await dispatchChannel.tracePromise(async () => {
@@ -1264,7 +1384,21 @@ var FakeAdapter = class {
1264
1384
  #pendingTimeouts = /* @__PURE__ */ new Set();
1265
1385
  #schedules = /* @__PURE__ */ new Map();
1266
1386
  #pushedJobs = [];
1267
- setWorkerId(_workerId) {
1387
+ #dedupIndex = /* @__PURE__ */ new Map();
1388
+ #onDispose;
1389
+ #workerId = "";
1390
+ /**
1391
+ * Set the function to call when the fake is disposed
1392
+ */
1393
+ onDispose(fn) {
1394
+ this.#onDispose = fn;
1395
+ return this;
1396
+ }
1397
+ [Symbol.dispose]() {
1398
+ this.#onDispose?.();
1399
+ }
1400
+ setWorkerId(workerId) {
1401
+ this.#workerId = workerId;
1268
1402
  }
1269
1403
  getPushedJobs() {
1270
1404
  return [...this.#pushedJobs];
@@ -1290,6 +1424,7 @@ var FakeAdapter = class {
1290
1424
  this.#failedJobs.clear();
1291
1425
  this.#schedules.clear();
1292
1426
  this.#pushedJobs = [];
1427
+ this.#dedupIndex.clear();
1293
1428
  }
1294
1429
  assertPushed(matcher, query) {
1295
1430
  const record = this.findPushed(matcher, query);
@@ -1322,21 +1457,33 @@ var FakeAdapter = class {
1322
1457
  return this.pushOn("default", jobData);
1323
1458
  }
1324
1459
  async pushOn(queue, jobData) {
1460
+ const deduped = await this.#applyDedup(queue, jobData);
1461
+ if (deduped) return deduped;
1325
1462
  this.#recordPush(queue, jobData);
1326
1463
  this.#enqueue(queue, jobData);
1464
+ if (jobData.dedup) {
1465
+ return { outcome: "added", jobId: jobData.id };
1466
+ }
1327
1467
  }
1328
1468
  async pushLater(jobData, delay) {
1329
1469
  return this.pushLaterOn("default", jobData, delay);
1330
1470
  }
1331
- pushLaterOn(queue, jobData, delay) {
1471
+ async pushLaterOn(queue, jobData, delay) {
1472
+ const deduped = await this.#applyDedup(queue, jobData);
1473
+ if (deduped) return deduped;
1332
1474
  this.#recordPush(queue, jobData, delay);
1333
1475
  this.#schedulePush(queue, jobData, delay);
1334
- return Promise.resolve();
1476
+ if (jobData.dedup) {
1477
+ return { outcome: "added", jobId: jobData.id };
1478
+ }
1335
1479
  }
1336
1480
  async pushMany(jobs) {
1337
1481
  return this.pushManyOn("default", jobs);
1338
1482
  }
1339
1483
  async pushManyOn(queue, jobs) {
1484
+ if (jobs.some((j) => j.dedup)) {
1485
+ throw new Error("dedup is not supported in batch dispatch; use single dispatch");
1486
+ }
1340
1487
  for (const job of jobs) {
1341
1488
  await this.pushOn(queue, job);
1342
1489
  }
@@ -1363,7 +1510,7 @@ var FakeAdapter = class {
1363
1510
  return null;
1364
1511
  }
1365
1512
  const acquiredAt = Date.now();
1366
- this.#activeJobs.set(job.id, { job, acquiredAt, queue });
1513
+ this.#activeJobs.set(job.id, { job, acquiredAt, queue, workerId: this.#workerId });
1367
1514
  return { ...job, acquiredAt };
1368
1515
  }
1369
1516
  async completeJob(jobId, queue, removeOnComplete) {
@@ -1371,6 +1518,7 @@ var FakeAdapter = class {
1371
1518
  if (!active) return;
1372
1519
  this.#activeJobs.delete(jobId);
1373
1520
  if (removeOnComplete === void 0 || removeOnComplete === true) {
1521
+ this.#cleanupDedupForJob(queue, active.job);
1374
1522
  return;
1375
1523
  }
1376
1524
  this.#storeHistory(queue, "completed", active.job, removeOnComplete);
@@ -1380,6 +1528,7 @@ var FakeAdapter = class {
1380
1528
  if (!active) return;
1381
1529
  this.#activeJobs.delete(jobId);
1382
1530
  if (removeOnFail === void 0 || removeOnFail === true) {
1531
+ this.#cleanupDedupForJob(queue, active.job);
1383
1532
  return;
1384
1533
  }
1385
1534
  this.#storeHistory(queue, "failed", active.job, removeOnFail, error);
@@ -1415,6 +1564,7 @@ var FakeAdapter = class {
1415
1564
  const currentStalledCount = active.job.stalledCount ?? 0;
1416
1565
  if (currentStalledCount >= maxStalledCount) {
1417
1566
  this.#activeJobs.delete(jobId);
1567
+ this.#cleanupDedupForJob(active.queue, active.job);
1418
1568
  continue;
1419
1569
  }
1420
1570
  this.#activeJobs.delete(jobId);
@@ -1427,6 +1577,18 @@ var FakeAdapter = class {
1427
1577
  }
1428
1578
  return recovered;
1429
1579
  }
1580
+ async renewJobs(queue, jobIds) {
1581
+ const now = Date.now();
1582
+ let renewed = 0;
1583
+ for (const jobId of jobIds) {
1584
+ const active = this.#activeJobs.get(jobId);
1585
+ if (active && active.queue === queue && active.workerId === this.#workerId) {
1586
+ active.acquiredAt = now;
1587
+ renewed++;
1588
+ }
1589
+ }
1590
+ return renewed;
1591
+ }
1430
1592
  async getJob(jobId, queue) {
1431
1593
  const active = this.#activeJobs.get(jobId);
1432
1594
  if (active && active.queue === queue) {
@@ -1583,23 +1745,110 @@ var FakeAdapter = class {
1583
1745
  const records = store.get(queue);
1584
1746
  records.push(record);
1585
1747
  if (retention && retention !== true) {
1586
- this.#applyRetention(records, retention);
1748
+ this.#applyRetention(records, retention, queue);
1587
1749
  }
1588
1750
  }
1589
- #applyRetention(records, retention) {
1751
+ #applyRetention(records, retention, queue) {
1590
1752
  if (retention === false || retention === true) {
1591
1753
  return;
1592
1754
  }
1755
+ const pruned = [];
1593
1756
  if (retention.age !== void 0) {
1594
1757
  const maxAgeMs = parse(retention.age);
1595
1758
  if (maxAgeMs > 0) {
1596
1759
  const cutoff = Date.now() - maxAgeMs;
1597
- const filtered = records.filter((record) => (record.finishedAt ?? 0) >= cutoff);
1598
- records.splice(0, records.length, ...filtered);
1760
+ const kept = [];
1761
+ for (const record of records) {
1762
+ if ((record.finishedAt ?? 0) >= cutoff) {
1763
+ kept.push(record);
1764
+ } else {
1765
+ pruned.push(record);
1766
+ }
1767
+ }
1768
+ records.splice(0, records.length, ...kept);
1599
1769
  }
1600
1770
  }
1601
1771
  if (retention.count !== void 0 && retention.count > 0 && records.length > retention.count) {
1602
- records.splice(0, records.length - retention.count);
1772
+ const excess = records.length - retention.count;
1773
+ pruned.push(...records.slice(0, excess));
1774
+ records.splice(0, excess);
1775
+ }
1776
+ for (const record of pruned) {
1777
+ this.#cleanupDedupForJob(queue, record.data);
1778
+ }
1779
+ }
1780
+ #applyDedup(queue, jobData) {
1781
+ if (!jobData.dedup) return null;
1782
+ const dedupId = jobData.dedup.id;
1783
+ const now = Date.now();
1784
+ const entry = this.#getDedupEntry(queue, dedupId);
1785
+ if (entry) {
1786
+ const withinTtl = !entry.ttl || now - entry.createdAt < entry.ttl;
1787
+ if (withinTtl) {
1788
+ const existing = this.#findJobById(queue, entry.jobId);
1789
+ if (existing) {
1790
+ const replaceable = existing.location === "pending" || existing.location === "delayed";
1791
+ if (jobData.dedup.replace && replaceable) {
1792
+ existing.job.payload = structuredClone(jobData.payload);
1793
+ if (jobData.dedup.extend && entry.ttl) {
1794
+ entry.createdAt = now;
1795
+ }
1796
+ return { outcome: "replaced", jobId: entry.jobId };
1797
+ }
1798
+ if (jobData.dedup.extend && entry.ttl) {
1799
+ entry.createdAt = now;
1800
+ return { outcome: "extended", jobId: entry.jobId };
1801
+ }
1802
+ return { outcome: "skipped", jobId: entry.jobId };
1803
+ }
1804
+ }
1805
+ }
1806
+ this.#setDedupEntry(queue, dedupId, {
1807
+ jobId: jobData.id,
1808
+ createdAt: now,
1809
+ ttl: jobData.dedup.ttl,
1810
+ replace: jobData.dedup.replace,
1811
+ extend: jobData.dedup.extend
1812
+ });
1813
+ return null;
1814
+ }
1815
+ #findJobById(queue, jobId) {
1816
+ const active = this.#activeJobs.get(jobId);
1817
+ if (active && active.queue === queue) {
1818
+ return { job: active.job, location: "active" };
1819
+ }
1820
+ const pending = this.#queues.get(queue)?.find((j) => j.id === jobId);
1821
+ if (pending) {
1822
+ return { job: pending, location: "pending" };
1823
+ }
1824
+ const delayed = this.#delayedJobs.get(queue)?.get(jobId);
1825
+ if (delayed) {
1826
+ return { job: delayed.job, location: "delayed" };
1827
+ }
1828
+ const completed = this.#findHistory(this.#completedJobs, queue, jobId);
1829
+ if (completed) {
1830
+ return { job: completed.data, location: "completed" };
1831
+ }
1832
+ const failed = this.#findHistory(this.#failedJobs, queue, jobId);
1833
+ if (failed) {
1834
+ return { job: failed.data, location: "failed" };
1835
+ }
1836
+ return null;
1837
+ }
1838
+ #getDedupEntry(queue, dedupId) {
1839
+ return this.#dedupIndex.get(queue)?.get(dedupId);
1840
+ }
1841
+ #setDedupEntry(queue, dedupId, entry) {
1842
+ if (!this.#dedupIndex.has(queue)) {
1843
+ this.#dedupIndex.set(queue, /* @__PURE__ */ new Map());
1844
+ }
1845
+ this.#dedupIndex.get(queue).set(dedupId, entry);
1846
+ }
1847
+ #cleanupDedupForJob(queue, job) {
1848
+ if (!job.dedup) return;
1849
+ const entry = this.#getDedupEntry(queue, job.dedup.id);
1850
+ if (entry && entry.jobId === job.id) {
1851
+ this.#dedupIndex.get(queue)?.delete(job.dedup.id);
1603
1852
  }
1604
1853
  }
1605
1854
  #findHistory(store, queue, jobId) {
@@ -1677,4 +1926,4 @@ export {
1677
1926
  ScheduleBuilder,
1678
1927
  Job
1679
1928
  };
1680
- //# sourceMappingURL=chunk-VHN3XZDC.js.map
1929
+ //# sourceMappingURL=chunk-AHUVTAI7.js.map