@bumail/queue 0.11.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.
Files changed (44) hide show
  1. package/README.md +47 -1
  2. package/dist/conformance/claims.d.ts +4 -0
  3. package/dist/conformance/claims.d.ts.map +1 -0
  4. package/dist/conformance/complete-dsns.d.ts +4 -0
  5. package/dist/conformance/complete-dsns.d.ts.map +1 -0
  6. package/dist/conformance/concurrency.d.ts +4 -0
  7. package/dist/conformance/concurrency.d.ts.map +1 -0
  8. package/dist/conformance/describe-queue-store.d.ts +11 -0
  9. package/dist/conformance/describe-queue-store.d.ts.map +1 -0
  10. package/dist/conformance/doubt.d.ts +5 -0
  11. package/dist/conformance/doubt.d.ts.map +1 -0
  12. package/dist/conformance/dsn.d.ts +4 -0
  13. package/dist/conformance/dsn.d.ts.map +1 -0
  14. package/dist/conformance/index.d.ts +3 -0
  15. package/dist/conformance/index.d.ts.map +1 -0
  16. package/dist/conformance/index.js +820 -0
  17. package/dist/conformance/index.js.map +18 -0
  18. package/dist/conformance/items.d.ts +4 -0
  19. package/dist/conformance/items.d.ts.map +1 -0
  20. package/dist/conformance/outcomes.d.ts +4 -0
  21. package/dist/conformance/outcomes.d.ts.map +1 -0
  22. package/dist/conformance/setup.d.ts +40 -0
  23. package/dist/conformance/setup.d.ts.map +1 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +77 -8
  27. package/dist/index.js.map +10 -9
  28. package/dist/queue/attempt.d.ts.map +1 -1
  29. package/dist/queue/events.d.ts +7 -0
  30. package/dist/queue/events.d.ts.map +1 -1
  31. package/dist/queue/fallback-session.d.ts.map +1 -1
  32. package/dist/queue/options.d.ts +15 -1
  33. package/dist/queue/options.d.ts.map +1 -1
  34. package/dist/queue/session.d.ts +5 -1
  35. package/dist/queue/session.d.ts.map +1 -1
  36. package/dist/queue/settings/index.d.ts +5 -2
  37. package/dist/queue/settings/index.d.ts.map +1 -1
  38. package/dist/queue/settings/sender.d.ts +16 -0
  39. package/dist/queue/settings/sender.d.ts.map +1 -0
  40. package/dist/queue/worker.d.ts.map +1 -1
  41. package/docs/guide.md +119 -3
  42. package/docs/roadmap.md +20 -6
  43. package/docs/troubleshooting.md +68 -0
  44. package/package.json +10 -4
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/queue/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,8GAA8G;AAC9G,MAAM,MAAM,MAAM,GAAG,CACpB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,eAAe,KACpB,OAAO,CAAC,cAAc,CAAC,CAAC;AAE7B,qGAAqG;AACrG,MAAM,WAAW,KAAK;IACrB,GAAG,IAAI,MAAM,CAAC;CACd;AAED,iGAAiG;AACjG,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,gGAAgG;AAChG,MAAM,MAAM,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC;AAErC;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB;IACjB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC3B,6BAA6B;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,iEAAiE;IACjE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,YAAY;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,mGAAmG;IACnG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAClD;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,uGAAuG;IACvG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,8HAA8H;IAC9H,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CAC/B"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/queue/options.ts"],"names":[],"mappings":"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"}
@@ -3,12 +3,16 @@ import type { DeliveryContext } from './deliver';
3
3
  import type { RecipientEvent } from './events';
4
4
  import type { Route } from './options';
5
5
  /** The route a recipient's last session took, as its events tell it. */
6
- export type Used = Pick<RecipientEvent, 'route' | 'fallback' | 'mxReply'>;
6
+ export type Used = Pick<RecipientEvent, 'route' | 'fallback' | 'mxReply' | 'reused'>;
7
7
  /** One session's outcomes, and the error that ended it, if any. */
