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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alan Hoffmeister
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# mastra-pg-pubsub
|
|
2
|
+
|
|
3
|
+
[](https://github.com/alanhoff/mastra-pg-pubsub/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/mastra-pg-pubsub)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
PostgreSQL-backed [`PubSub`](https://mastra.ai/reference/pubsub/base) for Mastra. It gives Mastra apps at-least-once delivery, consumer groups, replay by offset, optional dead-lettering, and low-latency `LISTEN/NOTIFY` wakeups using a database you already operate.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Use this when you want Mastra agent/workflow events to survive process restarts and coordinate across multiple Node processes without adding Redis, NATS, or a cloud queue.
|
|
12
|
+
|
|
13
|
+
- **At-least-once delivery** with ack/nack and visibility timeouts.
|
|
14
|
+
- **Consumer groups** for competing workers (`subscribe(..., { group })`).
|
|
15
|
+
- **Fan-out** for groupless subscribers.
|
|
16
|
+
- **Replay** via `getHistory`, `subscribeWithReplay`, and `subscribeFromOffset`.
|
|
17
|
+
- **Crash recovery** through durable delivery rows and visibility timeout redelivery.
|
|
18
|
+
- **Low latency** through Postgres `LISTEN/NOTIFY`, with polling as the correctness backstop.
|
|
19
|
+
- **Small runtime surface**: one runtime dependency (`pg`), ESM, strict TypeScript, Node-native tests.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install mastra-pg-pubsub @mastra/core
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`@mastra/core` is a peer dependency. `pg` is installed as this package's runtime dependency. If your app already owns a `pg.Pool`, pass it in instead of a connection string.
|
|
28
|
+
|
|
29
|
+
## Quickstart
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { Agent } from '@mastra/core/agent';
|
|
33
|
+
import { Mastra } from '@mastra/core/mastra';
|
|
34
|
+
import { PostgresPubSub } from 'mastra-pg-pubsub';
|
|
35
|
+
|
|
36
|
+
const pubsub = new PostgresPubSub({
|
|
37
|
+
connectionString: process.env.DATABASE_URL,
|
|
38
|
+
schema: 'mastra_pubsub',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await pubsub.migrate(); // optional; methods migrate lazily too
|
|
42
|
+
|
|
43
|
+
export const mastra = new Mastra({
|
|
44
|
+
pubsub,
|
|
45
|
+
agents: {
|
|
46
|
+
assistant: new Agent({
|
|
47
|
+
id: 'assistant',
|
|
48
|
+
name: 'Assistant',
|
|
49
|
+
instructions: 'You are helpful.',
|
|
50
|
+
model: 'openai/gpt-4o-mini',
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You can also use it directly through the Mastra PubSub contract:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
await pubsub.subscribe('agent.stream.run-123', (event, ack, nack) => {
|
|
60
|
+
try {
|
|
61
|
+
console.log(event.type, event.index, event.data);
|
|
62
|
+
ack?.();
|
|
63
|
+
} catch {
|
|
64
|
+
nack?.();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await pubsub.publish('agent.stream.run-123', {
|
|
69
|
+
type: 'chunk',
|
|
70
|
+
data: { text: 'hello' },
|
|
71
|
+
runId: 'run-123',
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Replay examples
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const history = await pubsub.getHistory('agent.stream.run-123', 10);
|
|
79
|
+
|
|
80
|
+
await pubsub.subscribeWithReplay('agent.stream.run-123', (event, ack) => {
|
|
81
|
+
ack?.();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await pubsub.subscribeFromOffset('agent.stream.run-123', 42, (event, ack) => {
|
|
85
|
+
ack?.();
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Replay registers the live subscription first, then replays history, deduping the boundary by event `index` so no event is missed or delivered twice at the transition.
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
Provide exactly one of `connectionString` or `pool`.
|
|
94
|
+
|
|
95
|
+
| Option | Default | Description |
|
|
96
|
+
| --- | ---: | --- |
|
|
97
|
+
| `connectionString` | — | PostgreSQL connection string. The adapter owns and closes its pool. |
|
|
98
|
+
| `pool` | — | Bring-your-own `pg.Pool`; never closed by `PostgresPubSub.close()`. |
|
|
99
|
+
| `schema` | `mastra_pubsub` | Schema for all tables. Must match `^[a-z_][a-z0-9_]*$`. |
|
|
100
|
+
| `pollIntervalMs` | `1000` | Backstop polling interval and redelivery detection bound. |
|
|
101
|
+
| `ackDeadlineMs` | `30000` | Visibility timeout before unacked deliveries can be reclaimed. |
|
|
102
|
+
| `nackDelayMs` | `0` | Delay before a nacked delivery becomes visible again. |
|
|
103
|
+
| `maxDeliveryAttempts` | `5` | Attempts before drop/dead-letter. `Infinity` disables the cap; `0` is treated as `Infinity`. |
|
|
104
|
+
| `batchSize` | `32` | Deliveries claimed per consume-loop tick. |
|
|
105
|
+
| `maxEventsPerTopic` | `10000` | Retention cap per topic. `0` keeps everything. |
|
|
106
|
+
| `cleanupIntervalMs` | `60000` | Maintenance interval. `0` disables maintenance. |
|
|
107
|
+
| `staleSubscriptionMs` | `300000` | Age before stale private subscriptions are pruned. |
|
|
108
|
+
| `listen` | `true` | Enable `LISTEN/NOTIFY` wakeups. `false` uses polling only. |
|
|
109
|
+
| `deadLetter` | `false` | Copy exhausted events to `dead_events`. |
|
|
110
|
+
| `logger` | silent | Optional `debug`, `warn`, and `error` functions. |
|
|
111
|
+
|
|
112
|
+
## Delivery guarantees
|
|
113
|
+
|
|
114
|
+
| Property | Guarantee |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| Delivery | At least once; `ack()` settles, missing ack redelivers after `ackDeadlineMs`. |
|
|
117
|
+
| Ordering | Per-topic `index` order for normal delivery; retries can interleave with newer events. |
|
|
118
|
+
| Groups | Each event is delivered to one member per group. |
|
|
119
|
+
| Fan-out | Each groupless subscriber receives every event published after it subscribes. |
|
|
120
|
+
| Replay | Historical events are ordered by per-topic `index` and available until retention trims them. |
|
|
121
|
+
| Idempotency | Event `id` is stable across redeliveries for consumer-side dedupe. |
|
|
122
|
+
| Lifecycle | `flush()` drains in-flight local work; `close()` is idempotent and cleans private subscriptions. |
|
|
123
|
+
|
|
124
|
+
This is intentionally **not exactly-once** delivery. Consumers that perform side effects should dedupe by `event.id` or a domain idempotency key.
|
|
125
|
+
|
|
126
|
+
## Architecture
|
|
127
|
+
|
|
128
|
+
```mermaid
|
|
129
|
+
flowchart LR
|
|
130
|
+
P[publish] -->|tx: bump topic counter, insert event + deliveries, NOTIFY| DB[(PostgreSQL)]
|
|
131
|
+
DB -->|NOTIFY wakeup / poll| L[consume loop per subscription]
|
|
132
|
+
L -->|claim batch: FOR UPDATE SKIP LOCKED + visibility timeout| DB
|
|
133
|
+
L -->|event, ack, nack| CB[EventCallback]
|
|
134
|
+
CB -->|ack: DELETE delivery| DB
|
|
135
|
+
CB -->|nack: visible_at = now()+nackDelay| DB
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The schema is created lazily under a Postgres advisory lock, or explicitly with `await pubsub.migrate()`.
|
|
139
|
+
|
|
140
|
+
## Local development
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npm install
|
|
144
|
+
npm run db:up
|
|
145
|
+
npm test
|
|
146
|
+
npm run test:coverage
|
|
147
|
+
npm run typecheck
|
|
148
|
+
npm run lint
|
|
149
|
+
npm run build
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`npm test` is key-free and uses the pinned Postgres service from `docker-compose.yml` on port `5544`.
|
|
153
|
+
|
|
154
|
+
### Real e2e tests
|
|
155
|
+
|
|
156
|
+
The e2e suite includes one real Mastra durable-agent stream backed by OpenAI and Postgres memory, plus no-OpenAI delivery semantics tests. The real agent test intentionally validates the durable-agent stream API and topic shape for the locked `@mastra/core` version; refresh it when upgrading Mastra.
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
OPENAI_API_KEY=... # or put it in .env
|
|
160
|
+
npm run db:up
|
|
161
|
+
npm run test:e2e
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The script loads `.env` when present with Node's `--env-file-if-exists=.env`, so an exported `OPENAI_API_KEY` also works. Keep `.env` out of git.
|
|
165
|
+
|
|
166
|
+
## Package contents
|
|
167
|
+
|
|
168
|
+
`npm pack --dry-run` should include only the built `dist/` files plus package metadata, README, and license. Source, tests, local research notes, and `.env` are not published.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { EventCallback } from '@mastra/core/events';
|
|
2
|
+
import type { Pool } from 'pg';
|
|
3
|
+
import type { ResolvedConfig } from './types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Round-robin set of local callbacks bound to one (topic, subscription) pair.
|
|
6
|
+
* Group subscriptions round-robin a claimed event across local members;
|
|
7
|
+
* private (fan-out) subscriptions deliver every event to their single member.
|
|
8
|
+
*/
|
|
9
|
+
export interface CallbackRegistry {
|
|
10
|
+
/** Ordered callbacks; group loops round-robin, private loops use index 0. */
|
|
11
|
+
readonly callbacks: EventCallback[];
|
|
12
|
+
/** Rotating cursor for round-robin group dispatch. */
|
|
13
|
+
cursor: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One consume loop per (topic, subscription id). Claims batches with
|
|
17
|
+
* `FOR UPDATE SKIP LOCKED`, extends visibility, delivers sequentially, and
|
|
18
|
+
* settles deliveries via ack/nack or visibility timeout. Woken by
|
|
19
|
+
* `LISTEN/NOTIFY` and by a polling backstop that also reclaims expired
|
|
20
|
+
* deliveries.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ConsumeLoop {
|
|
23
|
+
#private;
|
|
24
|
+
/**
|
|
25
|
+
* @param pool - Connection pool.
|
|
26
|
+
* @param schema - Validated schema name.
|
|
27
|
+
* @param config - Resolved configuration.
|
|
28
|
+
* @param topic - Subscribed topic.
|
|
29
|
+
* @param subscriptionId - Subscription row id (group name or private id).
|
|
30
|
+
* @param isGroup - Whether this is a competing-consumer group.
|
|
31
|
+
* @param registry - Shared local callback registry for this subscription.
|
|
32
|
+
*/
|
|
33
|
+
constructor(pool: Pool, schema: string, config: ResolvedConfig, topic: string, subscriptionId: string, isGroup: boolean, registry: CallbackRegistry);
|
|
34
|
+
/** Start the background loop. Safe to call once. */
|
|
35
|
+
start(): void;
|
|
36
|
+
/** Request an immediate poll (used by NOTIFY wakeups). */
|
|
37
|
+
wake(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve once the loop is idle with no claimable work and no in-flight
|
|
40
|
+
* deliveries — the basis for `flush()`.
|
|
41
|
+
*/
|
|
42
|
+
drain(): Promise<void>;
|
|
43
|
+
/** Stop the loop and wait for it to settle. Idempotent. */
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=consume-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consume-loop.d.ts","sourceRoot":"","sources":["../src/consume-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAajD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAC;IACpC,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;;IAiBtB;;;;;;;;OAQG;gBAED,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,gBAAgB;IAgB5B,oDAAoD;IACpD,KAAK,IAAI,IAAI;IAOb,0DAA0D;IAC1D,IAAI,IAAI,IAAI;IASZ;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkN5B,2DAA2D;IACrD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ5B"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { quoteIdentifier } from "./sql.js";
|
|
2
|
+
/**
|
|
3
|
+
* One consume loop per (topic, subscription id). Claims batches with
|
|
4
|
+
* `FOR UPDATE SKIP LOCKED`, extends visibility, delivers sequentially, and
|
|
5
|
+
* settles deliveries via ack/nack or visibility timeout. Woken by
|
|
6
|
+
* `LISTEN/NOTIFY` and by a polling backstop that also reclaims expired
|
|
7
|
+
* deliveries.
|
|
8
|
+
*/
|
|
9
|
+
export class ConsumeLoop {
|
|
10
|
+
#pool;
|
|
11
|
+
#schema;
|
|
12
|
+
#config;
|
|
13
|
+
#topic;
|
|
14
|
+
#subscriptionId;
|
|
15
|
+
#isGroup;
|
|
16
|
+
#registry;
|
|
17
|
+
#stopped = false;
|
|
18
|
+
#wakeRequested = false;
|
|
19
|
+
#wake;
|
|
20
|
+
#idle;
|
|
21
|
+
#resolveIdle;
|
|
22
|
+
#inFlight = 0;
|
|
23
|
+
#loopPromise;
|
|
24
|
+
/**
|
|
25
|
+
* @param pool - Connection pool.
|
|
26
|
+
* @param schema - Validated schema name.
|
|
27
|
+
* @param config - Resolved configuration.
|
|
28
|
+
* @param topic - Subscribed topic.
|
|
29
|
+
* @param subscriptionId - Subscription row id (group name or private id).
|
|
30
|
+
* @param isGroup - Whether this is a competing-consumer group.
|
|
31
|
+
* @param registry - Shared local callback registry for this subscription.
|
|
32
|
+
*/
|
|
33
|
+
constructor(pool, schema, config, topic, subscriptionId, isGroup, registry) {
|
|
34
|
+
this.#pool = pool;
|
|
35
|
+
this.#schema = schema;
|
|
36
|
+
this.#config = config;
|
|
37
|
+
this.#topic = topic;
|
|
38
|
+
this.#subscriptionId = subscriptionId;
|
|
39
|
+
this.#isGroup = isGroup;
|
|
40
|
+
this.#registry = registry;
|
|
41
|
+
this.#idle = Promise.resolve();
|
|
42
|
+
}
|
|
43
|
+
#q(table) {
|
|
44
|
+
return `${quoteIdentifier(this.#schema)}.${quoteIdentifier(table)}`;
|
|
45
|
+
}
|
|
46
|
+
/** Start the background loop. Safe to call once. */
|
|
47
|
+
start() {
|
|
48
|
+
if (this.#loopPromise) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.#loopPromise = this.#run();
|
|
52
|
+
}
|
|
53
|
+
/** Request an immediate poll (used by NOTIFY wakeups). */
|
|
54
|
+
wake() {
|
|
55
|
+
this.#wakeRequested = true;
|
|
56
|
+
if (this.#wake) {
|
|
57
|
+
const fn = this.#wake;
|
|
58
|
+
this.#wake = undefined;
|
|
59
|
+
fn();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve once the loop is idle with no claimable work and no in-flight
|
|
64
|
+
* deliveries — the basis for `flush()`.
|
|
65
|
+
*/
|
|
66
|
+
async drain() {
|
|
67
|
+
this.wake();
|
|
68
|
+
await this.#idle;
|
|
69
|
+
}
|
|
70
|
+
async #run() {
|
|
71
|
+
while (!this.#stopped) {
|
|
72
|
+
this.#wakeRequested = false;
|
|
73
|
+
let delivered = 0;
|
|
74
|
+
try {
|
|
75
|
+
delivered = await this.#tick();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (!this.#stopped) {
|
|
79
|
+
this.#config.logger.error?.('consume loop tick failed', error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (this.#stopped) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (delivered > 0 || this.#wakeRequested) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
this.#markIdle();
|
|
89
|
+
await this.#sleep();
|
|
90
|
+
}
|
|
91
|
+
this.#markIdle();
|
|
92
|
+
}
|
|
93
|
+
#markIdle() {
|
|
94
|
+
if (this.#inFlight === 0 && this.#resolveIdle) {
|
|
95
|
+
const resolve = this.#resolveIdle;
|
|
96
|
+
this.#resolveIdle = undefined;
|
|
97
|
+
resolve();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
#armIdle() {
|
|
101
|
+
if (!this.#resolveIdle) {
|
|
102
|
+
this.#idle = new Promise((resolve) => {
|
|
103
|
+
this.#resolveIdle = resolve;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async #sleep() {
|
|
108
|
+
this.#armIdle();
|
|
109
|
+
if (this.#wakeRequested || this.#stopped) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await new Promise((resolve) => {
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
this.#wake = undefined;
|
|
115
|
+
resolve();
|
|
116
|
+
}, this.#config.pollIntervalMs);
|
|
117
|
+
this.#wake = () => {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
resolve();
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async #tick() {
|
|
124
|
+
if (this.#registry.callbacks.length === 0) {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
const rows = await this.#claim();
|
|
128
|
+
if (rows.length === 0) {
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
this.#armIdle();
|
|
132
|
+
for (const row of rows) {
|
|
133
|
+
if (this.#stopped) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
await this.#deliver(row);
|
|
137
|
+
}
|
|
138
|
+
return rows.length;
|
|
139
|
+
}
|
|
140
|
+
async #claim() {
|
|
141
|
+
const visibility = this.#config.ackDeadlineMs;
|
|
142
|
+
const sql = `
|
|
143
|
+
WITH claimed AS (
|
|
144
|
+
SELECT d.event_seq
|
|
145
|
+
FROM ${this.#q('deliveries')} d
|
|
146
|
+
WHERE d.subscription_id = $1 AND d.visible_at <= now()
|
|
147
|
+
ORDER BY d.event_seq
|
|
148
|
+
FOR UPDATE SKIP LOCKED
|
|
149
|
+
LIMIT $2
|
|
150
|
+
)
|
|
151
|
+
UPDATE ${this.#q('deliveries')} d
|
|
152
|
+
SET delivery_attempt = d.delivery_attempt + 1,
|
|
153
|
+
visible_at = now() + ($3::double precision * interval '1 millisecond')
|
|
154
|
+
FROM claimed, ${this.#q('events')} e
|
|
155
|
+
WHERE d.subscription_id = $1
|
|
156
|
+
AND d.event_seq = claimed.event_seq
|
|
157
|
+
AND e.seq = d.event_seq
|
|
158
|
+
RETURNING d.event_seq AS seq, e.id AS event_id, e.index, e.type,
|
|
159
|
+
e.run_id, e.data, e.created_at, d.delivery_attempt`;
|
|
160
|
+
const result = await this.#pool.query(sql, [
|
|
161
|
+
this.#subscriptionId,
|
|
162
|
+
this.#config.batchSize,
|
|
163
|
+
visibility,
|
|
164
|
+
]);
|
|
165
|
+
return [...result.rows].sort((a, b) => Number(BigInt(a.seq) - BigInt(b.seq)));
|
|
166
|
+
}
|
|
167
|
+
async #deliver(row) {
|
|
168
|
+
if (row.delivery_attempt > this.#config.maxDeliveryAttempts) {
|
|
169
|
+
await this.#dropExhausted(row);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const callback = this.#nextCallback();
|
|
173
|
+
if (!callback) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const event = {
|
|
177
|
+
id: row.event_id,
|
|
178
|
+
type: row.type,
|
|
179
|
+
data: row.data,
|
|
180
|
+
runId: row.run_id,
|
|
181
|
+
createdAt: row.created_at,
|
|
182
|
+
index: Number(row.index),
|
|
183
|
+
deliveryAttempt: row.delivery_attempt,
|
|
184
|
+
};
|
|
185
|
+
this.#inFlight++;
|
|
186
|
+
let settled = false;
|
|
187
|
+
const ack = async () => {
|
|
188
|
+
if (settled) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
settled = true;
|
|
192
|
+
await this.#ack(row.seq);
|
|
193
|
+
};
|
|
194
|
+
const nack = async () => {
|
|
195
|
+
if (settled) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
settled = true;
|
|
199
|
+
await this.#nack(row.seq);
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
await callback(event, ack, nack);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
this.#config.logger.error?.('subscriber callback threw', error);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
this.#inFlight--;
|
|
209
|
+
this.#markIdle();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
#nextCallback() {
|
|
213
|
+
const callbacks = this.#registry.callbacks;
|
|
214
|
+
if (callbacks.length === 0) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
if (!this.#isGroup) {
|
|
218
|
+
return callbacks[0];
|
|
219
|
+
}
|
|
220
|
+
const index = this.#registry.cursor % callbacks.length;
|
|
221
|
+
this.#registry.cursor = (this.#registry.cursor + 1) % callbacks.length;
|
|
222
|
+
return callbacks[index];
|
|
223
|
+
}
|
|
224
|
+
async #ack(seq) {
|
|
225
|
+
await this.#pool.query(`DELETE FROM ${this.#q('deliveries')} WHERE subscription_id = $1 AND event_seq = $2`, [this.#subscriptionId, seq]);
|
|
226
|
+
}
|
|
227
|
+
async #nack(seq) {
|
|
228
|
+
await this.#pool.query(`UPDATE ${this.#q('deliveries')}
|
|
229
|
+
SET visible_at = now() + ($3::double precision * interval '1 millisecond')
|
|
230
|
+
WHERE subscription_id = $1 AND event_seq = $2`, [this.#subscriptionId, seq, this.#config.nackDelayMs]);
|
|
231
|
+
this.wake();
|
|
232
|
+
}
|
|
233
|
+
async #dropExhausted(row) {
|
|
234
|
+
this.#config.logger.warn?.(`dropping event ${row.event_id} on subscription ${this.#subscriptionId} after ${row.delivery_attempt - 1} attempts`);
|
|
235
|
+
if (this.#config.deadLetter) {
|
|
236
|
+
await this.#pool.query(`INSERT INTO ${this.#q('dead_events')}
|
|
237
|
+
(event_id, subscription_id, topic, index, type, run_id, data, created_at, delivery_attempt)
|
|
238
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
|
|
239
|
+
row.event_id,
|
|
240
|
+
this.#subscriptionId,
|
|
241
|
+
this.#topic,
|
|
242
|
+
row.index,
|
|
243
|
+
row.type,
|
|
244
|
+
row.run_id,
|
|
245
|
+
row.data === null || row.data === undefined ? null : JSON.stringify(row.data),
|
|
246
|
+
row.created_at,
|
|
247
|
+
row.delivery_attempt - 1,
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
await this.#ack(row.seq);
|
|
251
|
+
}
|
|
252
|
+
/** Stop the loop and wait for it to settle. Idempotent. */
|
|
253
|
+
async stop() {
|
|
254
|
+
this.#stopped = true;
|
|
255
|
+
this.wake();
|
|
256
|
+
if (this.#loopPromise) {
|
|
257
|
+
// #run() swallows tick errors internally, so this never rejects.
|
|
258
|
+
await this.#loopPromise;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=consume-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consume-loop.js","sourceRoot":"","sources":["../src/consume-loop.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EACV,KAAK,EACL,oBAAoB,EACpB,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { PubSubLogger } from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Owns a single dedicated `LISTEN` connection and dispatches `NOTIFY` payloads
|
|
5
|
+
* (topic names) to registered per-topic wakeup handlers. Reconnects on
|
|
6
|
+
* connection loss so wakeups survive transient database blips; polling remains
|
|
7
|
+
* the correctness backstop in the meantime.
|
|
8
|
+
*/
|
|
9
|
+
export declare class NotifyListener {
|
|
10
|
+
#private;
|
|
11
|
+
/**
|
|
12
|
+
* @param pool - Pool used to acquire the dedicated listen connection.
|
|
13
|
+
* @param schema - Validated schema name; determines the channel.
|
|
14
|
+
* @param logger - Logger for connection diagnostics.
|
|
15
|
+
*/
|
|
16
|
+
constructor(pool: Pool, schema: string, logger: PubSubLogger);
|
|
17
|
+
/**
|
|
18
|
+
* Register a wakeup handler for a topic, ensuring the listen connection is
|
|
19
|
+
* established. Returns an unregister function.
|
|
20
|
+
*
|
|
21
|
+
* @param topic - The topic to wake on.
|
|
22
|
+
* @param handler - Invoked when a `NOTIFY` for the topic arrives.
|
|
23
|
+
* @returns A function that removes this handler.
|
|
24
|
+
*/
|
|
25
|
+
register(topic: string, handler: () => void): Promise<() => void>;
|
|
26
|
+
/**
|
|
27
|
+
* Release the listen connection and stop dispatching. Idempotent.
|
|
28
|
+
*/
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAE3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;GAKG;AACH,qBAAa,cAAc;;IASzB;;;;OAIG;gBACS,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAM5D;;;;;;;OAOG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAwEvE;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAmB7B"}
|