@relaybus/postgres 0.1.0 → 0.3.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 +177 -48
- package/dist/drizzle.cjs +1 -0
- package/dist/drizzle.cjs.map +1 -1
- package/dist/drizzle.d.cts +1 -1
- package/dist/drizzle.d.ts +1 -1
- package/dist/drizzle.js +1 -0
- package/dist/drizzle.js.map +1 -1
- package/dist/executor-CfJgktC6.d.cts +51 -0
- package/dist/executor-CfJgktC6.d.ts +51 -0
- package/dist/index.cjs +2797 -661
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +176 -35
- package/dist/index.d.ts +176 -35
- package/dist/index.js +2799 -664
- package/dist/index.js.map +1 -1
- package/dist/knex.cjs +1 -0
- package/dist/knex.cjs.map +1 -1
- package/dist/knex.d.cts +1 -1
- package/dist/knex.d.ts +1 -1
- package/dist/knex.js +1 -0
- package/dist/knex.js.map +1 -1
- package/dist/kysely.cjs +1 -0
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +1 -1
- package/dist/kysely.d.ts +1 -1
- package/dist/kysely.js +1 -0
- package/dist/kysely.js.map +1 -1
- package/dist/prisma.cjs +1 -0
- package/dist/prisma.cjs.map +1 -1
- package/dist/prisma.d.cts +1 -1
- package/dist/prisma.d.ts +1 -1
- package/dist/prisma.js +1 -0
- package/dist/prisma.js.map +1 -1
- package/dist/typeorm.cjs +1 -0
- package/dist/typeorm.cjs.map +1 -1
- package/dist/typeorm.d.cts +1 -1
- package/dist/typeorm.d.ts +1 -1
- package/dist/typeorm.js +1 -0
- package/dist/typeorm.js.map +1 -1
- package/package.json +24 -11
- package/dist/executor-CSM7ZOhz.d.cts +0 -24
- package/dist/executor-CSM7ZOhz.d.ts +0 -24
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,49 @@
|
|
|
1
|
-
import { P as PostgresExecutor, a as PostgresParameter, b as PostgresRow } from './executor-
|
|
1
|
+
import { P as PostgresExecutor, a as PostgresParameter, b as PostgresQueryOptions, c as PostgresRow } from './executor-CfJgktC6.cjs';
|
|
2
|
+
import { RelayBusLogger, EventStore, EventStoreTransactionOptions, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics, CloseOptions, RelayBusError } from '@relaybus/core';
|
|
2
3
|
import { PoolClient, Pool, PoolConfig } from 'pg';
|
|
3
|
-
import { RelayBusLogger, EventStore, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics } from '@relaybus/core';
|
|
4
4
|
|
|
5
|
+
interface PgExecutorOptions {
|
|
6
|
+
/** Receives the warning sent when prepared statements are switched off. Default `console`. */
|
|
7
|
+
readonly logger?: RelayBusLogger;
|
|
8
|
+
/**
|
|
9
|
+
* The pool is this executor's alone (the store sets it for the pool it creates from
|
|
10
|
+
* `connectionString`). A transaction budget then becomes a session setting of the connection, sent
|
|
11
|
+
* with `BEGIN` only when the connection does not have it yet, and a transaction without a budget on
|
|
12
|
+
* such a connection puts the session defaults back in its `BEGIN`. Statements outside a
|
|
13
|
+
* transaction run under the budget the connection last got. Never set it for a pool other code
|
|
14
|
+
* uses, or behind a pooler in transaction mode (session settings would reach other clients); the
|
|
15
|
+
* executor stops it by itself when it notices a pooler (see prepared statements). Default `false`.
|
|
16
|
+
*/
|
|
17
|
+
readonly sessionBudget?: boolean;
|
|
18
|
+
}
|
|
5
19
|
/**
|
|
6
20
|
* Executor for a `pg` {@link Pool}. A transaction is a dedicated pool client
|
|
7
21
|
* between `BEGIN` and `COMMIT`; it is handed to callers as the transaction
|
|
8
22
|
* handle, so they can run their own queries on it.
|
|
23
|
+
*
|
|
24
|
+
* A statement sent with `prepare: true` is a named prepared statement: each
|
|
25
|
+
* connection parses and plans its text once and from then on only binds the
|
|
26
|
+
* parameters. Its name is derived from the text, so stores with different
|
|
27
|
+
* tables never share one. When the server reports that a prepared statement
|
|
28
|
+
* does not exist on the connection, or already does (a pooler such as
|
|
29
|
+
* PgBouncer before 1.21 in transaction mode hands every transaction another
|
|
30
|
+
* server session), the executor stops preparing for good, logs a warning, and
|
|
31
|
+
* runs the statement again unprepared unless it belonged to a transaction,
|
|
32
|
+
* which has failed by then and is the caller's to retry.
|
|
9
33
|
*/
|
|
10
34
|
declare class PgExecutor implements PostgresExecutor<PoolClient> {
|
|
11
35
|
#private;
|
|
12
|
-
constructor(pool: Pool);
|
|
36
|
+
constructor(pool: Pool, options?: PgExecutorOptions);
|
|
37
|
+
/** The connection listens for `error` during the transaction, so a server-ended session fails the transaction instead of the process. */
|
|
38
|
+
readonly sessionTimeouts = true;
|
|
13
39
|
transaction<T>(work: (transaction: PoolClient) => Promise<T>): Promise<T>;
|
|
14
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Sends `BEGIN` and the budget settings as one simple query (the value as a literal: the simple
|
|
42
|
+
* query protocol has no parameters), so the budget costs no round trip. With `sessionBudget` a
|
|
43
|
+
* connection that already keeps this budget only gets `BEGIN`.
|
|
44
|
+
*/
|
|
45
|
+
transactionWithBudget<T>(work: (transaction: PoolClient) => Promise<T>, timeoutMs: number): Promise<T>;
|
|
46
|
+
query(sql: string, parameters: readonly PostgresParameter[], transaction?: PoolClient, options?: PostgresQueryOptions): Promise<readonly PostgresRow[]>;
|
|
15
47
|
}
|
|
16
48
|
|
|
17
49
|
/** Where relaybus keeps its tables. */
|
|
@@ -24,6 +56,14 @@ interface PostgresSchemaOptions {
|
|
|
24
56
|
* Lower-case letters, digits and underscores. Default `'relaybus_'`.
|
|
25
57
|
*/
|
|
26
58
|
readonly tablePrefix?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Keep an append-only record of completed deliveries in
|
|
61
|
+
* `<prefix>completed_deliveries`, so `listDeliveries({ status: 'completed' })`,
|
|
62
|
+
* `statistics().completed` and `retention.completed` keep working. Default
|
|
63
|
+
* `false`: a completed delivery is deleted, which avoids the dead tuples and
|
|
64
|
+
* index churn of updating it. Use the same value in every process.
|
|
65
|
+
*/
|
|
66
|
+
readonly completionHistory?: boolean;
|
|
27
67
|
}
|
|
28
68
|
/** Validated, quoted names used to build SQL. */
|
|
29
69
|
interface PostgresNames {
|
|
@@ -32,18 +72,32 @@ interface PostgresNames {
|
|
|
32
72
|
readonly events: string;
|
|
33
73
|
readonly deliveries: string;
|
|
34
74
|
readonly keySequences: string;
|
|
75
|
+
/** Lane lease rows: one per lane, see {@link LANE_COUNT}. */
|
|
76
|
+
readonly lanes: string;
|
|
77
|
+
/** Unquoted, unqualified name of {@link lanes}, for catalog lookups. */
|
|
78
|
+
readonly lanesTableName: string;
|
|
79
|
+
/** One row per event that had a delivery complete: when the last one did. Retention of completed work is measured from it. */
|
|
80
|
+
readonly eventCompletions: string;
|
|
81
|
+
/** Completion history, present only with `completionHistory`. */
|
|
82
|
+
readonly completions: string;
|
|
83
|
+
readonly completionHistory: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Channel of the notifications that wake dispatchers when deliveries were published:
|
|
86
|
+
* `<schema>.<prefix>deliveries`, or a hash of it when that is longer than a channel name may be.
|
|
87
|
+
*/
|
|
88
|
+
readonly notificationChannel: string;
|
|
35
89
|
/** Index names, quoted but not schema-qualified (an index lives in its table's schema). */
|
|
36
90
|
readonly indexes: {
|
|
37
91
|
readonly deliveryEventHandler: string;
|
|
38
|
-
readonly
|
|
39
|
-
readonly
|
|
40
|
-
readonly deliveryHandlerCreated: string;
|
|
92
|
+
readonly deliveryLaneDue: string;
|
|
93
|
+
readonly deliveryKeyPending: string;
|
|
41
94
|
readonly eventCreated: string;
|
|
42
|
-
readonly deliveryKeyUnfinished: string;
|
|
43
|
-
readonly deliveryKeyProcessing: string;
|
|
44
|
-
readonly deliveryStatusCreated: string;
|
|
45
95
|
readonly deliveryCompleted: string;
|
|
46
96
|
readonly deliveryDead: string;
|
|
97
|
+
readonly completionCompleted: string;
|
|
98
|
+
readonly eventCompletionsCompleted: string;
|
|
99
|
+
/** Indexes of the 0.1.0 schema that the current schema drops. */
|
|
100
|
+
readonly obsolete: readonly string[];
|
|
47
101
|
};
|
|
48
102
|
}
|
|
49
103
|
/**
|
|
@@ -55,15 +109,23 @@ interface PostgresNames {
|
|
|
55
109
|
declare function resolvePostgresNames(options?: PostgresSchemaOptions): PostgresNames;
|
|
56
110
|
|
|
57
111
|
/**
|
|
58
|
-
* Statements that create the schema, tables and indexes, in dependency order
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
112
|
+
* Statements that create the schema, tables and indexes, in dependency order,
|
|
113
|
+
* and upgrade a 0.1.0 schema in place. Every statement is idempotent
|
|
114
|
+
* (`IF NOT EXISTS`, or guarded by a check for the 0.1.0 columns). Run them in
|
|
115
|
+
* one transaction and serialize concurrent runs (see
|
|
116
|
+
* {@link PostgresEventStore.setup}), or paste {@link createPostgresSchemaSql}
|
|
117
|
+
* into your own migration tool.
|
|
62
118
|
*
|
|
63
119
|
* `events.id` and `deliveries.id` are `text` so that any caller-supplied id
|
|
64
120
|
* (UUID, ULID, deterministic business id) is accepted. `deliveries.id` uses the
|
|
65
121
|
* `C` collation so that ordering by id matches a byte-wise comparison on every
|
|
66
122
|
* database, whatever its locale.
|
|
123
|
+
*
|
|
124
|
+
* A delivery row is written once (insert) and removed once (delete) on the
|
|
125
|
+
* normal path. The lease lives in the lane row a delivery belongs to, so
|
|
126
|
+
* claiming, renewing and completing never update an indexed column of the
|
|
127
|
+
* delivery. The lane table is tiny, has no index besides its primary key and
|
|
128
|
+
* leaves free space in its pages, so its updates are heap-only (HOT).
|
|
67
129
|
*/
|
|
68
130
|
declare function createPostgresSchemaStatements(options?: PostgresSchemaOptions): string[];
|
|
69
131
|
/**
|
|
@@ -87,7 +149,29 @@ interface PostgresEventStoreSettings extends PostgresSchemaOptions {
|
|
|
87
149
|
* it, otherwise `pg` raises an unhandled `error` event and the process exits.
|
|
88
150
|
*/
|
|
89
151
|
readonly logger?: RelayBusLogger;
|
|
152
|
+
/**
|
|
153
|
+
* Send the statements the store repeats (publish, claim, complete, renew) as named prepared
|
|
154
|
+
* statements, so each connection parses and plans their text once instead of on every call. Default
|
|
155
|
+
* `true`. Set `false` behind a pooler in transaction mode that does not keep prepared statements per
|
|
156
|
+
* client (PgBouncer before 1.21, or 1.21+ with `max_prepared_statements = 0`). Left on there, the
|
|
157
|
+
* first statement that lands on another server session fails once, then the store stops preparing
|
|
158
|
+
* and logs a warning. Only executors that support it prepare (`PgExecutor`, the default); the
|
|
159
|
+
* other adapters send every statement unprepared.
|
|
160
|
+
*/
|
|
161
|
+
readonly preparedStatements?: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* How dispatchers in other processes learn about new deliveries.
|
|
164
|
+
* - `'notify'` (default): a publishing transaction sends `NOTIFY` (at most one per 10 ms per store,
|
|
165
|
+
* the rest merged into one trailing notification after the commit) and each dispatcher listens
|
|
166
|
+
* with `LISTEN` on a connection of its own, so a delivery is picked up within milliseconds.
|
|
167
|
+
* - `'poll'`: no `NOTIFY`, no listening connection. Other processes find new deliveries when they
|
|
168
|
+
* poll (`dispatcher.pollInterval`, default 1 s); a dispatcher in the publishing process is still
|
|
169
|
+
* woken at once. Use it behind a pooler in transaction mode, where `LISTEN` does not work.
|
|
170
|
+
*/
|
|
171
|
+
readonly wakeUp?: PostgresWakeUp;
|
|
90
172
|
}
|
|
173
|
+
/** See {@link PostgresEventStoreSettings.wakeUp}. */
|
|
174
|
+
type PostgresWakeUp = 'notify' | 'poll';
|
|
91
175
|
/** Use an existing `pg` pool. The store never closes it. */
|
|
92
176
|
interface PostgresPoolOptions extends PostgresEventStoreSettings {
|
|
93
177
|
readonly pool: Pool;
|
|
@@ -126,13 +210,26 @@ type PostgresEventStoreOptions<TTransaction = PoolClient> = PostgresPoolOptions
|
|
|
126
210
|
* Publishing locks that row and holds the lock until commit, so two
|
|
127
211
|
* transactions publishing the same key are serialized and sequence order
|
|
128
212
|
* equals commit order. Claiming then hands out only the lowest unfinished
|
|
129
|
-
* sequence of a (handler, key)
|
|
130
|
-
*
|
|
131
|
-
*
|
|
213
|
+
* sequence of a (handler, key), and all deliveries of a key live in one lane,
|
|
214
|
+
* so one worker at a time sees them.
|
|
215
|
+
* - **Lane leases.** Workers lease lanes (256 hash buckets of the ordering key
|
|
216
|
+
* or event id), not single deliveries. A claim writes one lane row for a
|
|
217
|
+
* whole batch and never writes a delivery row; a completion deletes the
|
|
218
|
+
* delivery rows. Delivery rows are therefore inserted once and deleted once,
|
|
219
|
+
* which leaves no dead index entries from updates on the normal path.
|
|
132
220
|
* - **Fenced state changes.** Complete, reschedule, bury and extend only apply
|
|
133
|
-
*
|
|
221
|
+
* with the epoch of the lane lease that claimed the delivery. A lane taken
|
|
222
|
+
* over by another worker gets a new epoch, which fences everything the
|
|
223
|
+
* previous holder still runs. Completion takes a key-share lock on the lane
|
|
224
|
+
* row, so it cannot interleave with a takeover.
|
|
225
|
+
* - **Attempts survive crashes.** A claim records which deliveries it put in
|
|
226
|
+
* flight in the lane row. If the holder dies, the next holder charges one
|
|
227
|
+
* attempt to each of them, so a poison message still reaches the retry limit.
|
|
134
228
|
* - **App clock.** Every timestamp comes from the `now` the caller passes in,
|
|
135
229
|
* never from the database clock.
|
|
230
|
+
*
|
|
231
|
+
* Completed deliveries are deleted. Pass `completionHistory: true` to keep an
|
|
232
|
+
* append-only record for `listDeliveries({ status: 'completed' })`.
|
|
136
233
|
*/
|
|
137
234
|
declare class PostgresEventStore<TTransaction = PoolClient> implements EventStore<TTransaction> {
|
|
138
235
|
#private;
|
|
@@ -141,36 +238,66 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
|
|
|
141
238
|
* Creates the schema, tables and indexes if they do not exist. Safe to call
|
|
142
239
|
* from many processes at once: a transaction-scoped advisory lock serializes
|
|
143
240
|
* the DDL, so racing `CREATE TABLE IF NOT EXISTS` statements cannot collide.
|
|
241
|
+
* When the lane table already carries the marker of the current statements
|
|
242
|
+
* ({@link postgresSchemaMarker}) nothing runs and no table is locked, so instances
|
|
243
|
+
* that start next to running dispatchers cannot deadlock with their claims.
|
|
144
244
|
* Skip it (`setupStore: false`) when a migration tool applies
|
|
145
245
|
* `createPostgresSchemaSql()` instead.
|
|
146
246
|
*/
|
|
147
247
|
setup(): Promise<void>;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
|
|
250
|
+
* the server holds the transaction to the budget: `statement_timeout`, and for executors with
|
|
251
|
+
* `sessionTimeouts` (`pg`) also `idle_in_transaction_session_timeout` and, on PostgreSQL 17+,
|
|
252
|
+
* `transaction_timeout`, all scoped to the transaction. An executor with `transactionWithBudget`
|
|
253
|
+
* (`PgExecutor`) sets them with `BEGIN`, so the budget costs no round trip; on a pool the store
|
|
254
|
+
* created they are kept per connection and sent only when the connection does not have them yet.
|
|
255
|
+
*/
|
|
256
|
+
transaction<T>(work: (transaction: TTransaction) => Promise<T>, options?: EventStoreTransactionOptions): Promise<T>;
|
|
257
|
+
/**
|
|
258
|
+
* Writes the entries in `transaction`, normally with one statement
|
|
259
|
+
* (`AppendStatements.insertFreshEvents`): keyed events reserve their sequence numbers (locking
|
|
260
|
+
* the key rows in sorted order, so publishers cannot deadlock inside one call), then the new events and
|
|
261
|
+
* their deliveries are inserted, ordered by primary key, and one notification wakes the dispatchers
|
|
262
|
+
* when a delivery is due now. Events that already existed get their missing handlers' deliveries in
|
|
263
|
+
* two more statements. Two transactions that append keys in opposite order across several calls can
|
|
264
|
+
* still deadlock (40P01); the database rolls one back and the caller retries.
|
|
156
265
|
* A duplicate event id still consumes a sequence number; gaps are harmless
|
|
157
266
|
* because only the order of sequences matters.
|
|
158
267
|
*/
|
|
159
268
|
append(transaction: TTransaction, entries: readonly AppendEntry[], now: Date): Promise<AppendResult>;
|
|
160
269
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
270
|
+
* Claims up to `limit` due deliveries, normally with one statement
|
|
271
|
+
* (`LaneStatements.claimLanes`): from the lanes this owner already holds, then from free lanes
|
|
272
|
+
* it takes in the same statement, recording the batch in the lane rows. A claim that would have to take
|
|
273
|
+
* over a lane whose previous holder still runs deliveries under an expired lease continues in a
|
|
274
|
+
* transaction that charges their lost attempts first, taking free lanes a few at a time (4, 8, 16, ...);
|
|
275
|
+
* so does a claim whose statement kept seeing a lane it holds change under it, and one that came up
|
|
276
|
+
* short with more free lanes left than it looked at. The step that fails after
|
|
277
|
+
* the first one has recorded deliveries returns those instead of failing: a recorded delivery nobody
|
|
278
|
+
* received would be held for a whole lease. See `ClaimStatements.claim` for the rules a delivery
|
|
279
|
+
* must satisfy.
|
|
166
280
|
*/
|
|
167
281
|
claim(request: ClaimRequest): Promise<ClaimedDelivery[]>;
|
|
168
|
-
extendLease(lease: LeaseReference, leaseExpiresAt: Date
|
|
282
|
+
extendLease(lease: LeaseReference, leaseExpiresAt: Date): Promise<boolean>;
|
|
169
283
|
complete(lease: LeaseReference, now: Date, transaction?: TTransaction): Promise<boolean>;
|
|
284
|
+
completeMany(leases: readonly LeaseReference[], now: Date): Promise<boolean[]>;
|
|
170
285
|
reschedule(lease: LeaseReference, availableAt: Date, error: ErrorInfo, now: Date): Promise<boolean>;
|
|
171
286
|
bury(lease: LeaseReference, error: ErrorInfo, now: Date): Promise<boolean>;
|
|
172
287
|
/** Returns a leased delivery to pending, due now, without counting the attempt. */
|
|
173
288
|
release(lease: LeaseReference, now: Date): Promise<boolean>;
|
|
289
|
+
/**
|
|
290
|
+
* Calls `listener` soon after a transaction that published deliveries due at once commits, in any
|
|
291
|
+
* process: `append` sends one `pg_notify` per call (merged per transaction, none for delayed
|
|
292
|
+
* deliveries), and this listens with `LISTEN` on a connection of its own, opened with the settings of
|
|
293
|
+
* the store's pool (one connection more than the pool's `max`). A lost connection is replaced after a
|
|
294
|
+
* second. Resolves with `undefined` for a store built on an `executor`, which has no connection of its
|
|
295
|
+
* own to listen on, and with `wakeUp: 'poll'`: polling then finds new deliveries (and the bus still
|
|
296
|
+
* wakes its own dispatcher).
|
|
297
|
+
*/
|
|
298
|
+
watchNewDeliveries(listener: () => void): Promise<(() => Promise<void>) | undefined>;
|
|
299
|
+
/** Frees the lanes `leaseOwner` holds that run no delivery, so other workers need not wait for the lease to expire. */
|
|
300
|
+
relinquish(leaseOwner: string): Promise<void>;
|
|
174
301
|
listDeliveries(query: DeliveryQuery): Promise<DeliveryRecord[]>;
|
|
175
302
|
requeue(deliveryIds: readonly string[], now: Date): Promise<number>;
|
|
176
303
|
/**
|
|
@@ -184,8 +311,22 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
|
|
|
184
311
|
*/
|
|
185
312
|
purge(request: PurgeRequest): Promise<PurgeResult>;
|
|
186
313
|
statistics(now: Date): Promise<EventStoreStatistics>;
|
|
187
|
-
/**
|
|
188
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Ends the pool this store created from `connectionString`. Pools and executors you passed in stay
|
|
316
|
+
* open. Once `options.signal` aborts (the shutdown deadline passed), the connections the store opened
|
|
317
|
+
* itself are destroyed: queries still running on them fail at once instead of holding the shutdown.
|
|
318
|
+
*/
|
|
319
|
+
close(options?: CloseOptions): Promise<void>;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* A claim was refused because giving back the deliveries of an earlier, failed
|
|
324
|
+
* claim still fails. Claiming before that would take the owner's live lanes for
|
|
325
|
+
* foreign ones; the next claim tries the reconciliation again.
|
|
326
|
+
*/
|
|
327
|
+
declare class ReconcileFailedError extends RelayBusError {
|
|
328
|
+
name: string;
|
|
329
|
+
readonly code: string;
|
|
189
330
|
}
|
|
190
331
|
|
|
191
|
-
export { PgExecutor, type PostgresConnectionOptions, PostgresEventStore, type PostgresEventStoreOptions, type PostgresEventStoreSettings, PostgresExecutor, type PostgresExecutorOptions, type PostgresNames, PostgresParameter, type PostgresPoolOptions, PostgresRow, type PostgresSchemaOptions, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
|
|
332
|
+
export { PgExecutor, type PgExecutorOptions, type PostgresConnectionOptions, PostgresEventStore, type PostgresEventStoreOptions, type PostgresEventStoreSettings, PostgresExecutor, type PostgresExecutorOptions, type PostgresNames, PostgresParameter, type PostgresPoolOptions, PostgresQueryOptions, PostgresRow, type PostgresSchemaOptions, type PostgresWakeUp, ReconcileFailedError, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,49 @@
|
|
|
1
|
-
import { P as PostgresExecutor, a as PostgresParameter, b as PostgresRow } from './executor-
|
|
1
|
+
import { P as PostgresExecutor, a as PostgresParameter, b as PostgresQueryOptions, c as PostgresRow } from './executor-CfJgktC6.js';
|
|
2
|
+
import { RelayBusLogger, EventStore, EventStoreTransactionOptions, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics, CloseOptions, RelayBusError } from '@relaybus/core';
|
|
2
3
|
import { PoolClient, Pool, PoolConfig } from 'pg';
|
|
3
|
-
import { RelayBusLogger, EventStore, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics } from '@relaybus/core';
|
|
4
4
|
|
|
5
|
+
interface PgExecutorOptions {
|
|
6
|
+
/** Receives the warning sent when prepared statements are switched off. Default `console`. */
|
|
7
|
+
readonly logger?: RelayBusLogger;
|
|
8
|
+
/**
|
|
9
|
+
* The pool is this executor's alone (the store sets it for the pool it creates from
|
|
10
|
+
* `connectionString`). A transaction budget then becomes a session setting of the connection, sent
|
|
11
|
+
* with `BEGIN` only when the connection does not have it yet, and a transaction without a budget on
|
|
12
|
+
* such a connection puts the session defaults back in its `BEGIN`. Statements outside a
|
|
13
|
+
* transaction run under the budget the connection last got. Never set it for a pool other code
|
|
14
|
+
* uses, or behind a pooler in transaction mode (session settings would reach other clients); the
|
|
15
|
+
* executor stops it by itself when it notices a pooler (see prepared statements). Default `false`.
|
|
16
|
+
*/
|
|
17
|
+
readonly sessionBudget?: boolean;
|
|
18
|
+
}
|
|
5
19
|
/**
|
|
6
20
|
* Executor for a `pg` {@link Pool}. A transaction is a dedicated pool client
|
|
7
21
|
* between `BEGIN` and `COMMIT`; it is handed to callers as the transaction
|
|
8
22
|
* handle, so they can run their own queries on it.
|
|
23
|
+
*
|
|
24
|
+
* A statement sent with `prepare: true` is a named prepared statement: each
|
|
25
|
+
* connection parses and plans its text once and from then on only binds the
|
|
26
|
+
* parameters. Its name is derived from the text, so stores with different
|
|
27
|
+
* tables never share one. When the server reports that a prepared statement
|
|
28
|
+
* does not exist on the connection, or already does (a pooler such as
|
|
29
|
+
* PgBouncer before 1.21 in transaction mode hands every transaction another
|
|
30
|
+
* server session), the executor stops preparing for good, logs a warning, and
|
|
31
|
+
* runs the statement again unprepared unless it belonged to a transaction,
|
|
32
|
+
* which has failed by then and is the caller's to retry.
|
|
9
33
|
*/
|
|
10
34
|
declare class PgExecutor implements PostgresExecutor<PoolClient> {
|
|
11
35
|
#private;
|
|
12
|
-
constructor(pool: Pool);
|
|
36
|
+
constructor(pool: Pool, options?: PgExecutorOptions);
|
|
37
|
+
/** The connection listens for `error` during the transaction, so a server-ended session fails the transaction instead of the process. */
|
|
38
|
+
readonly sessionTimeouts = true;
|
|
13
39
|
transaction<T>(work: (transaction: PoolClient) => Promise<T>): Promise<T>;
|
|
14
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Sends `BEGIN` and the budget settings as one simple query (the value as a literal: the simple
|
|
42
|
+
* query protocol has no parameters), so the budget costs no round trip. With `sessionBudget` a
|
|
43
|
+
* connection that already keeps this budget only gets `BEGIN`.
|
|
44
|
+
*/
|
|
45
|
+
transactionWithBudget<T>(work: (transaction: PoolClient) => Promise<T>, timeoutMs: number): Promise<T>;
|
|
46
|
+
query(sql: string, parameters: readonly PostgresParameter[], transaction?: PoolClient, options?: PostgresQueryOptions): Promise<readonly PostgresRow[]>;
|
|
15
47
|
}
|
|
16
48
|
|
|
17
49
|
/** Where relaybus keeps its tables. */
|
|
@@ -24,6 +56,14 @@ interface PostgresSchemaOptions {
|
|
|
24
56
|
* Lower-case letters, digits and underscores. Default `'relaybus_'`.
|
|
25
57
|
*/
|
|
26
58
|
readonly tablePrefix?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Keep an append-only record of completed deliveries in
|
|
61
|
+
* `<prefix>completed_deliveries`, so `listDeliveries({ status: 'completed' })`,
|
|
62
|
+
* `statistics().completed` and `retention.completed` keep working. Default
|
|
63
|
+
* `false`: a completed delivery is deleted, which avoids the dead tuples and
|
|
64
|
+
* index churn of updating it. Use the same value in every process.
|
|
65
|
+
*/
|
|
66
|
+
readonly completionHistory?: boolean;
|
|
27
67
|
}
|
|
28
68
|
/** Validated, quoted names used to build SQL. */
|
|
29
69
|
interface PostgresNames {
|
|
@@ -32,18 +72,32 @@ interface PostgresNames {
|
|
|
32
72
|
readonly events: string;
|
|
33
73
|
readonly deliveries: string;
|
|
34
74
|
readonly keySequences: string;
|
|
75
|
+
/** Lane lease rows: one per lane, see {@link LANE_COUNT}. */
|
|
76
|
+
readonly lanes: string;
|
|
77
|
+
/** Unquoted, unqualified name of {@link lanes}, for catalog lookups. */
|
|
78
|
+
readonly lanesTableName: string;
|
|
79
|
+
/** One row per event that had a delivery complete: when the last one did. Retention of completed work is measured from it. */
|
|
80
|
+
readonly eventCompletions: string;
|
|
81
|
+
/** Completion history, present only with `completionHistory`. */
|
|
82
|
+
readonly completions: string;
|
|
83
|
+
readonly completionHistory: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Channel of the notifications that wake dispatchers when deliveries were published:
|
|
86
|
+
* `<schema>.<prefix>deliveries`, or a hash of it when that is longer than a channel name may be.
|
|
87
|
+
*/
|
|
88
|
+
readonly notificationChannel: string;
|
|
35
89
|
/** Index names, quoted but not schema-qualified (an index lives in its table's schema). */
|
|
36
90
|
readonly indexes: {
|
|
37
91
|
readonly deliveryEventHandler: string;
|
|
38
|
-
readonly
|
|
39
|
-
readonly
|
|
40
|
-
readonly deliveryHandlerCreated: string;
|
|
92
|
+
readonly deliveryLaneDue: string;
|
|
93
|
+
readonly deliveryKeyPending: string;
|
|
41
94
|
readonly eventCreated: string;
|
|
42
|
-
readonly deliveryKeyUnfinished: string;
|
|
43
|
-
readonly deliveryKeyProcessing: string;
|
|
44
|
-
readonly deliveryStatusCreated: string;
|
|
45
95
|
readonly deliveryCompleted: string;
|
|
46
96
|
readonly deliveryDead: string;
|
|
97
|
+
readonly completionCompleted: string;
|
|
98
|
+
readonly eventCompletionsCompleted: string;
|
|
99
|
+
/** Indexes of the 0.1.0 schema that the current schema drops. */
|
|
100
|
+
readonly obsolete: readonly string[];
|
|
47
101
|
};
|
|
48
102
|
}
|
|
49
103
|
/**
|
|
@@ -55,15 +109,23 @@ interface PostgresNames {
|
|
|
55
109
|
declare function resolvePostgresNames(options?: PostgresSchemaOptions): PostgresNames;
|
|
56
110
|
|
|
57
111
|
/**
|
|
58
|
-
* Statements that create the schema, tables and indexes, in dependency order
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
112
|
+
* Statements that create the schema, tables and indexes, in dependency order,
|
|
113
|
+
* and upgrade a 0.1.0 schema in place. Every statement is idempotent
|
|
114
|
+
* (`IF NOT EXISTS`, or guarded by a check for the 0.1.0 columns). Run them in
|
|
115
|
+
* one transaction and serialize concurrent runs (see
|
|
116
|
+
* {@link PostgresEventStore.setup}), or paste {@link createPostgresSchemaSql}
|
|
117
|
+
* into your own migration tool.
|
|
62
118
|
*
|
|
63
119
|
* `events.id` and `deliveries.id` are `text` so that any caller-supplied id
|
|
64
120
|
* (UUID, ULID, deterministic business id) is accepted. `deliveries.id` uses the
|
|
65
121
|
* `C` collation so that ordering by id matches a byte-wise comparison on every
|
|
66
122
|
* database, whatever its locale.
|
|
123
|
+
*
|
|
124
|
+
* A delivery row is written once (insert) and removed once (delete) on the
|
|
125
|
+
* normal path. The lease lives in the lane row a delivery belongs to, so
|
|
126
|
+
* claiming, renewing and completing never update an indexed column of the
|
|
127
|
+
* delivery. The lane table is tiny, has no index besides its primary key and
|
|
128
|
+
* leaves free space in its pages, so its updates are heap-only (HOT).
|
|
67
129
|
*/
|
|
68
130
|
declare function createPostgresSchemaStatements(options?: PostgresSchemaOptions): string[];
|
|
69
131
|
/**
|
|
@@ -87,7 +149,29 @@ interface PostgresEventStoreSettings extends PostgresSchemaOptions {
|
|
|
87
149
|
* it, otherwise `pg` raises an unhandled `error` event and the process exits.
|
|
88
150
|
*/
|
|
89
151
|
readonly logger?: RelayBusLogger;
|
|
152
|
+
/**
|
|
153
|
+
* Send the statements the store repeats (publish, claim, complete, renew) as named prepared
|
|
154
|
+
* statements, so each connection parses and plans their text once instead of on every call. Default
|
|
155
|
+
* `true`. Set `false` behind a pooler in transaction mode that does not keep prepared statements per
|
|
156
|
+
* client (PgBouncer before 1.21, or 1.21+ with `max_prepared_statements = 0`). Left on there, the
|
|
157
|
+
* first statement that lands on another server session fails once, then the store stops preparing
|
|
158
|
+
* and logs a warning. Only executors that support it prepare (`PgExecutor`, the default); the
|
|
159
|
+
* other adapters send every statement unprepared.
|
|
160
|
+
*/
|
|
161
|
+
readonly preparedStatements?: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* How dispatchers in other processes learn about new deliveries.
|
|
164
|
+
* - `'notify'` (default): a publishing transaction sends `NOTIFY` (at most one per 10 ms per store,
|
|
165
|
+
* the rest merged into one trailing notification after the commit) and each dispatcher listens
|
|
166
|
+
* with `LISTEN` on a connection of its own, so a delivery is picked up within milliseconds.
|
|
167
|
+
* - `'poll'`: no `NOTIFY`, no listening connection. Other processes find new deliveries when they
|
|
168
|
+
* poll (`dispatcher.pollInterval`, default 1 s); a dispatcher in the publishing process is still
|
|
169
|
+
* woken at once. Use it behind a pooler in transaction mode, where `LISTEN` does not work.
|
|
170
|
+
*/
|
|
171
|
+
readonly wakeUp?: PostgresWakeUp;
|
|
90
172
|
}
|
|
173
|
+
/** See {@link PostgresEventStoreSettings.wakeUp}. */
|
|
174
|
+
type PostgresWakeUp = 'notify' | 'poll';
|
|
91
175
|
/** Use an existing `pg` pool. The store never closes it. */
|
|
92
176
|
interface PostgresPoolOptions extends PostgresEventStoreSettings {
|
|
93
177
|
readonly pool: Pool;
|
|
@@ -126,13 +210,26 @@ type PostgresEventStoreOptions<TTransaction = PoolClient> = PostgresPoolOptions
|
|
|
126
210
|
* Publishing locks that row and holds the lock until commit, so two
|
|
127
211
|
* transactions publishing the same key are serialized and sequence order
|
|
128
212
|
* equals commit order. Claiming then hands out only the lowest unfinished
|
|
129
|
-
* sequence of a (handler, key)
|
|
130
|
-
*
|
|
131
|
-
*
|
|
213
|
+
* sequence of a (handler, key), and all deliveries of a key live in one lane,
|
|
214
|
+
* so one worker at a time sees them.
|
|
215
|
+
* - **Lane leases.** Workers lease lanes (256 hash buckets of the ordering key
|
|
216
|
+
* or event id), not single deliveries. A claim writes one lane row for a
|
|
217
|
+
* whole batch and never writes a delivery row; a completion deletes the
|
|
218
|
+
* delivery rows. Delivery rows are therefore inserted once and deleted once,
|
|
219
|
+
* which leaves no dead index entries from updates on the normal path.
|
|
132
220
|
* - **Fenced state changes.** Complete, reschedule, bury and extend only apply
|
|
133
|
-
*
|
|
221
|
+
* with the epoch of the lane lease that claimed the delivery. A lane taken
|
|
222
|
+
* over by another worker gets a new epoch, which fences everything the
|
|
223
|
+
* previous holder still runs. Completion takes a key-share lock on the lane
|
|
224
|
+
* row, so it cannot interleave with a takeover.
|
|
225
|
+
* - **Attempts survive crashes.** A claim records which deliveries it put in
|
|
226
|
+
* flight in the lane row. If the holder dies, the next holder charges one
|
|
227
|
+
* attempt to each of them, so a poison message still reaches the retry limit.
|
|
134
228
|
* - **App clock.** Every timestamp comes from the `now` the caller passes in,
|
|
135
229
|
* never from the database clock.
|
|
230
|
+
*
|
|
231
|
+
* Completed deliveries are deleted. Pass `completionHistory: true` to keep an
|
|
232
|
+
* append-only record for `listDeliveries({ status: 'completed' })`.
|
|
136
233
|
*/
|
|
137
234
|
declare class PostgresEventStore<TTransaction = PoolClient> implements EventStore<TTransaction> {
|
|
138
235
|
#private;
|
|
@@ -141,36 +238,66 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
|
|
|
141
238
|
* Creates the schema, tables and indexes if they do not exist. Safe to call
|
|
142
239
|
* from many processes at once: a transaction-scoped advisory lock serializes
|
|
143
240
|
* the DDL, so racing `CREATE TABLE IF NOT EXISTS` statements cannot collide.
|
|
241
|
+
* When the lane table already carries the marker of the current statements
|
|
242
|
+
* ({@link postgresSchemaMarker}) nothing runs and no table is locked, so instances
|
|
243
|
+
* that start next to running dispatchers cannot deadlock with their claims.
|
|
144
244
|
* Skip it (`setupStore: false`) when a migration tool applies
|
|
145
245
|
* `createPostgresSchemaSql()` instead.
|
|
146
246
|
*/
|
|
147
247
|
setup(): Promise<void>;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
|
|
250
|
+
* the server holds the transaction to the budget: `statement_timeout`, and for executors with
|
|
251
|
+
* `sessionTimeouts` (`pg`) also `idle_in_transaction_session_timeout` and, on PostgreSQL 17+,
|
|
252
|
+
* `transaction_timeout`, all scoped to the transaction. An executor with `transactionWithBudget`
|
|
253
|
+
* (`PgExecutor`) sets them with `BEGIN`, so the budget costs no round trip; on a pool the store
|
|
254
|
+
* created they are kept per connection and sent only when the connection does not have them yet.
|
|
255
|
+
*/
|
|
256
|
+
transaction<T>(work: (transaction: TTransaction) => Promise<T>, options?: EventStoreTransactionOptions): Promise<T>;
|
|
257
|
+
/**
|
|
258
|
+
* Writes the entries in `transaction`, normally with one statement
|
|
259
|
+
* (`AppendStatements.insertFreshEvents`): keyed events reserve their sequence numbers (locking
|
|
260
|
+
* the key rows in sorted order, so publishers cannot deadlock inside one call), then the new events and
|
|
261
|
+
* their deliveries are inserted, ordered by primary key, and one notification wakes the dispatchers
|
|
262
|
+
* when a delivery is due now. Events that already existed get their missing handlers' deliveries in
|
|
263
|
+
* two more statements. Two transactions that append keys in opposite order across several calls can
|
|
264
|
+
* still deadlock (40P01); the database rolls one back and the caller retries.
|
|
156
265
|
* A duplicate event id still consumes a sequence number; gaps are harmless
|
|
157
266
|
* because only the order of sequences matters.
|
|
158
267
|
*/
|
|
159
268
|
append(transaction: TTransaction, entries: readonly AppendEntry[], now: Date): Promise<AppendResult>;
|
|
160
269
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
270
|
+
* Claims up to `limit` due deliveries, normally with one statement
|
|
271
|
+
* (`LaneStatements.claimLanes`): from the lanes this owner already holds, then from free lanes
|
|
272
|
+
* it takes in the same statement, recording the batch in the lane rows. A claim that would have to take
|
|
273
|
+
* over a lane whose previous holder still runs deliveries under an expired lease continues in a
|
|
274
|
+
* transaction that charges their lost attempts first, taking free lanes a few at a time (4, 8, 16, ...);
|
|
275
|
+
* so does a claim whose statement kept seeing a lane it holds change under it, and one that came up
|
|
276
|
+
* short with more free lanes left than it looked at. The step that fails after
|
|
277
|
+
* the first one has recorded deliveries returns those instead of failing: a recorded delivery nobody
|
|
278
|
+
* received would be held for a whole lease. See `ClaimStatements.claim` for the rules a delivery
|
|
279
|
+
* must satisfy.
|
|
166
280
|
*/
|
|
167
281
|
claim(request: ClaimRequest): Promise<ClaimedDelivery[]>;
|
|
168
|
-
extendLease(lease: LeaseReference, leaseExpiresAt: Date
|
|
282
|
+
extendLease(lease: LeaseReference, leaseExpiresAt: Date): Promise<boolean>;
|
|
169
283
|
complete(lease: LeaseReference, now: Date, transaction?: TTransaction): Promise<boolean>;
|
|
284
|
+
completeMany(leases: readonly LeaseReference[], now: Date): Promise<boolean[]>;
|
|
170
285
|
reschedule(lease: LeaseReference, availableAt: Date, error: ErrorInfo, now: Date): Promise<boolean>;
|
|
171
286
|
bury(lease: LeaseReference, error: ErrorInfo, now: Date): Promise<boolean>;
|
|
172
287
|
/** Returns a leased delivery to pending, due now, without counting the attempt. */
|
|
173
288
|
release(lease: LeaseReference, now: Date): Promise<boolean>;
|
|
289
|
+
/**
|
|
290
|
+
* Calls `listener` soon after a transaction that published deliveries due at once commits, in any
|
|
291
|
+
* process: `append` sends one `pg_notify` per call (merged per transaction, none for delayed
|
|
292
|
+
* deliveries), and this listens with `LISTEN` on a connection of its own, opened with the settings of
|
|
293
|
+
* the store's pool (one connection more than the pool's `max`). A lost connection is replaced after a
|
|
294
|
+
* second. Resolves with `undefined` for a store built on an `executor`, which has no connection of its
|
|
295
|
+
* own to listen on, and with `wakeUp: 'poll'`: polling then finds new deliveries (and the bus still
|
|
296
|
+
* wakes its own dispatcher).
|
|
297
|
+
*/
|
|
298
|
+
watchNewDeliveries(listener: () => void): Promise<(() => Promise<void>) | undefined>;
|
|
299
|
+
/** Frees the lanes `leaseOwner` holds that run no delivery, so other workers need not wait for the lease to expire. */
|
|
300
|
+
relinquish(leaseOwner: string): Promise<void>;
|
|
174
301
|
listDeliveries(query: DeliveryQuery): Promise<DeliveryRecord[]>;
|
|
175
302
|
requeue(deliveryIds: readonly string[], now: Date): Promise<number>;
|
|
176
303
|
/**
|
|
@@ -184,8 +311,22 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
|
|
|
184
311
|
*/
|
|
185
312
|
purge(request: PurgeRequest): Promise<PurgeResult>;
|
|
186
313
|
statistics(now: Date): Promise<EventStoreStatistics>;
|
|
187
|
-
/**
|
|
188
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Ends the pool this store created from `connectionString`. Pools and executors you passed in stay
|
|
316
|
+
* open. Once `options.signal` aborts (the shutdown deadline passed), the connections the store opened
|
|
317
|
+
* itself are destroyed: queries still running on them fail at once instead of holding the shutdown.
|
|
318
|
+
*/
|
|
319
|
+
close(options?: CloseOptions): Promise<void>;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* A claim was refused because giving back the deliveries of an earlier, failed
|
|
324
|
+
* claim still fails. Claiming before that would take the owner's live lanes for
|
|
325
|
+
* foreign ones; the next claim tries the reconciliation again.
|
|
326
|
+
*/
|
|
327
|
+
declare class ReconcileFailedError extends RelayBusError {
|
|
328
|
+
name: string;
|
|
329
|
+
readonly code: string;
|
|
189
330
|
}
|
|
190
331
|
|
|
191
|
-
export { PgExecutor, type PostgresConnectionOptions, PostgresEventStore, type PostgresEventStoreOptions, type PostgresEventStoreSettings, PostgresExecutor, type PostgresExecutorOptions, type PostgresNames, PostgresParameter, type PostgresPoolOptions, PostgresRow, type PostgresSchemaOptions, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
|
|
332
|
+
export { PgExecutor, type PgExecutorOptions, type PostgresConnectionOptions, PostgresEventStore, type PostgresEventStoreOptions, type PostgresEventStoreSettings, PostgresExecutor, type PostgresExecutorOptions, type PostgresNames, PostgresParameter, type PostgresPoolOptions, PostgresQueryOptions, PostgresRow, type PostgresSchemaOptions, type PostgresWakeUp, ReconcileFailedError, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
|