8
8
  export interface Attempt {
9
9
  readonly updates: RecipientUpdate[];
10
10
  readonly error?: unknown;
11
+ /** The session went over a connection that had carried a message before. */
12
+ readonly reused?: boolean;
11
13
  }
14
+ /** What `used` keeps of a session's connection: `reused` when the sender said. */
15
+ export declare const reusedOf: (a: Attempt) => Pick<Used, "reused">;
12
16
  /** One session, by `route` (the domain's own when none is given): each recipient's outcome, whatever happened. */
13
17
  export declare function session(ctx: DeliveryContext, item: QueueItem, message: Uint8Array, domain: string, group: readonly string[], route: Route, override?: Route): Promise<Attempt>;
14
18
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/queue/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAyCvC,wEAAwE;AACxE,MAAM,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC,CAAC;AAE1E,mEAAmE;AACnE,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,kHAAkH;AAClH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,EAAE,KAAK,EACZ,QAAQ,CAAC,EAAE,KAAK,GACd,OAAO,CAAC,OAAO,CAAC,CAsBlB"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/queue/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAyCvC,wEAAwE;AACxE,MAAM,MAAM,IAAI,GAAG,IAAI,CACtB,cAAc,EACd,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAC3C,CAAC;AAEF,mEAAmE;AACnE,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,kFAAkF;AAClF,eAAO,MAAM,QAAQ,GAAI,GAAG,OAAO,KAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,CACN,CAAC;AAEpD,kHAAkH;AAClH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,EAAE,KAAK,EACZ,QAAQ,CAAC,EAAE,KAAK,GACd,OAAO,CAAC,OAAO,CAAC,CAyBlB"}
@@ -1,6 +1,7 @@
1
- import type { QueueOptions, Route, Sender } from '../options';
1
+ import type { QueueOptions, Route } from '../options';
2
2
  import { type DsnSettings } from './dsn';
3
3
  import { type LimitSettings, type RetrySettings } from './limits';
4
+ import { type SenderSettings } from './sender';
4
5
  export type { DsnSettings, LimitSettings, RetrySettings };
5
6
  /** Every option checked, with its default. */
