@bumail/queue 0.7.2 → 0.9.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 +27 -3
- package/dist/chunks/{index-3pt7rc59.js → index-9yp15arf.js} +98 -25
- package/dist/chunks/index-9yp15arf.js.map +14 -0
- package/dist/chunks/index-r8wb840a.js +24 -0
- package/dist/chunks/index-r8wb840a.js.map +10 -0
- package/dist/contract/checks.d.ts +2 -6
- package/dist/contract/checks.d.ts.map +1 -1
- package/dist/contract/dsn-checks.d.ts +4 -0
- package/dist/contract/dsn-checks.d.ts.map +1 -0
- package/dist/contract/dsn-column.d.ts +12 -0
- package/dist/contract/dsn-column.d.ts.map +1 -0
- package/dist/contract/queue-store.d.ts +2 -1
- package/dist/contract/queue-store.d.ts.map +1 -1
- package/dist/contract/storable.d.ts +7 -0
- package/dist/contract/storable.d.ts.map +1 -0
- package/dist/contract/types.d.ts +52 -0
- package/dist/contract/types.d.ts.map +1 -1
- package/dist/dsn/build.d.ts +2 -0
- package/dist/dsn/build.d.ts.map +1 -1
- package/dist/dsn/delivered.d.ts +39 -0
- package/dist/dsn/delivered.d.ts.map +1 -0
- package/dist/dsn/fields.d.ts +11 -2
- package/dist/dsn/fields.d.ts.map +1 -1
- package/dist/dsn/human.d.ts +7 -0
- package/dist/dsn/human.d.ts.map +1 -0
- package/dist/dsn/policy.d.ts +17 -0
- package/dist/dsn/policy.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +474 -300
- package/dist/index.js.map +20 -15
- package/dist/memory/index.js +1 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/postgres/index.js +17 -9
- package/dist/postgres/index.js.map +6 -6
- package/dist/postgres/rows.d.ts +6 -1
- package/dist/postgres/rows.d.ts.map +1 -1
- package/dist/postgres/schema.d.ts.map +1 -1
- package/dist/postgres/statements.d.ts +9 -8
- package/dist/postgres/statements.d.ts.map +1 -1
- package/dist/postgres/store.d.ts.map +1 -1
- package/dist/queue/attempt.d.ts +7 -7
- package/dist/queue/attempt.d.ts.map +1 -1
- package/dist/queue/deliver.d.ts.map +1 -1
- package/dist/queue/envelope.d.ts +24 -4
- package/dist/queue/envelope.d.ts.map +1 -1
- package/dist/queue/events.d.ts +8 -1
- package/dist/queue/events.d.ts.map +1 -1
- package/dist/queue/fallback-session.d.ts +22 -0
- package/dist/queue/fallback-session.d.ts.map +1 -0
- package/dist/queue/notify.d.ts +6 -3
- package/dist/queue/notify.d.ts.map +1 -1
- package/dist/queue/outcome.d.ts.map +1 -1
- package/dist/queue/queue.d.ts.map +1 -1
- package/dist/queue/route.d.ts +7 -0
- package/dist/queue/route.d.ts.map +1 -1
- package/dist/queue/session.d.ts +14 -0
- package/dist/queue/session.d.ts.map +1 -0
- package/dist/queue/settle.d.ts.map +1 -1
- package/dist/redis/index.js +26 -13
- package/dist/redis/index.js.map +5 -5
- package/dist/redis/items.d.ts +2 -0
- package/dist/redis/items.d.ts.map +1 -1
- package/dist/redis/scripts.d.ts +1 -1
- package/dist/redis/scripts.d.ts.map +1 -1
- package/dist/redis/store.d.ts.map +1 -1
- package/dist/sqlite/index.js +15 -8
- package/dist/sqlite/index.js.map +5 -5
- package/dist/sqlite/rows.d.ts +2 -1
- package/dist/sqlite/rows.d.ts.map +1 -1
- package/dist/sqlite/schema.d.ts.map +1 -1
- package/dist/sqlite/store.d.ts.map +1 -1
- package/docs/guide.md +97 -7
- package/docs/roadmap.md +14 -7
- package/docs/troubleshooting.md +52 -1
- package/package.json +3 -3
- package/dist/chunks/index-3pt7rc59.js.map +0 -12
package/dist/memory/index.js
CHANGED
package/dist/memory/index.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import {\n\tapplyAttempt,\n\tcheckClaim,\n\tcheckList,\n\tcheckMaxItems,\n\tcheckNewItem,\n\tcheckOwner,\n\tcheckResult,\n\tcheckTime,\n\tclaimable,\n\tisDone,\n\tnewItem,\n} from '../contract/checks';\nimport type { QueueStore } from '../contract/queue-store';\nimport type {\n\tAddOptions,\n\tAttemptResult,\n\tClaimRequest,\n\tNewQueueItem,\n\tQueueItem,\n\tQueueListOptions,\n} from '../contract/types';\nimport { QueueError } from '../errors';\n\ninterface Entry {\n\titem: QueueItem;\n\tmessage: Uint8Array;\n\t/** The order items were added in: the oldest first among those equally due. */\n\treadonly seq: number;\n}\n\n/** The earliest due first, then the oldest. */\nconst byDue = (a: Entry, b: Entry) =>\n\ta.item.nextAttemptAt - b.item.nextAttemptAt || a.seq - b.seq;\n\n/**\n * A `QueueStore` in memory: for specs, and for a single process that can\n * lose its queue on a restart. Every operation runs without awaiting, so\n * claims never interleave, and every item it returns is a copy.\n */\nexport class MemoryQueueStore implements QueueStore {\n\treadonly #entries = new Map<string, Entry>();\n\t#seq = 0;\n\n\tasync add(item: NewQueueItem, options: AddOptions = {}): Promise<QueueItem> {\n\t\tcheckNewItem(item);\n\t\tconst max = checkMaxItems(options.maxItems);\n\t\tif (max !== undefined && this.#entries.size >= max) {\n\t\t\tthrow new QueueError(\n\t\t\t\t'QUEUE_FULL',\n\t\t\t\t`The queue holds ${this.#entries.size} items, its limit`,\n\t\t\t);\n\t\t}\n\t\tconst entry: Entry = {\n\t\t\titem: newItem(crypto.randomUUID(), item),\n\t\t\tmessage: item.message.slice(),\n\t\t\tseq: this.#seq++,\n\t\t};\n\t\tthis.#entries.set(entry.item.id, entry);\n\t\treturn structuredClone(entry.item);\n\t}\n\n\tasync get(id: string): Promise<QueueItem | undefined> {\n\t\tconst entry = this.#entries.get(id);\n\t\treturn entry && structuredClone(entry.item);\n\t}\n\n\tasync list(options?: QueueListOptions): Promise<QueueItem[]> {\n\t\tconst { offset, limit } = checkList(options);\n\t\treturn [...this.#entries.values()]\n\t\t\t.sort(byDue)\n\t\t\t.slice(offset, offset + limit)\n\t\t\t.map((entry) => structuredClone(entry.item));\n\t}\n\n\tasync count(): Promise<number> {\n\t\treturn this.#entries.size;\n\t}\n\n\tasync readMessage(id: string): Promise<Uint8Array | undefined> {\n\t\treturn this.#entries.get(id)?.message.slice();\n\t}\n\n\tasync claim(request: ClaimRequest): Promise<QueueItem | undefined> {\n\t\tcheckClaim(request);\n\t\tlet next: Entry | undefined;\n\t\tfor (const entry of this.#entries.values()) {\n\t\t\tif (!claimable(entry.item, request.now)) continue;\n\t\t\tif (!next || byDue(entry, next) < 0) next = entry;\n\t\t}\n\t\tif (!next) return undefined;\n\t\tnext.item = {\n\t\t\t...next.item,\n\t\t\tlease: {\n\t\t\t\towner: request.owner,\n\t\t\t\texpiresAt: request.now + request.leaseMs,\n\t\t\t},\n\t\t};\n\t\treturn structuredClone(next.item);\n\t}\n\n\t/** The entry while `owner` holds its lease. */\n\t#held(id: string, owner: string): Entry | undefined {\n\t\tcheckOwner(owner);\n\t\tconst entry = this.#entries.get(id);\n\t\treturn entry?.item.lease?.owner === owner ? entry : undefined;\n\t}\n\n\tasync renew(id: string, owner: string, expiresAt: number): Promise<boolean> {\n\t\tcheckTime('expiresAt', expiresAt);\n\t\tconst entry = this.#held(id, owner);\n\t\tif (!entry) return false;\n\t\tentry.item = { ...entry.item, lease: { owner, expiresAt } };\n\t\treturn true;\n\t}\n\n\tasync complete(\n\t\tid: string,\n\t\towner: string,\n\t\tresult: AttemptResult,\n\t): Promise<QueueItem | undefined> {\n\t\tcheckResult(result);\n\t\tconst entry = this.#held(id, owner);\n\t\tif (!entry) return undefined;\n\t\tentry.item = applyAttempt(entry.item, result);\n\t\tif (isDone(entry.item)) this.#entries.delete(id);\n\t\treturn structuredClone(entry.item);\n\t}\n\n\tasync reschedule(id: string, at: number, owner?: string): Promise<boolean> {\n\t\tcheckTime('at', at);\n\t\tconst entry =\n\t\t\towner === undefined ? this.#entries.get(id) : this.#held(id, owner);\n\t\tif (!entry) return false;\n\t\tif (owner === undefined) {\n\t\t\tentry.item = { ...entry.item, nextAttemptAt: at };\n\t\t} else {\n\t\t\tconst { lease: _, ...rest } = entry.item;\n\t\t\tentry.item = { ...rest, nextAttemptAt: at };\n\t\t}\n\t\treturn true;\n\t}\n\n\tasync cancel(id: string): Promise<QueueItem | undefined> {\n\t\tconst entry = this.#entries.get(id);\n\t\tif (!entry) return undefined;\n\t\tthis.#entries.delete(id);\n\t\treturn structuredClone(entry.item);\n\t}\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;AAgCA,IAAM,QAAQ,CAAC,GAAU,MACxB,EAAE,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,EAAE;AAAA;AAOnD,MAAM,iBAAuC;AAAA,EAC1C,WAAW,IAAI;AAAA,EACxB,OAAO;AAAA,
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "E6F9A2B10DF5B49964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/postgres/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dsnColumn,
|
|
3
|
+
dsnParams
|
|
4
|
+
} from "../chunks/index-r8wb840a.js";
|
|
1
5
|
import {
|
|
2
6
|
masked
|
|
3
7
|
} from "../chunks/index-b4xvg3xn.js";
|
|
4
8
|
import {
|
|
9
|
+
isStorable,
|
|
5
10
|
QueueError2,
|
|
6
11
|
invalid,
|
|
7
|
-
isStorable,
|
|
8
12
|
checkTime,
|
|
9
13
|
checkOwner,
|
|
10
14
|
checkNewItem,
|
|
@@ -15,7 +19,7 @@ import {
|
|
|
15
19
|
isDone,
|
|
16
20
|
applyAttempt,
|
|
17
21
|
newItem
|
|
18
|
-
} from "../chunks/index-
|
|
22
|
+
} from "../chunks/index-9yp15arf.js";
|
|
19
23
|
|
|
20
24
|
// src/postgres/connect.ts
|
|
21
25
|
var DEFAULT_PREFIX = "bumail_queue_";
|
|
@@ -84,7 +88,7 @@ function connect(options) {
|
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
// src/postgres/rows.ts
|
|
87
|
-
var COLUMNS = "id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at";
|
|
91
|
+
var COLUMNS = "id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn";
|
|
88
92
|
function itemOf(row) {
|
|
89
93
|
const recipients = typeof row.recipients === "string" ? JSON.parse(row.recipients) : structuredClone(row.recipients);
|
|
90
94
|
const item = {
|
|
@@ -95,7 +99,8 @@ function itemOf(row) {
|
|
|
95
99
|
createdAt: row.created_at,
|
|
96
100
|
nextAttemptAt: row.next_attempt_at,
|
|
97
101
|
attempts: row.attempts,
|
|
98
|
-
delayNotified: row.delay_notified
|
|
102
|
+
delayNotified: row.delay_notified,
|
|
103
|
+
...dsnParams(row.dsn)
|
|
99
104
|
};
|
|
100
105
|
if (row.lease_owner === null || row.lease_expires_at === null)
|
|
101
106
|
return item;
|
|
@@ -145,7 +150,8 @@ function migrations({
|
|
|
145
150
|
item_id text PRIMARY KEY REFERENCES ${items} (id) ON DELETE CASCADE,
|
|
146
151
|
content bytea NOT NULL
|
|
147
152
|
)`
|
|
148
|
-
]
|
|
153
|
+
],
|
|
154
|
+
[`ALTER TABLE ${items} ADD COLUMN dsn jsonb`]
|
|
149
155
|
];
|
|
150
156
|
}
|
|
151
157
|
function checkNotNewer(version, known) {
|
|
@@ -197,8 +203,9 @@ function statementsOf({ items, messages }) {
|
|
|
197
203
|
return {
|
|
198
204
|
insert: `WITH item AS (
|
|
199
205
|
INSERT INTO ${items} (id, sender, recipients, size, created_at,
|
|
200
|
-
next_attempt_at, attempts, delay_notified)
|
|
201
|
-
VALUES ($1, $2, $3::text::jsonb, $4::int, $5::float8, $5::float8, 0, false
|
|
206
|
+
next_attempt_at, attempts, delay_notified, dsn)
|
|
207
|
+
VALUES ($1, $2, $3::text::jsonb, $4::int, $5::float8, $5::float8, 0, false,
|
|
208
|
+
$7::text::jsonb)
|
|
202
209
|
RETURNING id)
|
|
203
210
|
INSERT INTO ${messages} (item_id, content) SELECT id, $6::bytea FROM item`,
|
|
204
211
|
lockAdds: `SELECT pg_advisory_xact_lock(hashtext('${items}')) IS NULL`,
|
|
@@ -292,7 +299,8 @@ class PostgresQueueStore {
|
|
|
292
299
|
JSON.stringify(added.recipients),
|
|
293
300
|
added.size,
|
|
294
301
|
added.createdAt,
|
|
295
|
-
item.message
|
|
302
|
+
item.message,
|
|
303
|
+
dsnColumn(added)
|
|
296
304
|
];
|
|
297
305
|
const q = this.#q;
|
|
298
306
|
if (max === undefined) {
|
|
@@ -400,5 +408,5 @@ export {
|
|
|
400
408
|
PostgresQueueStore
|
|
401
409
|
};
|
|
402
410
|
|
|
403
|
-
//# debugId=
|
|
411
|
+
//# debugId=E11119E44742AD2264756E2164756E21
|
|
404
412
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sources": ["../src/postgres/connect.ts", "../src/postgres/rows.ts", "../src/postgres/isolation.ts", "../src/postgres/schema.ts", "../src/postgres/statements.ts", "../src/postgres/store.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { invalid } from '../errors';\nimport { masked } from '../masked';\nimport type { PostgresClient, PostgresQueueStoreOptions } from './options';\n\n/** The tables of one queue, each name its prefix and a fixed suffix. */\nexport interface Tables {\n\treadonly prefix: string;\n\treadonly items: string;\n\treadonly messages: string;\n\treadonly schema: string;\n}\n\n/** The client a store uses, and whether it opened it (and so closes it). */\nexport interface Connection {\n\treadonly client: PostgresClient;\n\treadonly owned: boolean;\n\treadonly tables: Tables;\n\t/**\n\t * The password the client logs in with, as far as the store can know\n\t * it — its URL's, or a given `Bun.SQL`'s `options.password` — so an\n\t * error that repeats it is masked; `''` when unknown.\n\t */\n\treadonly password: string;\n}\n\nconst DEFAULT_PREFIX = 'bumail_queue_';\n\n/**\n * Within PostgreSQL's 63 bytes for a name once the longest name derived\n * from it (`messages_item_id_fkey`, 21) follows. Nothing else is allowed:\n * the prefix is written into the statements, never bound.\n */\nconst PREFIX = /^[a-z_][a-z0-9_]{0,39}$/;\n\nfunction tablesOf(value: unknown): Tables {\n\tconst prefix = value ?? DEFAULT_PREFIX;\n\tif (typeof prefix !== 'string' || !PREFIX.test(prefix)) {\n\t\tthrow invalid(\n\t\t\t`tablePrefix must be lowercase letters, digits and underscores, starting with a letter or an underscore, at most 40 characters, not ${JSON.stringify(prefix)}`,\n\t\t);\n\t}\n\treturn {\n\t\tprefix,\n\t\titems: `${prefix}items`,\n\t\tmessages: `${prefix}messages`,\n\t\tschema: `${prefix}schema`,\n\t};\n}\n\nconst NEEDS_SQL =\n\t'A PostgreSQL queue store needs sql: a Bun.SQL client or a postgres:// URL';\n\n/** A `postgres://` or `postgresql://` URL; never echoed, since it may hold a password. */\nfunction urlOf(value: string | URL): URL {\n\tconst url = URL.parse(String(value));\n\tif (url === null || !['postgres:', 'postgresql:'].includes(url.protocol)) {\n\t\tthrow invalid(NEEDS_SQL);\n\t}\n\treturn url;\n}\n\n/**\n * A client of the store's own for the URL. `Bun.SQL` refuses some\n * parameters at once (a `sslmode` it does not know): that is `INVALID`,\n * its reason kept and the password, should it be repeated, masked.\n */\nfunction clientFor(url: URL): PostgresClient {\n\ttry {\n\t\treturn new Bun.SQL(url.href);\n\t} catch (error) {\n\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\tthrow invalid(\n\t\t\t`The URL in sql cannot be opened: ${masked(reason, url.password)}`,\n\t\t);\n\t}\n}\n\nconst isClient = (value: unknown): value is PostgresClient =>\n\t(typeof value === 'object' || typeof value === 'function') &&\n\tvalue !== null &&\n\ttypeof (value as PostgresClient).unsafe === 'function' &&\n\ttypeof (value as PostgresClient).begin === 'function' &&\n\ttypeof (value as PostgresClient).close === 'function';\n\n/** A `Bun.SQL` client says which database it speaks: the queue needs PostgreSQL. */\nfunction checkAdapter(client: PostgresClient): void {\n\tconst adapter = (client as { options?: { adapter?: unknown } }).options\n\t\t?.adapter;\n\tif (adapter !== undefined && adapter !== 'postgres') {\n\t\tthrow invalid(\n\t\t\t`sql is a ${String(adapter)} client; the queue needs a PostgreSQL one`,\n\t\t);\n\t}\n}\n\n/** A given client's password, when it says it as text (`Bun.SQL` does). */\nfunction passwordOf(client: PostgresClient): string {\n\tconst password = (client as { options?: { password?: unknown } }).options\n\t\t?.password;\n\treturn typeof password === 'string' ? password : '';\n}\n\n/** Checks the options and opens a client for a URL; connects to nothing yet. */\nexport function connect(options: PostgresQueueStoreOptions): Connection {\n\tconst given = (options as PostgresQueueStoreOptions | undefined)?.sql;\n\tconst tables = tablesOf(options?.tablePrefix);\n\tif (isClient(given)) {\n\t\tcheckAdapter(given);\n\t\treturn {\n\t\t\tclient: given,\n\t\t\towned: false,\n\t\t\ttables,\n\t\t\tpassword: passwordOf(given),\n\t\t};\n\t}\n\tif (typeof given !== 'string' && !(given instanceof URL)) {\n\t\tthrow invalid(NEEDS_SQL);\n\t}\n\tconst url = urlOf(given);\n\treturn {\n\t\tclient: clientFor(url),\n\t\towned: true,\n\t\ttables,\n\t\tpassword: url.password,\n\t};\n}\n",
|
|
6
|
-
"import type { QueueItem, RecipientState } from '../contract/types';\n\n/** A row of the items table, as `Bun.SQL` reads it. */\nexport interface ItemRow {\n\treadonly id: string;\n\treadonly sender: string;\n\t/** `jsonb`: parsed or not, depending on the client. */\n\treadonly recipients: string | RecipientState[];\n\treadonly size: number;\n\treadonly created_at: number;\n\treadonly next_attempt_at: number;\n\treadonly attempts: number;\n\treadonly delay_notified: boolean;\n\treadonly lease_owner: string | null;\n\treadonly lease_expires_at: number | null;\n}\n\n/** The columns a query reads to build an item. */\nexport const COLUMNS =\n\t'id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at';\n\nexport function itemOf(row: ItemRow): QueueItem {\n\tconst recipients =\n\t\ttypeof row.recipients === 'string'\n\t\t\t? (JSON.parse(row.recipients) as RecipientState[])\n\t\t\t: structuredClone(row.recipients);\n\tconst item: QueueItem = {\n\t\tid: row.id,\n\t\tfrom: row.sender,\n\t\trecipients,\n\t\tsize: row.size,\n\t\tcreatedAt: row.created_at,\n\t\tnextAttemptAt: row.next_attempt_at,\n\t\tattempts: row.attempts,\n\t\tdelayNotified: row.delay_notified,\n\t};\n\tif (row.lease_owner === null || row.lease_expires_at === null) return item;\n\treturn {\n\t\t...item,\n\t\tlease: { owner: row.lease_owner, expiresAt: row.lease_expires_at },\n\t};\n}\n\n/** The rows a statement returned. */\nexport const rowsOf = async <T>(pending: PromiseLike<unknown>) =>\n\t(await pending) as T[];\n",
|
|
6
|
+
"import { dsnParams } from '../contract/dsn-column';\nimport type { QueueItem, RecipientState } from '../contract/types';\n\n/** A row of the items table, as `Bun.SQL` reads it. */\nexport interface ItemRow {\n\treadonly id: string;\n\treadonly sender: string;\n\t/** `jsonb`: parsed or not, depending on the client. */\n\treadonly recipients: string | RecipientState[];\n\treadonly size: number;\n\treadonly created_at: number;\n\treadonly next_attempt_at: number;\n\treadonly attempts: number;\n\treadonly delay_notified: boolean;\n\treadonly lease_owner: string | null;\n\treadonly lease_expires_at: number | null;\n\t/** `jsonb`: parsed or not, depending on the client. */\n\treadonly dsn: string | { ret?: 'FULL' | 'HDRS'; envid?: string } | null;\n}\n\n/** The columns a query reads to build an item. */\nexport const COLUMNS =\n\t'id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn';\n\nexport function itemOf(row: ItemRow): QueueItem {\n\tconst recipients =\n\t\ttypeof row.recipients === 'string'\n\t\t\t? (JSON.parse(row.recipients) as RecipientState[])\n\t\t\t: structuredClone(row.recipients);\n\tconst item: QueueItem = {\n\t\tid: row.id,\n\t\tfrom: row.sender,\n\t\trecipients,\n\t\tsize: row.size,\n\t\tcreatedAt: row.created_at,\n\t\tnextAttemptAt: row.next_attempt_at,\n\t\tattempts: row.attempts,\n\t\tdelayNotified: row.delay_notified,\n\t\t...dsnParams(row.dsn),\n\t};\n\tif (row.lease_owner === null || row.lease_expires_at === null) return item;\n\treturn {\n\t\t...item,\n\t\tlease: { owner: row.lease_owner, expiresAt: row.lease_expires_at },\n\t};\n}\n\n/** The rows a statement returned. */\nexport const rowsOf = async <T>(pending: PromiseLike<unknown>) =>\n\t(await pending) as T[];\n",
|
|
7
7
|
"import type { PostgresClient, PostgresQueryable } from './options';\nimport { rowsOf } from './rows';\n\n/**\n * The first statement of every transaction that writes: READ COMMITTED\n * whatever the client's sessions default to. The claim's `FOR UPDATE SKIP\n * LOCKED` and the `maxItems` advisory lock count on it: under `repeatable\n * read` or `serializable`, a row another transaction changed since the\n * snapshot fails with SQLSTATE 40001, and the count taken after the lock\n * would read a snapshot from before it.\n */\nconst READ_COMMITTED =\n\t'SET TRANSACTION ISOLATION LEVEL READ COMMITTED, READ WRITE';\n\n/** `fn` in a transaction at READ COMMITTED: every write of the store, its migration included, goes through here. */\nexport async function writing<T>(\n\tsql: PostgresClient,\n\tfn: (tx: PostgresQueryable) => Promise<T>,\n): Promise<T> {\n\treturn (await sql.begin(async (tx: PostgresQueryable) => {\n\t\tawait tx.unsafe(READ_COMMITTED);\n\t\treturn fn(tx);\n\t})) as T;\n}\n\n/** One statement that writes, at READ COMMITTED; its rows. */\nexport function written<T>(\n\tsql: PostgresClient,\n\tquery: string,\n\tvalues: unknown[] = [],\n): Promise<T[]> {\n\treturn writing(sql, (tx) => rowsOf<T>(tx.unsafe(query, values)));\n}\n",
|
|
8
|
-
"import { QueueError } from '../errors';\nimport type { Tables } from './connect';\nimport { writing } from './isolation';\nimport type { PostgresClient, PostgresQueryable } from './options';\nimport { rowsOf } from './rows';\n\n/**\n * The migrations, in order, each a list of statements: the schema table's\n * `version` is how many of them the database has run. A migration is\n * never edited once released; a change is a new one at the end. They\n * follow the `bun:sqlite` store's, column for column: times are\n * milliseconds as `double precision` (SQLite's `REAL`), the recipients\n * JSON, the message its bytes, in a table of its own.\n */\nexport function migrations({\n\tprefix,\n\titems,\n\tmessages,\n}: Tables): readonly (readonly string[])[] {\n\treturn [\n\t\t// 1: the items, and their messages in a table of their own.\n\t\t[\n\t\t\t`CREATE TABLE ${items} (\n\t\t\t\tseq bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n\t\t\t\tid text NOT NULL UNIQUE,\n\t\t\t\tsender text NOT NULL,\n\t\t\t\trecipients jsonb NOT NULL CHECK (jsonb_typeof(recipients) = 'array'),\n\t\t\t\tsize integer NOT NULL,\n\t\t\t\tcreated_at double precision NOT NULL,\n\t\t\t\tnext_attempt_at double precision NOT NULL,\n\t\t\t\tattempts integer NOT NULL,\n\t\t\t\tdelay_notified boolean NOT NULL,\n\t\t\t\tlease_owner text,\n\t\t\t\tlease_expires_at double precision,\n\t\t\t\tCHECK ((lease_owner IS NULL) = (lease_expires_at IS NULL))\n\t\t\t)`,\n\t\t\t// The claim's order, and its range: the earliest due first.\n\t\t\t`CREATE INDEX ${prefix}items_due ON ${items} (next_attempt_at, seq)`,\n\t\t\t`CREATE TABLE ${messages} (\n\t\t\t\titem_id text PRIMARY KEY REFERENCES ${items} (id) ON DELETE CASCADE,\n\t\t\t\tcontent bytea NOT NULL\n\t\t\t)`,\n\t\t],\n\t];\n}\n\n/** Tables a newer store wrote, with more migrations than this one knows, are refused. */\nfunction checkNotNewer(version: number, known: number): void {\n\tif (version > known) {\n\t\tthrow new QueueError(\n\t\t\t'INVALID',\n\t\t\t`The database is at schema version ${version}, newer than this store's ${known}`,\n\t\t);\n\t}\n}\n\n/**\n * The version the tables are at, read with no lock and no DDL — so a role\n * with no `CREATE` reads it — or `undefined` with no schema table yet.\n */\nasync function versionOf(\n\tsql: PostgresQueryable,\n\ttables: Tables,\n): Promise<number | undefined> {\n\tconst [found] = await rowsOf<{ present: boolean }>(\n\t\tsql.unsafe('SELECT to_regclass($1) IS NOT NULL AS present', [\n\t\t\ttables.schema,\n\t\t]),\n\t);\n\tif (!found?.present) return undefined;\n\tconst [row] = await rowsOf<{ version: number }>(\n\t\tsql.unsafe(`SELECT version FROM ${tables.schema}`),\n\t);\n\treturn row?.version ?? 0;\n}\n\n/**\n * Brings the tables to the last migration. Tables already current are\n * only read: no lock, no DDL, so a worker's role needs no `CREATE`.\n * Otherwise in one transaction — a migration that fails leaves them as\n * they were — under one advisory lock, so instances starting together\n * run each migration once.\n */\nexport async function migrate(\n\tclient: PostgresClient,\n\ttables: Tables,\n): Promise<void> {\n\tconst all = migrations(tables);\n\tconst current = await versionOf(client, tables);\n\tif (current !== undefined) {\n\t\tcheckNotNewer(current, all.length);\n\t\tif (current === all.length) return;\n\t}\n\t// READ COMMITTED, as every write: under a stricter level the version\n\t// read after the lock would be the snapshot's, from before it.\n\tawait writing(client, async (sql: PostgresQueryable) => {\n\t\tawait sql.unsafe('SELECT pg_advisory_xact_lock(hashtext($1)) IS NULL', [\n\t\t\ttables.schema,\n\t\t]);\n\t\tawait sql.unsafe(\n\t\t\t`CREATE TABLE IF NOT EXISTS ${tables.schema} (version integer NOT NULL)`,\n\t\t);\n\t\tconst [row] = await rowsOf<{ version: number }>(\n\t\t\tsql.unsafe(`SELECT version FROM ${tables.schema}`),\n\t\t);\n\t\tconst version = row?.version ?? 0;\n\t\tcheckNotNewer(version, all.length);\n\t\tfor (const statements of all.slice(version)) {\n\t\t\tfor (const statement of statements) await sql.unsafe(statement);\n\t\t}\n\t\tif (row === undefined) {\n\t\t\tawait sql.unsafe(\n\t\t\t\t`INSERT INTO ${tables.schema} (version) VALUES ($1::int)`,\n\t\t\t\t[all.length],\n\t\t\t);\n\t\t} else if (version < all.length) {\n\t\t\tawait sql.unsafe(`UPDATE ${tables.schema} SET version = $1::int`, [\n\t\t\t\tall.length,\n\t\t\t]);\n\t\t}\n\t});\n}\n",
|
|
9
|
-
"import type { Tables } from './connect';\nimport { COLUMNS } from './rows';\n\n/**\n * The statements a store runs on its tables, written once per store: the\n * table names are its prefix's (checked to be a plain name), and every\n * value is a parameter. Each parameter is cast, so nothing depends on the\n * type a client infers for it; JSON goes as text, cast to `jsonb`.\n */\nexport function statementsOf({ items, messages }: Tables) {\n\treturn {\n\t\t/** $1 id, $2 sender, $3 recipients, $4 size, $5 created_at, $6 message: the item and its message in one statement, or neither. */\n\t\tinsert: `WITH item AS (\n\t\t\tINSERT INTO ${items} (id, sender, recipients, size, created_at,\n\t\t\t\tnext_attempt_at, attempts, delay_notified)\n\t\t\tVALUES ($1, $2, $3::text::jsonb, $4::int, $5::float8, $5::float8, 0, false)\n\t\t\tRETURNING id)\n\t\tINSERT INTO ${messages} (item_id, content) SELECT id, $6::bytea FROM item`,\n\t\t/** Taken before counting, by every add given `maxItems`. */\n\t\tlockAdds: `SELECT pg_advisory_xact_lock(hashtext('${items}')) IS NULL`,\n\t\tcount: `SELECT count(*)::int AS n FROM ${items}`,\n\t\tget: `SELECT ${COLUMNS} FROM ${items} WHERE id = $1`,\n\t\tlist: `SELECT ${COLUMNS} FROM ${items}\n\t\t\tORDER BY next_attempt_at, seq LIMIT $1::int OFFSET $2::int`,\n\t\tmessage: `SELECT content FROM ${messages} WHERE item_id = $1`,\n\t\t/**\n\t\t * $1 owner, $2 now, $3 expiry. The earliest due item no other\n\t\t * transaction holds, locked as it is picked — one being claimed\n\t\t * elsewhere is skipped, not waited for — then leased.\n\t\t */\n\t\tclaim: `UPDATE ${items} SET lease_owner = $1, lease_expires_at = $3::float8\n\t\t\tWHERE seq = (SELECT seq FROM ${items}\n\t\t\t\tWHERE next_attempt_at <= $2::float8\n\t\t\t\t\tAND (lease_owner IS NULL OR lease_expires_at <= $2::float8)\n\t\t\t\tORDER BY next_attempt_at, seq LIMIT 1\n\t\t\t\tFOR UPDATE SKIP LOCKED)\n\t\t\tRETURNING ${COLUMNS}`,\n\t\trenew: `UPDATE ${items} SET lease_expires_at = $1::float8\n\t\t\tWHERE id = $2 AND lease_owner = $3 RETURNING seq`,\n\t\t/** The owner's item, locked until the transaction ends. */\n\t\theld: `SELECT ${COLUMNS} FROM ${items}\n\t\t\tWHERE id = $1 AND lease_owner = $2 FOR UPDATE`,\n\t\t/** $1 id, $2 recipients, $3 next attempt, $4 attempts, $5 delay notified: the lease let go of. */\n\t\trecord: `UPDATE ${items} SET recipients = $2::text::jsonb,\n\t\t\tnext_attempt_at = $3::float8, attempts = $4::int,\n\t\t\tdelay_notified = $5::boolean, lease_owner = NULL,\n\t\t\tlease_expires_at = NULL WHERE id = $1`,\n\t\t/** The message goes with it: ON DELETE CASCADE. */\n\t\tdrop: `DELETE FROM ${items} WHERE id = $1 RETURNING ${COLUMNS}`,\n\t\tmoveDue: `UPDATE ${items} SET next_attempt_at = $1::float8\n\t\t\tWHERE id = $2 RETURNING seq`,\n\t\tgiveBack: `UPDATE ${items} SET next_attempt_at = $1::float8,\n\t\t\tlease_owner = NULL, lease_expires_at = NULL\n\t\t\tWHERE id = $2 AND lease_owner = $3 RETURNING seq`,\n\t} as const;\n}\n\nexport type Statements = ReturnType<typeof statementsOf>;\n",
|
|
10
|
-
"import {\n\tapplyAttempt,\n\tcheckClaim,\n\tcheckList,\n\tcheckMaxItems,\n\tcheckNewItem,\n\tcheckOwner,\n\tcheckResult,\n\tcheckTime,\n\tisDone,\n\tnewItem,\n} from '../contract/checks';\nimport type { QueueStore } from '../contract/queue-store';\nimport type {\n\tAddOptions,\n\tAttemptResult,\n\tClaimRequest,\n\tNewQueueItem,\n\tQueueItem,\n\tQueueListOptions,\n} from '../contract/types';\nimport { QueueError } from '../errors';\nimport { masked } from '../masked';\nimport { isStorable } from '../text';\nimport { type Connection, connect, type Tables } from './connect';\nimport { writing, written } from './isolation';\nimport type { PostgresClient, PostgresQueueStoreOptions } from './options';\nimport { type ItemRow, itemOf, rowsOf } from './rows';\nimport { migrate } from './schema';\nimport { type Statements, statementsOf } from './statements';\n\nexport type {\n\tPostgresClient,\n\tPostgresQueryable,\n\tPostgresQueueStoreOptions,\n} from './options';\n\nconst messageOf = (error: unknown) =>\n\terror instanceof Error ? error.message : String(error);\n\n/**\n * A `QueueStore` on PostgreSQL through Bun's own `Bun.SQL`: several\n * instances, on one machine or many, share one queue. A claim is one\n * `UPDATE … RETURNING` whose item a `SELECT … FOR UPDATE SKIP LOCKED`\n * picks, so two instances never take the same item, and none waits on an\n * item another is taking. Messages live in a table of their own, dropped\n * with their item. The tables are made, or brought up to date, by\n * `migrate()`, or by the first call that needs them.\n */\nexport class PostgresQueueStore implements QueueStore {\n\treadonly #client: PostgresClient;\n\treadonly #owned: boolean;\n\treadonly #tables: Tables;\n\treadonly #q: Statements;\n\treadonly #password: string;\n\t#migrated: Promise<void> | undefined;\n\t#closed = false;\n\n\tprivate constructor({ client, owned, tables, password }: Connection) {\n\t\tthis.#client = client;\n\t\tthis.#owned = owned;\n\t\tthis.#tables = tables;\n\t\tthis.#password = password;\n\t\tthis.#q = statementsOf(tables);\n\t}\n\n\t/** Checks the options; connects to nothing until the first call. What is wrong is `INVALID`. */\n\tstatic open(options: PostgresQueueStoreOptions): PostgresQueueStore {\n\t\treturn new PostgresQueueStore(connect(options));\n\t}\n\n\t/**\n\t * Makes the tables, or brings them to the last migration; once per\n\t * store, and safe while other instances do the same. A failure is\n\t * `INVALID`, and the next call tries again.\n\t */\n\tmigrate(): Promise<void> {\n\t\tif (this.#closed) return Promise.reject(closed());\n\t\tthis.#migrated ??= migrate(this.#client, this.#tables).catch((error) => {\n\t\t\tthis.#migrated = undefined;\n\t\t\tif (error instanceof QueueError) throw error;\n\t\t\tthrow new QueueError(\n\t\t\t\t'INVALID',\n\t\t\t\t`The PostgreSQL queue cannot be set up: ${masked(messageOf(error), this.#password)}`,\n\t\t\t);\n\t\t});\n\t\treturn this.#migrated;\n\t}\n\n\t/** Closes the client the store opened for a URL, never one it was given; closing twice is fine. */\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tif (this.#owned) await this.#client.close();\n\t}\n\n\t/** The client, once the tables are there; `CLOSED` after `close()`. */\n\tasync #sql(): Promise<PostgresClient> {\n\t\tif (this.#closed) throw closed();\n\t\tawait this.migrate();\n\t\treturn this.#client;\n\t}\n\n\tasync #rows<T>(query: string, values: unknown[] = []): Promise<T[]> {\n\t\treturn rowsOf<T>((await this.#sql()).unsafe(query, values));\n\t}\n\n\t/** One statement that writes, at READ COMMITTED; its rows. */\n\tasync #written<T>(query: string, values: unknown[] = []): Promise<T[]> {\n\t\treturn written<T>(await this.#sql(), query, values);\n\t}\n\n\tasync add(item: NewQueueItem, options: AddOptions = {}): Promise<QueueItem> {\n\t\tcheckNewItem(item);\n\t\tconst max = checkMaxItems(options.maxItems);\n\t\tconst added = newItem(crypto.randomUUID(), item);\n\t\tconst values = [\n\t\t\tadded.id,\n\t\t\tadded.from,\n\t\t\tJSON.stringify(added.recipients),\n\t\t\tadded.size,\n\t\t\tadded.createdAt,\n\t\t\titem.message,\n\t\t];\n\t\tconst q = this.#q;\n\t\tif (max === undefined) {\n\t\t\tawait this.#written(q.insert, values);\n\t\t\treturn added;\n\t\t}\n\t\t// Adds that count wait on one lock, so two never both take the last place.\n\t\tawait writing(await this.#sql(), async (tx) => {\n\t\t\tawait tx.unsafe(q.lockAdds);\n\t\t\tconst [row] = await rowsOf<{ n: number }>(tx.unsafe(q.count));\n\t\t\tconst n = row?.n ?? 0;\n\t\t\tif (n >= max) {\n\t\t\t\tthrow new QueueError(\n\t\t\t\t\t'QUEUE_FULL',\n\t\t\t\t\t`The queue holds ${n} items, its limit`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait tx.unsafe(q.insert, values);\n\t\t});\n\t\treturn added;\n\t}\n\n\tasync get(id: string): Promise<QueueItem | undefined> {\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst [row] = await this.#rows<ItemRow>(this.#q.get, [id]);\n\t\treturn row ? itemOf(row) : undefined;\n\t}\n\n\tasync list(options?: QueueListOptions): Promise<QueueItem[]> {\n\t\tconst { offset, limit } = checkList(options);\n\t\tconst rows = await this.#rows<ItemRow>(this.#q.list, [limit, offset]);\n\t\treturn rows.map(itemOf);\n\t}\n\n\tasync count(): Promise<number> {\n\t\tconst [row] = await this.#rows<{ n: number }>(this.#q.count);\n\t\treturn row?.n ?? 0;\n\t}\n\n\tasync readMessage(id: string): Promise<Uint8Array | undefined> {\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst [row] = await this.#rows<{ content: Uint8Array }>(this.#q.message, [\n\t\t\tid,\n\t\t]);\n\t\treturn row ? new Uint8Array(row.content) : undefined;\n\t}\n\n\tasync claim(request: ClaimRequest): Promise<QueueItem | undefined> {\n\t\tcheckClaim(request);\n\t\tconst { owner, now, leaseMs } = request;\n\t\tconst [row] = await this.#written<ItemRow>(this.#q.claim, [\n\t\t\towner,\n\t\t\tnow,\n\t\t\tnow + leaseMs,\n\t\t]);\n\t\treturn row ? itemOf(row) : undefined;\n\t}\n\n\tasync renew(id: string, owner: string, expiresAt: number): Promise<boolean> {\n\t\tcheckOwner(owner);\n\t\tcheckTime('expiresAt', expiresAt);\n\t\tif (!isStorable(id)) return false;\n\t\tconst rows = await this.#written(this.#q.renew, [expiresAt, id, owner]);\n\t\treturn rows.length > 0;\n\t}\n\n\tasync complete(\n\t\tid: string,\n\t\towner: string,\n\t\tresult: AttemptResult,\n\t): Promise<QueueItem | undefined> {\n\t\tcheckOwner(owner);\n\t\tcheckResult(result);\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst q = this.#q;\n\t\tlet done: QueueItem | undefined;\n\t\tawait writing(await this.#sql(), async (tx) => {\n\t\t\tconst [row] = await rowsOf<ItemRow>(tx.unsafe(q.held, [id, owner]));\n\t\t\tif (!row) return;\n\t\t\tconst item = applyAttempt(itemOf(row), result);\n\t\t\tif (isDone(item)) {\n\t\t\t\tawait tx.unsafe(q.drop, [id]);\n\t\t\t} else {\n\t\t\t\tawait tx.unsafe(q.record, [\n\t\t\t\t\tid,\n\t\t\t\t\tJSON.stringify(item.recipients),\n\t\t\t\t\titem.nextAttemptAt,\n\t\t\t\t\titem.attempts,\n\t\t\t\t\titem.delayNotified,\n\t\t\t\t]);\n\t\t\t}\n\t\t\tdone = item;\n\t\t});\n\t\treturn done;\n\t}\n\n\tasync reschedule(id: string, at: number, owner?: string): Promise<boolean> {\n\t\tcheckTime('at', at);\n\t\tif (!isStorable(id)) return false;\n\t\tif (owner === undefined) {\n\t\t\treturn (await this.#written(this.#q.moveDue, [at, id])).length > 0;\n\t\t}\n\t\tcheckOwner(owner);\n\t\treturn (await this.#written(this.#q.giveBack, [at, id, owner])).length > 0;\n\t}\n\n\tasync cancel(id: string): Promise<QueueItem | undefined> {\n\t\t// An id PostgreSQL cannot hold is one no item has.\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst [row] = await this.#written<ItemRow>(this.#q.drop, [id]);\n\t\treturn row ? itemOf(row) : undefined;\n\t}\n}\n\nconst closed = () => new QueueError('CLOSED', 'The queue store is closed');\n"
|
|
8
|
+
"import { QueueError } from '../errors';\nimport type { Tables } from './connect';\nimport { writing } from './isolation';\nimport type { PostgresClient, PostgresQueryable } from './options';\nimport { rowsOf } from './rows';\n\n/**\n * The migrations, in order, each a list of statements: the schema table's\n * `version` is how many of them the database has run. A migration is\n * never edited once released; a change is a new one at the end. They\n * follow the `bun:sqlite` store's, column for column: times are\n * milliseconds as `double precision` (SQLite's `REAL`), the recipients\n * JSON, the message its bytes, in a table of its own.\n */\nexport function migrations({\n\tprefix,\n\titems,\n\tmessages,\n}: Tables): readonly (readonly string[])[] {\n\treturn [\n\t\t// 1: the items, and their messages in a table of their own.\n\t\t[\n\t\t\t`CREATE TABLE ${items} (\n\t\t\t\tseq bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n\t\t\t\tid text NOT NULL UNIQUE,\n\t\t\t\tsender text NOT NULL,\n\t\t\t\trecipients jsonb NOT NULL CHECK (jsonb_typeof(recipients) = 'array'),\n\t\t\t\tsize integer NOT NULL,\n\t\t\t\tcreated_at double precision NOT NULL,\n\t\t\t\tnext_attempt_at double precision NOT NULL,\n\t\t\t\tattempts integer NOT NULL,\n\t\t\t\tdelay_notified boolean NOT NULL,\n\t\t\t\tlease_owner text,\n\t\t\t\tlease_expires_at double precision,\n\t\t\t\tCHECK ((lease_owner IS NULL) = (lease_expires_at IS NULL))\n\t\t\t)`,\n\t\t\t// The claim's order, and its range: the earliest due first.\n\t\t\t`CREATE INDEX ${prefix}items_due ON ${items} (next_attempt_at, seq)`,\n\t\t\t`CREATE TABLE ${messages} (\n\t\t\t\titem_id text PRIMARY KEY REFERENCES ${items} (id) ON DELETE CASCADE,\n\t\t\t\tcontent bytea NOT NULL\n\t\t\t)`,\n\t\t],\n\t\t// 2: the item's DSN parameters (RET, ENVID) as JSON; NULL for the rows of before.\n\t\t[`ALTER TABLE ${items} ADD COLUMN dsn jsonb`],\n\t];\n}\n\n/** Tables a newer store wrote, with more migrations than this one knows, are refused. */\nfunction checkNotNewer(version: number, known: number): void {\n\tif (version > known) {\n\t\tthrow new QueueError(\n\t\t\t'INVALID',\n\t\t\t`The database is at schema version ${version}, newer than this store's ${known}`,\n\t\t);\n\t}\n}\n\n/**\n * The version the tables are at, read with no lock and no DDL — so a role\n * with no `CREATE` reads it — or `undefined` with no schema table yet.\n */\nasync function versionOf(\n\tsql: PostgresQueryable,\n\ttables: Tables,\n): Promise<number | undefined> {\n\tconst [found] = await rowsOf<{ present: boolean }>(\n\t\tsql.unsafe('SELECT to_regclass($1) IS NOT NULL AS present', [\n\t\t\ttables.schema,\n\t\t]),\n\t);\n\tif (!found?.present) return undefined;\n\tconst [row] = await rowsOf<{ version: number }>(\n\t\tsql.unsafe(`SELECT version FROM ${tables.schema}`),\n\t);\n\treturn row?.version ?? 0;\n}\n\n/**\n * Brings the tables to the last migration. Tables already current are\n * only read: no lock, no DDL, so a worker's role needs no `CREATE`.\n * Otherwise in one transaction — a migration that fails leaves them as\n * they were — under one advisory lock, so instances starting together\n * run each migration once.\n */\nexport async function migrate(\n\tclient: PostgresClient,\n\ttables: Tables,\n): Promise<void> {\n\tconst all = migrations(tables);\n\tconst current = await versionOf(client, tables);\n\tif (current !== undefined) {\n\t\tcheckNotNewer(current, all.length);\n\t\tif (current === all.length) return;\n\t}\n\t// READ COMMITTED, as every write: under a stricter level the version\n\t// read after the lock would be the snapshot's, from before it.\n\tawait writing(client, async (sql: PostgresQueryable) => {\n\t\tawait sql.unsafe('SELECT pg_advisory_xact_lock(hashtext($1)) IS NULL', [\n\t\t\ttables.schema,\n\t\t]);\n\t\tawait sql.unsafe(\n\t\t\t`CREATE TABLE IF NOT EXISTS ${tables.schema} (version integer NOT NULL)`,\n\t\t);\n\t\tconst [row] = await rowsOf<{ version: number }>(\n\t\t\tsql.unsafe(`SELECT version FROM ${tables.schema}`),\n\t\t);\n\t\tconst version = row?.version ?? 0;\n\t\tcheckNotNewer(version, all.length);\n\t\tfor (const statements of all.slice(version)) {\n\t\t\tfor (const statement of statements) await sql.unsafe(statement);\n\t\t}\n\t\tif (row === undefined) {\n\t\t\tawait sql.unsafe(\n\t\t\t\t`INSERT INTO ${tables.schema} (version) VALUES ($1::int)`,\n\t\t\t\t[all.length],\n\t\t\t);\n\t\t} else if (version < all.length) {\n\t\t\tawait sql.unsafe(`UPDATE ${tables.schema} SET version = $1::int`, [\n\t\t\t\tall.length,\n\t\t\t]);\n\t\t}\n\t});\n}\n",
|
|
9
|
+
"import type { Tables } from './connect';\nimport { COLUMNS } from './rows';\n\n/**\n * The statements a store runs on its tables, written once per store: the\n * table names are its prefix's (checked to be a plain name), and every\n * value is a parameter. Each parameter is cast, so nothing depends on the\n * type a client infers for it; JSON goes as text, cast to `jsonb`.\n */\nexport function statementsOf({ items, messages }: Tables) {\n\treturn {\n\t\t/** $1 id, $2 sender, $3 recipients, $4 size, $5 created_at, $6 message, $7 dsn (JSON or null): the item and its message in one statement, or neither. */\n\t\tinsert: `WITH item AS (\n\t\t\tINSERT INTO ${items} (id, sender, recipients, size, created_at,\n\t\t\t\tnext_attempt_at, attempts, delay_notified, dsn)\n\t\t\tVALUES ($1, $2, $3::text::jsonb, $4::int, $5::float8, $5::float8, 0, false,\n\t\t\t\t$7::text::jsonb)\n\t\t\tRETURNING id)\n\t\tINSERT INTO ${messages} (item_id, content) SELECT id, $6::bytea FROM item`,\n\t\t/** Taken before counting, by every add given `maxItems`. */\n\t\tlockAdds: `SELECT pg_advisory_xact_lock(hashtext('${items}')) IS NULL`,\n\t\tcount: `SELECT count(*)::int AS n FROM ${items}`,\n\t\tget: `SELECT ${COLUMNS} FROM ${items} WHERE id = $1`,\n\t\tlist: `SELECT ${COLUMNS} FROM ${items}\n\t\t\tORDER BY next_attempt_at, seq LIMIT $1::int OFFSET $2::int`,\n\t\tmessage: `SELECT content FROM ${messages} WHERE item_id = $1`,\n\t\t/**\n\t\t * $1 owner, $2 now, $3 expiry. The earliest due item no other\n\t\t * transaction holds, locked as it is picked — one being claimed\n\t\t * elsewhere is skipped, not waited for — then leased.\n\t\t */\n\t\tclaim: `UPDATE ${items} SET lease_owner = $1, lease_expires_at = $3::float8\n\t\t\tWHERE seq = (SELECT seq FROM ${items}\n\t\t\t\tWHERE next_attempt_at <= $2::float8\n\t\t\t\t\tAND (lease_owner IS NULL OR lease_expires_at <= $2::float8)\n\t\t\t\tORDER BY next_attempt_at, seq LIMIT 1\n\t\t\t\tFOR UPDATE SKIP LOCKED)\n\t\t\tRETURNING ${COLUMNS}`,\n\t\trenew: `UPDATE ${items} SET lease_expires_at = $1::float8\n\t\t\tWHERE id = $2 AND lease_owner = $3 RETURNING seq`,\n\t\t/** The owner's item, locked until the transaction ends. */\n\t\theld: `SELECT ${COLUMNS} FROM ${items}\n\t\t\tWHERE id = $1 AND lease_owner = $2 FOR UPDATE`,\n\t\t/** $1 id, $2 recipients, $3 next attempt, $4 attempts, $5 delay notified: the lease let go of. */\n\t\trecord: `UPDATE ${items} SET recipients = $2::text::jsonb,\n\t\t\tnext_attempt_at = $3::float8, attempts = $4::int,\n\t\t\tdelay_notified = $5::boolean, lease_owner = NULL,\n\t\t\tlease_expires_at = NULL WHERE id = $1`,\n\t\t/** The message goes with it: ON DELETE CASCADE. */\n\t\tdrop: `DELETE FROM ${items} WHERE id = $1 RETURNING ${COLUMNS}`,\n\t\tmoveDue: `UPDATE ${items} SET next_attempt_at = $1::float8\n\t\t\tWHERE id = $2 RETURNING seq`,\n\t\tgiveBack: `UPDATE ${items} SET next_attempt_at = $1::float8,\n\t\t\tlease_owner = NULL, lease_expires_at = NULL\n\t\t\tWHERE id = $2 AND lease_owner = $3 RETURNING seq`,\n\t} as const;\n}\n\nexport type Statements = ReturnType<typeof statementsOf>;\n",
|
|
10
|
+
"import {\n\tapplyAttempt,\n\tcheckClaim,\n\tcheckList,\n\tcheckMaxItems,\n\tcheckNewItem,\n\tcheckOwner,\n\tcheckResult,\n\tcheckTime,\n\tisDone,\n\tnewItem,\n} from '../contract/checks';\nimport { dsnColumn } from '../contract/dsn-column';\nimport type { QueueStore } from '../contract/queue-store';\nimport type {\n\tAddOptions,\n\tAttemptResult,\n\tClaimRequest,\n\tNewQueueItem,\n\tQueueItem,\n\tQueueListOptions,\n} from '../contract/types';\nimport { QueueError } from '../errors';\nimport { masked } from '../masked';\nimport { isStorable } from '../text';\nimport { type Connection, connect, type Tables } from './connect';\nimport { writing, written } from './isolation';\nimport type { PostgresClient, PostgresQueueStoreOptions } from './options';\nimport { type ItemRow, itemOf, rowsOf } from './rows';\nimport { migrate } from './schema';\nimport { type Statements, statementsOf } from './statements';\n\nexport type {\n\tPostgresClient,\n\tPostgresQueryable,\n\tPostgresQueueStoreOptions,\n} from './options';\n\nconst messageOf = (error: unknown) =>\n\terror instanceof Error ? error.message : String(error);\n\n/**\n * A `QueueStore` on PostgreSQL through Bun's own `Bun.SQL`: several\n * instances, on one machine or many, share one queue. A claim is one\n * `UPDATE … RETURNING` whose item a `SELECT … FOR UPDATE SKIP LOCKED`\n * picks, so two instances never take the same item, and none waits on an\n * item another is taking. Messages live in a table of their own, dropped\n * with their item. The tables are made, or brought up to date, by\n * `migrate()`, or by the first call that needs them.\n */\nexport class PostgresQueueStore implements QueueStore {\n\treadonly #client: PostgresClient;\n\treadonly #owned: boolean;\n\treadonly #tables: Tables;\n\treadonly #q: Statements;\n\treadonly #password: string;\n\t#migrated: Promise<void> | undefined;\n\t#closed = false;\n\n\tprivate constructor({ client, owned, tables, password }: Connection) {\n\t\tthis.#client = client;\n\t\tthis.#owned = owned;\n\t\tthis.#tables = tables;\n\t\tthis.#password = password;\n\t\tthis.#q = statementsOf(tables);\n\t}\n\n\t/** Checks the options; connects to nothing until the first call. What is wrong is `INVALID`. */\n\tstatic open(options: PostgresQueueStoreOptions): PostgresQueueStore {\n\t\treturn new PostgresQueueStore(connect(options));\n\t}\n\n\t/**\n\t * Makes the tables, or brings them to the last migration; once per\n\t * store, and safe while other instances do the same. A failure is\n\t * `INVALID`, and the next call tries again.\n\t */\n\tmigrate(): Promise<void> {\n\t\tif (this.#closed) return Promise.reject(closed());\n\t\tthis.#migrated ??= migrate(this.#client, this.#tables).catch((error) => {\n\t\t\tthis.#migrated = undefined;\n\t\t\tif (error instanceof QueueError) throw error;\n\t\t\tthrow new QueueError(\n\t\t\t\t'INVALID',\n\t\t\t\t`The PostgreSQL queue cannot be set up: ${masked(messageOf(error), this.#password)}`,\n\t\t\t);\n\t\t});\n\t\treturn this.#migrated;\n\t}\n\n\t/** Closes the client the store opened for a URL, never one it was given; closing twice is fine. */\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tif (this.#owned) await this.#client.close();\n\t}\n\n\t/** The client, once the tables are there; `CLOSED` after `close()`. */\n\tasync #sql(): Promise<PostgresClient> {\n\t\tif (this.#closed) throw closed();\n\t\tawait this.migrate();\n\t\treturn this.#client;\n\t}\n\n\tasync #rows<T>(query: string, values: unknown[] = []): Promise<T[]> {\n\t\treturn rowsOf<T>((await this.#sql()).unsafe(query, values));\n\t}\n\n\t/** One statement that writes, at READ COMMITTED; its rows. */\n\tasync #written<T>(query: string, values: unknown[] = []): Promise<T[]> {\n\t\treturn written<T>(await this.#sql(), query, values);\n\t}\n\n\tasync add(item: NewQueueItem, options: AddOptions = {}): Promise<QueueItem> {\n\t\tcheckNewItem(item);\n\t\tconst max = checkMaxItems(options.maxItems);\n\t\tconst added = newItem(crypto.randomUUID(), item);\n\t\tconst values = [\n\t\t\tadded.id,\n\t\t\tadded.from,\n\t\t\tJSON.stringify(added.recipients),\n\t\t\tadded.size,\n\t\t\tadded.createdAt,\n\t\t\titem.message,\n\t\t\tdsnColumn(added),\n\t\t];\n\t\tconst q = this.#q;\n\t\tif (max === undefined) {\n\t\t\tawait this.#written(q.insert, values);\n\t\t\treturn added;\n\t\t}\n\t\t// Adds that count wait on one lock, so two never both take the last place.\n\t\tawait writing(await this.#sql(), async (tx) => {\n\t\t\tawait tx.unsafe(q.lockAdds);\n\t\t\tconst [row] = await rowsOf<{ n: number }>(tx.unsafe(q.count));\n\t\t\tconst n = row?.n ?? 0;\n\t\t\tif (n >= max) {\n\t\t\t\tthrow new QueueError(\n\t\t\t\t\t'QUEUE_FULL',\n\t\t\t\t\t`The queue holds ${n} items, its limit`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait tx.unsafe(q.insert, values);\n\t\t});\n\t\treturn added;\n\t}\n\n\tasync get(id: string): Promise<QueueItem | undefined> {\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst [row] = await this.#rows<ItemRow>(this.#q.get, [id]);\n\t\treturn row ? itemOf(row) : undefined;\n\t}\n\n\tasync list(options?: QueueListOptions): Promise<QueueItem[]> {\n\t\tconst { offset, limit } = checkList(options);\n\t\tconst rows = await this.#rows<ItemRow>(this.#q.list, [limit, offset]);\n\t\treturn rows.map(itemOf);\n\t}\n\n\tasync count(): Promise<number> {\n\t\tconst [row] = await this.#rows<{ n: number }>(this.#q.count);\n\t\treturn row?.n ?? 0;\n\t}\n\n\tasync readMessage(id: string): Promise<Uint8Array | undefined> {\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst [row] = await this.#rows<{ content: Uint8Array }>(this.#q.message, [\n\t\t\tid,\n\t\t]);\n\t\treturn row ? new Uint8Array(row.content) : undefined;\n\t}\n\n\tasync claim(request: ClaimRequest): Promise<QueueItem | undefined> {\n\t\tcheckClaim(request);\n\t\tconst { owner, now, leaseMs } = request;\n\t\tconst [row] = await this.#written<ItemRow>(this.#q.claim, [\n\t\t\towner,\n\t\t\tnow,\n\t\t\tnow + leaseMs,\n\t\t]);\n\t\treturn row ? itemOf(row) : undefined;\n\t}\n\n\tasync renew(id: string, owner: string, expiresAt: number): Promise<boolean> {\n\t\tcheckOwner(owner);\n\t\tcheckTime('expiresAt', expiresAt);\n\t\tif (!isStorable(id)) return false;\n\t\tconst rows = await this.#written(this.#q.renew, [expiresAt, id, owner]);\n\t\treturn rows.length > 0;\n\t}\n\n\tasync complete(\n\t\tid: string,\n\t\towner: string,\n\t\tresult: AttemptResult,\n\t): Promise<QueueItem | undefined> {\n\t\tcheckOwner(owner);\n\t\tcheckResult(result);\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst q = this.#q;\n\t\tlet done: QueueItem | undefined;\n\t\tawait writing(await this.#sql(), async (tx) => {\n\t\t\tconst [row] = await rowsOf<ItemRow>(tx.unsafe(q.held, [id, owner]));\n\t\t\tif (!row) return;\n\t\t\tconst item = applyAttempt(itemOf(row), result);\n\t\t\tif (isDone(item)) {\n\t\t\t\tawait tx.unsafe(q.drop, [id]);\n\t\t\t} else {\n\t\t\t\tawait tx.unsafe(q.record, [\n\t\t\t\t\tid,\n\t\t\t\t\tJSON.stringify(item.recipients),\n\t\t\t\t\titem.nextAttemptAt,\n\t\t\t\t\titem.attempts,\n\t\t\t\t\titem.delayNotified,\n\t\t\t\t]);\n\t\t\t}\n\t\t\tdone = item;\n\t\t});\n\t\treturn done;\n\t}\n\n\tasync reschedule(id: string, at: number, owner?: string): Promise<boolean> {\n\t\tcheckTime('at', at);\n\t\tif (!isStorable(id)) return false;\n\t\tif (owner === undefined) {\n\t\t\treturn (await this.#written(this.#q.moveDue, [at, id])).length > 0;\n\t\t}\n\t\tcheckOwner(owner);\n\t\treturn (await this.#written(this.#q.giveBack, [at, id, owner])).length > 0;\n\t}\n\n\tasync cancel(id: string): Promise<QueueItem | undefined> {\n\t\t// An id PostgreSQL cannot hold is one no item has.\n\t\tif (!isStorable(id)) return undefined;\n\t\tconst [row] = await this.#written<ItemRow>(this.#q.drop, [id]);\n\t\treturn row ? itemOf(row) : undefined;\n\t}\n}\n\nconst closed = () => new QueueError('CLOSED', 'The queue store is closed');\n"
|
|
11
11
|
],
|
|
12
|
-
"mappings": "
|
|
13
|
-
"debugId": "
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "E11119E44742AD2264756E2164756E21",
|
|
14
14
|
"names": []
|
|
15
15
|
}
|
package/dist/postgres/rows.d.ts
CHANGED
|
@@ -12,9 +12,14 @@ export interface ItemRow {
|
|
|
12
12
|
readonly delay_notified: boolean;
|
|
13
13
|
readonly lease_owner: string | null;
|
|
14
14
|
readonly lease_expires_at: number | null;
|
|
15
|
+
/** `jsonb`: parsed or not, depending on the client. */
|
|
16
|
+
readonly dsn: string | {
|
|
17
|
+
ret?: 'FULL' | 'HDRS';
|
|
18
|
+
envid?: string;
|
|
19
|
+
} | null;
|
|
15
20
|
}
|
|
16
21
|
/** The columns a query reads to build an item. */
|
|
17
|
-
export declare const COLUMNS = "id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at";
|
|
22
|
+
export declare const COLUMNS = "id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn";
|
|
18
23
|
export declare function itemOf(row: ItemRow): QueueItem;
|
|
19
24
|
/** The rows a statement returned. */
|
|
20
25
|
export declare const rowsOf: <T>(pending: PromiseLike<unknown>) => Promise<T[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../src/postgres/rows.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../src/postgres/rows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnE,uDAAuD;AACvD,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,uDAAuD;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACxE;AAED,kDAAkD;AAClD,eAAO,MAAM,OAAO,4HACsG,CAAC;AAE3H,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CAqB9C;AAED,qCAAqC;AACrC,eAAO,MAAM,MAAM,GAAU,CAAC,EAAE,SAAS,WAAW,CAAC,OAAO,CAAC,iBACtC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/postgres/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,WAAW,CAAC;AAGnE;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAC1B,MAAM,EACN,KAAK,EACL,QAAQ,GACR,EAAE,MAAM,GAAG,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/postgres/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,WAAW,CAAC;AAGnE;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAC1B,MAAM,EACN,KAAK,EACL,QAAQ,GACR,EAAE,MAAM,GAAG,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CA4BzC;AAgCD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAmCf"}
|
|
@@ -6,18 +6,19 @@ import type { Tables } from './connect';
|
|
|
6
6
|
* type a client infers for it; JSON goes as text, cast to `jsonb`.
|
|
7
7
|
*/
|
|
8
8
|
export declare function statementsOf({ items, messages }: Tables): {
|
|
9
|
-
/** $1 id, $2 sender, $3 recipients, $4 size, $5 created_at, $6 message: the item and its message in one statement, or neither. */
|
|
9
|
+
/** $1 id, $2 sender, $3 recipients, $4 size, $5 created_at, $6 message, $7 dsn (JSON or null): the item and its message in one statement, or neither. */
|
|
10
10
|
readonly insert: `WITH item AS (
|
|
11
11
|
\t\t\tINSERT INTO ${string} (id, sender, recipients, size, created_at,
|
|
12
|
-
\t\t\t\tnext_attempt_at, attempts, delay_notified)
|
|
13
|
-
\t\t\tVALUES ($1, $2, $3::text::jsonb, $4::int, $5::float8, $5::float8, 0, false
|
|
12
|
+
\t\t\t\tnext_attempt_at, attempts, delay_notified, dsn)
|
|
13
|
+
\t\t\tVALUES ($1, $2, $3::text::jsonb, $4::int, $5::float8, $5::float8, 0, false,
|
|
14
|
+
\t\t\t\t$7::text::jsonb)
|
|
14
15
|
\t\t\tRETURNING id)
|
|
15
16
|
\t\tINSERT INTO ${string} (item_id, content) SELECT id, $6::bytea FROM item`;
|
|
16
17
|
/** Taken before counting, by every add given `maxItems`. */
|
|
17
18
|
readonly lockAdds: `SELECT pg_advisory_xact_lock(hashtext('${string}')) IS NULL`;
|
|
18
19
|
readonly count: `SELECT count(*)::int AS n FROM ${string}`;
|
|
19
|
-
readonly get: `SELECT id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at FROM ${string} WHERE id = $1`;
|
|
20
|
-
readonly list: `SELECT id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at FROM ${string}
|
|
20
|
+
readonly get: `SELECT id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn FROM ${string} WHERE id = $1`;
|
|
21
|
+
readonly list: `SELECT id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn FROM ${string}
|
|
21
22
|
\t\t\tORDER BY next_attempt_at, seq LIMIT $1::int OFFSET $2::int`;
|
|
22
23
|
readonly message: `SELECT content FROM ${string} WHERE item_id = $1`;
|
|
23
24
|
/**
|
|
@@ -31,11 +32,11 @@ export declare function statementsOf({ items, messages }: Tables): {
|
|
|
31
32
|
\t\t\t\t\tAND (lease_owner IS NULL OR lease_expires_at <= $2::float8)
|
|
32
33
|
\t\t\t\tORDER BY next_attempt_at, seq LIMIT 1
|
|
33
34
|
\t\t\t\tFOR UPDATE SKIP LOCKED)
|
|
34
|
-
\t\t\tRETURNING id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at`;
|
|
35
|
+
\t\t\tRETURNING id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn`;
|
|
35
36
|
readonly renew: `UPDATE ${string} SET lease_expires_at = $1::float8
|
|
36
37
|
\t\t\tWHERE id = $2 AND lease_owner = $3 RETURNING seq`;
|
|
37
38
|
/** The owner's item, locked until the transaction ends. */
|
|
38
|
-
readonly held: `SELECT id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at FROM ${string}
|
|
39
|
+
readonly held: `SELECT id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn FROM ${string}
|
|
39
40
|
\t\t\tWHERE id = $1 AND lease_owner = $2 FOR UPDATE`;
|
|
40
41
|
/** $1 id, $2 recipients, $3 next attempt, $4 attempts, $5 delay notified: the lease let go of. */
|
|
41
42
|
readonly record: `UPDATE ${string} SET recipients = $2::text::jsonb,
|
|
@@ -43,7 +44,7 @@ export declare function statementsOf({ items, messages }: Tables): {
|
|
|
43
44
|
\t\t\tdelay_notified = $5::boolean, lease_owner = NULL,
|
|
44
45
|
\t\t\tlease_expires_at = NULL WHERE id = $1`;
|
|
45
46
|
/** The message goes with it: ON DELETE CASCADE. */
|
|
46
|
-
readonly drop: `DELETE FROM ${string} WHERE id = $1 RETURNING id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at`;
|
|
47
|
+
readonly drop: `DELETE FROM ${string} WHERE id = $1 RETURNING id, sender, recipients, size, created_at, next_attempt_at, attempts, delay_notified, lease_owner, lease_expires_at, dsn`;
|
|
47
48
|
readonly moveDue: `UPDATE ${string} SET next_attempt_at = $1::float8
|
|
48
49
|
\t\t\tWHERE id = $2 RETURNING seq`;
|
|
49
50
|
readonly giveBack: `UPDATE ${string} SET next_attempt_at = $1::float8,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../src/postgres/statements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM;IAEtD,
|
|
1
|
+
{"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../src/postgres/statements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM;IAEtD,yJAAyJ;;;;;;;;IAQzJ,4DAA4D;;;;;;;IAO5D;;;;OAIG;;;;;;;;;;IAUH,2DAA2D;;;IAG3D,kGAAkG;;;;;IAKlG,mDAAmD;;;;;;;EAQpD;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/postgres/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/postgres/store.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,KAAK,EAAkB,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAK3E,YAAY,EACX,cAAc,EACd,iBAAiB,EACjB,yBAAyB,GACzB,MAAM,WAAW,CAAC;AAKnB;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,YAAW,UAAU;;IASpD,OAAO;IAQP,gGAAgG;IAChG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB;IAInE;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAaxB,mGAAmG;IAC7F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBtB,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAkCrE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAM/C,IAAI,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAMtD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAKxB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAQxD,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAW5D,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQrE,QAAQ,CACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa,GACnB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IA0B3B,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUpE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAMxD"}
|
package/dist/queue/attempt.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { QueueItem, RecipientUpdate } from '../contract/types';
|
|
2
2
|
import type { DeliveryContext } from './deliver';
|
|
3
|
-
import type
|
|
4
|
-
|
|
5
|
-
export type Used = Pick<RecipientEvent, 'route' | 'fallback' | 'mxReply'>;
|
|
3
|
+
import { type Used } from './session';
|
|
4
|
+
export type { Used } from './session';
|
|
6
5
|
/**
|
|
7
6
|
* One delivery for one domain's recipients: each one's outcome, whatever
|
|
8
|
-
* happened, and in `used` the route each took. By MX with a `fallback
|
|
9
|
-
* the recipients
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* happened, and in `used` the route each took. By MX with a `fallback`:
|
|
8
|
+
* the recipients the smarthost may already hold (`mayHaveReachedFallback`)
|
|
9
|
+
* go to it first, skipping the MX (`deliverHeld`); the others go by MX, and
|
|
10
|
+
* those whose outcome is a connection failure, a timeout or a reputation
|
|
11
|
+
* refusal go at once to the fallback smarthost (`fallBackAtOnce`).
|
|
12
12
|
*/
|
|
13
13
|
export declare function attemptGroup(ctx: DeliveryContext, item: QueueItem, message: Uint8Array, domain: string, recipients: readonly string[], used: Map<string, Used>): Promise<RecipientUpdate[]>;
|
|
14
14
|
//# sourceMappingURL=attempt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attempt.d.ts","sourceRoot":"","sources":["../../src/queue/attempt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"attempt.d.ts","sourceRoot":"","sources":["../../src/queue/attempt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAOjD,OAAO,EAAW,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAE/C,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAiBtC;;;;;;;GAOG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GACrB,OAAO,CAAC,eAAe,EAAE,CAAC,CAiC5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/queue/deliver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,mBAAmB,CAAC;AAIpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,aAAa,EAAU,MAAM,UAAU,CAAC;AAGtD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACjC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CAClE;
|
|
1
|
+
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/queue/deliver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,mBAAmB,CAAC;AAIpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,aAAa,EAAU,MAAM,UAAU,CAAC;AAGtD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACjC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CAClE;AAiGD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,GACb,OAAO,CAAC,IAAI,CAAC,CAuCf"}
|
package/dist/queue/envelope.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RecipientDsn, RetMode } from '../contract/types';
|
|
1
2
|
/** What `enqueue` takes as the message: its bytes, its text, or a stream of it. */
|
|
2
3
|
export type MessageSource = Uint8Array | string | ReadableStream<Uint8Array>;
|
|
3
4
|
/** The SMTP envelope of a message to send. */
|
|
@@ -6,15 +7,34 @@ export interface QueueEnvelope {
|
|
|
6
7
|
readonly from: string;
|
|
7
8
|
/** One recipient or more, `local@domain` each. */
|
|
8
9
|
readonly to: string | readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* `RET` (RFC 3461 §4.3): what a DSN returns of the message, `HDRS` the
|
|
12
|
+
* header fields only, `FULL` the whole message (still bounded by
|
|
13
|
+
* `limits.maxDsnReturn`). Absent: the queue's `dsn.returnContent`.
|
|
14
|
+
*/
|
|
15
|
+
readonly ret?: RetMode;
|
|
16
|
+
/** `ENVID` (RFC 3461 §4.4), decoded: 1 to 100 printable ASCII characters. Passed on and echoed in a DSN. */
|
|
17
|
+
readonly envid?: string;
|
|
18
|
+
/**
|
|
19
|
+
* `NOTIFY` and `ORCPT` (RFC 3461 §4.1, §4.2) of the recipients that carry
|
|
20
|
+
* some, matched to `to` by address (the SMTP server's
|
|
21
|
+
* `Envelope.recipients` fits as it is).
|
|
22
|
+
*/
|
|
23
|
+
readonly recipients?: readonly RecipientDsn[];
|
|
24
|
+
}
|
|
25
|
+
/** What `checkEnvelope` returns: the envelope without its duplicates. */
|
|
26
|
+
export interface CheckedEnvelope {
|
|
27
|
+
readonly from: string;
|
|
28
|
+
readonly to: string[];
|
|
29
|
+
readonly ret?: RetMode;
|
|
30
|
+
readonly envid?: string;
|
|
31
|
+
readonly recipients?: readonly RecipientDsn[];
|
|
9
32
|
}
|
|
10
33
|
export declare function checkAddress(name: string, address: unknown): string;
|
|
11
34
|
/** The recipient's domain, in lowercase: what deliveries are grouped by. */
|
|
12
35
|
export declare const domainOf: (address: string) => string;
|
|
13
36
|
/** The envelope, checked: the sender, and each recipient once, at most `maxRecipients`. */
|
|
14
|
-
export declare function checkEnvelope(envelope: QueueEnvelope, maxRecipients: number):
|
|
15
|
-
from: string;
|
|
16
|
-
to: string[];
|
|
17
|
-
};
|
|
37
|
+
export declare function checkEnvelope(envelope: QueueEnvelope, maxRecipients: number): CheckedEnvelope;
|
|
18
38
|
/**
|
|
19
39
|
* Refuses a message with a CR not followed by an LF, or an LF not after a
|
|
20
40
|
* CR: `sendMail` would refuse it at delivery (`BARE_LINE_BREAK`, SMTP
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/queue/envelope.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/queue/envelope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAI/D,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;AAE7E,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,4GAA4G;IAC5G,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAC9C;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAC9C;AAYD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAanE;AAED,4EAA4E;AAC5E,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,WACkB,CAAC;AAE3D,2FAA2F;AAC3F,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,aAAa,EACvB,aAAa,EAAE,MAAM,GACnB,eAAe,CAkDjB;AAWD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAarD;AAED,6FAA6F;AAC7F,wBAAsB,WAAW,CAChC,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,UAAU,CAAC,CAsCrB"}
|
package/dist/queue/events.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface RecipientEvent {
|
|
|
27
27
|
* queue never fell back to a smarthost for this recipient on its own.
|
|
28
28
|
*/
|
|
29
29
|
readonly mayHaveReachedMx?: true;
|
|
30
|
+
/**
|
|
31
|
+
* Only on `deferred` and `failed`: `true` when the fallback smarthost's
|
|
32
|
+
* session ended after the final dot with no reply, so it may hold a copy
|
|
33
|
+
* and the next attempt goes to it again instead of the MX.
|
|
34
|
+
*/
|
|
35
|
+
readonly mayHaveReachedFallback?: true;
|
|
30
36
|
}
|
|
31
37
|
export interface DeferredEvent extends RecipientEvent {
|
|
32
38
|
/** When the recipient is tried again, in milliseconds since the epoch. */
|
|
@@ -34,7 +40,8 @@ export interface DeferredEvent extends RecipientEvent {
|
|
|
34
40
|
}
|
|
35
41
|
/** A DSN, enqueued back to the sender. */
|
|
36
42
|
export interface DsnEvent {
|
|
37
|
-
|
|
43
|
+
/** `relayed`: SUCCESS was asked and the next hop sends no DSN (RFC 3461 §5.2.2). */
|
|
44
|
+
readonly kind: 'delayed' | 'failed' | 'relayed';
|
|
38
45
|
/** The DSN's own item id. */
|
|
39
46
|
readonly id: string;
|
|
40
47
|
/** The id of the item it reports on. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/queue/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,+BAA+B;AAC/B,MAAM,WAAW,cAAc;IAC9B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,WAAW,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/queue/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,+BAA+B;AAC/B,MAAM,WAAW,cAAc;IAC9B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,WAAW,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,aAAc,SAAQ,cAAc;IACpD,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,0CAA0C;AAC1C,MAAM,WAAW,QAAQ;IACxB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAChD,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,gGAAgG;AAChG,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;CACvB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,WAAW,IAAI,CACxD,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,KACjB,IAAI,CAAC;AAEV,iFAAiF;AACjF,qBAAa,MAAM;;IAGlB,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAC7B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,MAAM,IAAI;IAYb,IAAI,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI;CAS1E"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { QueueItem, RecipientUpdate } from '../contract/types';
|
|
2
|
+
import type { DeliveryContext } from './deliver';
|
|
3
|
+
import type { Route } from './options';
|
|
4
|
+
import { type Attempt, type Used } from './session';
|
|
5
|
+
/**
|
|
6
|
+
* The automatic fallback, after the MX session: the recipients whose MX
|
|
7
|
+
* outcome is a connection failure, a timeout or a reputation refusal
|
|
8
|
+
* (`fallsBack`) go at once to the fallback smarthost, in a session of their
|
|
9
|
+
* own, and its outcome replaces the first (`merged`). A recipient marked
|
|
10
|
+
* `mayHaveReachedMx` never does. Returns every update, replaced or not.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fallBackAtOnce(ctx: DeliveryContext, item: QueueItem, message: Uint8Array, domain: string, first: Attempt, fallback: Route, used: Map<string, Used>): Promise<RecipientUpdate[]>;
|
|
13
|
+
/**
|
|
14
|
+
* The recipients marked `mayHaveReachedFallback` (and not `mayHaveReachedMx`)
|
|
15
|
+
* while a `fallback` is configured and the domain goes by MX: the smarthost
|
|
16
|
+
* may hold a copy, so they skip the MX and go to it again, in one session of
|
|
17
|
+
* their own. A refusal leaves them deferred (`stillHeld`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function heldAtFallback(item: QueueItem, group: readonly string[], byMx: boolean, fallback: Route | undefined): string[];
|
|
20
|
+
/** The held recipients' session at the fallback smarthost. */
|
|
21
|
+
export declare function deliverHeld(ctx: DeliveryContext, item: QueueItem, message: Uint8Array, domain: string, held: readonly string[], fallback: Route, used: Map<string, Used>): Promise<RecipientUpdate[]>;
|
|
22
|
+
//# sourceMappingURL=fallback-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback-session.d.ts","sourceRoot":"","sources":["../../src/queue/fallback-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,KAAK,OAAO,EAAW,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAuB7D;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,KAAK,EACf,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GACrB,OAAO,CAAC,eAAe,EAAE,CAAC,CAwC5B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC7B,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,KAAK,GAAG,SAAS,GACzB,MAAM,EAAE,CAQV;AAED,8DAA8D;AAC9D,wBAAsB,WAAW,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,KAAK,EACf,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GACrB,OAAO,CAAC,eAAe,EAAE,CAAC,CAe5B"}
|
package/dist/queue/notify.d.ts
CHANGED
|
@@ -9,12 +9,15 @@ export interface NotifyContext {
|
|
|
9
9
|
readonly events: Events;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Enqueues a DSN (RFC 3464) back to the item's sender for these
|
|
13
|
-
* recipients
|
|
12
|
+
* Enqueues a DSN (RFC 3464) back to the item's sender for those of these
|
|
13
|
+
* recipients that asked for one of this kind (`NOTIFY`, RFC 3461 §4.1:
|
|
14
|
+
* `NEVER`, or the events listed; failure and delay when absent), from the
|
|
15
|
+
* null sender, so it never causes one of its own. `ret` and `envid` of the
|
|
16
|
+
* item shape its content and name the original (RFC 3461 §4.3, §4.4).
|
|
14
17
|
* Nothing for an item from the null sender (RFC 5321 §4.5.5, RFC 3464
|
|
15
18
|
* §2: a DSN is never sent about a DSN). It bypasses `maxItems`: a full
|
|
16
19
|
* queue loses no bounce. A failure goes to the `error` event. With no
|
|
17
20
|
* `message` — the store lost it — the DSN returns nothing of it.
|
|
18
21
|
*/
|
|
19
|
-
export declare function notify(ctx: NotifyContext, item: QueueItem, message: Uint8Array | undefined, kind: DsnKind, recipients: readonly RecipientUpdate[], now: number): Promise<void>;
|
|
22
|
+
export declare function notify(ctx: NotifyContext, item: QueueItem, message: Uint8Array | undefined, kind: Exclude<DsnKind, 'delivered'>, recipients: readonly RecipientUpdate[], now: number): Promise<void>;
|
|
20
23
|
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/queue/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAc,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/queue/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAc,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAwBD;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAC3B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,UAAU,GAAG,SAAS,EAC/B,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,EACnC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAsDf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outcome.d.ts","sourceRoot":"","sources":["../../src/queue/outcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EACL,cAAc,EACd,SAAS,EAET,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGrE;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,SAGC,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,OACQ,CAAC;AAKvD,8CAA8C;AAC9C,wBAAgB,YAAY,CAC3B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,UAAU,CAWZ;
|
|
1
|
+
{"version":3,"file":"outcome.d.ts","sourceRoot":"","sources":["../../src/queue/outcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EACL,cAAc,EACd,SAAS,EAET,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGrE;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,SAGC,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,OACQ,CAAC;AAKvD,8CAA8C;AAC9C,wBAAgB,YAAY,CAC3B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,UAAU,CAWZ;AA2CD,sFAAsF;AACtF,wBAAgB,UAAU,CACzB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,GACT,eAAe,EAAE,CA+BnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,eAAe,EAAE,CAuBnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/queue/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,aAAa,EAElB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI9C,4EAA4E;AAC5E,MAAM,WAAW,KAAK;IACrB,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7E,wFAAwF;IACxF,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,4FAA4F;IAC5F,KAAK,IAAI,IAAI,CAAC;IACd,uFAAuF;IACvF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,yDAAyD;IACzD,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAC7B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,MAAM,IAAI,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACvD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,wGAAwG;IACxG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CACnD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/queue/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,aAAa,EAElB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI9C,4EAA4E;AAC5E,MAAM,WAAW,KAAK;IACrB,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7E,wFAAwF;IACxF,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,4FAA4F;IAC5F,KAAK,IAAI,IAAI,CAAC;IACd,uFAAuF;IACvF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,yDAAyD;IACzD,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAC7B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,MAAM,IAAI,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACvD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,wGAAwG;IACxG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CACnD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,KAAK,CAsCxD"}
|
package/dist/queue/route.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { SendMailOptions } from '@bumail/smtp/client';
|
|
2
|
+
import type { QueueItem } from '../contract/types';
|
|
2
3
|
import type { Route } from './options';
|
|
3
4
|
import type { Settings } from './settings';
|
|
4
5
|
/** The route for a recipient domain: its own, else the default. */
|
|
5
6
|
export declare function routeOf(settings: Settings, domain: string): Route;
|
|
6
7
|
/** The host an attempt goes to, as far as the route says: the smarthost, or nothing for MX. */
|
|
7
8
|
export declare function hostOf(route: Route): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* The DSN parameters `sendMail` passes on (RFC 3461 §4): the item's `RET`
|
|
11
|
+
* and `ENVID`, and the `NOTIFY` and `ORCPT` of the recipients of this
|
|
12
|
+
* session that carry some. Nothing for an item that has none.
|
|
13
|
+
*/
|
|
14
|
+
export declare function dsnOptionsOf(item: QueueItem, group: readonly string[]): Pick<SendMailOptions, 'ret' | 'envid' | 'recipients'>;
|
|
8
15
|
/** What `sendMail` takes to send one domain's recipients of a message. */
|
|
9
16
|
export declare function sendOptionsOf(settings: Settings, domain: string, from: string, to: readonly string[], override?: Route): SendMailOptions;
|
|
10
17
|
//# sourceMappingURL=route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/queue/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAEjE;AAED,+FAA+F;AAC/F,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAEvD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,MAAM,EAAE,EACrB,QAAQ,CAAC,EAAE,KAAK,GACd,eAAe,CA8BjB"}
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/queue/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAEjE;AAED,+FAA+F;AAC/F,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,SAAS,MAAM,EAAE,GACtB,IAAI,CAAC,eAAe,EAAE,KAAK,GAAG,OAAO,GAAG,YAAY,CAAC,CAkBvD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,MAAM,EAAE,EACrB,QAAQ,CAAC,EAAE,KAAK,GACd,eAAe,CA8BjB"}
|