@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
package/README.md CHANGED
@@ -28,6 +28,7 @@ queue that only uses a smarthost needs none.
28
28
  | `@bumail/queue/sqlite` | `SqliteQueueStore`: on disk with `bun:sqlite`, shared by the processes of one machine |
29
29
  | `@bumail/queue/postgres` | `PostgresQueueStore`: on PostgreSQL through Bun's own `Bun.sql`, shared by instances on several machines |
30
30
  | `@bumail/queue/redis` | `RedisQueueStore`: on Redis through Bun's own `Bun.redis`, shared by instances on several machines |
31
+ | `@bumail/queue/conformance` | `describeQueueStore`: the `QueueStore` contract suite, run with `bun test` against a store written outside the package |
31
32
 
32
33
  ## Usage
33
34
 
@@ -152,6 +153,27 @@ With `fallback` (a smarthost), a recipient whose session by MX failed by a
152
153
  connection failure, a timeout or a reputation refusal goes through that
153
154
  smarthost at once (not after the final dot of `DATA`, where the MX may have taken the message: every recipient the MX did not refuse at `RCPT` is marked `mayHaveReachedMx` and never falls back on its own; a refused one may). If the smarthost's own session ends after the final dot, the recipient is marked `mayHaveReachedFallback` and later attempts go to the smarthost again, not to the MX; see [Falling back to a smarthost](docs/guide.md#falling-back-to-a-smarthost).
154
155
 