6
7
  export interface Settings {
@@ -17,7 +18,9 @@ export interface Settings {
17
18
  readonly pollInterval: number;
18
19
  readonly owner: string;
19
20
  readonly now: () => number;
20
- readonly send: Sender;
21
+ readonly send: SenderSettings['send'];
22
+ /** Closes the queue's own connection pool, if it has one. */
23
+ readonly close: SenderSettings['close'];
21
24
  readonly random: () => number;
22
25
  }
23
26
  /** The options, checked, with their defaults; `INVALID` names the first wrong one. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/queue/settings/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,OAAO,CAAC;AAChD,OAAO,EACN,KAAK,aAAa,EAElB,KAAK,aAAa,EAElB,MAAM,UAAU,CAAC;AAKlB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AAE1D,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B;AAKD,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,CAkD1D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/queue/settings/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,OAAO,CAAC;AAChD,OAAO,EACN,KAAK,aAAa,EAElB,KAAK,aAAa,EAElB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,UAAU,CAAC;AAGzD,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AAE1D,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B;AAKD,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,CAoD1D"}
@@ -0,0 +1,16 @@
1
+ import type { QueueOptions, ReuseOptions, Sender } from '../options';
2
+ /** The sender the queue delivers with, and what stops it. */
3
+ export interface SenderSettings {
4
+ readonly send: Sender;
5
+ /** Closes the pool the queue owns, once its deliveries end; nothing when the sender is not the queue's. */
6
+ readonly close: () => Promise<void>;
7
+ }
8
+ /** `reuse` shaped: the limits given (the pool owns their defaults and checks them), or `false` for one connection per send. */
9
+ export declare function reuseOf(options: QueueOptions): ReuseOptions | false;
10
+ /**
11
+ * Your `send`, untouched; else a pool the queue owns (made on first use,
12
+ * and again after a `close()`, since `stop()` then `start()` is allowed);
13
+ * else `sendMail` with `reuse: false`.
14
+ */
15
+ export declare function senderOf(options: QueueOptions): SenderSettings;
16
+ //# sourceMappingURL=sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../../src/queue/settings/sender.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAErE,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2GAA2G;IAC3G,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,+HAA+H;AAC/H,wBAAgB,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,GAAG,KAAK,CAcnE;AAgBD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,cAAc,CAiB9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/queue/worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,MAAM;;gBAaN,GAAG,EAAE,aAAa;IAsD9B;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAgCnC,4FAA4F;IAC5F,IAAI,IAAI,IAAI;IAKZ,KAAK,IAAI,IAAI;IAqCb;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAa3B"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/queue/worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,MAAM;;gBAaN,GAAG,EAAE,aAAa;IAsD9B;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAgCnC,4FAA4F;IAC5F,IAAI,IAAI,IAAI;IAKZ,KAAK,IAAI,IAAI;IAqCb;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAiB3B"}
package/docs/guide.md CHANGED
@@ -260,6 +260,39 @@ createQueue({
260
260
 
261
261
  No resolver is needed when no route is `'mx'`.
262
262
 
263
+ ### Reusing connections
264
+
265
+ By default the queue sends through a pool of connections it owns: a
266
+ session to a server the queue already has an idle connection to (same
267
+ host, address, port, EHLO name, TLS and credentials) sends its message
268
+ over it, after an `RSET`, instead of opening a connection, a TLS
269
+ handshake and an `AUTH` again. A connection carries 100 messages and
270
+ waits 5 seconds idle, then closes. A `delivered`, `deferred` or `failed`
271
+ event carries `reused: true` when the recipient's last session went over a
272
+ connection that had carried a message before, `false` when it opened one.
273
+
274
+ ```ts
275
+ createQueue({
276
+ store,
277
+ hostname: 'mail.example.net',
278
+ resolver,
279
+ reuse: { maxMessages: 50, idleSeconds: 10 }, // or the defaults, or `false`
280
+ });
281
+ ```
282
+
283
+ `reuse: false` opens one connection per session, as `sendMail` does. A
284
+ `send` of your own is called as given: the queue never wraps it in a pool,
285
+ and `reuse` does nothing with it, though `createQueue` still checks it.
286
+ `reused` is on the events only if your `send` returns it.
287
+
288
+ `stop()` closes the pool once the deliveries under way end, so no socket
289
+ keeps the process alive; `start()` after it makes a new one. A connection
290
+ that went stale while idle (the server closed it) is never a reason to
291
+ fall back to a smarthost: the pool opens a new one to the same server.
292
+ `@bumail/server` builds its own pool for its `send` and closes it after
293
+ `queue.stop()`. The rules of [Falling back](#falling-back-to-a-smarthost) are
294
+ the same with or without the pool.
295
+
263
296
  ### Timeouts
264
297
 
265
298
  `timeouts` and `deadline` pass to `sendMail`: each step's timeout in
@@ -1046,8 +1079,90 @@ codes, and keeps its promises:
1046
1079
  The queue never gives a store such text: it cleans every reply.
1047
1080
 
1048
1081
  The contract's specs, `describeQueueStore`, which every store here runs,
1049
- are internal for now: a store written outside this package cannot run
1050
- them yet.
1082
+ are exported as `@bumail/queue/conformance`: see
1083
+ [Holding your own store to the contract](#holding-your-own-store-to-the-contract).
1084
+
1085
+ ## Holding your own store to the contract
1086
+
1087
+ A store you write (over a file, a database, a service) is only a
1088
+ `QueueStore` if it answers the contract above to the letter, in the cases
1089
+ nobody thinks of: a lease that expired, a DSN that fails the checks, a
1090
+ second process claiming at the same time. `describeQueueStore` is the
1091
+ suite the memory, SQLite, PostgreSQL and Redis stores run; call
1092
+ it from a spec of yours and it registers them.
1093
+
1094
+ **It needs Bun.** The suite imports `bun:test`, so the spec runs with
1095
+ `bun test`, not with Node, Vitest or Jest. It is a development
1096
+ dependency: install `@bumail/queue` and write the spec.
1097
+
1098
+ ```sh
1099
+ bun add -d @bumail/queue
1100
+ ```
1101
+
1102
+ ```ts
1103
+ // my-store.spec.ts
1104
+ import { describeQueueStore } from '@bumail/queue/conformance';
1105
+ import { MyQueueStore } from './my-store';
1106
+
1107
+ describeQueueStore('MyQueueStore', {
1108
+ // a new store, empty: called once per test
1109
+ create: () => MyQueueStore.open({ url: process.env.MY_STORE_URL }),
1110
+ // another handle on the same queue, as a second connection or process
1111
+ share: (store) => (store as MyQueueStore).connectAgain(),
1112
+ // closes a store `create` made, after the test that used it
1113
+ dispose: (store) => (store as MyQueueStore).close(),
1114
+ });
1115
+ ```
1116
+
1117
+ ```sh
1118
+ bun test my-store.spec.ts
1119
+ ```
1120
+
1121
+ A spec project typechecked with `tsc` needs `@types/bun` (for `bun:test`), as
1122
+ the example's `devDependencies` have it.
1123
+
1124
+ [`examples/custom-store`](https://github.com/softistx/bumail/tree/develop/examples/custom-store)
1125
+ in the repository holds a complete one, a store over the memory store with its
1126
+ spec, that passes.
1127
+
1128
+ ### What it covers
1129
+
1130
+ | group | what is held |
1131
+ | --- | --- |
1132
+ | `items` | `add` keeps the envelope, every recipient `pending`, due at once; the null sender; `readMessage` and every item are copies; an unknown id, or one no store could hold, is `undefined`; `list` order and paging; `cancel`; `maxItems` and `QUEUE_FULL`; what is not an item is `INVALID`; the DSN parameters (`ret`, `envid`, `notify`, `orcpt`) and their limits |
1133
+ | `claims and leases` | `claim` takes the earliest due, then the oldest, and only what is due; an expired lease is claimed again and its old owner has lost it; `renew`; `reschedule` with and without an owner; a bad request is `INVALID` |
1134
+ | `outcomes` | `complete` records each recipient, the schedule and lets go of the lease; a finished item is dropped with its message; text with a NUL or a lone surrogate is refused; a final state never changes; the DSNs stored in the same step (new ids, in order, none on a lost lease, all refused when one is bad); `mayHaveReachedMx` and `mayHaveReachedFallback` recorded and kept |
1135
+ | `several workers` | many claimers on one queue deliver every item once; a crashed worker's items are claimed again when its leases expire |
1136
+
1137
+ ### `share`: a second handle
1138
+
1139
+ The two `several workers` tests need `share`, another handle on **the
1140
+ same queue**: a second connection to the same database, as a second
1141
+ process would open. Without it they are registered as skipped, named
1142
+ `… (skipped: harness has no share)`, because one handle cannot show that
1143
+ a claim is atomic across handles.
1144
+
1145
+ A store that lives in one process, such as an in-memory one, may return a
1146
+ second object over the same data, as the example does. A **networked**
1147
+ store (PostgreSQL, Redis, any server) should pass a real second
1148
+ connection: that is where two workers race, and where a claim that reads
1149
+ then writes loses. Passing none for a store like that leaves the one
1150
+ property that matters unproved.
1151
+
1152
+ ### `dispose`
1153
+
1154
+ `dispose(store)` runs after every test, for each store `create` made in
1155
+ that test, once the test is over (pass or fail). Use it to close a
1156
+ connection or remove a file. It may be async.
1157
+
1158
+ - Every created store is disposed, even when the disposal of another
1159
+ one fails.
1160
+ - One failure is rethrown as it is. Several in one test are thrown
1161
+ together as an `AggregateError` (`several stores failed to dispose`),
1162
+ each reason in its `errors`.
1163
+ - Handles returned by `share` are **not** passed to `dispose`: the
1164
+ harness closes them, for instance in the same `dispose`, from the store
1165
+ they were made from.
1051
1166
 
1052
1167
  ## Options
1053
1168
 
@@ -1077,5 +1192,6 @@ them yet.
1077
1192
  | `pollInterval` | `number`, to 2147483647 | 5000 | ms between passes of `start()` |
1078
1193
  | `owner` | `string` | a random UUID | this worker's name on its leases |
1079
1194
  | `clock` | `{ now(): number }` | `Date.now` | the time |
1080
- | `send` | `Sender` | `sendMail` | delivers one session; a custom one should return `dsnOffered` (leave it out and no `relayed` DSN is ever sent) |
1195
+ | `reuse` | `false \| { maxMessages?, idleSeconds? }` | `{ maxMessages: 100, idleSeconds: 5 }` | connection pool the queue owns and `stop()` closes; `false`: one connection per session; see [Reusing connections](#reusing-connections) |
1196
+ | `send` | `Sender` | the queue's pool, or `sendMail` with `reuse: false` | delivers one session; a custom one should return `dsnOffered` (leave it out and no `relayed` DSN is ever sent) |
1081
1197
  | `random` | `() => number` | `Math.random` | the jitter |
package/docs/roadmap.md CHANGED
@@ -6,7 +6,7 @@ only number on it.
6
6
 
7
7
  ## Now
8
8
 
9
- Nothing under way.
9
+ Nothing is under way.
10
10
 
11
11
  ## Next
12
12
 
@@ -15,14 +15,11 @@ Nothing under way.
15
15
  certificate checked against the policy's MX names, and the reports.
16
16
  - **DANE (RFC 7672)** — TLSA records checked for MX hosts, once a
17
17
  resolver that validates DNSSEC can be given to the queue.
18
- - **One session for several messages** — the queue hands several items
19
- for one domain to one SMTP session, once `@bumail/smtp`'s client reuses
20
- connections.
21
- - **The contract's specs, exported**, so a store written outside this
22
- package can hold itself to them.
23
18
 
24
19
  ## Later
25
20
 
21
+ - **A suite that is not tied to `bun:test`** — the contract's specs run under
22
+ any test runner, for a store written where Bun is not the runner.
26
23
  - **REQUIRETLS (RFC 8689)** — a message that asks for TLS all the way is
27
24
  never sent in clear, and fails rather than downgrade.
28
25
  - **Retries per recipient** — a schedule of its own for each recipient,
@@ -46,6 +43,23 @@ Nothing under way.
46
43
 
47
44
  ## Shipped
48
45
 
46
+ ### 0.13.0
47
+
48
+ - **One session for several messages** — the queue delivers several
49
+ messages for one MX over one connection, using `@bumail/smtp`'s pool of
50
+ reusable connections. On by default (100 messages per connection, idle 5
51
+ seconds); `reuse: false` turns it off, and a `send` of your own is never
52
+ wrapped. `stop()` closes the pool, and the events may carry `reused`. The
53
+ `QueueStore` contract does not change.
54
+
55
+ ### 0.12.0
56
+
57
+ - **The contract's specs, exported** — `@bumail/queue/conformance` exports
58
+ `describeQueueStore(name, { create, share?, dispose? })`, run under
59
+ `bun test`, so a queue store written outside this package runs the same
60
+ contract suite as the built-in ones; a capability a store lacks shows as a
61
+ skipped test with its reason. Additive.
62
+
49
63
  ### 0.11.0
50
64
 
51
65
  - **A DSN recorded in the same step as the outcome** — the failure, relayed
@@ -28,6 +28,9 @@ parts shown as … vary.
28
28
  - [`QueueError: timeouts.… must be a number of seconds above 0 and at most 2147483, not …`](#queueerror-timeouts-must-be-a-number-of-seconds-above-0-and-at-most-2147483-not-)
29
29
  - [`QueueError: store must be a QueueStore, such as MemoryQueueStore`](#queueerror-store-must-be-a-queuestore-such-as-memoryqueuestore)
30
30
  - [`QueueError: … must be an integer of at least …, not …`](#queueerror--must-be-an-integer-of-at-least--not-)
31
+ - [`QueueError: reuse must be false or an object of limits, not …`](#queueerror-reuse-must-be-false-or-an-object-of-limits-not-)
32
+ - [`QueueError: reuse.idleSeconds must be a number of seconds above 0 and at most 2147483, not …`](#queueerror-reuseidleseconds-must-be-a-number-of-seconds-above-0-and-at-most-2147483-not-)
33
+ - [`QueueError: reuse.maxMessages must be an integer of at least 1, not …`](#queueerror-reusemaxmessages-must-be-an-integer-of-at-least-1-not-)
31
34
  - [`QueueError: dsn.returnContent must be 'headers' or 'full', not …`](#queueerror-dsnreturncontent-must-be-headers-or-full-not-)
32
35
  - [`QueueError: createQueue() takes an options object`](#queueerror-createqueue-takes-an-options-object)
33
36
  - [`QueueError: owner must be a non-empty string`](#queueerror-owner-must-be-a-non-empty-string)
@@ -89,6 +92,11 @@ parts shown as … vary.
89
92
  - [`QueueError: reply.host must be a string`](#queueerror-replyhost-must-be-a-string)
90
93
  - [`QueueError: … holds a NUL or a lone surrogate, which a store cannot keep`](#queueerror--holds-a-nul-or-a-lone-surrogate-which-a-store-cannot-keep)
91
94
 
95
+ **A store's spec** (`describeQueueStore`)
96
+
97
+ - [`AggregateError: several stores failed to dispose`](#aggregateerror-several-stores-failed-to-dispose)
98
+ - [`… (skipped: harness has no share)` in the test names](#-skipped-harness-has-no-share-in-the-test-names)
99
+
92
100
  **Delivery**
93
101
 
94
102
  - [A recipient stays `deferred` with `4.4.1` or `4.4.2`](#a-recipient-stays-deferred-with-441-or-442)
@@ -297,6 +305,29 @@ or `… must be a number from … to …, not …`. A number option out of its r
297
305
  1), `dsn.delayAfter`, `limits.*` (`limits.maxReplyText` from 64 to
298
306
  900). Times are milliseconds.
299
307
 
308
+ ### `QueueError: reuse must be false or an object of limits, not …`
309
+
310
+ **Code:** `INVALID`. `reuse` was `true`, `null` or another value. Give
311
+ `false` for one connection per session, an object of limits, or leave it out
312
+ for the defaults.
313
+
314
+ ```ts
315
+ createQueue({ store, hostname, resolver, reuse: { maxMessages: 50 } });
316
+ createQueue({ store, hostname, resolver, reuse: false });
317
+ ```
318
+
319
+ ### `QueueError: reuse.idleSeconds must be a number of seconds above 0 and at most 2147483, not …`
320
+
321
+ **Code:** `INVALID`. `reuse.idleSeconds` is in seconds, not milliseconds:
322
+ `5`, not `5000`. `reuse.maxMessages` has its own entry below.
323
+
324
+ ### `QueueError: reuse.maxMessages must be an integer of at least 1, not …`
325
+
326
+ **Code:** `INVALID`. `reuse.maxMessages` is `0`, negative, fractional or not a
327
+ number. The pool owns the limits and checks them when `createQueue` runs;
328
+ the message is the pool's, prefixed with `reuse.`. Pass an integer of at
329
+ least 1, or leave it out for the default (100).
330
+
300
331
  ### `QueueError: dsn.returnContent must be 'headers' or 'full', not …`
301
332
 
302
333
  **Code:** `INVALID`.
@@ -545,6 +576,43 @@ it.
545
576
  default. The error from a transaction of your own on the same client is
546
577
  yours to retry.
547
578
 
579
+ ## Your own store's spec
580
+
581
+ ### `AggregateError: several stores failed to dispose`
582
+
583
+ `describeQueueStore`, after a test. The test created more than one store
584
+ (a spec that calls `create` again, as the several-workers tests do) and
585
+ the `dispose` of more than one of them threw. The suite disposes every
586
+ store it created, collects the failures and throws them together; each
587
+ reason is in `error.errors`. A single failure is thrown as it is, not
588
+ wrapped.
589
+
590
+ **Fix:** read `error.errors`; each entry is one store's failure. The suite
591
+ calls `dispose` once per created store, so the causes are a store's own
592
+ `close` failing (each entry says why), or a `create` that hands back a
593
+ shared object, so that each test's `dispose` closes the same thing. Have
594
+ `create` return a fresh store each call. Handles from `share` are not passed
595
+ to `dispose`; close them in the harness.
596
+
597
+ ```ts
598
+ create: () => new MyQueueStore(freshPath()), // a new store each call, never a shared one
599
+ ```
600
+
601
+ ### `… (skipped: harness has no share)` in the test names
602
+
603
+ `describeQueueStore`. The harness passed no `share`, so the two tests of
604
+ "several workers" (many claimers race, a crashed worker's items) are
605
+ registered as skipped. This is not a failure, but the atomicity of a claim
606
+ across handles is then unproved.
607
+
608
+ **Fix:** pass `share`, which returns another handle on the same queue (a
609
+ second connection to the same database), and give a networked store a real
610
+ second connection:
611
+
612
+ ```ts
613
+ share: (store) => (store as MyQueueStore).connectAgain(),
614
+ ```
615
+
548
616
  ## The `bun:sqlite` store
549
617
 
550
618
  ### `QueueError: A SQLite queue store needs a directory`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bumail/queue",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "The outbound mail queue of a mail server: every recipient's state, retries with back-off, delivery status notifications (RFC 3464), leases for several workers, and a memory, a bun:sqlite, a PostgreSQL (Bun.sql) and a Redis (Bun.redis) store, delivering through @bumail/smtp/client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,6 +39,11 @@
39
39
  "import": "./dist/redis/index.js",
40
40
  "default": "./dist/redis/index.js"
41
41
  },
42
+ "./conformance": {
43
+ "types": "./dist/conformance/index.d.ts",
44
+ "import": "./dist/conformance/index.js",
45
+ "default": "./dist/conformance/index.js"
46
+ },
42
47
  "./package.json": "./package.json"
43
48
  },
44
49
  "repository": {
@@ -61,18 +66,19 @@
61
66
  "src/memory/index.ts",
62
67
  "src/sqlite/index.ts",
63
68
  "src/postgres/index.ts",
64
- "src/redis/index.ts"
69
+ "src/redis/index.ts",
70
+ "src/conformance/index.ts"
65
71
  ]
66
72
  },
67
73
  "devDependencies": {
68
74
  "@bumail/dns": "^0.2.2",
69
75
  "@bumail/mime": "^0.2.0",
70
- "@bumail/smtp": "^0.11.1",
76
+ "@bumail/smtp": "^0.12.0",
71
77
  "@types/bun": "^1.4.2"
72
78
  },
73
79
  "peerDependencies": {
74
80
  "@bumail/mime": "^0.2.0",
75
- "@bumail/smtp": "^0.11.1",
81
+ "@bumail/smtp": "^0.12.0",
76
82
  "typescript": "^6.0.3 || ^7.0.0"
77
83
  },
78
84
  "peerDependenciesMeta": {