@bumail/queue 0.11.0 → 0.12.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 +25 -0
- package/dist/conformance/claims.d.ts +4 -0
- package/dist/conformance/claims.d.ts.map +1 -0
- package/dist/conformance/complete-dsns.d.ts +4 -0
- package/dist/conformance/complete-dsns.d.ts.map +1 -0
- package/dist/conformance/concurrency.d.ts +4 -0
- package/dist/conformance/concurrency.d.ts.map +1 -0
- package/dist/conformance/describe-queue-store.d.ts +11 -0
- package/dist/conformance/describe-queue-store.d.ts.map +1 -0
- package/dist/conformance/doubt.d.ts +5 -0
- package/dist/conformance/doubt.d.ts.map +1 -0
- package/dist/conformance/dsn.d.ts +4 -0
- package/dist/conformance/dsn.d.ts.map +1 -0
- package/dist/conformance/index.d.ts +3 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +820 -0
- package/dist/conformance/index.js.map +18 -0
- package/dist/conformance/items.d.ts +4 -0
- package/dist/conformance/items.d.ts.map +1 -0
- package/dist/conformance/outcomes.d.ts +4 -0
- package/dist/conformance/outcomes.d.ts.map +1 -0
- package/dist/conformance/setup.d.ts +40 -0
- package/dist/conformance/setup.d.ts.map +1 -0
- package/docs/guide.md +84 -2
- package/docs/roadmap.md +10 -2
- package/docs/troubleshooting.md +42 -0
- package/package.json +8 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/conformance/describe-queue-store.ts", "../src/conformance/claims.ts", "../src/conformance/setup.ts", "../src/conformance/concurrency.ts", "../src/conformance/items.ts", "../src/conformance/dsn.ts", "../src/conformance/outcomes.ts", "../src/conformance/complete-dsns.ts", "../src/conformance/doubt.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { afterEach, describe } from 'bun:test';\nimport type { QueueStore } from '../contract/queue-store';\nimport { describeClaims } from './claims';\nimport { describeConcurrency } from './concurrency';\nimport { describeItems } from './items';\nimport { describeOutcomes } from './outcomes';\nimport type { QueueStoreHarness } from './setup';\n\n/**\n * The specs every `QueueStore` must pass, to call from a spec file run by\n * `bun test`. The harness builds an empty store with `create`; `share` is\n * another handle on the same queue (a second connection, as a second\n * process opens), so the claim's atomicity is checked across handles, not\n * only within one: without it those tests are registered as skipped.\n * `dispose` runs after every test with the stores that test created.\n */\nexport function describeQueueStore(\n\tname: string,\n\tharness: QueueStoreHarness,\n): void {\n\tdescribe(`${name}: the QueueStore contract`, () => {\n\t\tconst created: QueueStore[] = [];\n\t\tconst factories = {\n\t\t\tshare: harness.share,\n\t\t\tcreate: async () => {\n\t\t\t\tconst store = await harness.create();\n\t\t\t\tcreated.push(store);\n\t\t\t\treturn store;\n\t\t\t},\n\t\t};\n\t\tafterEach(async () => {\n\t\t\tconst stores = created.splice(0);\n\t\t\tconst settled = await Promise.allSettled(\n\t\t\t\tstores.map(async (store) => harness.dispose?.(store)),\n\t\t\t);\n\t\t\tconst failed = settled.filter((r) => r.status === 'rejected');\n\t\t\tif (failed.length === 1) throw failed[0]?.reason;\n\t\t\tif (failed.length > 1)\n\t\t\t\tthrow new AggregateError(\n\t\t\t\t\tfailed.map((r) => r.reason),\n\t\t\t\t\t'several stores failed to dispose',\n\t\t\t\t);\n\t\t});\n\t\tdescribeItems(factories);\n\t\tdescribeClaims(factories);\n\t\tdescribeOutcomes(factories);\n\t\tdescribeConcurrency(factories);\n\t});\n}\n",
|
|
6
|
+
"import { describe, expect, test } from 'bun:test';\nimport type { AttemptResult } from '../contract/types';\nimport { entry, MINUTE, rejects, type StoreFactories, T0 } from './setup';\n\nconst lease = { leaseMs: 10 * MINUTE };\n\nconst result = (overrides: Partial<AttemptResult> = {}): AttemptResult => ({\n\tnow: T0 + MINUTE,\n\trecipients: [],\n\tnextAttemptAt: T0 + 30 * MINUTE,\n\tattempts: 1,\n\tdelayNotified: false,\n\t...overrides,\n});\n\n/** Claims, leases and schedules. */\nexport function describeClaims(factories: StoreFactories): void {\n\tdescribe('claims and leases', () => {\n\t\tclaiming(factories);\n\t\tleasing(factories);\n\t\trescheduling(factories);\n\t});\n}\n\nfunction claiming({ create }: StoreFactories): void {\n\ttest('claim takes a due item with a lease; nothing else is due', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tconst claimed = await store.claim({ owner: 'w1', now: T0, ...lease });\n\t\texpect(claimed?.id).toBe(item.id);\n\t\texpect(claimed?.lease).toEqual({\n\t\t\towner: 'w1',\n\t\t\texpiresAt: T0 + 10 * MINUTE,\n\t\t});\n\t\texpect((await store.get(item.id))?.lease?.owner).toBe('w1');\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w2', now: T0, ...lease }),\n\t\t).toBeUndefined();\n\t});\n\n\ttest('an item not yet due is not claimed', async () => {\n\t\tconst store = await create();\n\t\tawait store.add(entry({ createdAt: T0 + MINUTE }));\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w1', now: T0, ...lease }),\n\t\t).toBeUndefined();\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w1', now: T0 + MINUTE, ...lease }),\n\t\t).toBeDefined();\n\t});\n\n\ttest('the earliest due is claimed first, then the oldest', async () => {\n\t\tconst store = await create();\n\t\tconst late = await store.add(entry({ createdAt: T0 + MINUTE }));\n\t\tconst first = await store.add(entry());\n\t\tconst second = await store.add(entry());\n\t\tconst now = T0 + MINUTE;\n\t\tconst order = [];\n\t\tfor (let i = 0; i < 3; i++) {\n\t\t\torder.push((await store.claim({ owner: 'w', now, ...lease }))?.id);\n\t\t}\n\t\texpect(order).toEqual([first.id, second.id, late.id]);\n\t});\n}\n\nfunction leasing({ create }: StoreFactories): void {\n\ttest('an expired lease is claimed again, and its old owner has lost it', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'crashed', now: T0, leaseMs: MINUTE });\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w2', now: T0 + MINUTE - 1, ...lease }),\n\t\t).toBeUndefined();\n\t\tconst again = await store.claim({\n\t\t\towner: 'w2',\n\t\t\tnow: T0 + MINUTE,\n\t\t\t...lease,\n\t\t});\n\t\texpect(again?.id).toBe(item.id);\n\t\texpect(again?.lease?.owner).toBe('w2');\n\t\texpect(await store.renew(item.id, 'crashed', T0 + 5 * MINUTE)).toBe(false);\n\t\texpect(\n\t\t\tawait store.complete(item.id, 'crashed', result(), []),\n\t\t).toBeUndefined();\n\t\texpect(await store.reschedule(item.id, T0, 'crashed')).toBe(false);\n\t\texpect((await store.get(item.id))?.lease?.owner).toBe('w2');\n\t});\n\n\ttest('renew moves the expiry while the owner holds the lease', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, leaseMs: MINUTE });\n\t\texpect(await store.renew(item.id, 'w1', T0 + 3 * MINUTE)).toBe(true);\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w2', now: T0 + 2 * MINUTE, ...lease }),\n\t\t).toBeUndefined();\n\t\texpect(await store.renew('nope', 'w1', T0)).toBe(false);\n\t});\n}\n\nfunction rescheduling({ create }: StoreFactories): void {\n\ttest('reschedule with the owner gives the item back, due at the new time', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\texpect(await store.reschedule(item.id, T0 + MINUTE, 'w2')).toBe(false);\n\t\texpect(await store.reschedule(item.id, T0 + MINUTE, 'w1')).toBe(true);\n\t\tconst stored = await store.get(item.id);\n\t\texpect(stored?.lease).toBeUndefined();\n\t\texpect(stored?.nextAttemptAt).toBe(T0 + MINUTE);\n\t\texpect(stored?.attempts).toBe(0);\n\t});\n\n\ttest('reschedule without an owner moves the time and leaves the lease alone', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tawait store.complete(item.id, 'w1', result(), []);\n\t\texpect(await store.reschedule(item.id, T0 + MINUTE)).toBe(true);\n\t\texpect(\n\t\t\t(await store.claim({ owner: 'w2', now: T0 + MINUTE, ...lease }))?.id,\n\t\t).toBe(item.id);\n\t\texpect(await store.reschedule(item.id, T0)).toBe(true);\n\t\texpect((await store.get(item.id))?.lease?.owner).toBe('w2');\n\t});\n\n\ttest('claim refuses a bad request', async () => {\n\t\tconst store = await create();\n\t\tawait rejects(store.claim({ owner: '', now: T0, ...lease }), 'INVALID');\n\t\tawait rejects(store.claim({ owner: 'w', now: T0, leaseMs: 0 }), 'INVALID');\n\t\tawait rejects(\n\t\t\tstore.claim({ owner: 'w', now: Number.NaN, ...lease }),\n\t\t\t'INVALID',\n\t\t);\n\t\tawait rejects(store.reschedule('nope', T0, ''), 'INVALID');\n\t});\n}\n",
|
|
7
|
+
"import { expect } from 'bun:test';\nimport type { QueueStore } from '../contract/queue-store';\nimport type { AttemptResult, NewQueueItem } from '../contract/types';\n\n/** A new store, empty. */\nexport type CreateQueueStore = () => Promise<QueueStore> | QueueStore;\n\n/**\n * Another handle on the same queue as `store`: a second connection to the\n * same database, as a second process would open. A harness with no second\n * handle omits `share`, and the two several-workers tests are then skipped.\n */\nexport type ShareStore = (store: QueueStore) => QueueStore;\n\n/** What a store's spec hands the suite. */\nexport interface QueueStoreHarness {\n\t/** A new store, empty. */\n\treadonly create: CreateQueueStore;\n\t/** Another handle on the same queue; without it the share tests are skipped. */\n\treadonly share?: ShareStore | undefined;\n\t/**\n\t * Closes a store `create` made, after the test that used it. Handles\n\t * returned by `share` are the harness's to close.\n\t */\n\treadonly dispose?: ((store: QueueStore) => Promise<void> | void) | undefined;\n}\n\n/** What the suite is handed: how to build a store, and what the harness can do beyond that. */\nexport interface StoreFactories {\n\treadonly create: CreateQueueStore;\n\t/** Absent when the harness has no second handle: the share tests are skipped. */\n\treadonly share?: ShareStore | undefined;\n}\n\nexport const bytes = (text: string) => new TextEncoder().encode(text);\n\nexport const T0 = Date.UTC(2026, 9, 1, 12);\nexport const MINUTE = 60_000;\n\nexport const lease = { leaseMs: 10 * MINUTE };\n\nexport const result = (\n\toverrides: Partial<AttemptResult> = {},\n): AttemptResult => ({\n\tnow: T0 + MINUTE,\n\trecipients: [],\n\tnextAttemptAt: T0 + 30 * MINUTE,\n\tattempts: 1,\n\tdelayNotified: false,\n\t...overrides,\n});\n\n/** A new item from mary to two recipients, created at `T0` unless told otherwise. */\nexport function entry(overrides: Partial<NewQueueItem> = {}): NewQueueItem {\n\treturn {\n\t\tfrom: 'mary@example.net',\n\t\tto: ['joe@example.com', 'ann@example.org'],\n\t\tmessage: bytes('Subject: Hi\\r\\n\\r\\nHello\\r\\n'),\n\t\tcreatedAt: T0,\n\t\t...overrides,\n\t};\n}\n\n/** Expects a promise to reject with a QueueError of this code. */\nexport async function rejects(promise: Promise<unknown>, code: string) {\n\tawait expect(promise).rejects.toMatchObject({ name: 'QueueError', code });\n}\n",
|
|
8
|
+
"import { describe, expect, test } from 'bun:test';\nimport type { QueueStore } from '../contract/queue-store';\nimport { entry, MINUTE, type StoreFactories, T0 } from './setup';\n\nconst delivered = (now: number) => ({\n\tnow,\n\trecipients: [\n\t\t{ address: 'joe@example.com', status: 'delivered' as const },\n\t\t{ address: 'ann@example.org', status: 'delivered' as const },\n\t],\n\tnextAttemptAt: now,\n\tattempts: 1,\n\tdelayNotified: false,\n});\n\n/** A worker: claims and delivers until nothing is due, yielding between steps. */\nasync function work(store: QueueStore, owner: string, now: number) {\n\tconst taken: string[] = [];\n\tfor (;;) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, Math.random() * 2));\n\t\tconst item = await store.claim({ owner, now, leaseMs: 10 * MINUTE });\n\t\tif (!item) return taken;\n\t\ttaken.push(item.id);\n\t\tawait new Promise((resolve) => setTimeout(resolve, Math.random() * 2));\n\t\tconst done = await store.complete(item.id, owner, delivered(now), []);\n\t\texpect(done).toBeDefined();\n\t}\n}\n\n/** Several claimers on one queue, and a claimer that crashed. */\nexport function describeConcurrency({ create, share }: StoreFactories): void {\n\t// A harness with no second handle cannot prove atomicity across handles:\n\t// the test is registered as skipped, with the reason in its name.\n\tconst shared = share ? test : test.skip;\n\tconst note = share ? '' : ' (skipped: harness has no share)';\n\tdescribe('several workers', () => {\n\t\tshared(\n\t\t\t`many claimers race: every item is delivered once${note}`,\n\t\t\tasync () => {\n\t\t\t\tconst store = await create();\n\t\t\t\tconst ids = new Set<string>();\n\t\t\t\tfor (let i = 0; i < 60; i++) ids.add((await store.add(entry())).id);\n\t\t\t\tconst workers = Array.from({ length: 8 }, (_, i) =>\n\t\t\t\t\twork(share?.(store) ?? store, `w${i}`, T0),\n\t\t\t\t);\n\t\t\t\tconst taken = (await Promise.all(workers)).flat();\n\t\t\t\texpect(taken.length).toBe(60);\n\t\t\t\texpect(new Set(taken)).toEqual(ids);\n\t\t\t\texpect(await store.count()).toBe(0);\n\t\t\t},\n\t\t);\n\n\t\tshared(\n\t\t\t`a crashed worker’s items are claimed again once its leases expire${note}`,\n\t\t\tasync () => {\n\t\t\t\tconst store = await create();\n\t\t\t\tfor (let i = 0; i < 5; i++) await store.add(entry());\n\t\t\t\tconst crashed: string[] = [];\n\t\t\t\tfor (let i = 0; i < 5; i++) {\n\t\t\t\t\tconst item = await store.claim({\n\t\t\t\t\t\towner: 'crashed',\n\t\t\t\t\t\tnow: T0,\n\t\t\t\t\t\tleaseMs: MINUTE,\n\t\t\t\t\t});\n\t\t\t\t\tif (item) crashed.push(item.id);\n\t\t\t\t}\n\t\t\t\texpect(crashed.length).toBe(5);\n\t\t\t\tconst other = share?.(store) ?? store;\n\t\t\t\texpect(await work(other, 'w2', T0 + MINUTE - 1)).toEqual([]);\n\t\t\t\tconst recovered = await work(other, 'w2', T0 + MINUTE);\n\t\t\t\texpect(new Set(recovered)).toEqual(new Set(crashed));\n\t\t\t\tfor (const id of crashed) {\n\t\t\t\t\texpect(\n\t\t\t\t\t\tawait store.complete(id, 'crashed', delivered(T0), []),\n\t\t\t\t\t).toBeUndefined();\n\t\t\t\t}\n\t\t\t\texpect(await store.count()).toBe(0);\n\t\t\t},\n\t\t);\n\t});\n}\n",
|
|
9
|
+
"import { describe, expect, test } from 'bun:test';\nimport { dsnParameters } from './dsn';\nimport {\n\tbytes,\n\tentry,\n\tMINUTE,\n\trejects,\n\ttype StoreFactories,\n\tT0,\n} from './setup';\n\n/** Adding, reading, listing and cancelling items. */\nexport function describeItems(factories: StoreFactories): void {\n\tdescribe('items', () => {\n\t\tadding(factories);\n\t\tlisting(factories);\n\t\tdsnParameters(factories);\n\t});\n}\n\nfunction adding({ create }: StoreFactories): void {\n\ttest('add keeps the envelope, every recipient pending, due at once', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\texpect(item).toMatchObject({\n\t\t\tfrom: 'mary@example.net',\n\t\t\trecipients: [\n\t\t\t\t{ address: 'joe@example.com', status: 'pending' },\n\t\t\t\t{ address: 'ann@example.org', status: 'pending' },\n\t\t\t],\n\t\t\tsize: 22,\n\t\t\tcreatedAt: T0,\n\t\t\tnextAttemptAt: T0,\n\t\t\tattempts: 0,\n\t\t\tdelayNotified: false,\n\t\t});\n\t\texpect(item.lease).toBeUndefined();\n\t\texpect(await store.get(item.id)).toEqual(item);\n\t\texpect(await store.count()).toBe(1);\n\t});\n\n\ttest('the null sender is kept as the empty string', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry({ from: '' }));\n\t\texpect((await store.get(item.id))?.from).toBe('');\n\t});\n\n\ttest('readMessage gives the bytes back, a copy each time', async () => {\n\t\tconst store = await create();\n\t\tconst message = bytes('Subject: x\\r\\n\\r\\nbody\\r\\n');\n\t\tconst item = await store.add(entry({ message }));\n\t\tmessage[0] = 0;\n\t\tconst read = await store.readMessage(item.id);\n\t\texpect(new TextDecoder().decode(read)).toBe('Subject: x\\r\\n\\r\\nbody\\r\\n');\n\t\tif (read) read[0] = 0;\n\t\texpect((await store.readMessage(item.id))?.[0]).toBe(83);\n\t});\n\n\ttest('an item returned is a copy', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tconst first = (item.recipients as unknown as { status: string }[])[0];\n\t\tif (first) first.status = 'failed';\n\t\texpect((await store.get(item.id))?.recipients[0]?.status).toBe('pending');\n\t});\n\n\ttest('an unknown id is undefined, not an error', async () => {\n\t\tconst store = await create();\n\t\texpect(await store.get('nope')).toBeUndefined();\n\t\texpect(await store.readMessage('nope')).toBeUndefined();\n\t\texpect(await store.cancel('nope')).toBeUndefined();\n\t\texpect(await store.reschedule('nope', T0)).toBe(false);\n\t});\n\n\ttest('an id no store could hold, a NUL or a lone surrogate in it, is unknown too', async () => {\n\t\tconst store = await create();\n\t\tawait store.add(entry());\n\t\tfor (const id of ['a\\u0000b', 'a\\ud83d']) {\n\t\t\texpect(await store.get(id)).toBeUndefined();\n\t\t\texpect(await store.readMessage(id)).toBeUndefined();\n\t\t\texpect(await store.renew(id, 'w1', T0)).toBe(false);\n\t\t\texpect(await store.reschedule(id, T0)).toBe(false);\n\t\t\texpect(await store.cancel(id)).toBeUndefined();\n\t\t}\n\t\texpect(await store.count()).toBe(1);\n\t});\n}\n\nfunction listing({ create }: StoreFactories): void {\n\ttest('list gives the next due first, then the oldest, a page at a time', async () => {\n\t\tconst store = await create();\n\t\tconst a = await store.add(entry({ createdAt: T0 + 2 * MINUTE }));\n\t\tconst b = await store.add(entry({ createdAt: T0 }));\n\t\tconst c = await store.add(entry({ createdAt: T0 }));\n\t\tconst ids = (await store.list()).map((i) => i.id);\n\t\texpect(ids).toEqual([b.id, c.id, a.id]);\n\t\tconst page = await store.list({ offset: 1, limit: 1 });\n\t\texpect(page.map((i) => i.id)).toEqual([c.id]);\n\t});\n\n\ttest('list refuses a bad page', async () => {\n\t\tconst store = await create();\n\t\tawait rejects(store.list({ limit: 0 }), 'INVALID');\n\t\tawait rejects(store.list({ offset: -1 }), 'INVALID');\n\t});\n\n\ttest('cancel drops the item and its message, leased or not', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, leaseMs: MINUTE });\n\t\tconst cancelled = await store.cancel(item.id);\n\t\texpect(cancelled?.id).toBe(item.id);\n\t\texpect(await store.get(item.id)).toBeUndefined();\n\t\texpect(await store.readMessage(item.id)).toBeUndefined();\n\t\texpect(await store.count()).toBe(0);\n\t});\n\n\ttest('maxItems refuses an add past the limit with QUEUE_FULL', async () => {\n\t\tconst store = await create();\n\t\tawait store.add(entry(), { maxItems: 2 });\n\t\tawait store.add(entry(), { maxItems: 2 });\n\t\tawait rejects(store.add(entry(), { maxItems: 2 }), 'QUEUE_FULL');\n\t\texpect(await store.count()).toBe(2);\n\t\tawait rejects(store.add(entry(), { maxItems: 0 }), 'INVALID');\n\t});\n\n\ttest('add refuses what is not an item', async () => {\n\t\tconst store = await create();\n\t\tawait rejects(store.add(entry({ to: [] })), 'INVALID');\n\t\tawait rejects(\n\t\t\tstore.add(entry({ message: 'text' as unknown as Uint8Array })),\n\t\t\t'INVALID',\n\t\t);\n\t\tawait rejects(store.add(entry({ createdAt: Number.NaN })), 'INVALID');\n\t\tawait rejects(store.add(entry({ from: 'a\\u0000@example.net' })), 'INVALID');\n\t\tawait rejects(\n\t\t\tstore.add(entry({ to: ['jo\\ud83d@example.com'] })),\n\t\t\t'INVALID',\n\t\t);\n\t\texpect(await store.count()).toBe(0);\n\t});\n}\n",
|
|
10
|
+
"import { expect, test } from 'bun:test';\nimport {\n\tentry,\n\tlease,\n\trejects,\n\tresult,\n\ttype StoreFactories,\n\tT0,\n} from './setup';\n\n/** RFC 3461's parameters: kept as given, for the item's whole life. */\nexport function dsnParameters({ create }: StoreFactories): void {\n\tconst asked = {\n\t\tret: 'HDRS' as const,\n\t\tenvid: 'QQ314159 + x=',\n\t\trecipients: [\n\t\t\t{\n\t\t\t\taddress: 'joe@example.com',\n\t\t\t\tnotify: ['SUCCESS', 'DELAY'] as ('SUCCESS' | 'DELAY')[],\n\t\t\t\torcpt: { type: 'rfc822', address: 'Joe.Original@example.com' },\n\t\t\t},\n\t\t\t{ address: 'ann@example.org', notify: 'NEVER' as const },\n\t\t],\n\t};\n\n\ttest('RFC 3461 §4: ret, envid, notify and orcpt are kept by add, get, list and claim', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry(asked));\n\t\tconst kept = {\n\t\t\tret: 'HDRS',\n\t\t\tenvid: 'QQ314159 + x=',\n\t\t\trecipients: [\n\t\t\t\t{\n\t\t\t\t\taddress: 'joe@example.com',\n\t\t\t\t\tnotify: ['SUCCESS', 'DELAY'],\n\t\t\t\t\torcpt: { type: 'rfc822', address: 'Joe.Original@example.com' },\n\t\t\t\t},\n\t\t\t\t{ address: 'ann@example.org', notify: 'NEVER' },\n\t\t\t],\n\t\t};\n\t\texpect(item).toMatchObject(kept);\n\t\texpect(await store.get(item.id)).toEqual(item);\n\t\texpect((await store.list())[0]).toEqual(item);\n\t\texpect(await store.claim({ owner: 'w1', now: T0, ...lease })).toMatchObject(\n\t\t\tkept,\n\t\t);\n\t});\n\n\ttest('complete keeps them: the notify and orcpt of a deferred recipient, and the item level', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry(asked));\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst done = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({\n\t\t\t\trecipients: [\n\t\t\t\t\t{\n\t\t\t\t\t\taddress: 'joe@example.com',\n\t\t\t\t\t\tstatus: 'deferred',\n\t\t\t\t\t\treply: { text: 'try later' },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t\t[],\n\t\t);\n\t\texpect(done?.item.recipients[0]).toMatchObject({\n\t\t\tnotify: ['SUCCESS', 'DELAY'],\n\t\t\torcpt: { type: 'rfc822', address: 'Joe.Original@example.com' },\n\t\t});\n\t\tconst again = await store.get(item.id);\n\t\texpect(again?.ret).toBe('HDRS');\n\t\texpect(again?.envid).toBe('QQ314159 + x=');\n\t\texpect(again?.recipients[1]?.notify).toBe('NEVER');\n\t});\n\n\ttest('an item added with none carries none: the fields are absent, as before', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tconst read = await store.get(item.id);\n\t\tfor (const key of ['ret', 'envid'] as const) {\n\t\t\texpect(read).not.toHaveProperty(key);\n\t\t}\n\t\tfor (const r of read?.recipients ?? []) {\n\t\t\texpect(r).not.toHaveProperty('notify');\n\t\t\texpect(r).not.toHaveProperty('orcpt');\n\t\t}\n\t});\n\n\ttest('RFC 6533: 50 emoji in a utf-8 ORCPT are within 500 characters, as the server counts them', async () => {\n\t\tconst store = await create();\n\t\tconst orcpt = { type: 'utf-8', address: `${'😀'.repeat(50)}@x` };\n\t\tconst item = await store.add(\n\t\t\tentry({ recipients: [{ address: 'joe@example.com', orcpt }] }),\n\t\t);\n\t\texpect(item.recipients[0]?.orcpt).toEqual(orcpt);\n\t\tawait expect(\n\t\t\tstore.add(\n\t\t\t\tentry({\n\t\t\t\t\trecipients: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\taddress: 'joe@example.com',\n\t\t\t\t\t\t\torcpt: { type: 'rfc822', address: orcpt.address },\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t),\n\t\t).rejects.toMatchObject({ code: 'INVALID' });\n\t});\n\n\ttest('RFC 6533: a utf-8 ORCPT of 500 characters (82 escapes) is kept, 506 is INVALID', async () => {\n\t\tconst store = await create();\n\t\tconst orcpt = (n: number) => ({\n\t\t\ttype: 'utf-8',\n\t\t\taddress: `${'+'.repeat(n)}@x`,\n\t\t});\n\t\tconst item = await store.add(\n\t\t\tentry({ recipients: [{ address: 'joe@example.com', orcpt: orcpt(82) }] }),\n\t\t);\n\t\texpect(item.recipients[0]?.orcpt).toEqual(orcpt(82));\n\t\tawait rejects(\n\t\t\tstore.add(\n\t\t\t\tentry({\n\t\t\t\t\trecipients: [{ address: 'joe@example.com', orcpt: orcpt(83) }],\n\t\t\t\t}),\n\t\t\t),\n\t\t\t'INVALID',\n\t\t);\n\t});\n\n\ttest('what is not a parameter is INVALID, the same for every store', async () => {\n\t\tconst store = await create();\n\t\tconst bad: Record<string, unknown>[] = [\n\t\t\t{ ret: 'ALL' },\n\t\t\t{ envid: '' },\n\t\t\t{ envid: 'a\\r\\nb' },\n\t\t\t{ envid: 'x'.repeat(101) },\n\t\t\t{ recipients: [{ address: 'zed@example.com', notify: 'NEVER' }] },\n\t\t\t{ recipients: [{ address: 'joe@example.com', notify: 'SOMETIMES' }] },\n\t\t\t{ recipients: [{ address: 'joe@example.com', notify: [] }] },\n\t\t\t{\n\t\t\t\trecipients: [\n\t\t\t\t\t{ address: 'joe@example.com', notify: ['DELAY', 'DELAY'] },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\trecipients: [{ address: 'joe@example.com', orcpt: { type: 'rfc822' } }],\n\t\t\t},\n\t\t\t{\n\t\t\t\trecipients: [\n\t\t\t\t\t{ address: 'joe@example.com' },\n\t\t\t\t\t{ address: 'joe@example.com' },\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t\tfor (const params of bad) {\n\t\t\tawait rejects(store.add(entry(params as never)), 'INVALID');\n\t\t}\n\t\texpect(await store.count()).toBe(0);\n\t});\n}\n",
|
|
11
|
+
"import { describe, expect, test } from 'bun:test';\nimport { completeDsns } from './complete-dsns';\nimport { doubt, fallbackDoubt } from './doubt';\nimport {\n\tentry,\n\tlease,\n\tMINUTE,\n\trejects,\n\tresult,\n\ttype StoreFactories,\n\tT0,\n} from './setup';\n\n/** What `complete` records. */\nexport function describeOutcomes(factories: StoreFactories): void {\n\tdescribe('outcomes', () => {\n\t\trecording(factories);\n\t\trefusing(factories);\n\t\tfinality(factories);\n\t\tcompleteDsns(factories);\n\t\tdoubt(factories);\n\t\tfallbackDoubt(factories);\n\t});\n}\n\nfunction recording({ create }: StoreFactories): void {\n\ttest('complete records each outcome, the schedule, and lets go of the lease', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst reply = {\n\t\t\tcode: 451,\n\t\t\tstatus: '4.3.0',\n\t\t\ttext: 'try later',\n\t\t\thost: 'mx.example.com',\n\t\t};\n\t\tconst done = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({\n\t\t\t\trecipients: [{ address: 'joe@example.com', status: 'deferred', reply }],\n\t\t\t}),\n\t\t\t[],\n\t\t);\n\t\texpect(done?.item.recipients).toEqual([\n\t\t\t{\n\t\t\t\taddress: 'joe@example.com',\n\t\t\t\tstatus: 'deferred',\n\t\t\t\treply,\n\t\t\t\tupdatedAt: T0 + MINUTE,\n\t\t\t},\n\t\t\t{ address: 'ann@example.org', status: 'pending' },\n\t\t]);\n\t\tconst stored = await store.get(item.id);\n\t\texpect(stored).toEqual(done?.item);\n\t\texpect(stored).toMatchObject({\n\t\t\tnextAttemptAt: T0 + 30 * MINUTE,\n\t\t\tattempts: 1,\n\t\t});\n\t\texpect(stored?.lease).toBeUndefined();\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w2', now: T0 + 29 * MINUTE, ...lease }),\n\t\t).toBeUndefined();\n\t\texpect(\n\t\t\tawait store.claim({ owner: 'w2', now: T0 + 30 * MINUTE, ...lease }),\n\t\t).toBeDefined();\n\t});\n\n\ttest('an item whose every recipient is final is dropped with its message', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst done = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({\n\t\t\t\trecipients: [\n\t\t\t\t\t{\n\t\t\t\t\t\taddress: 'joe@example.com',\n\t\t\t\t\t\tstatus: 'delivered',\n\t\t\t\t\t\treply: { code: 250, text: 'ok' },\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\taddress: 'ann@example.org',\n\t\t\t\t\t\tstatus: 'failed',\n\t\t\t\t\t\treply: { code: 550, text: 'no' },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdelayNotified: true,\n\t\t\t}),\n\t\t\t[],\n\t\t);\n\t\texpect(done?.item.recipients.map((r) => r.status)).toEqual([\n\t\t\t'delivered',\n\t\t\t'failed',\n\t\t]);\n\t\texpect(done?.item.delayNotified).toBe(true);\n\t\texpect(await store.get(item.id)).toBeUndefined();\n\t\texpect(await store.readMessage(item.id)).toBeUndefined();\n\t\texpect(await store.count()).toBe(0);\n\t});\n}\n\nfunction refusing({ create }: StoreFactories): void {\n\ttest('an outcome holding a NUL or a lone surrogate is refused, and the item stays as it was', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst bad = [\n\t\t\t{ code: 451, text: `${'a'.repeat(8)}\\ud83d` },\n\t\t\t{ code: 451, text: 'nul \\u0000 here' },\n\t\t\t{ code: 451, text: 'ok', host: 'mx\\udc00.example.com' },\n\t\t\t{ code: 451, status: '4.3.0\\u0000', text: 'ok' },\n\t\t];\n\t\tfor (const reply of bad) {\n\t\t\tawait rejects(\n\t\t\t\tstore.complete(\n\t\t\t\t\titem.id,\n\t\t\t\t\t'w1',\n\t\t\t\t\tresult({\n\t\t\t\t\t\trecipients: [\n\t\t\t\t\t\t\t{ address: 'joe@example.com', status: 'deferred', reply },\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t\t[],\n\t\t\t\t),\n\t\t\t\t'INVALID',\n\t\t\t);\n\t\t}\n\t\tawait rejects(\n\t\t\tstore.claim({ owner: 'w\\ud800', now: T0, ...lease }),\n\t\t\t'INVALID',\n\t\t);\n\t\tconst stored = await store.get(item.id);\n\t\texpect(stored?.attempts).toBe(0);\n\t\texpect(stored?.lease?.owner).toBe('w1');\n\t\tconst done = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({\n\t\t\t\trecipients: [\n\t\t\t\t\t{\n\t\t\t\t\t\taddress: 'joe@example.com',\n\t\t\t\t\t\tstatus: 'deferred',\n\t\t\t\t\t\treply: { code: 451, text: 'Try later 😀' },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t\t[],\n\t\t);\n\t\texpect(done?.item.recipients[0]?.reply?.text).toBe('Try later 😀');\n\t\texpect((await store.get(item.id))?.recipients[0]?.reply?.text).toBe(\n\t\t\t'Try later 😀',\n\t\t);\n\t});\n}\n\nfunction finality({ create }: StoreFactories): void {\n\ttest('a final state is never changed by a later outcome', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tawait store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({\n\t\t\t\trecipients: [{ address: 'joe@example.com', status: 'delivered' }],\n\t\t\t}),\n\t\t\t[],\n\t\t);\n\t\tawait store.claim({ owner: 'w1', now: T0 + 30 * MINUTE, ...lease });\n\t\tconst after = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({\n\t\t\t\trecipients: [{ address: 'joe@example.com', status: 'failed' }],\n\t\t\t\tattempts: 2,\n\t\t\t}),\n\t\t\t[],\n\t\t);\n\t\texpect(after?.item.recipients[0]?.status).toBe('delivered');\n\t});\n}\n",
|
|
12
|
+
"import { expect, test } from 'bun:test';\nimport type { NewQueueItem } from '../contract/types';\nimport {\n\tbytes,\n\tentry,\n\tlease,\n\tMINUTE,\n\trejects,\n\tresult,\n\ttype StoreFactories,\n\tT0,\n} from './setup';\n\nconst dsn = (text: string): NewQueueItem =>\n\tentry({\n\t\tfrom: '',\n\t\tto: ['mary@example.net'],\n\t\tmessage: bytes(`Subject: ${text}\\r\\n\\r\\n${text}\\r\\n`),\n\t});\n\nconst delivered = result({\n\trecipients: [\n\t\t{ address: 'joe@example.com', status: 'delivered' },\n\t\t{ address: 'ann@example.org', status: 'failed' },\n\t],\n});\n\n/** The DSNs `complete` takes: stored with the outcome, or not at all. */\nexport function completeDsns(factories: StoreFactories): void {\n\tstored(factories);\n\tleaseHeld(factories);\n\trefused(factories);\n\tnotFinal(factories);\n}\n\nfunction stored({ create }: StoreFactories): void {\n\ttest('complete stores its DSNs with the outcome: new ids, in the order given, due at once', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst done = await store.complete(item.id, 'w1', delivered, [\n\t\t\tdsn('failed'),\n\t\t\tdsn('relayed'),\n\t\t]);\n\t\texpect(done?.item.id).toBe(item.id);\n\t\texpect(done?.dsns).toHaveLength(2);\n\t\tconst [first, second] = done?.dsns ?? [];\n\t\texpect(first?.id).not.toBe(second?.id);\n\t\texpect([first?.id, second?.id]).not.toContain(item.id);\n\t\texpect(first).toMatchObject({ from: '', attempts: 0 });\n\t\texpect(await store.get(item.id)).toBeUndefined();\n\t\tfor (const [kept, subject] of [\n\t\t\t[first, 'failed'],\n\t\t\t[second, 'relayed'],\n\t\t] as const) {\n\t\t\texpect(await store.get(kept?.id ?? '')).toEqual(kept);\n\t\t\texpect(\n\t\t\t\tnew TextDecoder().decode(await store.readMessage(kept?.id ?? '')),\n\t\t\t).toContain(`Subject: ${subject}`);\n\t\t}\n\t\t// Due at once, the first stored first.\n\t\texpect(\n\t\t\t(await store.claim({ owner: 'w2', now: T0 + MINUTE, ...lease }))?.id,\n\t\t).toBe(first?.id);\n\t\texpect(await store.count()).toBe(2);\n\t});\n}\n\nfunction leaseHeld({ create }: StoreFactories): void {\n\ttest('complete by an owner that no longer holds the lease stores no DSN, and a second one by the first stores none twice', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\texpect(\n\t\t\tawait store.complete(item.id, 'w2', delivered, [dsn('failed')]),\n\t\t).toBeUndefined();\n\t\texpect(await store.count()).toBe(1);\n\t\texpect(\n\t\t\tawait store.complete(item.id, 'w1', delivered, [dsn('failed')]),\n\t\t).toBeDefined();\n\t\texpect(await store.count()).toBe(1);\n\t\texpect(\n\t\t\tawait store.complete(item.id, 'w1', delivered, [dsn('failed')]),\n\t\t).toBeUndefined();\n\t\texpect(await store.count()).toBe(1);\n\t});\n}\n\nfunction refused({ create }: StoreFactories): void {\n\ttest('a DSN that fails the checks of add refuses the whole call with INVALID: no outcome, no DSN', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tawait rejects(\n\t\t\tstore.complete(item.id, 'w1', delivered, [\n\t\t\t\tdsn('failed'),\n\t\t\t\t{ ...dsn('bad'), to: [] },\n\t\t\t]),\n\t\t\t'INVALID',\n\t\t);\n\t\texpect(await store.count()).toBe(1);\n\t\texpect(await store.get(item.id)).toMatchObject({\n\t\t\tattempts: 0,\n\t\t\tlease: { owner: 'w1' },\n\t\t});\n\t});\n\n\ttest('dsns that are not an array refuse the call with INVALID, nothing recorded', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tawait rejects(\n\t\t\tstore.complete(item.id, 'w1', delivered, undefined as never),\n\t\t\t'INVALID',\n\t\t);\n\t\texpect(await store.get(item.id)).toMatchObject({ attempts: 0 });\n\t});\n}\n\nfunction notFinal({ create }: StoreFactories): void {\n\ttest('complete with no DSN returns an empty list; a not-final item stays with its DSNs beside it', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst none = await store.complete(item.id, 'w1', result(), []);\n\t\texpect(none?.dsns).toEqual([]);\n\t\tawait store.claim({ owner: 'w1', now: T0 + 30 * MINUTE, ...lease });\n\t\tconst deferred = result({\n\t\t\trecipients: [{ address: 'joe@example.com', status: 'deferred' }],\n\t\t\tattempts: 2,\n\t\t\tdelayNotified: true,\n\t\t});\n\t\tconst done = await store.complete(item.id, 'w1', deferred, [\n\t\t\tdsn('delayed'),\n\t\t]);\n\t\texpect(done?.item.delayNotified).toBe(true);\n\t\texpect(done?.dsns).toHaveLength(1);\n\t\texpect(await store.count()).toBe(2);\n\t\texpect(await store.get(item.id)).toEqual(done?.item);\n\t});\n}\n",
|
|
13
|
+
"import { expect, test } from 'bun:test';\nimport type { AttemptResult } from '../contract/types';\nimport {\n\tentry,\n\tlease,\n\tMINUTE,\n\trejects,\n\tresult,\n\ttype StoreFactories,\n\tT0,\n} from './setup';\n\n/** The marks a recipient keeps when a server may hold a copy. */\nexport function doubt({ create }: StoreFactories): void {\n\tconst deferred = (mayHaveReachedMx?: unknown) =>\n\t\t({\n\t\t\taddress: 'joe@example.com',\n\t\t\tstatus: 'deferred',\n\t\t\treply: { status: '4.4.2', text: 'no reply to the final dot' },\n\t\t\t...(mayHaveReachedMx === undefined ? {} : { mayHaveReachedMx }),\n\t\t}) as AttemptResult['recipients'][number];\n\n\ttest('mayHaveReachedMx: recorded, read by get and list, and kept by a later outcome without it', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst done = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({ recipients: [deferred(true)] }),\n\t\t\t[],\n\t\t);\n\t\texpect(done?.item.recipients[0]?.mayHaveReachedMx).toBe(true);\n\t\texpect(done?.item.recipients[1]).not.toHaveProperty('mayHaveReachedMx');\n\t\texpect((await store.get(item.id))?.recipients[0]?.mayHaveReachedMx).toBe(\n\t\t\ttrue,\n\t\t);\n\t\texpect((await store.list())[0]?.recipients[0]?.mayHaveReachedMx).toBe(true);\n\t\tawait store.claim({ owner: 'w1', now: T0 + 30 * MINUTE, ...lease });\n\t\tconst again = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({ recipients: [deferred()], attempts: 2 }),\n\t\t\t[],\n\t\t);\n\t\texpect(again?.item.recipients[0]?.mayHaveReachedMx).toBe(true);\n\t\texpect((await store.get(item.id))?.recipients[0]?.mayHaveReachedMx).toBe(\n\t\t\ttrue,\n\t\t);\n\t});\n\n\ttest('mayHaveReachedMx: an unmarked recipient has no such key', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tawait store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({ recipients: [deferred()] }),\n\t\t\t[],\n\t\t);\n\t\tfor (const r of (await store.get(item.id))?.recipients ?? []) {\n\t\t\texpect(r).not.toHaveProperty('mayHaveReachedMx');\n\t\t}\n\t});\n\n\ttest('mayHaveReachedMx: a value other than true is INVALID, and the item stays as it was', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tfor (const bad of [false, 'true', 1, null]) {\n\t\t\tawait rejects(\n\t\t\t\tstore.complete(\n\t\t\t\t\titem.id,\n\t\t\t\t\t'w1',\n\t\t\t\t\tresult({ recipients: [deferred(bad)] }),\n\t\t\t\t\t[],\n\t\t\t\t),\n\t\t\t\t'INVALID',\n\t\t\t);\n\t\t}\n\t\tconst kept = await store.get(item.id);\n\t\texpect(kept?.recipients[0]?.status).toBe('pending');\n\t\texpect(kept?.lease?.owner).toBe('w1');\n\t});\n}\n\nexport function fallbackDoubt({ create }: StoreFactories): void {\n\tconst deferred = (mayHaveReachedFallback?: unknown) =>\n\t\t({\n\t\t\taddress: 'joe@example.com',\n\t\t\tstatus: 'deferred',\n\t\t\treply: { status: '4.4.2', text: 'no reply to the final dot' },\n\t\t\t...(mayHaveReachedFallback === undefined\n\t\t\t\t? {}\n\t\t\t\t: { mayHaveReachedFallback }),\n\t\t}) as AttemptResult['recipients'][number];\n\n\ttest('mayHaveReachedFallback: recorded, read by get and list, and kept by a later outcome without it', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tconst done = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({ recipients: [deferred(true)] }),\n\t\t\t[],\n\t\t);\n\t\texpect(done?.item.recipients[0]?.mayHaveReachedFallback).toBe(true);\n\t\texpect(done?.item.recipients[1]).not.toHaveProperty(\n\t\t\t'mayHaveReachedFallback',\n\t\t);\n\t\texpect(\n\t\t\t(await store.get(item.id))?.recipients[0]?.mayHaveReachedFallback,\n\t\t).toBe(true);\n\t\texpect((await store.list())[0]?.recipients[0]?.mayHaveReachedFallback).toBe(\n\t\t\ttrue,\n\t\t);\n\t\tawait store.claim({ owner: 'w1', now: T0 + 30 * MINUTE, ...lease });\n\t\tconst again = await store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({ recipients: [deferred()], attempts: 2 }),\n\t\t\t[],\n\t\t);\n\t\texpect(again?.item.recipients[0]?.mayHaveReachedFallback).toBe(true);\n\t\texpect(\n\t\t\t(await store.get(item.id))?.recipients[0]?.mayHaveReachedFallback,\n\t\t).toBe(true);\n\t});\n\n\ttest('mayHaveReachedFallback: an unmarked recipient has no such key', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tawait store.complete(\n\t\t\titem.id,\n\t\t\t'w1',\n\t\t\tresult({ recipients: [deferred()] }),\n\t\t\t[],\n\t\t);\n\t\tfor (const r of (await store.get(item.id))?.recipients ?? []) {\n\t\t\texpect(r).not.toHaveProperty('mayHaveReachedFallback');\n\t\t}\n\t});\n\n\ttest('mayHaveReachedFallback: a value other than true is INVALID, and the item stays as it was', async () => {\n\t\tconst store = await create();\n\t\tconst item = await store.add(entry());\n\t\tawait store.claim({ owner: 'w1', now: T0, ...lease });\n\t\tfor (const bad of [false, 'true', 1, null]) {\n\t\t\tawait rejects(\n\t\t\t\tstore.complete(\n\t\t\t\t\titem.id,\n\t\t\t\t\t'w1',\n\t\t\t\t\tresult({ recipients: [deferred(bad)] }),\n\t\t\t\t\t[],\n\t\t\t\t),\n\t\t\t\t'INVALID',\n\t\t\t);\n\t\t}\n\t\tconst kept = await store.get(item.id);\n\t\texpect(kept?.recipients[0]?.status).toBe('pending');\n\t\texpect(kept?.lease?.owner).toBe('w1');\n\t});\n}\n"
|
|
14
|
+
],
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "434F5188249F7D1F64756E2164756E21",
|
|
17
|
+
"names": []
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"items.d.ts","sourceRoot":"","sources":["../../src/conformance/items.ts"],"names":[],"mappings":"AAEA,OAAO,EAKN,KAAK,cAAc,EAEnB,MAAM,SAAS,CAAC;AAEjB,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAM7D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcomes.d.ts","sourceRoot":"","sources":["../../src/conformance/outcomes.ts"],"names":[],"mappings":"AAGA,OAAO,EAMN,KAAK,cAAc,EAEnB,MAAM,SAAS,CAAC;AAEjB,+BAA+B;AAC/B,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAShE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { QueueStore } from '../contract/queue-store';
|
|
2
|
+
import type { AttemptResult, NewQueueItem } from '../contract/types';
|
|
3
|
+
/** A new store, empty. */
|
|
4
|
+
export type CreateQueueStore = () => Promise<QueueStore> | QueueStore;
|
|
5
|
+
/**
|
|
6
|
+
* Another handle on the same queue as `store`: a second connection to the
|
|
7
|
+
* same database, as a second process would open. A harness with no second
|
|
8
|
+
* handle omits `share`, and the two several-workers tests are then skipped.
|
|
9
|
+
*/
|
|
10
|
+
export type ShareStore = (store: QueueStore) => QueueStore;
|
|
11
|
+
/** What a store's spec hands the suite. */
|
|
12
|
+
export interface QueueStoreHarness {
|
|
13
|
+
/** A new store, empty. */
|
|
14
|
+
readonly create: CreateQueueStore;
|
|
15
|
+
/** Another handle on the same queue; without it the share tests are skipped. */
|
|
16
|
+
readonly share?: ShareStore | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Closes a store `create` made, after the test that used it. Handles
|
|
19
|
+
* returned by `share` are the harness's to close.
|
|
20
|
+
*/
|
|
21
|
+
readonly dispose?: ((store: QueueStore) => Promise<void> | void) | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** What the suite is handed: how to build a store, and what the harness can do beyond that. */
|
|
24
|
+
export interface StoreFactories {
|
|
25
|
+
readonly create: CreateQueueStore;
|
|
26
|
+
/** Absent when the harness has no second handle: the share tests are skipped. */
|
|
27
|
+
readonly share?: ShareStore | undefined;
|
|
28
|
+
}
|
|
29
|
+
export declare const bytes: (text: string) => Uint8Array<ArrayBuffer>;
|
|
30
|
+
export declare const T0: number;
|
|
31
|
+
export declare const MINUTE = 60000;
|
|
32
|
+
export declare const lease: {
|
|
33
|
+
leaseMs: number;
|
|
34
|
+
};
|
|
35
|
+
export declare const result: (overrides?: Partial<AttemptResult>) => AttemptResult;
|
|
36
|
+
/** A new item from mary to two recipients, created at `T0` unless told otherwise. */
|
|
37
|
+
export declare function entry(overrides?: Partial<NewQueueItem>): NewQueueItem;
|
|
38
|
+
/** Expects a promise to reject with a QueueError of this code. */
|
|
39
|
+
export declare function rejects(promise: Promise<unknown>, code: string): Promise<void>;
|
|
40
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/conformance/setup.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAErE,0BAA0B;AAC1B,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;AAEtE;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,UAAU,CAAC;AAE3D,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IACjC,0BAA0B;IAC1B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;CAC7E;AAED,+FAA+F;AAC/F,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACxC;AAED,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,4BAAmC,CAAC;AAEtE,eAAO,MAAM,EAAE,QAA2B,CAAC;AAC3C,eAAO,MAAM,MAAM,QAAS,CAAC;AAE7B,eAAO,MAAM,KAAK;;CAA2B,CAAC;AAE9C,eAAO,MAAM,MAAM,GAClB,YAAW,OAAO,CAAC,aAAa,CAAM,KACpC,aAOD,CAAC;AAEH,qFAAqF;AACrF,wBAAgB,KAAK,CAAC,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,YAAY,CAQzE;AAED,kEAAkE;AAClE,wBAAsB,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,iBAEpE"}
|
package/docs/guide.md
CHANGED
|
@@ -1046,8 +1046,90 @@ codes, and keeps its promises:
|
|
|
1046
1046
|
The queue never gives a store such text: it cleans every reply.
|
|
1047
1047
|
|
|
1048
1048
|
The contract's specs, `describeQueueStore`, which every store here runs,
|
|
1049
|
-
are
|
|
1050
|
-
|
|
1049
|
+
are exported as `@bumail/queue/conformance`: see
|
|
1050
|
+
[Holding your own store to the contract](#holding-your-own-store-to-the-contract).
|
|
1051
|
+
|
|
1052
|
+
## Holding your own store to the contract
|
|
1053
|
+
|
|
1054
|
+
A store you write (over a file, a database, a service) is only a
|
|
1055
|
+
`QueueStore` if it answers the contract above to the letter, in the cases
|
|
1056
|
+
nobody thinks of: a lease that expired, a DSN that fails the checks, a
|
|
1057
|
+
second process claiming at the same time. `describeQueueStore` is the
|
|
1058
|
+
suite the memory, SQLite, PostgreSQL and Redis stores run; call
|
|
1059
|
+
it from a spec of yours and it registers them.
|
|
1060
|
+
|
|
1061
|
+
**It needs Bun.** The suite imports `bun:test`, so the spec runs with
|
|
1062
|
+
`bun test`, not with Node, Vitest or Jest. It is a development
|
|
1063
|
+
dependency: install `@bumail/queue` and write the spec.
|
|
1064
|
+
|
|
1065
|
+
```sh
|
|
1066
|
+
bun add -d @bumail/queue
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
```ts
|
|
1070
|
+
// my-store.spec.ts
|
|
1071
|
+
import { describeQueueStore } from '@bumail/queue/conformance';
|
|
1072
|
+
import { MyQueueStore } from './my-store';
|
|
1073
|
+
|
|
1074
|
+
describeQueueStore('MyQueueStore', {
|
|
1075
|
+
// a new store, empty: called once per test
|
|
1076
|
+
create: () => MyQueueStore.open({ url: process.env.MY_STORE_URL }),
|
|
1077
|
+
// another handle on the same queue, as a second connection or process
|
|
1078
|
+
share: (store) => (store as MyQueueStore).connectAgain(),
|
|
1079
|
+
// closes a store `create` made, after the test that used it
|
|
1080
|
+
dispose: (store) => (store as MyQueueStore).close(),
|
|
1081
|
+
});
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
```sh
|
|
1085
|
+
bun test my-store.spec.ts
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
A spec project typechecked with `tsc` needs `@types/bun` (for `bun:test`), as
|
|
1089
|
+
the example's `devDependencies` have it.
|
|
1090
|
+
|
|
1091
|
+
[`examples/custom-store`](https://github.com/softistx/bumail/tree/develop/examples/custom-store)
|
|
1092
|
+
in the repository holds a complete one, a store over the memory store with its
|
|
1093
|
+
spec, that passes.
|
|
1094
|
+
|
|
1095
|
+
### What it covers
|
|
1096
|
+
|
|
1097
|
+
| group | what is held |
|
|
1098
|
+
| --- | --- |
|
|
1099
|
+
| `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 |
|
|
1100
|
+
| `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` |
|
|
1101
|
+
| `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 |
|
|
1102
|
+
| `several workers` | many claimers on one queue deliver every item once; a crashed worker's items are claimed again when its leases expire |
|
|
1103
|
+
|
|
1104
|
+
### `share`: a second handle
|
|
1105
|
+
|
|
1106
|
+
The two `several workers` tests need `share`, another handle on **the
|
|
1107
|
+
same queue**: a second connection to the same database, as a second
|
|
1108
|
+
process would open. Without it they are registered as skipped, named
|
|
1109
|
+
`… (skipped: harness has no share)`, because one handle cannot show that
|
|
1110
|
+
a claim is atomic across handles.
|
|
1111
|
+
|
|
1112
|
+
A store that lives in one process, such as an in-memory one, may return a
|
|
1113
|
+
second object over the same data, as the example does. A **networked**
|
|
1114
|
+
store (PostgreSQL, Redis, any server) should pass a real second
|
|
1115
|
+
connection: that is where two workers race, and where a claim that reads
|
|
1116
|
+
then writes loses. Passing none for a store like that leaves the one
|
|
1117
|
+
property that matters unproved.
|
|
1118
|
+
|
|
1119
|
+
### `dispose`
|
|
1120
|
+
|
|
1121
|
+
`dispose(store)` runs after every test, for each store `create` made in
|
|
1122
|
+
that test, once the test is over (pass or fail). Use it to close a
|
|
1123
|
+
connection or remove a file. It may be async.
|
|
1124
|
+
|
|
1125
|
+
- Every created store is disposed, even when the disposal of another
|
|
1126
|
+
one fails.
|
|
1127
|
+
- One failure is rethrown as it is. Several in one test are thrown
|
|
1128
|
+
together as an `AggregateError` (`several stores failed to dispose`),
|
|
1129
|
+
each reason in its `errors`.
|
|
1130
|
+
- Handles returned by `share` are **not** passed to `dispose`: the
|
|
1131
|
+
harness closes them, for instance in the same `dispose`, from the store
|
|
1132
|
+
they were made from.
|
|
1051
1133
|
|
|
1052
1134
|
## Options
|
|
1053
1135
|
|
package/docs/roadmap.md
CHANGED
|
@@ -18,11 +18,11 @@ Nothing under way.
|
|
|
18
18
|
- **One session for several messages** — the queue hands several items
|
|
19
19
|
for one domain to one SMTP session, once `@bumail/smtp`'s client reuses
|
|
20
20
|
connections.
|
|
21
|
-
- **The contract's specs, exported**, so a store written outside this
|
|
22
|
-
package can hold itself to them.
|
|
23
21
|
|
|
24
22
|
## Later
|
|
25
23
|
|
|
24
|
+
- **A suite that is not tied to `bun:test`** — the contract's specs run under
|
|
25
|
+
any test runner, for a store written where Bun is not the runner.
|
|
26
26
|
- **REQUIRETLS (RFC 8689)** — a message that asks for TLS all the way is
|
|
27
27
|
never sent in clear, and fails rather than downgrade.
|
|
28
28
|
- **Retries per recipient** — a schedule of its own for each recipient,
|
|
@@ -46,6 +46,14 @@ Nothing under way.
|
|
|
46
46
|
|
|
47
47
|
## Shipped
|
|
48
48
|
|
|
49
|
+
### 0.12.0
|
|
50
|
+
|
|
51
|
+
- **The contract's specs, exported** — `@bumail/queue/conformance` exports
|
|
52
|
+
`describeQueueStore(name, { create, share?, dispose? })`, run under
|
|
53
|
+
`bun test`, so a queue store written outside this package runs the same
|
|
54
|
+
contract suite as the built-in ones; a capability a store lacks shows as a
|
|
55
|
+
skipped test with its reason. Additive.
|
|
56
|
+
|
|
49
57
|
### 0.11.0
|
|
50
58
|
|
|
51
59
|
- **A DSN recorded in the same step as the outcome** — the failure, relayed
|
package/docs/troubleshooting.md
CHANGED
|
@@ -89,6 +89,11 @@ parts shown as … vary.
|
|
|
89
89
|
- [`QueueError: reply.host must be a string`](#queueerror-replyhost-must-be-a-string)
|
|
90
90
|
- [`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
91
|
|
|
92
|
+
**A store's spec** (`describeQueueStore`)
|
|
93
|
+
|
|
94
|
+
- [`AggregateError: several stores failed to dispose`](#aggregateerror-several-stores-failed-to-dispose)
|
|
95
|
+
- [`… (skipped: harness has no share)` in the test names](#-skipped-harness-has-no-share-in-the-test-names)
|
|
96
|
+
|
|
92
97
|
**Delivery**
|
|
93
98
|
|
|
94
99
|
- [A recipient stays `deferred` with `4.4.1` or `4.4.2`](#a-recipient-stays-deferred-with-441-or-442)
|
|
@@ -545,6 +550,43 @@ it.
|
|
|
545
550
|
default. The error from a transaction of your own on the same client is
|
|
546
551
|
yours to retry.
|
|
547
552
|
|
|
553
|
+
## Your own store's spec
|
|
554
|
+
|
|
555
|
+
### `AggregateError: several stores failed to dispose`
|
|
556
|
+
|
|
557
|
+
`describeQueueStore`, after a test. The test created more than one store
|
|
558
|
+
(a spec that calls `create` again, as the several-workers tests do) and
|
|
559
|
+
the `dispose` of more than one of them threw. The suite disposes every
|
|
560
|
+
store it created, collects the failures and throws them together; each
|
|
561
|
+
reason is in `error.errors`. A single failure is thrown as it is, not
|
|
562
|
+
wrapped.
|
|
563
|
+
|
|
564
|
+
**Fix:** read `error.errors`; each entry is one store's failure. The suite
|
|
565
|
+
calls `dispose` once per created store, so the causes are a store's own
|
|
566
|
+
`close` failing (each entry says why), or a `create` that hands back a
|
|
567
|
+
shared object, so that each test's `dispose` closes the same thing. Have
|
|
568
|
+
`create` return a fresh store each call. Handles from `share` are not passed
|
|
569
|
+
to `dispose`; close them in the harness.
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
create: () => new MyQueueStore(freshPath()), // a new store each call, never a shared one
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### `… (skipped: harness has no share)` in the test names
|
|
576
|
+
|
|
577
|
+
`describeQueueStore`. The harness passed no `share`, so the two tests of
|
|
578
|
+
"several workers" (many claimers race, a crashed worker's items) are
|
|
579
|
+
registered as skipped. This is not a failure, but the atomicity of a claim
|
|
580
|
+
across handles is then unproved.
|
|
581
|
+
|
|
582
|
+
**Fix:** pass `share`, which returns another handle on the same queue (a
|
|
583
|
+
second connection to the same database), and give a networked store a real
|
|
584
|
+
second connection:
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
share: (store) => (store as MyQueueStore).connectAgain(),
|
|
588
|
+
```
|
|
589
|
+
|
|
548
590
|
## The `bun:sqlite` store
|
|
549
591
|
|
|
550
592
|
### `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.
|
|
3
|
+
"version": "0.12.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,7 +66,8 @@
|
|
|
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": {
|