@bumail/queue 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/chunks/index-b4xvg3xn.js +23 -0
- package/dist/chunks/index-b4xvg3xn.js.map +10 -0
- package/dist/chunks/index-vg971acr.js.map +2 -2
- package/dist/contract/queue-store.d.ts +8 -2
- package/dist/contract/queue-store.d.ts.map +1 -1
- package/dist/dsn/build.d.ts +7 -3
- package/dist/dsn/build.d.ts.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +45 -11
- package/dist/index.js.map +5 -5
- package/dist/masked.d.ts +2 -1
- package/dist/masked.d.ts.map +1 -1
- package/dist/postgres/connect.d.ts +6 -0
- package/dist/postgres/connect.d.ts.map +1 -1
- package/dist/postgres/index.js +47 -17
- package/dist/postgres/index.js.map +7 -6
- package/dist/postgres/isolation.d.ts +6 -0
- package/dist/postgres/isolation.d.ts.map +1 -0
- package/dist/postgres/schema.d.ts.map +1 -1
- package/dist/postgres/store.d.ts.map +1 -1
- package/dist/queue/deliver.d.ts +3 -1
- package/dist/queue/deliver.d.ts.map +1 -1
- package/dist/queue/notify.d.ts +3 -2
- package/dist/queue/notify.d.ts.map +1 -1
- package/dist/redis/connect.d.ts +2 -0
- package/dist/redis/connect.d.ts.map +1 -1
- package/dist/redis/index.js +14 -6
- package/dist/redis/index.js.map +4 -4
- package/dist/redis/store.d.ts.map +1 -1
- package/docs/guide.md +87 -11
- package/docs/roadmap.md +25 -18
- package/docs/troubleshooting.md +76 -5
- package/package.json +3 -3
- package/dist/chunks/index-y3n1w2az.js +0 -18
- package/dist/chunks/index-y3n1w2az.js.map +0 -10
package/README.md
CHANGED
|
@@ -173,7 +173,9 @@ process.on('SIGTERM', async () => {
|
|
|
173
173
|
A claim is one `UPDATE … RETURNING` whose item a `SELECT … FOR UPDATE
|
|
174
174
|
SKIP LOCKED` picks: two instances never take the same item, and none
|
|
175
175
|
waits on one another is taking. An instance that crashes loses its items
|
|
176
|
-
when their leases expire, as with `bun:sqlite`.
|
|
176
|
+
when their leases expire, as with `bun:sqlite`. Every write runs at
|
|
177
|
+
`READ COMMITTED`, so a client whose sessions default to `repeatable
|
|
178
|
+
read` or `serializable` serves the queue too. No driver to install:
|
|
177
179
|
`Bun.sql` is Bun's.
|
|
178
180
|
|
|
179
181
|
## Redis, for several machines
|
|
@@ -293,7 +295,7 @@ control character in it.
|
|
|
293
295
|
| `QueueStore` | the contract a store answers: `add`, `get`, `list`, `count`, `readMessage`, `claim`, `renew`, `complete`, `reschedule`, `cancel` |
|
|
294
296
|
| `QueueItem`, `RecipientState`, `RecipientStatus`, `FinalStatus`, `Diagnostic`, `Lease` | an item, and where each recipient stands |
|
|
295
297
|
| `NewQueueItem`, `AddOptions`, `ClaimRequest`, `AttemptResult`, `RecipientUpdate`, `QueueListOptions` | what a store takes |
|
|
296
|
-
| `QueueError`, `QueueErrorCode` | `INVALID`, `MESSAGE_TOO_BIG`, `TOO_MANY_RECIPIENTS`, `QUEUE_FULL`, `CLOSED`, `LEASE_LOST` |
|
|
298
|
+
| `QueueError`, `QueueErrorCode` | `INVALID`, `MESSAGE_TOO_BIG`, `TOO_MANY_RECIPIENTS`, `QUEUE_FULL`, `CLOSED`, `LEASE_LOST`, `MESSAGE_UNREADABLE` |
|
|
297
299
|
| `MemoryQueueStore` | from `@bumail/queue/memory` |
|
|
298
300
|
| `SqliteQueueStore`, `SqliteQueueStoreOptions` | from `@bumail/queue/sqlite`: `SqliteQueueStore.open({ directory, busyTimeout? })`, `close()` |
|
|
299
301
|
| `PostgresQueueStore`, `PostgresQueueStoreOptions`, `PostgresClient`, `PostgresQueryable` | from `@bumail/queue/postgres`: `PostgresQueueStore.open({ sql, tablePrefix? })`, `migrate()`, `close()`; `sql` a `Bun.SQL` client or a `postgres://` URL |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/masked.ts
|
|
2
|
+
var MIN_BARE = 4;
|
|
3
|
+
function masked(text, raw) {
|
|
4
|
+
let decoded = raw;
|
|
5
|
+
try {
|
|
6
|
+
decoded = decodeURIComponent(raw);
|
|
7
|
+
} catch {}
|
|
8
|
+
const forms = [raw, decoded].filter((password) => password !== "");
|
|
9
|
+
let out = text;
|
|
10
|
+
for (const password of forms) {
|
|
11
|
+
out = out.replaceAll(`:${password}@`, ":…@");
|
|
12
|
+
}
|
|
13
|
+
for (const password of forms) {
|
|
14
|
+
if ([...password].length >= MIN_BARE)
|
|
15
|
+
out = out.replaceAll(password, "…");
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { masked };
|
|
21
|
+
|
|
22
|
+
//# debugId=DC4C707DD68894B564756E2164756E21
|
|
23
|
+
//# sourceMappingURL=index-b4xvg3xn.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/masked.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * The shortest password masked wherever it occurs. A shorter one, in\n * characters, is masked only where a URL holds it (`:…@`): replacing\n * `x` everywhere would turn \"execute\" into \"e…ecute\", and the reason\n * would no longer say what went wrong — while a password that short is\n * guessed anyway.\n */\nconst MIN_BARE = 4;\n\n/**\n * `text` with a URL's password masked, as written in the URL and decoded,\n * if it decodes: what a store opening a URL repeats of the client's\n * reason, which may name it. Its userinfo form, `:password@`, is always\n * masked; the password alone, elsewhere, only from `MIN_BARE` characters.\n */\nexport function masked(text: string, raw: string): string {\n\tlet decoded = raw;\n\ttry {\n\t\tdecoded = decodeURIComponent(raw);\n\t} catch {\n\t\t// Not percent-encoding a client reads either: the raw form is what it would repeat.\n\t}\n\tconst forms = [raw, decoded].filter((password) => password !== '');\n\tlet out = text;\n\tfor (const password of forms) {\n\t\tout = out.replaceAll(`:${password}@`, ':…@');\n\t}\n\tfor (const password of forms) {\n\t\tif ([...password].length >= MIN_BARE) out = out.replaceAll(password, '…');\n\t}\n\treturn out;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAOA,IAAM,WAAW;AAQV,SAAS,MAAM,CAAC,MAAc,KAAqB;AAAA,EACzD,IAAI,UAAU;AAAA,EACd,IAAI;AAAA,IACH,UAAU,mBAAmB,GAAG;AAAA,IAC/B,MAAM;AAAA,EAGR,MAAM,QAAQ,CAAC,KAAK,OAAO,EAAE,OAAO,CAAC,aAAa,aAAa,EAAE;AAAA,EACjE,IAAI,MAAM;AAAA,EACV,WAAW,YAAY,OAAO;AAAA,IAC7B,MAAM,IAAI,WAAW,IAAI,aAAa,KAAK;AAAA,EAC5C;AAAA,EACA,WAAW,YAAY,OAAO;AAAA,IAC7B,IAAI,CAAC,GAAG,QAAQ,EAAE,UAAU;AAAA,MAAU,MAAM,IAAI,WAAW,UAAU,GAAG;AAAA,EACzE;AAAA,EACA,OAAO;AAAA;",
|
|
8
|
+
"debugId": "DC4C707DD68894B564756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/errors.ts", "../src/text.ts", "../src/contract/checks.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Why the queue or a store refused a call. `INVALID`: an option or an\n * argument it cannot take. `MESSAGE_TOO_BIG`, `TOO_MANY_RECIPIENTS` and\n * `QUEUE_FULL`: a limit `enqueue` enforces. `CLOSED`: a store used after\n * `close()`. `LEASE_LOST`: given to the `error` event when another worker\n * took an item before its outcome was recorded — or, once the lease's\n * expiry passed, when the item is gone: lost or cancelled.\n */\nexport type QueueErrorCode =\n\t| 'INVALID'\n\t| 'MESSAGE_TOO_BIG'\n\t| 'TOO_MANY_RECIPIENTS'\n\t| 'QUEUE_FULL'\n\t| 'CLOSED'\n\t| 'LEASE_LOST';\n\n/** Thrown by the queue and by every store, whichever answers the contract, with the same codes. */\nexport class QueueError extends Error {\n\toverride readonly name = 'QueueError';\n\treadonly code: QueueErrorCode;\n\n\tconstructor(code: QueueErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n\n/** `INVALID`, for an option or an argument. */\nexport const invalid = (message: string) => new QueueError('INVALID', message);\n",
|
|
5
|
+
"/**\n * Why the queue or a store refused a call. `INVALID`: an option or an\n * argument it cannot take. `MESSAGE_TOO_BIG`, `TOO_MANY_RECIPIENTS` and\n * `QUEUE_FULL`: a limit `enqueue` enforces. `CLOSED`: a store used after\n * `close()`. `LEASE_LOST`: given to the `error` event when another worker\n * took an item before its outcome was recorded — or, once the lease's\n * expiry passed, when the item is gone: lost or cancelled.\n * `MESSAGE_UNREADABLE`: given to the `error` event when the store holds an\n * item but not its message, whose pending recipients then fail.\n */\nexport type QueueErrorCode =\n\t| 'INVALID'\n\t| 'MESSAGE_TOO_BIG'\n\t| 'TOO_MANY_RECIPIENTS'\n\t| 'QUEUE_FULL'\n\t| 'CLOSED'\n\t| 'LEASE_LOST'\n\t| 'MESSAGE_UNREADABLE';\n\n/** Thrown by the queue and by every store, whichever answers the contract, with the same codes. */\nexport class QueueError extends Error {\n\toverride readonly name = 'QueueError';\n\treadonly code: QueueErrorCode;\n\n\tconstructor(code: QueueErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n\n/** `INVALID`, for an option or an argument. */\nexport const invalid = (message: string) => new QueueError('INVALID', message);\n",
|
|
6
6
|
"/** Text from the other side, made safe to keep and to write in a header field. */\n\n/** C0, DEL and C1: every control character, CR, LF and TAB included. */\nconst isControl = (code: number) =>\n\tcode < 0x20 || (code >= 0x7f && code <= 0x9f);\n\n/** Whether the text holds a control character. */\nexport function hasControl(text: string): boolean {\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (isControl(text.charCodeAt(i))) return true;\n\t}\n\treturn false;\n}\n\n/** Each control character replaced by `by`. */\nexport function replaceControls(text: string, by: string): string {\n\tlet out = '';\n\tfor (let i = 0; i < text.length; i++) {\n\t\tout += isControl(text.charCodeAt(i)) ? by : (text[i] as string);\n\t}\n\treturn out;\n}\n\n/**\n * Every control character (CR and LF included) as a space, a lone\n * surrogate as U+FFFD, runs of space as one, cut at `max` characters with\n * `...`, never inside a surrogate pair: what a reply leaves in an item,\n * an event and a DSN.\n */\nexport function cleanText(text: string, max: number): string {\n\tconst clean = replaceControls(text.toWellFormed(), ' ')\n\t\t.replace(/ {2,}/g, ' ')\n\t\t.trim();\n\treturn clean.length > max ? `${cut(clean, max - 3)}...` : clean;\n}\n\n/**\n * The first `length` UTF-16 units, never ending inside a surrogate pair:\n * a lone surrogate is text no store can keep (PostgreSQL's `jsonb` refuses\n * it), nor any UTF-8 encode.\n */\nfunction cut(text: string, length: number): string {\n\tconst head = text.slice(0, length);\n\tconst last = head.charCodeAt(head.length - 1);\n\treturn last >= 0xd800 && last <= 0xdbff ? head.slice(0, -1) : head;\n}\n\n/**\n * Text every store can keep: no NUL (PostgreSQL's `text` and `jsonb` hold\n * none) and no lone surrogate. `cleanText`'s output always is.\n */\nexport const isStorable = (text: string): boolean =>\n\ttext.isWellFormed() && !text.includes('\\0');\n\n/** `cleanText`, then anything not printable ASCII as `?`: for a `message/delivery-status` field. */\nexport const asciiText = (text: string, max: number): string =>\n\tcleanText(text, max).replace(/[^ -~]/g, '?');\n",
|
|
7
7
|
"import { invalid } from '../errors';\nimport { isStorable } from '../text';\nimport type {\n\tAttemptResult,\n\tClaimRequest,\n\tDiagnostic,\n\tNewQueueItem,\n\tQueueItem,\n\tQueueListOptions,\n\tRecipientState,\n\tRecipientUpdate,\n} from './types';\n\n/** What every store checks the same way, so each refuses the same input with the same error. */\n\nconst isTime = (value: unknown): value is number =>\n\ttypeof value === 'number' && Number.isFinite(value);\n\n/** A time in milliseconds since the epoch, or `INVALID` naming it. */\nexport function checkTime(name: string, value: unknown): number {\n\tif (!isTime(value)) throw invalid(`${name} must be a finite number`);\n\treturn value;\n}\n\n/**\n * Text a store keeps: refused, the same way by every store, when it holds\n * a NUL or a lone surrogate, which PostgreSQL cannot keep and no UTF-8\n * encode can carry.\n */\nexport function checkStorable(name: string, text: string): void {\n\tif (!isStorable(text)) {\n\t\tthrow invalid(\n\t\t\t`${name} holds a NUL or a lone surrogate, which a store cannot keep`,\n\t\t);\n\t}\n}\n\n/** A lease owner: a non-empty string. */\nexport function checkOwner(owner: unknown): string {\n\tif (typeof owner !== 'string' || owner === '') {\n\t\tthrow invalid('owner must be a non-empty string');\n\t}\n\tcheckStorable('owner', owner);\n\treturn owner;\n}\n\n/** Every text of a recipient's outcome. */\nfunction checkUpdate(update: RecipientUpdate): void {\n\tcheckStorable('A recipient', update.address);\n\tconst reply = update.reply as Partial<Diagnostic> | undefined;\n\tif (reply === undefined) return;\n\tif (\n\t\ttypeof reply !== 'object' ||\n\t\treply === null ||\n\t\ttypeof reply.text !== 'string'\n\t) {\n\t\tthrow invalid('A reply is an object with a text');\n\t}\n\tcheckStorable('reply.text', reply.text);\n\tfor (const name of ['status', 'host'] as const) {\n\t\tconst value = reply[name];\n\t\tif (value === undefined) continue;\n\t\tif (typeof value !== 'string')\n\t\t\tthrow invalid(`reply.${name} must be a string`);\n\t\tcheckStorable(`reply.${name}`, value);\n\t}\n}\n\nexport function checkNewItem(item: NewQueueItem): void {\n\tif (typeof item !== 'object' || item === null) {\n\t\tthrow invalid('A new item is an object');\n\t}\n\tif (typeof item.from !== 'string') throw invalid('from must be a string');\n\tcheckStorable('from', item.from);\n\tconst to = item.to as unknown;\n\tif (\n\t\t!Array.isArray(to) ||\n\t\tto.length === 0 ||\n\t\t!to.every((a) => typeof a === 'string' && a !== '')\n\t) {\n\t\tthrow invalid('to must be a non-empty array of addresses');\n\t}\n\tfor (const address of to as string[]) checkStorable('A recipient', address);\n\tif (!(item.message instanceof Uint8Array)) {\n\t\tthrow invalid('message must be a Uint8Array');\n\t}\n\tcheckTime('createdAt', item.createdAt);\n}\n\n/** `maxItems`: a positive integer, or nothing. */\nexport function checkMaxItems(max: unknown): number | undefined {\n\tif (max === undefined) return undefined;\n\tif (typeof max !== 'number' || !Number.isInteger(max) || max < 1) {\n\t\tthrow invalid(`maxItems must be an integer of at least 1, not ${max}`);\n\t}\n\treturn max;\n}\n\nexport function checkClaim(request: ClaimRequest): void {\n\tif (typeof request !== 'object' || request === null) {\n\t\tthrow invalid('A claim is an object');\n\t}\n\tcheckOwner(request.owner);\n\tcheckTime('now', request.now);\n\tif (!isTime(request.leaseMs) || request.leaseMs <= 0) {\n\t\tthrow invalid('leaseMs must be a positive number');\n\t}\n}\n\nexport function checkResult(result: AttemptResult): void {\n\tif (typeof result !== 'object' || result === null) {\n\t\tthrow invalid('An attempt result is an object');\n\t}\n\tcheckTime('now', result.now);\n\tcheckTime('nextAttemptAt', result.nextAttemptAt);\n\tif (!Number.isInteger(result.attempts) || result.attempts < 0) {\n\t\tthrow invalid('attempts must be an integer of at least 0');\n\t}\n\tif (typeof result.delayNotified !== 'boolean') {\n\t\tthrow invalid('delayNotified must be true or false');\n\t}\n\tconst statuses = ['delivered', 'deferred', 'failed'];\n\tif (\n\t\t!Array.isArray(result.recipients) ||\n\t\t!result.recipients.every(\n\t\t\t(u) => typeof u?.address === 'string' && statuses.includes(u.status),\n\t\t)\n\t) {\n\t\tthrow invalid(\n\t\t\t'recipients must be an array of { address, status: delivered, deferred or failed }',\n\t\t);\n\t}\n\tfor (const update of result.recipients) checkUpdate(update);\n}\n\n/** The offset and limit of a list: integers, the limit 1 to 1000. */\nexport function checkList(options: QueueListOptions = {}): {\n\toffset: number;\n\tlimit: number;\n} {\n\tconst offset = options.offset ?? 0;\n\tconst limit = options.limit ?? 100;\n\tif (!Number.isInteger(offset) || offset < 0) {\n\t\tthrow invalid(`offset must be an integer of at least 0, not ${offset}`);\n\t}\n\tif (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n\t\tthrow invalid(`limit must be an integer from 1 to 1000, not ${limit}`);\n\t}\n\treturn { offset, limit };\n}\n\n/** Every recipient is `delivered` or `failed`. */\nexport const isDone = (item: Pick<QueueItem, 'recipients'>): boolean =>\n\titem.recipients.every(\n\t\t(r) => r.status === 'delivered' || r.status === 'failed',\n\t);\n\n/**\n * The item once an attempt is recorded: its recipients' outcomes, a final\n * state never changed again, the schedule, and no lease.\n */\nexport function applyAttempt(\n\titem: QueueItem,\n\tresult: AttemptResult,\n): QueueItem {\n\tconst updates = new Map(result.recipients.map((u) => [u.address, u]));\n\tconst recipients = item.recipients.map((r): RecipientState => {\n\t\tconst update = updates.get(r.address);\n\t\tif (!update || r.status === 'delivered' || r.status === 'failed') return r;\n\t\treturn {\n\t\t\taddress: r.address,\n\t\t\tstatus: update.status,\n\t\t\t...(update.reply ? { reply: update.reply } : {}),\n\t\t\tupdatedAt: result.now,\n\t\t};\n\t});\n\tconst { lease: _, ...rest } = item;\n\treturn {\n\t\t...rest,\n\t\trecipients,\n\t\tnextAttemptAt: result.nextAttemptAt,\n\t\tattempts: result.attempts,\n\t\tdelayNotified: result.delayNotified,\n\t};\n}\n\n/** A new item as a store first keeps it. */\nexport function newItem(id: string, item: NewQueueItem): QueueItem {\n\treturn {\n\t\tid,\n\t\tfrom: item.from,\n\t\trecipients: item.to.map((address) => ({ address, status: 'pending' })),\n\t\tsize: item.message.length,\n\t\tcreatedAt: item.createdAt,\n\t\tnextAttemptAt: item.createdAt,\n\t\tattempts: 0,\n\t\tdelayNotified: false,\n\t};\n}\n\n/** Whether a lease, if any, leaves the item free to claim at `now`. */\nexport const claimable = (item: QueueItem, now: number): boolean =>\n\titem.nextAttemptAt <= now &&\n\t(item.lease === undefined || item.lease.expiresAt <= now);\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": ";
|
|
9
|
+
"mappings": "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",
|
|
10
10
|
"debugId": "81614FFC0DB6330C64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -17,7 +17,9 @@ export interface QueueStore {
|
|
|
17
17
|
/**
|
|
18
18
|
* Adds an item, due at once, with every recipient `pending`. With
|
|
19
19
|
* `maxItems`, refuses with `QUEUE_FULL` when the store holds that many
|
|
20
|
-
* already, in the same step as the add.
|
|
20
|
+
* already, in the same step as the add. The item is never claimable
|
|
21
|
+
* before its message is readable: write the message first, or both in
|
|
22
|
+
* one step — a claim that finds no message fails the item's recipients.
|
|
21
23
|
*/
|
|
22
24
|
add(item: NewQueueItem, options?: AddOptions): Promise<QueueItem>;
|
|
23
25
|
/** The item, or `undefined` when there is none by that id (done, cancelled, or never added). */
|
|
@@ -26,7 +28,11 @@ export interface QueueStore {
|
|
|
26
28
|
list(options?: QueueListOptions): Promise<QueueItem[]>;
|
|
27
29
|
/** How many items the store holds. */
|
|
28
30
|
count(): Promise<number>;
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The message of an item, or `undefined` once the item is gone. A
|
|
33
|
+
* store that holds the item but has lost its message gives `undefined`
|
|
34
|
+
* too: the queue then fails the item's pending recipients.
|
|
35
|
+
*/
|
|
30
36
|
readMessage(id: string): Promise<Uint8Array | undefined>;
|
|
31
37
|
/**
|
|
32
38
|
* Takes the next item due at `now` — no lease, or a lease expired —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-store.d.ts","sourceRoot":"","sources":["../../src/contract/queue-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAU;IAC1B
|
|
1
|
+
{"version":3,"file":"queue-store.d.ts","sourceRoot":"","sources":["../../src/contract/queue-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAU;IAC1B;;;;;;OAMG;IACH,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAElE,gGAAgG;IAChG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAEhD,0DAA0D;IAC1D,IAAI,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAEvD,sCAAsC;IACtC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IAEzD;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAE7D;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtE;;;;;OAKG;IACH,QAAQ,CACP,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa,GACnB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAElC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErE,0FAA0F;IAC1F,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CACnD"}
|
package/dist/dsn/build.d.ts
CHANGED
|
@@ -16,8 +16,11 @@ export interface DsnInput {
|
|
|
16
16
|
readonly recipients: readonly DsnRecipient[];
|
|
17
17
|
/** With `delayed`: when the queue gives up. */
|
|
18
18
|
readonly willRetryUntil?: Date;
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The original message, as enqueued; absent when it could not be read,
|
|
21
|
+
* and the DSN then has no third part (RFC 6522 §3 makes it optional).
|
|
22
|
+
*/
|
|
23
|
+
readonly original?: Uint8Array;
|
|
21
24
|
readonly returnContent: 'headers' | 'full';
|
|
22
25
|
/** The most bytes of the original returned. */
|
|
23
26
|
readonly maxReturn: number;
|
|
@@ -27,7 +30,8 @@ export interface DsnInput {
|
|
|
27
30
|
* (RFC 6522) of `report-type=delivery-status` holding the text a person
|
|
28
31
|
* reads, the `message/delivery-status` fields a program reads, and the
|
|
29
32
|
* original's header fields (or the whole original, when asked and small
|
|
30
|
-
* enough), bounded by `maxReturn
|
|
33
|
+
* enough), bounded by `maxReturn` — or, with no `original`, no third
|
|
34
|
+
* part at all. Nothing from the remote server or the
|
|
31
35
|
* envelope reaches a header field raw: control characters, CR and LF are
|
|
32
36
|
* taken out first.
|
|
33
37
|
*/
|
package/dist/dsn/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/dsn/build.ts"],"names":[],"mappings":"AAQA,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,YAAY,EAGjB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEtD,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C,+CAA+C;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAC/B
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/dsn/build.ts"],"names":[],"mappings":"AAQA,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,YAAY,EAGjB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEtD,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C,+CAA+C;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3C,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAwFD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,UAAU,CA2CpD"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* `close()`. `LEASE_LOST`: given to the `error` event when another worker
|
|
6
6
|
* took an item before its outcome was recorded — or, once the lease's
|
|
7
7
|
* expiry passed, when the item is gone: lost or cancelled.
|
|
8
|
+
* `MESSAGE_UNREADABLE`: given to the `error` event when the store holds an
|
|
9
|
+
* item but not its message, whose pending recipients then fail.
|
|
8
10
|
*/
|
|
9
|
-
export type QueueErrorCode = 'INVALID' | 'MESSAGE_TOO_BIG' | 'TOO_MANY_RECIPIENTS' | 'QUEUE_FULL' | 'CLOSED' | 'LEASE_LOST';
|
|
11
|
+
export type QueueErrorCode = 'INVALID' | 'MESSAGE_TOO_BIG' | 'TOO_MANY_RECIPIENTS' | 'QUEUE_FULL' | 'CLOSED' | 'LEASE_LOST' | 'MESSAGE_UNREADABLE';
|
|
10
12
|
/** Thrown by the queue and by every store, whichever answers the contract, with the same codes. */
|
|
11
13
|
export declare class QueueError extends Error {
|
|
12
14
|
readonly name = "QueueError";
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GACvB,SAAS,GACT,iBAAiB,GACjB,qBAAqB,GACrB,YAAY,GACZ,QAAQ,GACR,YAAY,GACZ,oBAAoB,CAAC;AAExB,mGAAmG;AACnG,qBAAa,UAAW,SAAQ,KAAK;IACpC,SAAkB,IAAI,gBAAgB;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;gBAElB,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM;CAIjD;AAED,+CAA+C;AAC/C,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,eAAuC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -429,6 +429,9 @@ function humanText(input) {
|
|
|
429
429
|
...input.willRetryUntil ? [`until ${formatDate2(input.willRetryUntil)}.`] : [],
|
|
430
430
|
"You do not need to send it again."
|
|
431
431
|
];
|
|
432
|
+
if (!input.original) {
|
|
433
|
+
lines.push("", "Your message could not be read from the queue, so it is not returned.");
|
|
434
|
+
}
|
|
432
435
|
lines.push("");
|
|
433
436
|
for (const r of input.recipients) {
|
|
434
437
|
const said = r.reply ? `${r.reply.code ?? ""} ${cleanText(r.reply.text, 900)}`.trim() : statusOf(input.kind);
|
|
@@ -471,8 +474,8 @@ function boundaryFor(content) {
|
|
|
471
474
|
}
|
|
472
475
|
}
|
|
473
476
|
function buildDsn(input) {
|
|
474
|
-
const original = returned(input.original, input.returnContent, input.maxReturn);
|
|
475
|
-
const boundary = boundaryFor(original
|
|
477
|
+
const original = input.original ? returned(input.original, input.returnContent, input.maxReturn) : undefined;
|
|
478
|
+
const boundary = boundaryFor(original?.body ?? new Uint8Array);
|
|
476
479
|
const text = encodeQuotedPrintable(humanText(input));
|
|
477
480
|
const status = deliveryStatus({
|
|
478
481
|
kind: input.kind,
|
|
@@ -481,7 +484,7 @@ function buildDsn(input) {
|
|
|
481
484
|
recipients: input.recipients,
|
|
482
485
|
...input.willRetryUntil ? { willRetryUntil: input.willRetryUntil } : {}
|
|
483
486
|
});
|
|
484
|
-
const
|
|
487
|
+
const parts = [
|
|
485
488
|
...headerFields(input, boundary),
|
|
486
489
|
"",
|
|
487
490
|
"This is a MIME-formatted delivery status notification.",
|
|
@@ -494,7 +497,16 @@ function buildDsn(input) {
|
|
|
494
497
|
`--${boundary}`,
|
|
495
498
|
"Content-Type: message/delivery-status",
|
|
496
499
|
"",
|
|
497
|
-
status
|
|
500
|
+
status
|
|
501
|
+
];
|
|
502
|
+
const end = `--${boundary}--\r
|
|
503
|
+
`;
|
|
504
|
+
if (!original)
|
|
505
|
+
return encoder.encode(`${parts.join(`\r
|
|
506
|
+
`)}\r
|
|
507
|
+
${end}`);
|
|
508
|
+
const head = [
|
|
509
|
+
...parts,
|
|
498
510
|
`--${boundary}`,
|
|
499
511
|
`Content-Type: ${original.type}`,
|
|
500
512
|
...original.eightBit ? ["Content-Transfer-Encoding: 8bit"] : [],
|
|
@@ -506,8 +518,7 @@ function buildDsn(input) {
|
|
|
506
518
|
encoder.encode(head),
|
|
507
519
|
original.body,
|
|
508
520
|
encoder.encode(`\r
|
|
509
|
-
|
|
510
|
-
`)
|
|
521
|
+
${end}`)
|
|
511
522
|
]);
|
|
512
523
|
}
|
|
513
524
|
|
|
@@ -545,7 +556,7 @@ async function notify(ctx, item, message, kind, recipients, now) {
|
|
|
545
556
|
lastAttempt
|
|
546
557
|
})),
|
|
547
558
|
...kind === "delayed" ? { willRetryUntil: new Date(giveUpAt(item.createdAt, settings.retry)) } : {},
|
|
548
|
-
original: message,
|
|
559
|
+
...message ? { original: message } : {},
|
|
549
560
|
returnContent: settings.dsn.returnContent,
|
|
550
561
|
maxReturn: settings.limits.maxDsnReturn
|
|
551
562
|
});
|
|
@@ -745,6 +756,15 @@ var badSender = (address) => ({
|
|
|
745
756
|
text: "The sender's address is not one SMTP can carry"
|
|
746
757
|
}
|
|
747
758
|
});
|
|
759
|
+
var unreadable = (address) => ({
|
|
760
|
+
address,
|
|
761
|
+
status: "failed",
|
|
762
|
+
reply: {
|
|
763
|
+
status: "5.3.0",
|
|
764
|
+
text: "Message unreadable: the queue store holds the item but not its message"
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
var unreadableError = (id) => new QueueError2("MESSAGE_UNREADABLE", `The message of ${id} is unreadable: the store holds the item but not its message, so every pending recipient failed`);
|
|
748
768
|
async function attemptGroup(ctx, item, message, domain, recipients) {
|
|
749
769
|
const { settings } = ctx;
|
|
750
770
|
const max = settings.limits.maxReplyText;
|
|
@@ -784,10 +804,15 @@ function emitOutcomes(events, item, settled) {
|
|
|
784
804
|
events.emit("failed", base(u));
|
|
785
805
|
}
|
|
786
806
|
async function deliverItem(ctx, item) {
|
|
787
|
-
const {
|
|
807
|
+
const { store } = ctx;
|
|
788
808
|
const message = await store.readMessage(item.id);
|
|
789
|
-
if (!message)
|
|
809
|
+
if (!message) {
|
|
810
|
+
if (await store.get(item.id) === undefined)
|
|
811
|
+
return;
|
|
812
|
+
const failed = [...groupsOf(item).values()].flat().map(unreadable);
|
|
813
|
+
await record(ctx, item, failed, false, undefined);
|
|
790
814
|
return;
|
|
815
|
+
}
|
|
791
816
|
const groups = groupsOf(item);
|
|
792
817
|
let skipped = false;
|
|
793
818
|
const outcomes = item.from !== "" && !isMailbox2(item.from) ? [[...groups.values()].flat().map(badSender)] : await Promise.all([...groups].map(async ([domain, group]) => {
|
|
@@ -802,8 +827,12 @@ async function deliverItem(ctx, item) {
|
|
|
802
827
|
release();
|
|
803
828
|
}
|
|
804
829
|
}));
|
|
830
|
+
await record(ctx, item, outcomes.flat(), skipped, message);
|
|
831
|
+
}
|
|
832
|
+
async function record(ctx, item, updates, skipped, message) {
|
|
833
|
+
const { settings, store, events } = ctx;
|
|
805
834
|
const now = settings.now();
|
|
806
|
-
const settled = settle(item,
|
|
835
|
+
const settled = settle(item, updates, skipped, now, settings);
|
|
807
836
|
const after = await store.complete(item.id, settings.owner, settled.result);
|
|
808
837
|
ctx.recorded();
|
|
809
838
|
if (!after && await store.get(item.id) === undefined) {
|
|
@@ -812,6 +841,8 @@ async function deliverItem(ctx, item) {
|
|
|
812
841
|
events.emit("error", { error: goneAfter(lapse, item.id), id: item.id });
|
|
813
842
|
return;
|
|
814
843
|
}
|
|
844
|
+
if (!message)
|
|
845
|
+
return;
|
|
815
846
|
emitOutcomes(events, item, settled);
|
|
816
847
|
return;
|
|
817
848
|
}
|
|
@@ -820,6 +851,9 @@ async function deliverItem(ctx, item) {
|
|
|
820
851
|
events.emit("error", { error, id: item.id });
|
|
821
852
|
return;
|
|
822
853
|
}
|
|
854
|
+
if (!message) {
|
|
855
|
+
events.emit("error", { error: unreadableError(item.id), id: item.id });
|
|
856
|
+
}
|
|
823
857
|
emitOutcomes(events, item, settled);
|
|
824
858
|
await notify(ctx, item, message, "failed", settled.failed, now);
|
|
825
859
|
if (settled.delayDsn) {
|
|
@@ -1116,5 +1150,5 @@ export {
|
|
|
1116
1150
|
createQueue
|
|
1117
1151
|
};
|
|
1118
1152
|
|
|
1119
|
-
//# debugId=
|
|
1153
|
+
//# debugId=F644E902CC0F818C64756E2164756E21
|
|
1120
1154
|
//# sourceMappingURL=index.js.map
|