@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.
- package/README.md +103 -20
- package/build/chunk-6IO4P6RB.js +145 -0
- package/build/chunk-6IO4P6RB.js.map +1 -0
- package/build/{chunk-VHN3XZDC.js → chunk-AHUVTAI7.js} +278 -29
- package/build/chunk-AHUVTAI7.js.map +1 -0
- package/build/chunk-S37X3CBO.js +500 -0
- package/build/chunk-S37X3CBO.js.map +1 -0
- package/build/index.d.ts +34 -8
- package/build/index.js +187 -31
- package/build/index.js.map +1 -1
- package/build/{job-DImdhRFO.d.ts → job-C4oyCVxR.d.ts} +275 -15
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/fake_adapter.d.ts +12 -6
- package/build/src/drivers/fake_adapter.js +1 -1
- package/build/src/drivers/knex_adapter.d.ts +6 -5
- package/build/src/drivers/knex_adapter.js +112 -0
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/build/src/drivers/redis_adapter.d.ts +6 -5
- package/build/src/drivers/redis_adapter.js +166 -402
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/build/src/drivers/redis_job_storage.d.ts +17 -0
- package/build/src/drivers/redis_job_storage.js +14 -0
- package/build/src/drivers/redis_job_storage.js.map +1 -0
- package/build/src/drivers/redis_scripts.d.ts +87 -0
- package/build/src/drivers/redis_scripts.js +29 -0
- package/build/src/drivers/redis_scripts.js.map +1 -0
- package/build/src/drivers/sync_adapter.d.ts +2 -1
- package/build/src/drivers/sync_adapter.js +7 -1
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/otel.d.ts +2 -2
- package/build/src/otel.js +3 -0
- package/build/src/otel.js.map +1 -1
- package/build/src/types/index.d.ts +1 -1
- package/build/src/types/main.d.ts +1 -1
- package/build/src/types/tracing_channels.d.ts +7 -1
- package/package.json +18 -19
- 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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
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
|
-
*
|
|
442
|
+
* using fake = QueueManager.fake()
|
|
417
443
|
*
|
|
418
444
|
* await SendEmailJob.dispatch({ to: 'user@example.com' })
|
|
419
445
|
*
|
|
420
446
|
* fake.assertPushed(SendEmailJob)
|
|
421
|
-
*
|
|
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
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1598
|
-
|
|
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
|
-
|
|
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-
|
|
1929
|
+
//# sourceMappingURL=chunk-AHUVTAI7.js.map
|