156
+ ## Reusing connections
157
+
158
+ Without a `send` of your own, the queue sends through a pool of SMTP
159
+ connections it owns: several messages to one server share a connection
160
+ (100 at most, idle 5 seconds), and `stop()` closes the pool. `reuse` tunes
161
+ it, `reuse: false` opens one connection per session, and a `send` you give
162
+ is never wrapped. Events carry `reused` when the sender says so. The
163
+ `bumail serve` queue of `@bumail/server` sends through the same pool, with
164
+ no setting. See `createSmtpPool` in the `@bumail/smtp` README.
165
+
166
+ ```ts
167
+ createQueue({
168
+ store: new MemoryQueueStore(),
169
+ hostname: 'mail.example.net',
170
+ resolver: nodeResolver(),
171
+ reuse: { maxMessages: 50, idleSeconds: 10 }, // default { 100, 5 }; or false
172
+ });
173
+ ```
174
+
175
+ See [Reusing connections](docs/guide.md#reusing-connections).
176
+
155
177
  ## Several workers
156
178
 
157
179
  ```ts
@@ -255,6 +277,29 @@ if (next) await queue.retryNow(next.id); // false while a worker delivers it
255
277
  if (next) await queue.cancel(next.id); // no DSN
256
278
  ```
257
279
 
280
+ ## A store of your own
281
+
282
+ `QueueStore` may grow in a 0.x minor version (methods, fields); a store of your own follows it.
283
+
284
+ Hold a `QueueStore` you wrote to the same tests the stores here pass.
285
+ The spec runs with `bun test` (the suite imports `bun:test`):
286
+
287
+ ```ts
288
+ // my-store.spec.ts
289
+ import { describeQueueStore } from '@bumail/queue/conformance';
290
+ import { MyQueueStore } from './my-store';
291
+
292
+ describeQueueStore('MyQueueStore', {
293
+ create: () => MyQueueStore.open({ url: process.env.MY_STORE_URL }),
294
+ share: (store) => (store as MyQueueStore).connectAgain(), // a second handle on the same queue
295
+ dispose: (store) => (store as MyQueueStore).close(),
296
+ });
297
+ ```
298
+
299
+ Without `share` the two several-workers tests are skipped, and a store
300
+ shared over a network should pass a real second connection. See
301
+ [Holding your own store to the contract](https://github.com/softistx/bumail/blob/develop/packages/queue/docs/guide.md#holding-your-own-store-to-the-contract).
302
+
258
303
  ## Testing
259
304
 
260
305
  ```ts
@@ -344,8 +389,9 @@ control character in it.
344
389
  | export | |
345
390
  | --- | --- |
346
391
  | `createQueue(options)`, `Queue` | the queue: `enqueue`, `deliverDue`, `start`, `stop`, `on`, `list`, `get`, `retryNow`, `cancel`, `owner` |
347
- | `QueueOptions` (`route`, `routes`, `fallback`), `Route`, `Smarthost`, `RetrySchedule`, `DsnOptions`, `QueueLimits`, `Clock`, `Sender` | what `createQueue` takes |
392
+ | `QueueOptions` (`route`, `routes`, `fallback`, `reuse`), `Route`, `Smarthost`, `RetrySchedule`, `DsnOptions`, `ReuseOptions`, `QueueLimits`, `Clock`, `Sender` | what `createQueue` takes |
348
393
  | `MessageSource`, `QueueEnvelope` (`ret`, `envid`, `recipients`), `RecipientDsn`, `RecipientNotify`, `NotifyEvent`, `OriginalRecipient`, `RetMode` | what `enqueue` takes, with the DSN parameters |
394
+ | `describeQueueStore`, `QueueStoreHarness`, `CreateQueueStore`, `ShareStore` | from `@bumail/queue/conformance`: `describeQueueStore(name, { create, share?, dispose? })`, run under `bun test`; without `share` the two several-workers tests are skipped |
349
395
  | `deliveredDsn(input)`, `DeliveredDsnInput`, `DeliveredRecipient` | the `delivered` DSN for a local delivery: the bytes to enqueue, or `undefined` when no recipient asked for `SUCCESS` |
350
396
  | `failedDsn(input)`, `FailedDsnInput`, `FailedRecipient`, `DEFAULT_MAX_RETURN` | the `failed` DSN for a recipient the application could not deliver locally: the bytes to enqueue, or `undefined` for a null reverse-path or when no recipient asked for failures; `DEFAULT_MAX_RETURN` is the 64 KiB a DSN returns of the original by default |
351
397
  | `QueueEvents`, `QueueListener`, `RecipientEvent` (`route`, `fallback`, `mxReply`, `mayHaveReachedMx`, `mayHaveReachedFallback`), `DeferredEvent`, `DsnEvent`, `QueueErrorEvent` | the events |
@@ -0,0 +1,4 @@
1
+ import { type StoreFactories } from './setup';
2
+ /** Claims, leases and schedules. */
3
+ export declare function describeClaims(factories: StoreFactories): void;
4
+ //# sourceMappingURL=claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../src/conformance/claims.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0B,KAAK,cAAc,EAAM,MAAM,SAAS,CAAC;AAa1E,oCAAoC;AACpC,wBAAgB,cAAc,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAM9D"}
@@ -0,0 +1,4 @@
1
+ import { type StoreFactories } from './setup';
2
+ /** The DSNs `complete` takes: stored with the outcome, or not at all. */
3
+ export declare function completeDsns(factories: StoreFactories): void;
4
+ //# sourceMappingURL=complete-dsns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complete-dsns.d.ts","sourceRoot":"","sources":["../../src/conformance/complete-dsns.ts"],"names":[],"mappings":"AAEA,OAAO,EAON,KAAK,cAAc,EAEnB,MAAM,SAAS,CAAC;AAgBjB,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAK5D"}
@@ -0,0 +1,4 @@
1
+ import { type StoreFactories } from './setup';
2
+ /** Several claimers on one queue, and a claimer that crashed. */
3
+ export declare function describeConcurrency({ create, share }: StoreFactories): void;
4
+ //# sourceMappingURL=concurrency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/conformance/concurrency.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,cAAc,EAAM,MAAM,SAAS,CAAC;AA2BjE,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,cAAc,GAAG,IAAI,CAkD3E"}
@@ -0,0 +1,11 @@
1
+ import type { QueueStoreHarness } from './setup';
2
+ /**
3
+ * The specs every `QueueStore` must pass, to call from a spec file run by
4
+ * `bun test`. The harness builds an empty store with `create`; `share` is
5
+ * another handle on the same queue (a second connection, as a second
6
+ * process opens), so the claim's atomicity is checked across handles, not
7
+ * only within one: without it those tests are registered as skipped.
8
+ * `dispose` runs after every test with the stores that test created.
9
+ */
10
+ export declare function describeQueueStore(name: string, harness: QueueStoreHarness): void;
11
+ //# sourceMappingURL=describe-queue-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe-queue-store.d.ts","sourceRoot":"","sources":["../../src/conformance/describe-queue-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,GACxB,IAAI,CA6BN"}
@@ -0,0 +1,5 @@
1
+ import { type StoreFactories } from './setup';
2
+ /** The marks a recipient keeps when a server may hold a copy. */
3
+ export declare function doubt({ create }: StoreFactories): void;
4
+ export declare function fallbackDoubt({ create }: StoreFactories): void;
5
+ //# sourceMappingURL=doubt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doubt.d.ts","sourceRoot":"","sources":["../../src/conformance/doubt.ts"],"names":[],"mappings":"AAEA,OAAO,EAMN,KAAK,cAAc,EAEnB,MAAM,SAAS,CAAC;AAEjB,iEAAiE;AACjE,wBAAgB,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,IAAI,CAwEtD;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,IAAI,CA8E9D"}
@@ -0,0 +1,4 @@
1
+ import { type StoreFactories } from './setup';
2
+ /** RFC 3461's parameters: kept as given, for the item's whole life. */
3
+ export declare function dsnParameters({ create }: StoreFactories): void;
4
+ //# sourceMappingURL=dsn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsn.d.ts","sourceRoot":"","sources":["../../src/conformance/dsn.ts"],"names":[],"mappings":"AACA,OAAO,EAKN,KAAK,cAAc,EAEnB,MAAM,SAAS,CAAC;AAEjB,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,IAAI,CAqJ9D"}
@@ -0,0 +1,3 @@
1
+ export { describeQueueStore } from './describe-queue-store';
2
+ export type { CreateQueueStore, QueueStoreHarness, ShareStore } from './setup';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conformance/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}