mailery 0.12.0 → 0.13.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/dist/cli.cjs +109 -37
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +109 -37
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +71 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +71 -8
- package/dist/index.js.map +1 -1
- package/dist/{null-CfzvqNEp.d.cts → null-DDg3ojHe.d.cts} +16 -1
- package/dist/{null-CfzvqNEp.d.ts → null-DDg3ojHe.d.ts} +16 -1
- package/dist/testing.cjs +70 -7
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +70 -7
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -407,6 +407,20 @@ type QueueDriverConfig = {
|
|
|
407
407
|
processEverySeconds?: number;
|
|
408
408
|
lockLifetimeSeconds?: number;
|
|
409
409
|
collectionName?: string;
|
|
410
|
+
/**
|
|
411
|
+
* Namespaces the jobs collection (`_mailerJobs_<prefix>`) so multiple
|
|
412
|
+
* mailery instances can share one Mongo database — the counterpart to
|
|
413
|
+
* the Bull driver's Redis key prefix, and read from the same
|
|
414
|
+
* `MAILER_QUEUE_PREFIX` env var. Ignored when `collectionName` is set.
|
|
415
|
+
* Letters, digits, '_' and '-' only.
|
|
416
|
+
*/
|
|
417
|
+
prefix?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Days to retain failed job documents before the driver sweeps them
|
|
420
|
+
* (default 7, matching the Bull driver's `removeOnFail` age). 0 disables
|
|
421
|
+
* the sweep. Succeeded jobs are removed on completion regardless.
|
|
422
|
+
*/
|
|
423
|
+
failedJobRetentionDays?: number;
|
|
410
424
|
} | {
|
|
411
425
|
driver: 'noop';
|
|
412
426
|
};
|
|
@@ -1420,7 +1434,8 @@ declare class Mailer {
|
|
|
1420
1434
|
* MAILER_MONGODB_URI — Mongo connection string (required)
|
|
1421
1435
|
* MAILER_MONGODB_DB — database name (optional; defaults to the URI default)
|
|
1422
1436
|
* MAILER_REDIS_URL — Redis connection URL (required)
|
|
1423
|
-
* MAILER_QUEUE_PREFIX —
|
|
1437
|
+
* MAILER_QUEUE_PREFIX — namespaces this instance (optional). Bull: Redis key
|
|
1438
|
+
* prefix. Agenda: jobs collection suffix.
|
|
1424
1439
|
* MAILER_PUBLIC_URL — base for tracking/unsub URLs (required)
|
|
1425
1440
|
* MAILER_UNSUBSCRIBE_SECRET — HMAC key (required)
|
|
1426
1441
|
* MAILER_SENDER_ADDRESS — postal address for CAN-SPAM (optional)
|
|
@@ -407,6 +407,20 @@ type QueueDriverConfig = {
|
|
|
407
407
|
processEverySeconds?: number;
|
|
408
408
|
lockLifetimeSeconds?: number;
|
|
409
409
|
collectionName?: string;
|
|
410
|
+
/**
|
|
411
|
+
* Namespaces the jobs collection (`_mailerJobs_<prefix>`) so multiple
|
|
412
|
+
* mailery instances can share one Mongo database — the counterpart to
|
|
413
|
+
* the Bull driver's Redis key prefix, and read from the same
|
|
414
|
+
* `MAILER_QUEUE_PREFIX` env var. Ignored when `collectionName` is set.
|
|
415
|
+
* Letters, digits, '_' and '-' only.
|
|
416
|
+
*/
|
|
417
|
+
prefix?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Days to retain failed job documents before the driver sweeps them
|
|
420
|
+
* (default 7, matching the Bull driver's `removeOnFail` age). 0 disables
|
|
421
|
+
* the sweep. Succeeded jobs are removed on completion regardless.
|
|
422
|
+
*/
|
|
423
|
+
failedJobRetentionDays?: number;
|
|
410
424
|
} | {
|
|
411
425
|
driver: 'noop';
|
|
412
426
|
};
|
|
@@ -1420,7 +1434,8 @@ declare class Mailer {
|
|
|
1420
1434
|
* MAILER_MONGODB_URI — Mongo connection string (required)
|
|
1421
1435
|
* MAILER_MONGODB_DB — database name (optional; defaults to the URI default)
|
|
1422
1436
|
* MAILER_REDIS_URL — Redis connection URL (required)
|
|
1423
|
-
* MAILER_QUEUE_PREFIX —
|
|
1437
|
+
* MAILER_QUEUE_PREFIX — namespaces this instance (optional). Bull: Redis key
|
|
1438
|
+
* prefix. Agenda: jobs collection suffix.
|
|
1424
1439
|
* MAILER_PUBLIC_URL — base for tracking/unsub URLs (required)
|
|
1425
1440
|
* MAILER_UNSUBSCRIBE_SECRET — HMAC key (required)
|
|
1426
1441
|
* MAILER_SENDER_ADDRESS — postal address for CAN-SPAM (optional)
|
package/dist/testing.cjs
CHANGED
|
@@ -14808,6 +14808,9 @@ var QUEUE_NAMES2 = {
|
|
|
14808
14808
|
send: "mailer-send",
|
|
14809
14809
|
webhook: "mailer-webhook"
|
|
14810
14810
|
};
|
|
14811
|
+
var DEFAULT_COLLECTION = "_mailerJobs";
|
|
14812
|
+
var DEFAULT_FAILED_RETENTION_DAYS = 7;
|
|
14813
|
+
var FAILED_SWEEP_INTERVAL_MS = 60 * 60 * 1e3;
|
|
14811
14814
|
var AgendaDriver = class _AgendaDriver {
|
|
14812
14815
|
queues;
|
|
14813
14816
|
agenda;
|
|
@@ -14825,7 +14828,7 @@ var AgendaDriver = class _AgendaDriver {
|
|
|
14825
14828
|
"mailery: queue driver 'agenda' requires the 'agenda' and '@agendajs/mongo-backend' peer dependencies. Run `npm install agenda @agendajs/mongo-backend bottleneck`."
|
|
14826
14829
|
);
|
|
14827
14830
|
}
|
|
14828
|
-
const collectionName = opts.collectionName ??
|
|
14831
|
+
const collectionName = opts.collectionName ?? collectionFor(opts.prefix);
|
|
14829
14832
|
const backend = new backendMod.MongoBackend({
|
|
14830
14833
|
mongo: opts.db,
|
|
14831
14834
|
collection: collectionName
|
|
@@ -14835,17 +14838,29 @@ var AgendaDriver = class _AgendaDriver {
|
|
|
14835
14838
|
processEvery: `${opts.processEverySeconds ?? 5} seconds`,
|
|
14836
14839
|
defaultLockLifetime: (opts.lockLifetimeSeconds ?? 10 * 60) * 1e3,
|
|
14837
14840
|
maxConcurrency: 50,
|
|
14838
|
-
defaultConcurrency: 5
|
|
14841
|
+
defaultConcurrency: 5,
|
|
14842
|
+
// Drop succeeded one-shot jobs instead of retaining them forever.
|
|
14843
|
+
// Agenda guards this on `!nextRunAt`, so repeating jobs survive.
|
|
14844
|
+
removeOnComplete: true
|
|
14839
14845
|
});
|
|
14840
|
-
|
|
14846
|
+
const retentionDays = opts.failedJobRetentionDays ?? DEFAULT_FAILED_RETENTION_DAYS;
|
|
14847
|
+
try {
|
|
14848
|
+
await opts.db.collection(collectionName).createIndex({ failedAt: 1 }, { sparse: true });
|
|
14849
|
+
} catch (err) {
|
|
14850
|
+
console.error("mailery: could not index the Agenda jobs collection on failedAt", err);
|
|
14851
|
+
}
|
|
14852
|
+
return new _AgendaDriver(agenda, agendaMod, opts.db, collectionName, retentionDays);
|
|
14841
14853
|
}
|
|
14842
14854
|
db;
|
|
14843
14855
|
collName;
|
|
14844
|
-
|
|
14856
|
+
failedRetentionDays;
|
|
14857
|
+
sweepTimer = null;
|
|
14858
|
+
constructor(agenda, agendaMod, db, collectionName, failedRetentionDays) {
|
|
14845
14859
|
this.agenda = agenda;
|
|
14846
14860
|
this.agendaMod = agendaMod;
|
|
14847
14861
|
this.db = db;
|
|
14848
14862
|
this.collName = collectionName;
|
|
14863
|
+
this.failedRetentionDays = failedRetentionDays;
|
|
14849
14864
|
this.queues = {
|
|
14850
14865
|
tick: this.makeQueueAPI(QUEUE_NAMES2.tick),
|
|
14851
14866
|
advance: this.makeQueueAPI(QUEUE_NAMES2.advance),
|
|
@@ -14926,9 +14941,45 @@ var AgendaDriver = class _AgendaDriver {
|
|
|
14926
14941
|
if (!this.started) {
|
|
14927
14942
|
await this.agenda.start();
|
|
14928
14943
|
this.started = true;
|
|
14944
|
+
this.startFailedJobSweep();
|
|
14945
|
+
}
|
|
14946
|
+
}
|
|
14947
|
+
/**
|
|
14948
|
+
* Agenda's auto-remove only fires on success, so failed documents would
|
|
14949
|
+
* otherwise be retained forever. Runs once on worker start, then hourly.
|
|
14950
|
+
*/
|
|
14951
|
+
startFailedJobSweep() {
|
|
14952
|
+
if (this.failedRetentionDays <= 0 || this.sweepTimer) return;
|
|
14953
|
+
void this.sweepFailedJobs();
|
|
14954
|
+
this.sweepTimer = setInterval(() => void this.sweepFailedJobs(), FAILED_SWEEP_INTERVAL_MS);
|
|
14955
|
+
this.sweepTimer.unref?.();
|
|
14956
|
+
}
|
|
14957
|
+
/** Delete failed, fully-retired job documents older than the retention window. */
|
|
14958
|
+
async sweepFailedJobs() {
|
|
14959
|
+
const cutoff = new Date(Date.now() - this.failedRetentionDays * 24 * 3600 * 1e3);
|
|
14960
|
+
try {
|
|
14961
|
+
const res = await this.jobsCollection().deleteMany({
|
|
14962
|
+
failedAt: { $lt: cutoff },
|
|
14963
|
+
// Never touch the repeating tick.
|
|
14964
|
+
repeatInterval: { $in: [null, void 0] },
|
|
14965
|
+
// Leave anything still scheduled for a retry, and anything a worker
|
|
14966
|
+
// currently holds a lock on.
|
|
14967
|
+
$and: [
|
|
14968
|
+
{ $or: [{ nextRunAt: null }, { nextRunAt: { $exists: false } }, { nextRunAt: { $lt: cutoff } }] },
|
|
14969
|
+
{ $or: [{ lockedAt: null }, { lockedAt: { $exists: false } }, { lockedAt: { $lt: cutoff } }] }
|
|
14970
|
+
]
|
|
14971
|
+
});
|
|
14972
|
+
return res.deletedCount ?? 0;
|
|
14973
|
+
} catch (err) {
|
|
14974
|
+
console.error("mailery: failed-job sweep failed", err);
|
|
14975
|
+
return 0;
|
|
14929
14976
|
}
|
|
14930
14977
|
}
|
|
14931
14978
|
async stopWorkers() {
|
|
14979
|
+
if (this.sweepTimer) {
|
|
14980
|
+
clearInterval(this.sweepTimer);
|
|
14981
|
+
this.sweepTimer = null;
|
|
14982
|
+
}
|
|
14932
14983
|
if (!this.started) return;
|
|
14933
14984
|
await this.agenda.stop();
|
|
14934
14985
|
this.started = false;
|
|
@@ -14942,6 +14993,15 @@ var AgendaDriver = class _AgendaDriver {
|
|
|
14942
14993
|
await this.stopWorkers();
|
|
14943
14994
|
}
|
|
14944
14995
|
};
|
|
14996
|
+
function collectionFor(prefix) {
|
|
14997
|
+
if (!prefix) return DEFAULT_COLLECTION;
|
|
14998
|
+
if (!/^[A-Za-z0-9_-]+$/.test(prefix)) {
|
|
14999
|
+
throw new Error(
|
|
15000
|
+
`mailery: queue prefix "${prefix}" must contain only letters, digits, '_' or '-' \u2014 it becomes part of a MongoDB collection name.`
|
|
15001
|
+
);
|
|
15002
|
+
}
|
|
15003
|
+
return `${DEFAULT_COLLECTION}_${prefix}`;
|
|
15004
|
+
}
|
|
14945
15005
|
|
|
14946
15006
|
// src/server/queues/noop.ts
|
|
14947
15007
|
function noopQueueAPI() {
|
|
@@ -14978,7 +15038,9 @@ async function createQueueDriver(config, fallbackDb) {
|
|
|
14978
15038
|
db: config.db ?? fallbackDb,
|
|
14979
15039
|
processEverySeconds: config.processEverySeconds,
|
|
14980
15040
|
lockLifetimeSeconds: config.lockLifetimeSeconds,
|
|
14981
|
-
collectionName: config.collectionName
|
|
15041
|
+
collectionName: config.collectionName,
|
|
15042
|
+
prefix: config.prefix,
|
|
15043
|
+
failedJobRetentionDays: config.failedJobRetentionDays
|
|
14982
15044
|
});
|
|
14983
15045
|
case "noop":
|
|
14984
15046
|
return new NoopDriver();
|
|
@@ -17418,7 +17480,8 @@ var Mailer = class _Mailer {
|
|
|
17418
17480
|
* MAILER_MONGODB_URI — Mongo connection string (required)
|
|
17419
17481
|
* MAILER_MONGODB_DB — database name (optional; defaults to the URI default)
|
|
17420
17482
|
* MAILER_REDIS_URL — Redis connection URL (required)
|
|
17421
|
-
* MAILER_QUEUE_PREFIX —
|
|
17483
|
+
* MAILER_QUEUE_PREFIX — namespaces this instance (optional). Bull: Redis key
|
|
17484
|
+
* prefix. Agenda: jobs collection suffix.
|
|
17422
17485
|
* MAILER_PUBLIC_URL — base for tracking/unsub URLs (required)
|
|
17423
17486
|
* MAILER_UNSUBSCRIBE_SECRET — HMAC key (required)
|
|
17424
17487
|
* MAILER_SENDER_ADDRESS — postal address for CAN-SPAM (optional)
|
|
@@ -17467,7 +17530,7 @@ var Mailer = class _Mailer {
|
|
|
17467
17530
|
}
|
|
17468
17531
|
const defaultProvider = env.MAILER_DEFAULT_PROVIDER ?? Object.keys(providers)[0];
|
|
17469
17532
|
const driverEnv = env.MAILER_QUEUE_DRIVER ?? "bull";
|
|
17470
|
-
const queue = driverEnv === "agenda" ? { driver: "agenda" } : driverEnv === "noop" ? { driver: "noop" } : {
|
|
17533
|
+
const queue = driverEnv === "agenda" ? { driver: "agenda", prefix: env.MAILER_QUEUE_PREFIX } : driverEnv === "noop" ? { driver: "noop" } : {
|
|
17471
17534
|
driver: "bull",
|
|
17472
17535
|
redis: { url: required("MAILER_REDIS_URL") },
|
|
17473
17536
|
prefix: env.MAILER_QUEUE_PREFIX
|