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.
@@ -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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"}
@@ -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"}