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