mastra-pg-pubsub 0.1.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/LICENSE +21 -0
- package/README.md +168 -0
- package/dist/consume-loop.d.ts +46 -0
- package/dist/consume-loop.d.ts.map +1 -0
- package/dist/consume-loop.js +262 -0
- package/dist/consume-loop.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/listener.d.ts +31 -0
- package/dist/listener.d.ts.map +1 -0
- package/dist/listener.js +126 -0
- package/dist/listener.js.map +1 -0
- package/dist/postgres-pubsub.d.ts +99 -0
- package/dist/postgres-pubsub.d.ts.map +1 -0
- package/dist/postgres-pubsub.js +499 -0
- package/dist/postgres-pubsub.js.map +1 -0
- package/dist/schema.d.ts +21 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +97 -0
- package/dist/schema.js.map +1 -0
- package/dist/sql.d.ts +38 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +57 -0
- package/dist/sql.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { PubSub } from '@mastra/core/events';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
import { ConsumeLoop } from "./consume-loop.js";
|
|
5
|
+
import { NotifyListener } from "./listener.js";
|
|
6
|
+
import { runMigration } from "./schema.js";
|
|
7
|
+
import { assertValidSchema, notifyChannel, quoteIdentifier } from "./sql.js";
|
|
8
|
+
const DEFAULT_SCHEMA = 'mastra_pubsub';
|
|
9
|
+
function mapKey(topic, subscriptionId) {
|
|
10
|
+
return JSON.stringify([topic, subscriptionId]);
|
|
11
|
+
}
|
|
12
|
+
function hashPart(value) {
|
|
13
|
+
return createHash('sha256').update(value).digest('base64url');
|
|
14
|
+
}
|
|
15
|
+
function groupSubscriptionId(topic, group) {
|
|
16
|
+
return `__group:${hashPart(topic)}:${hashPart(group)}`;
|
|
17
|
+
}
|
|
18
|
+
function resolveConfig(config) {
|
|
19
|
+
const logger = config.logger ?? {};
|
|
20
|
+
const schema = config.schema ?? DEFAULT_SCHEMA;
|
|
21
|
+
assertValidSchema(schema);
|
|
22
|
+
let maxDeliveryAttempts = config.maxDeliveryAttempts ?? 5;
|
|
23
|
+
if (maxDeliveryAttempts === 0) {
|
|
24
|
+
logger.warn?.('maxDeliveryAttempts=0 is treated as Infinity (unbounded redelivery)');
|
|
25
|
+
maxDeliveryAttempts = Number.POSITIVE_INFINITY;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
schema,
|
|
29
|
+
pollIntervalMs: config.pollIntervalMs ?? 1000,
|
|
30
|
+
ackDeadlineMs: config.ackDeadlineMs ?? 30_000,
|
|
31
|
+
nackDelayMs: config.nackDelayMs ?? 0,
|
|
32
|
+
maxDeliveryAttempts,
|
|
33
|
+
batchSize: config.batchSize ?? 32,
|
|
34
|
+
maxEventsPerTopic: config.maxEventsPerTopic ?? 10_000,
|
|
35
|
+
cleanupIntervalMs: config.cleanupIntervalMs ?? 60_000,
|
|
36
|
+
staleSubscriptionMs: config.staleSubscriptionMs ?? 300_000,
|
|
37
|
+
listen: config.listen ?? true,
|
|
38
|
+
deadLetter: config.deadLetter ?? false,
|
|
39
|
+
logger,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A PostgreSQL-backed {@link PubSub} implementation for Mastra.
|
|
44
|
+
*
|
|
45
|
+
* Provides at-least-once delivery with ack/nack and visibility timeouts,
|
|
46
|
+
* competing-consumer groups and groupless fan-out, replay addressed by
|
|
47
|
+
* per-topic `index`, optional dead-lettering, and low-latency `LISTEN/NOTIFY`
|
|
48
|
+
* wakeups with polling as the correctness backstop. The only runtime
|
|
49
|
+
* dependency is `pg`; `@mastra/core` is a peer dependency.
|
|
50
|
+
*/
|
|
51
|
+
export class PostgresPubSub extends PubSub {
|
|
52
|
+
#config;
|
|
53
|
+
#pool;
|
|
54
|
+
#ownsPool;
|
|
55
|
+
#logger;
|
|
56
|
+
#instanceId;
|
|
57
|
+
#channel;
|
|
58
|
+
#subscriptions = new Map();
|
|
59
|
+
#cbIndex = new WeakMap();
|
|
60
|
+
#listener;
|
|
61
|
+
#migrated;
|
|
62
|
+
#maintenanceTimer;
|
|
63
|
+
#closed = false;
|
|
64
|
+
#pendingPublishes = new Set();
|
|
65
|
+
/**
|
|
66
|
+
* @param config - Adapter configuration. Provide exactly one of
|
|
67
|
+
* `connectionString` or `pool`.
|
|
68
|
+
*/
|
|
69
|
+
constructor(config) {
|
|
70
|
+
super();
|
|
71
|
+
this.#config = resolveConfig(config);
|
|
72
|
+
this.#logger = this.#config.logger;
|
|
73
|
+
if (config.pool && config.connectionString) {
|
|
74
|
+
throw new Error('Provide either connectionString or pool, not both');
|
|
75
|
+
}
|
|
76
|
+
if (config.pool) {
|
|
77
|
+
this.#pool = config.pool;
|
|
78
|
+
this.#ownsPool = false;
|
|
79
|
+
}
|
|
80
|
+
else if (config.connectionString) {
|
|
81
|
+
this.#pool = new pg.Pool({ connectionString: config.connectionString });
|
|
82
|
+
this.#ownsPool = true;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw new Error('Either connectionString or pool is required');
|
|
86
|
+
}
|
|
87
|
+
this.#instanceId = randomUUID();
|
|
88
|
+
this.#channel = notifyChannel(this.#config.schema);
|
|
89
|
+
}
|
|
90
|
+
#q(table) {
|
|
91
|
+
return `${quoteIdentifier(this.#config.schema)}.${quoteIdentifier(table)}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create the schema and tables explicitly. Idempotent and safe to call
|
|
95
|
+
* concurrently across instances (serialized by an advisory lock). When not
|
|
96
|
+
* called, migration happens lazily on first use.
|
|
97
|
+
*/
|
|
98
|
+
async migrate() {
|
|
99
|
+
if (!this.#migrated) {
|
|
100
|
+
this.#migrated = runMigration(this.#pool, this.#config.schema, this.#config.deadLetter);
|
|
101
|
+
}
|
|
102
|
+
await this.#migrated;
|
|
103
|
+
}
|
|
104
|
+
async #ensureReady() {
|
|
105
|
+
if (this.#closed) {
|
|
106
|
+
throw new Error('PostgresPubSub is closed');
|
|
107
|
+
}
|
|
108
|
+
await this.migrate();
|
|
109
|
+
this.#startMaintenance();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Publish an event to a topic. Assigns `id`, `createdAt`, and a per-topic
|
|
113
|
+
* `index` inside a single transaction, fans out one delivery row per active
|
|
114
|
+
* subscription, and emits a `NOTIFY` wakeup.
|
|
115
|
+
*
|
|
116
|
+
* @param topic - The topic to publish to.
|
|
117
|
+
* @param event - Event without `id`/`createdAt` (assigned here).
|
|
118
|
+
* @param _options - Accepted for interface compatibility; `localOnly` is
|
|
119
|
+
* ignored because delivery is always database-mediated.
|
|
120
|
+
*/
|
|
121
|
+
async publish(topic, event, _options) {
|
|
122
|
+
await this.#ensureReady();
|
|
123
|
+
const task = this.#publish(topic, event);
|
|
124
|
+
this.#pendingPublishes.add(task);
|
|
125
|
+
try {
|
|
126
|
+
await task;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
this.#pendingPublishes.delete(task);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async #publish(topic, event) {
|
|
133
|
+
const id = randomUUID();
|
|
134
|
+
const client = await this.#pool.connect();
|
|
135
|
+
try {
|
|
136
|
+
await client.query('BEGIN');
|
|
137
|
+
const indexResult = await client.query(`INSERT INTO ${this.#q('topics')} (topic, next_index)
|
|
138
|
+
VALUES ($1, 1)
|
|
139
|
+
ON CONFLICT (topic) DO UPDATE SET next_index = ${this.#q('topics')}.next_index + 1
|
|
140
|
+
RETURNING next_index`, [topic]);
|
|
141
|
+
const index = BigInt(indexResult.rows[0]?.next_index ?? '1') - 1n;
|
|
142
|
+
const eventResult = await client.query(`INSERT INTO ${this.#q('events')} (id, topic, index, type, run_id, data)
|
|
143
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
144
|
+
RETURNING seq`, [
|
|
145
|
+
id,
|
|
146
|
+
topic,
|
|
147
|
+
index.toString(),
|
|
148
|
+
event.type,
|
|
149
|
+
event.runId,
|
|
150
|
+
event.data === undefined ? null : JSON.stringify(event.data),
|
|
151
|
+
]);
|
|
152
|
+
const seq = eventResult.rows[0]?.seq;
|
|
153
|
+
await client.query(`INSERT INTO ${this.#q('deliveries')} (event_seq, subscription_id)
|
|
154
|
+
SELECT $1, s.id FROM ${this.#q('subscriptions')} s WHERE s.topic = $2`, [seq, topic]);
|
|
155
|
+
await client.query(`SELECT pg_notify($1, $2)`, [this.#channel, topic]);
|
|
156
|
+
await client.query('COMMIT');
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
await client.query('ROLLBACK').catch(() => undefined);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
client.release();
|
|
164
|
+
}
|
|
165
|
+
this.#wakeLocal(topic);
|
|
166
|
+
}
|
|
167
|
+
#wakeLocal(topic) {
|
|
168
|
+
for (const sub of this.#subscriptions.values()) {
|
|
169
|
+
if (sub.topic === topic) {
|
|
170
|
+
sub.loop.wake();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Subscribe to a topic. With `options.group`, members compete and each event
|
|
176
|
+
* reaches exactly one member (round-robin across local callbacks). Without a
|
|
177
|
+
* group, the subscription is private to this instance and receives every
|
|
178
|
+
* event (fan-out).
|
|
179
|
+
*
|
|
180
|
+
* @param topic - The topic to subscribe to.
|
|
181
|
+
* @param cb - Callback invoked per delivered event.
|
|
182
|
+
* @param options - Subscribe options; `group` selects competing-consumer
|
|
183
|
+
* semantics. `batch` is ignored (no native batching).
|
|
184
|
+
*/
|
|
185
|
+
async subscribe(topic, cb, options) {
|
|
186
|
+
await this.#subscribeInternal(topic, cb, cb, options?.group);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Shared subscribe path. `userKey` is the callback the caller will later
|
|
190
|
+
* pass to `unsubscribe`; `registered` is the callback actually invoked on
|
|
191
|
+
* delivery (they differ only for the replay wrappers).
|
|
192
|
+
*/
|
|
193
|
+
async #subscribeInternal(topic, userKey, registered, group, startImmediately = true) {
|
|
194
|
+
await this.#ensureReady();
|
|
195
|
+
const subscriptionId = group
|
|
196
|
+
? groupSubscriptionId(topic, group)
|
|
197
|
+
: `__private:${this.#instanceId}:${randomUUID()}`;
|
|
198
|
+
const key = mapKey(topic, subscriptionId);
|
|
199
|
+
let sub = this.#subscriptions.get(key);
|
|
200
|
+
if (!sub) {
|
|
201
|
+
const isGroup = group !== undefined;
|
|
202
|
+
await this.#upsertSubscription(subscriptionId, topic, isGroup);
|
|
203
|
+
const registry = { callbacks: [], cursor: 0 };
|
|
204
|
+
const loop = new ConsumeLoop(this.#pool, this.#config.schema, this.#config, topic, subscriptionId, isGroup, registry);
|
|
205
|
+
sub = {
|
|
206
|
+
id: subscriptionId,
|
|
207
|
+
topic,
|
|
208
|
+
isGroup,
|
|
209
|
+
registry,
|
|
210
|
+
loop,
|
|
211
|
+
started: false,
|
|
212
|
+
unregisterWake: undefined,
|
|
213
|
+
};
|
|
214
|
+
this.#subscriptions.set(key, sub);
|
|
215
|
+
}
|
|
216
|
+
sub.registry.callbacks.push(registered);
|
|
217
|
+
let registrations = this.#cbIndex.get(userKey);
|
|
218
|
+
if (!registrations) {
|
|
219
|
+
registrations = [];
|
|
220
|
+
this.#cbIndex.set(userKey, registrations);
|
|
221
|
+
}
|
|
222
|
+
registrations.push({ sub, registered });
|
|
223
|
+
if (startImmediately) {
|
|
224
|
+
await this.#startSubscription(sub);
|
|
225
|
+
sub.loop.wake();
|
|
226
|
+
}
|
|
227
|
+
return sub;
|
|
228
|
+
}
|
|
229
|
+
async #startSubscription(sub) {
|
|
230
|
+
if (sub.started) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
sub.loop.start();
|
|
234
|
+
sub.started = true;
|
|
235
|
+
if (this.#config.listen) {
|
|
236
|
+
await this.#registerWake(sub);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async #registerWake(sub) {
|
|
240
|
+
if (!this.#listener) {
|
|
241
|
+
this.#listener = new NotifyListener(this.#pool, this.#config.schema, this.#logger);
|
|
242
|
+
}
|
|
243
|
+
sub.unregisterWake = await this.#listener.register(sub.topic, () => sub.loop.wake());
|
|
244
|
+
}
|
|
245
|
+
async #upsertSubscription(id, topic, isGroup) {
|
|
246
|
+
await this.#pool.query(`INSERT INTO ${this.#q('subscriptions')} (id, topic, is_group, last_seen_at)
|
|
247
|
+
VALUES ($1, $2, $3, now())
|
|
248
|
+
ON CONFLICT (id) DO UPDATE SET last_seen_at = now()`, [id, topic, isGroup]);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Remove a callback from a topic. Tears down the underlying subscription
|
|
252
|
+
* (and, for private subscriptions, deletes its database rows) once no local
|
|
253
|
+
* callbacks remain.
|
|
254
|
+
*
|
|
255
|
+
* @param topic - The topic the callback was subscribed to.
|
|
256
|
+
* @param cb - The callback to remove.
|
|
257
|
+
*/
|
|
258
|
+
async unsubscribe(topic, cb) {
|
|
259
|
+
const registrations = this.#cbIndex.get(cb);
|
|
260
|
+
if (!registrations) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const remaining = [];
|
|
264
|
+
for (const reg of registrations) {
|
|
265
|
+
if (reg.sub.topic !== topic) {
|
|
266
|
+
remaining.push(reg);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const idx = reg.sub.registry.callbacks.indexOf(reg.registered);
|
|
270
|
+
if (idx !== -1) {
|
|
271
|
+
reg.sub.registry.callbacks.splice(idx, 1);
|
|
272
|
+
}
|
|
273
|
+
if (reg.sub.registry.callbacks.length === 0) {
|
|
274
|
+
await this.#teardownSubscription(reg.sub);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (remaining.length === 0) {
|
|
278
|
+
this.#cbIndex.delete(cb);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.#cbIndex.set(cb, remaining);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async #teardownSubscription(sub) {
|
|
285
|
+
const key = mapKey(sub.topic, sub.id);
|
|
286
|
+
this.#subscriptions.delete(key);
|
|
287
|
+
sub.unregisterWake?.();
|
|
288
|
+
await sub.loop.stop();
|
|
289
|
+
if (!sub.isGroup) {
|
|
290
|
+
await this.#pool
|
|
291
|
+
.query(`DELETE FROM ${this.#q('subscriptions')} WHERE id = $1`, [sub.id])
|
|
292
|
+
.catch((error) => this.#logger.warn?.('failed to delete private subscription', error));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Resolve once all in-flight publishes and local deliveries have settled.
|
|
297
|
+
* Per-event callback errors are logged, never thrown. If locally-owned
|
|
298
|
+
* deliveries remain unsettled after the bounded drain window, reject so
|
|
299
|
+
* shutdown callers do not mistake a stuck subscriber for a clean drain.
|
|
300
|
+
*/
|
|
301
|
+
async flush() {
|
|
302
|
+
await Promise.allSettled([...this.#pendingPublishes]);
|
|
303
|
+
let pending = 0;
|
|
304
|
+
for (let pass = 0; pass < 50; pass++) {
|
|
305
|
+
await Promise.all([...this.#subscriptions.values()].map((s) => s.loop.drain()));
|
|
306
|
+
pending = await this.#countPending();
|
|
307
|
+
if (pending === 0) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
311
|
+
}
|
|
312
|
+
throw new Error(`PostgresPubSub flush timed out with ${pending} unsettled deliveries`);
|
|
313
|
+
}
|
|
314
|
+
async #countPending() {
|
|
315
|
+
const ids = [...this.#subscriptions.values()].map((s) => s.id);
|
|
316
|
+
if (ids.length === 0) {
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
const result = await this.#pool.query(`SELECT count(*)::text AS count FROM ${this.#q('deliveries')}
|
|
320
|
+
WHERE subscription_id = ANY($1::text[])`, [ids]);
|
|
321
|
+
return Number(result.rows[0]?.count ?? '0');
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Fetch historical events for a topic with `index >= offset`, ordered by
|
|
325
|
+
* `index`.
|
|
326
|
+
*
|
|
327
|
+
* @param topic - The topic to read history for.
|
|
328
|
+
* @param offset - Inclusive starting `index`. Defaults to `0`.
|
|
329
|
+
* @returns Events in ascending `index` order.
|
|
330
|
+
*/
|
|
331
|
+
async getHistory(topic, offset = 0) {
|
|
332
|
+
await this.#ensureReady();
|
|
333
|
+
const result = await this.#pool.query(`SELECT id, topic, index, type, run_id, data, created_at
|
|
334
|
+
FROM ${this.#q('events')}
|
|
335
|
+
WHERE topic = $1 AND index >= $2
|
|
336
|
+
ORDER BY index`, [topic, offset]);
|
|
337
|
+
return result.rows.map(rowToEvent);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Subscribe and replay all history first, then continue live, deduping the
|
|
341
|
+
* boundary by event `index` so no event is delivered twice.
|
|
342
|
+
*
|
|
343
|
+
* @param topic - The topic to subscribe to.
|
|
344
|
+
* @param cb - Callback for replayed and live events.
|
|
345
|
+
*/
|
|
346
|
+
async subscribeWithReplay(topic, cb) {
|
|
347
|
+
await this.subscribeFromOffset(topic, 0, cb);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Subscribe and replay history starting at `offset`, then continue live,
|
|
351
|
+
* deduping the boundary by event `index`.
|
|
352
|
+
*
|
|
353
|
+
* @param topic - The topic to subscribe to.
|
|
354
|
+
* @param offset - Inclusive starting `index` for replay.
|
|
355
|
+
* @param cb - Callback for replayed and live events.
|
|
356
|
+
*/
|
|
357
|
+
async subscribeFromOffset(topic, offset, cb) {
|
|
358
|
+
const seen = new Set();
|
|
359
|
+
let live = false;
|
|
360
|
+
const wrapped = (event, ack, nack) => {
|
|
361
|
+
if (live && event.index !== undefined) {
|
|
362
|
+
seen.add(event.index);
|
|
363
|
+
}
|
|
364
|
+
return cb(event, ack, nack);
|
|
365
|
+
};
|
|
366
|
+
// Create the live subscription row first so events published during replay
|
|
367
|
+
// get delivery rows, but keep the consume loop paused until historical
|
|
368
|
+
// callbacks run in order. Replayed deliveries are then settled for this
|
|
369
|
+
// private subscription to avoid duplicate live delivery at the boundary.
|
|
370
|
+
const sub = await this.#subscribeInternal(topic, cb, wrapped, undefined, false);
|
|
371
|
+
try {
|
|
372
|
+
const replayedIndexes = [];
|
|
373
|
+
const history = await this.getHistory(topic, offset);
|
|
374
|
+
for (const event of history) {
|
|
375
|
+
if (event.index !== undefined && seen.has(event.index)) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
await cb(event);
|
|
380
|
+
if (event.index !== undefined) {
|
|
381
|
+
replayedIndexes.push(event.index);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
this.#logger.error?.('replay callback threw', error);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
await this.#ackReplayedDeliveries(topic, sub.id, replayedIndexes);
|
|
389
|
+
live = true;
|
|
390
|
+
await this.#startSubscription(sub);
|
|
391
|
+
sub.loop.wake();
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
await this.unsubscribe(topic, cb).catch((teardownError) => {
|
|
395
|
+
this.#logger.warn?.('failed to clean up replay subscription after setup failure', teardownError);
|
|
396
|
+
});
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async #ackReplayedDeliveries(topic, subscriptionId, indexes) {
|
|
401
|
+
if (indexes.length === 0) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
await this.#pool.query(`DELETE FROM ${this.#q('deliveries')} d
|
|
405
|
+
USING ${this.#q('events')} e
|
|
406
|
+
WHERE d.event_seq = e.seq
|
|
407
|
+
AND d.subscription_id = $1
|
|
408
|
+
AND e.topic = $2
|
|
409
|
+
AND e.index = ANY($3::bigint[])`, [subscriptionId, topic, indexes]);
|
|
410
|
+
}
|
|
411
|
+
#startMaintenance() {
|
|
412
|
+
if (this.#maintenanceTimer || this.#config.cleanupIntervalMs <= 0 || this.#closed) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.#maintenanceTimer = setInterval(() => {
|
|
416
|
+
this.#runMaintenance().catch((error) => this.#logger.warn?.('maintenance cycle failed', error));
|
|
417
|
+
}, this.#config.cleanupIntervalMs);
|
|
418
|
+
this.#maintenanceTimer.unref?.();
|
|
419
|
+
}
|
|
420
|
+
async #runMaintenance() {
|
|
421
|
+
await this.#heartbeat();
|
|
422
|
+
await this.#pruneStaleSubscriptions();
|
|
423
|
+
await this.#trimRetention();
|
|
424
|
+
}
|
|
425
|
+
async #heartbeat() {
|
|
426
|
+
const ids = [...this.#subscriptions.values()].filter((s) => !s.isGroup).map((s) => s.id);
|
|
427
|
+
if (ids.length === 0) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
await this.#pool.query(`UPDATE ${this.#q('subscriptions')} SET last_seen_at = now() WHERE id = ANY($1::text[])`, [ids]);
|
|
431
|
+
}
|
|
432
|
+
async #pruneStaleSubscriptions() {
|
|
433
|
+
await this.#pool.query(`DELETE FROM ${this.#q('subscriptions')}
|
|
434
|
+
WHERE is_group = false
|
|
435
|
+
AND id LIKE '__private:%'
|
|
436
|
+
AND last_seen_at < now() - ($1::double precision * interval '1 millisecond')`, [this.#config.staleSubscriptionMs]);
|
|
437
|
+
}
|
|
438
|
+
async #trimRetention() {
|
|
439
|
+
if (this.#config.maxEventsPerTopic <= 0) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
await this.#pool.query(`WITH ranked AS (
|
|
443
|
+
SELECT e.seq,
|
|
444
|
+
row_number() OVER (PARTITION BY e.topic ORDER BY e.index DESC) AS rn
|
|
445
|
+
FROM ${this.#q('events')} e
|
|
446
|
+
)
|
|
447
|
+
DELETE FROM ${this.#q('events')} e
|
|
448
|
+
USING ranked
|
|
449
|
+
WHERE e.seq = ranked.seq
|
|
450
|
+
AND ranked.rn > $1
|
|
451
|
+
AND NOT EXISTS (
|
|
452
|
+
SELECT 1 FROM ${this.#q('deliveries')} d WHERE d.event_seq = e.seq
|
|
453
|
+
)`, [this.#config.maxEventsPerTopic]);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Stop all loops, release the listener, delete this instance's private
|
|
457
|
+
* subscriptions, and end the pool when the adapter created it. Idempotent.
|
|
458
|
+
*/
|
|
459
|
+
async close() {
|
|
460
|
+
if (this.#closed) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this.#closed = true;
|
|
464
|
+
if (this.#maintenanceTimer) {
|
|
465
|
+
clearInterval(this.#maintenanceTimer);
|
|
466
|
+
this.#maintenanceTimer = undefined;
|
|
467
|
+
}
|
|
468
|
+
await Promise.allSettled([...this.#pendingPublishes]);
|
|
469
|
+
for (const sub of this.#subscriptions.values()) {
|
|
470
|
+
sub.unregisterWake?.();
|
|
471
|
+
}
|
|
472
|
+
await Promise.all([...this.#subscriptions.values()].map((s) => s.loop.stop()));
|
|
473
|
+
if (this.#listener) {
|
|
474
|
+
await this.#listener.close();
|
|
475
|
+
this.#listener = undefined;
|
|
476
|
+
}
|
|
477
|
+
const privateIds = [...this.#subscriptions.values()].filter((s) => !s.isGroup).map((s) => s.id);
|
|
478
|
+
this.#subscriptions.clear();
|
|
479
|
+
if (privateIds.length > 0) {
|
|
480
|
+
await this.#pool
|
|
481
|
+
.query(`DELETE FROM ${this.#q('subscriptions')} WHERE id = ANY($1::text[])`, [privateIds])
|
|
482
|
+
.catch((error) => this.#logger.warn?.('failed to delete private subscriptions', error));
|
|
483
|
+
}
|
|
484
|
+
if (this.#ownsPool) {
|
|
485
|
+
await this.#pool.end();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function rowToEvent(row) {
|
|
490
|
+
return {
|
|
491
|
+
id: row.id,
|
|
492
|
+
type: row.type,
|
|
493
|
+
data: row.data,
|
|
494
|
+
runId: row.run_id,
|
|
495
|
+
createdAt: row.created_at,
|
|
496
|
+
index: Number(row.index),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
//# sourceMappingURL=postgres-pubsub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-pubsub.js","sourceRoot":"","sources":["../src/postgres-pubsub.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
/**
|
|
3
|
+
* Build the ordered list of DDL statements that create the schema and all
|
|
4
|
+
* tables. Idempotent: every statement uses `IF NOT EXISTS`.
|
|
5
|
+
*
|
|
6
|
+
* @param schema - The validated, unquoted schema name.
|
|
7
|
+
* @param deadLetter - Whether to include the optional `dead_events` table.
|
|
8
|
+
* @returns Ordered DDL statements to execute within one transaction.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildDdl(schema: string, deadLetter: boolean): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Create the schema and tables under a transaction-scoped advisory lock so
|
|
13
|
+
* concurrent instances serialize their migrations. Idempotent and safe to call
|
|
14
|
+
* repeatedly.
|
|
15
|
+
*
|
|
16
|
+
* @param pool - The connection pool to run the migration on.
|
|
17
|
+
* @param schema - The validated schema name.
|
|
18
|
+
* @param deadLetter - Whether to include the optional `dead_events` table.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runMigration(pool: Pool, schema: string, deadLetter: boolean): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAG3C;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,EAAE,CA6DtE;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjG"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { advisoryLockKey, assertValidSchema, quoteIdentifier } from "./sql.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the ordered list of DDL statements that create the schema and all
|
|
4
|
+
* tables. Idempotent: every statement uses `IF NOT EXISTS`.
|
|
5
|
+
*
|
|
6
|
+
* @param schema - The validated, unquoted schema name.
|
|
7
|
+
* @param deadLetter - Whether to include the optional `dead_events` table.
|
|
8
|
+
* @returns Ordered DDL statements to execute within one transaction.
|
|
9
|
+
*/
|
|
10
|
+
export function buildDdl(schema, deadLetter) {
|
|
11
|
+
const s = quoteIdentifier(schema);
|
|
12
|
+
const statements = [
|
|
13
|
+
`CREATE SCHEMA IF NOT EXISTS ${s}`,
|
|
14
|
+
`CREATE TABLE IF NOT EXISTS ${s}.topics (
|
|
15
|
+
topic TEXT PRIMARY KEY,
|
|
16
|
+
next_index BIGINT NOT NULL DEFAULT 0
|
|
17
|
+
)`,
|
|
18
|
+
`CREATE TABLE IF NOT EXISTS ${s}.events (
|
|
19
|
+
seq BIGSERIAL PRIMARY KEY,
|
|
20
|
+
id UUID NOT NULL UNIQUE,
|
|
21
|
+
topic TEXT NOT NULL,
|
|
22
|
+
index BIGINT NOT NULL,
|
|
23
|
+
type TEXT NOT NULL,
|
|
24
|
+
run_id TEXT NOT NULL,
|
|
25
|
+
data JSONB,
|
|
26
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
27
|
+
UNIQUE (topic, index)
|
|
28
|
+
)`,
|
|
29
|
+
`CREATE INDEX IF NOT EXISTS events_topic_index_idx
|
|
30
|
+
ON ${s}.events (topic, index)`,
|
|
31
|
+
`CREATE TABLE IF NOT EXISTS ${s}.subscriptions (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
topic TEXT NOT NULL,
|
|
34
|
+
is_group BOOLEAN NOT NULL,
|
|
35
|
+
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
36
|
+
)`,
|
|
37
|
+
`CREATE INDEX IF NOT EXISTS subscriptions_topic_idx
|
|
38
|
+
ON ${s}.subscriptions (topic)`,
|
|
39
|
+
`CREATE TABLE IF NOT EXISTS ${s}.deliveries (
|
|
40
|
+
event_seq BIGINT NOT NULL REFERENCES ${s}.events (seq) ON DELETE CASCADE,
|
|
41
|
+
subscription_id TEXT NOT NULL REFERENCES ${s}.subscriptions (id) ON DELETE CASCADE,
|
|
42
|
+
delivery_attempt INT NOT NULL DEFAULT 0,
|
|
43
|
+
visible_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
44
|
+
PRIMARY KEY (subscription_id, event_seq)
|
|
45
|
+
)`,
|
|
46
|
+
`CREATE INDEX IF NOT EXISTS deliveries_claim_idx
|
|
47
|
+
ON ${s}.deliveries (subscription_id, visible_at, event_seq)`,
|
|
48
|
+
`CREATE INDEX IF NOT EXISTS deliveries_event_idx
|
|
49
|
+
ON ${s}.deliveries (event_seq)`,
|
|
50
|
+
];
|
|
51
|
+
if (deadLetter) {
|
|
52
|
+
statements.push(`CREATE TABLE IF NOT EXISTS ${s}.dead_events (
|
|
53
|
+
seq BIGSERIAL PRIMARY KEY,
|
|
54
|
+
event_id UUID NOT NULL,
|
|
55
|
+
subscription_id TEXT NOT NULL,
|
|
56
|
+
topic TEXT NOT NULL,
|
|
57
|
+
index BIGINT NOT NULL,
|
|
58
|
+
type TEXT NOT NULL,
|
|
59
|
+
run_id TEXT NOT NULL,
|
|
60
|
+
data JSONB,
|
|
61
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
62
|
+
delivery_attempt INT NOT NULL,
|
|
63
|
+
dead_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
64
|
+
)`);
|
|
65
|
+
}
|
|
66
|
+
return statements;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create the schema and tables under a transaction-scoped advisory lock so
|
|
70
|
+
* concurrent instances serialize their migrations. Idempotent and safe to call
|
|
71
|
+
* repeatedly.
|
|
72
|
+
*
|
|
73
|
+
* @param pool - The connection pool to run the migration on.
|
|
74
|
+
* @param schema - The validated schema name.
|
|
75
|
+
* @param deadLetter - Whether to include the optional `dead_events` table.
|
|
76
|
+
*/
|
|
77
|
+
export async function runMigration(pool, schema, deadLetter) {
|
|
78
|
+
assertValidSchema(schema);
|
|
79
|
+
const key = advisoryLockKey(schema);
|
|
80
|
+
const client = await pool.connect();
|
|
81
|
+
try {
|
|
82
|
+
await client.query('BEGIN');
|
|
83
|
+
await client.query('SELECT pg_advisory_xact_lock($1)', [key]);
|
|
84
|
+
for (const statement of buildDdl(schema, deadLetter)) {
|
|
85
|
+
await client.query(statement);
|
|
86
|
+
}
|
|
87
|
+
await client.query('COMMIT');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
await client.query('ROLLBACK').catch(() => undefined);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
client.release();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,UAAmB;IAC1D,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAa;QAC3B,+BAA+B,CAAC,EAAE;QAClC,8BAA8B,CAAC;;;OAG5B;QACH,8BAA8B,CAAC;;;;;;;;;;OAU5B;QACH;YACQ,CAAC,wBAAwB;QACjC,8BAA8B,CAAC;;;;;OAK5B;QACH;YACQ,CAAC,wBAAwB;QACjC,8BAA8B,CAAC;8CACW,CAAC;kDACG,CAAC;;;;OAI5C;QACH;YACQ,CAAC,sDAAsD;QAC/D;YACQ,CAAC,yBAAyB;KACnC,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,UAAU,CAAC,IAAI,CACb,8BAA8B,CAAC;;;;;;;;;;;;SAY5B,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAU,EAAE,MAAc,EAAE,UAAmB;IAChF,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,MAAM,GAAe,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC;YACrD,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACtD,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC"}
|