mailery 0.1.2 → 0.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.
- package/README.md +4 -4
- package/dist/index.cjs +341 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +341 -131
- package/dist/index.js.map +1 -1
- package/dist/{null-OzIqP7A8.d.cts → null-CWw3Gpbl.d.cts} +65 -43
- package/dist/{null-OzIqP7A8.d.ts → null-CWw3Gpbl.d.ts} +65 -43
- package/dist/testing.cjs +346 -136
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +346 -136
- package/dist/testing.js.map +1 -1
- package/package.json +29 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Db, ObjectId, Collection, ClientSession } from 'mongodb';
|
|
2
|
-
import IORedis from 'ioredis';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
import Handlebars from 'handlebars';
|
|
4
|
+
import IORedis from 'ioredis';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Shared types — used by both server and client. Stub placeholders for Phase 0.
|
|
@@ -310,6 +310,61 @@ declare const sendOneOffInputSchema: z.ZodObject<{
|
|
|
310
310
|
}, z.core.$strip>;
|
|
311
311
|
type SendOneOffInput = z.infer<typeof sendOneOffInputSchema>;
|
|
312
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Queue driver abstraction. Mailer talks to a `QueueDriver` whose only required
|
|
315
|
+
* vocabulary is "add a job to one of four named queues" and "spin up a worker
|
|
316
|
+
* per queue with these handlers." Concrete drivers (BullMQ, @hokify/agenda,
|
|
317
|
+
* noop) implement this surface.
|
|
318
|
+
*
|
|
319
|
+
* Mailery targets single-process deployments; drivers are not expected to
|
|
320
|
+
* coordinate state across processes. Rate-limit semantics are documented per
|
|
321
|
+
* driver below.
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
/** Per-call options passed to `queue.add(name, data, opts)`. */
|
|
325
|
+
interface AddOptions {
|
|
326
|
+
/** Defer execution by this many ms. */
|
|
327
|
+
delay?: number;
|
|
328
|
+
/** Max attempts including the initial run. Driver may apply per-define instead of per-add. */
|
|
329
|
+
attempts?: number;
|
|
330
|
+
/** Exponential backoff base delay (ms). Driver may apply per-define instead of per-add. */
|
|
331
|
+
backoff?: {
|
|
332
|
+
type: 'exponential';
|
|
333
|
+
delay: number;
|
|
334
|
+
};
|
|
335
|
+
/** Idempotency key: if a pending job with this id already exists for this queue, the add is a no-op. */
|
|
336
|
+
jobId?: string;
|
|
337
|
+
}
|
|
338
|
+
interface QueueAPI {
|
|
339
|
+
add(name: string, data: unknown, opts?: AddOptions): Promise<unknown>;
|
|
340
|
+
/** Approximate count of jobs eligible-but-not-running. Used by broadcast backpressure. */
|
|
341
|
+
getWaitingCount(): Promise<number>;
|
|
342
|
+
close(): Promise<void>;
|
|
343
|
+
}
|
|
344
|
+
interface Queues {
|
|
345
|
+
tick: QueueAPI;
|
|
346
|
+
advance: QueueAPI;
|
|
347
|
+
send: QueueAPI;
|
|
348
|
+
webhook: QueueAPI;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Driver selection at Mailer.init time. The driver and its connection are
|
|
352
|
+
* declared explicitly rather than inferred — this keeps the wiring obvious and
|
|
353
|
+
* lets hosts mix-and-match (e.g. Bull in prod, noop in tests).
|
|
354
|
+
*/
|
|
355
|
+
type QueueDriverConfig = {
|
|
356
|
+
driver: 'bull';
|
|
357
|
+
redis: RedisOptions | IORedis;
|
|
358
|
+
} | {
|
|
359
|
+
driver: 'agenda';
|
|
360
|
+
db?: Db;
|
|
361
|
+
processEverySeconds?: number;
|
|
362
|
+
lockLifetimeSeconds?: number;
|
|
363
|
+
collectionName?: string;
|
|
364
|
+
} | {
|
|
365
|
+
driver: 'noop';
|
|
366
|
+
};
|
|
367
|
+
|
|
313
368
|
/**
|
|
314
369
|
* Mailer configuration shape. Required + optional surfaces with sane defaults.
|
|
315
370
|
*/
|
|
@@ -337,10 +392,12 @@ interface MailerConfig {
|
|
|
337
392
|
collectionPrefix?: string;
|
|
338
393
|
adapter: ContactAdapter;
|
|
339
394
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
395
|
+
* Queue driver selection. One of:
|
|
396
|
+
* - `{ driver: 'bull', redis: ... }` — BullMQ (default for prod; requires Redis)
|
|
397
|
+
* - `{ driver: 'agenda' }` — @hokify/agenda using this Mongo (no Redis required, single-process)
|
|
398
|
+
* - `{ driver: 'noop' }` — no background workers (tests, synchronous-only hosts)
|
|
342
399
|
*/
|
|
343
|
-
|
|
400
|
+
queue: QueueDriverConfig;
|
|
344
401
|
providers: Record<string, MailProvider>;
|
|
345
402
|
defaultProvider: string;
|
|
346
403
|
defaultTransactionalProvider?: string;
|
|
@@ -641,6 +698,8 @@ interface SendDoc {
|
|
|
641
698
|
unsubscribedAt: Date | null;
|
|
642
699
|
complainedAt: Date | null;
|
|
643
700
|
queuedAt: Date;
|
|
701
|
+
/** Last time the row was mutated; used by the stranded-send sweep. */
|
|
702
|
+
updatedAt: Date;
|
|
644
703
|
sentAt: Date | null;
|
|
645
704
|
deliveredAt: Date | null;
|
|
646
705
|
}
|
|
@@ -804,42 +863,6 @@ declare class EventRegistry {
|
|
|
804
863
|
deriveKey(name: string, externalId: string, passedKey: string | undefined, now: Date): string | null;
|
|
805
864
|
}
|
|
806
865
|
|
|
807
|
-
/**
|
|
808
|
-
* BullMQ wiring: four queues + the corresponding worker factories.
|
|
809
|
-
*
|
|
810
|
-
* mailer:tick → recovery sweep + event-trigger scan + scheduled broadcasts + outbox drain
|
|
811
|
-
* mailer:advance → per-flow_run wakeup at nextActionAt (delayed jobs)
|
|
812
|
-
* mailer:send → provider dispatch for a single send row
|
|
813
|
-
* mailer:webhook → async normalization + apply of inbound provider events
|
|
814
|
-
*
|
|
815
|
-
* The Mailer class instantiates queues at init() and workers at startWorkers().
|
|
816
|
-
* Job handlers themselves live in `runner/` and `api/webhook-processor.ts`.
|
|
817
|
-
*/
|
|
818
|
-
|
|
819
|
-
/**
|
|
820
|
-
* Minimal queue surface the runner depends on. Production wraps BullMQ; tests
|
|
821
|
-
* can supply a no-op implementation.
|
|
822
|
-
*/
|
|
823
|
-
interface QueueAPI {
|
|
824
|
-
add(name: string, data: unknown, opts?: {
|
|
825
|
-
delay?: number;
|
|
826
|
-
attempts?: number;
|
|
827
|
-
backoff?: {
|
|
828
|
-
type: 'exponential';
|
|
829
|
-
delay: number;
|
|
830
|
-
};
|
|
831
|
-
jobId?: string;
|
|
832
|
-
}): Promise<unknown>;
|
|
833
|
-
getWaitingCount(): Promise<number>;
|
|
834
|
-
close(): Promise<void>;
|
|
835
|
-
}
|
|
836
|
-
interface Queues {
|
|
837
|
-
tick: QueueAPI;
|
|
838
|
-
advance: QueueAPI;
|
|
839
|
-
send: QueueAPI;
|
|
840
|
-
webhook: QueueAPI;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
866
|
/**
|
|
844
867
|
* Runner context + public entry points. The shared context object is passed
|
|
845
868
|
* to every handler so the runner stays a pure function over (state, action).
|
|
@@ -868,12 +891,11 @@ declare class Mailer {
|
|
|
868
891
|
readonly collections: Collections;
|
|
869
892
|
readonly adapter: ContactAdapter;
|
|
870
893
|
readonly providers: Record<string, MailProvider>;
|
|
871
|
-
readonly redis: IORedis | null;
|
|
872
894
|
readonly queues: Queues;
|
|
873
895
|
readonly config: ResolvedConfig;
|
|
874
896
|
readonly events: EventRegistry;
|
|
875
|
-
private
|
|
876
|
-
private
|
|
897
|
+
private queueDriver;
|
|
898
|
+
private workersStarted;
|
|
877
899
|
private runnerContext;
|
|
878
900
|
private constructor();
|
|
879
901
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Db, ObjectId, Collection, ClientSession } from 'mongodb';
|
|
2
|
-
import IORedis from 'ioredis';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
import Handlebars from 'handlebars';
|
|
4
|
+
import IORedis from 'ioredis';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Shared types — used by both server and client. Stub placeholders for Phase 0.
|
|
@@ -310,6 +310,61 @@ declare const sendOneOffInputSchema: z.ZodObject<{
|
|
|
310
310
|
}, z.core.$strip>;
|
|
311
311
|
type SendOneOffInput = z.infer<typeof sendOneOffInputSchema>;
|
|
312
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Queue driver abstraction. Mailer talks to a `QueueDriver` whose only required
|
|
315
|
+
* vocabulary is "add a job to one of four named queues" and "spin up a worker
|
|
316
|
+
* per queue with these handlers." Concrete drivers (BullMQ, @hokify/agenda,
|
|
317
|
+
* noop) implement this surface.
|
|
318
|
+
*
|
|
319
|
+
* Mailery targets single-process deployments; drivers are not expected to
|
|
320
|
+
* coordinate state across processes. Rate-limit semantics are documented per
|
|
321
|
+
* driver below.
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
/** Per-call options passed to `queue.add(name, data, opts)`. */
|
|
325
|
+
interface AddOptions {
|
|
326
|
+
/** Defer execution by this many ms. */
|
|
327
|
+
delay?: number;
|
|
328
|
+
/** Max attempts including the initial run. Driver may apply per-define instead of per-add. */
|
|
329
|
+
attempts?: number;
|
|
330
|
+
/** Exponential backoff base delay (ms). Driver may apply per-define instead of per-add. */
|
|
331
|
+
backoff?: {
|
|
332
|
+
type: 'exponential';
|
|
333
|
+
delay: number;
|
|
334
|
+
};
|
|
335
|
+
/** Idempotency key: if a pending job with this id already exists for this queue, the add is a no-op. */
|
|
336
|
+
jobId?: string;
|
|
337
|
+
}
|
|
338
|
+
interface QueueAPI {
|
|
339
|
+
add(name: string, data: unknown, opts?: AddOptions): Promise<unknown>;
|
|
340
|
+
/** Approximate count of jobs eligible-but-not-running. Used by broadcast backpressure. */
|
|
341
|
+
getWaitingCount(): Promise<number>;
|
|
342
|
+
close(): Promise<void>;
|
|
343
|
+
}
|
|
344
|
+
interface Queues {
|
|
345
|
+
tick: QueueAPI;
|
|
346
|
+
advance: QueueAPI;
|
|
347
|
+
send: QueueAPI;
|
|
348
|
+
webhook: QueueAPI;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Driver selection at Mailer.init time. The driver and its connection are
|
|
352
|
+
* declared explicitly rather than inferred — this keeps the wiring obvious and
|
|
353
|
+
* lets hosts mix-and-match (e.g. Bull in prod, noop in tests).
|
|
354
|
+
*/
|
|
355
|
+
type QueueDriverConfig = {
|
|
356
|
+
driver: 'bull';
|
|
357
|
+
redis: RedisOptions | IORedis;
|
|
358
|
+
} | {
|
|
359
|
+
driver: 'agenda';
|
|
360
|
+
db?: Db;
|
|
361
|
+
processEverySeconds?: number;
|
|
362
|
+
lockLifetimeSeconds?: number;
|
|
363
|
+
collectionName?: string;
|
|
364
|
+
} | {
|
|
365
|
+
driver: 'noop';
|
|
366
|
+
};
|
|
367
|
+
|
|
313
368
|
/**
|
|
314
369
|
* Mailer configuration shape. Required + optional surfaces with sane defaults.
|
|
315
370
|
*/
|
|
@@ -337,10 +392,12 @@ interface MailerConfig {
|
|
|
337
392
|
collectionPrefix?: string;
|
|
338
393
|
adapter: ContactAdapter;
|
|
339
394
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
395
|
+
* Queue driver selection. One of:
|
|
396
|
+
* - `{ driver: 'bull', redis: ... }` — BullMQ (default for prod; requires Redis)
|
|
397
|
+
* - `{ driver: 'agenda' }` — @hokify/agenda using this Mongo (no Redis required, single-process)
|
|
398
|
+
* - `{ driver: 'noop' }` — no background workers (tests, synchronous-only hosts)
|
|
342
399
|
*/
|
|
343
|
-
|
|
400
|
+
queue: QueueDriverConfig;
|
|
344
401
|
providers: Record<string, MailProvider>;
|
|
345
402
|
defaultProvider: string;
|
|
346
403
|
defaultTransactionalProvider?: string;
|
|
@@ -641,6 +698,8 @@ interface SendDoc {
|
|
|
641
698
|
unsubscribedAt: Date | null;
|
|
642
699
|
complainedAt: Date | null;
|
|
643
700
|
queuedAt: Date;
|
|
701
|
+
/** Last time the row was mutated; used by the stranded-send sweep. */
|
|
702
|
+
updatedAt: Date;
|
|
644
703
|
sentAt: Date | null;
|
|
645
704
|
deliveredAt: Date | null;
|
|
646
705
|
}
|
|
@@ -804,42 +863,6 @@ declare class EventRegistry {
|
|
|
804
863
|
deriveKey(name: string, externalId: string, passedKey: string | undefined, now: Date): string | null;
|
|
805
864
|
}
|
|
806
865
|
|
|
807
|
-
/**
|
|
808
|
-
* BullMQ wiring: four queues + the corresponding worker factories.
|
|
809
|
-
*
|
|
810
|
-
* mailer:tick → recovery sweep + event-trigger scan + scheduled broadcasts + outbox drain
|
|
811
|
-
* mailer:advance → per-flow_run wakeup at nextActionAt (delayed jobs)
|
|
812
|
-
* mailer:send → provider dispatch for a single send row
|
|
813
|
-
* mailer:webhook → async normalization + apply of inbound provider events
|
|
814
|
-
*
|
|
815
|
-
* The Mailer class instantiates queues at init() and workers at startWorkers().
|
|
816
|
-
* Job handlers themselves live in `runner/` and `api/webhook-processor.ts`.
|
|
817
|
-
*/
|
|
818
|
-
|
|
819
|
-
/**
|
|
820
|
-
* Minimal queue surface the runner depends on. Production wraps BullMQ; tests
|
|
821
|
-
* can supply a no-op implementation.
|
|
822
|
-
*/
|
|
823
|
-
interface QueueAPI {
|
|
824
|
-
add(name: string, data: unknown, opts?: {
|
|
825
|
-
delay?: number;
|
|
826
|
-
attempts?: number;
|
|
827
|
-
backoff?: {
|
|
828
|
-
type: 'exponential';
|
|
829
|
-
delay: number;
|
|
830
|
-
};
|
|
831
|
-
jobId?: string;
|
|
832
|
-
}): Promise<unknown>;
|
|
833
|
-
getWaitingCount(): Promise<number>;
|
|
834
|
-
close(): Promise<void>;
|
|
835
|
-
}
|
|
836
|
-
interface Queues {
|
|
837
|
-
tick: QueueAPI;
|
|
838
|
-
advance: QueueAPI;
|
|
839
|
-
send: QueueAPI;
|
|
840
|
-
webhook: QueueAPI;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
866
|
/**
|
|
844
867
|
* Runner context + public entry points. The shared context object is passed
|
|
845
868
|
* to every handler so the runner stays a pure function over (state, action).
|
|
@@ -868,12 +891,11 @@ declare class Mailer {
|
|
|
868
891
|
readonly collections: Collections;
|
|
869
892
|
readonly adapter: ContactAdapter;
|
|
870
893
|
readonly providers: Record<string, MailProvider>;
|
|
871
|
-
readonly redis: IORedis | null;
|
|
872
894
|
readonly queues: Queues;
|
|
873
895
|
readonly config: ResolvedConfig;
|
|
874
896
|
readonly events: EventRegistry;
|
|
875
|
-
private
|
|
876
|
-
private
|
|
897
|
+
private queueDriver;
|
|
898
|
+
private workersStarted;
|
|
877
899
|
private runnerContext;
|
|
878
900
|
private constructor();
|
|
879
901
|
/**
|