@relaybus/postgres 0.3.1 → 0.4.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 +29 -10
- package/dist/drizzle.d.cts +3 -2
- package/dist/drizzle.d.ts +3 -2
- package/dist/index.cjs +31 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +31 -5
- package/dist/index.js.map +1 -1
- package/dist/knex.d.cts +3 -2
- package/dist/knex.d.ts +3 -2
- package/dist/kysely.d.cts +3 -2
- package/dist/kysely.d.ts +3 -2
- package/dist/prisma.cjs.map +1 -1
- package/dist/prisma.d.cts +4 -4
- package/dist/prisma.d.ts +4 -4
- package/dist/prisma.js.map +1 -1
- package/dist/typeorm.d.cts +3 -2
- package/dist/typeorm.d.ts +3 -2
- package/package.json +19 -12
package/README.md
CHANGED
|
@@ -102,15 +102,29 @@ Where the budget lives depends on who owns the pool:
|
|
|
102
102
|
- **A pool or executor you pass in**: the budget is transaction-scoped (`SET LOCAL` semantics), set with `BEGIN` by
|
|
103
103
|
`PgExecutor` and by its own statement for the other adapters. Session settings of your pool are never changed.
|
|
104
104
|
|
|
105
|
-
**Prisma is
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
**Prisma** is integration-tested with a generated Prisma Client 7.10 (and 6.19 in CI) on the `@prisma/adapter-pg`
|
|
106
|
+
driver adapter: the full event store contract, publishing in `prisma.$transaction`, rollback and concurrent
|
|
107
|
+
transactions. The adapter is typed structurally, so the package has no Prisma dependency. Prisma interactive
|
|
108
|
+
transactions default to a 5 s timeout, so pass `createPrismaExecutor(prisma, { timeout, maxWait })` if handlers run
|
|
109
|
+
longer.
|
|
110
|
+
|
|
111
|
+
**Give an ORM store the `pg` pool too.** ORMs send raw statements unprepared and offer no connection to `LISTEN` on.
|
|
112
|
+
Pass the `pg` pool your ORM's driver uses as `pool` next to `executor`: the statements the store sends outside your
|
|
113
|
+
transactions (the dispatcher's claims, completions and renewals) then run on it as prepared statements, and the
|
|
114
|
+
store listens for wake-ups, so an event published in your ORM transaction is dispatched right after that
|
|
115
|
+
transaction commits. Your transactions still go through the ORM, and the store never closes the pool.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const pool = new Pool({ connectionString });
|
|
119
|
+
const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
|
|
120
|
+
const store = new PostgresEventStore({ executor: createPrismaExecutor(prisma), pool });
|
|
121
|
+
```
|
|
108
122
|
|
|
109
123
|
## Options
|
|
110
124
|
|
|
111
125
|
| Option | Default | Meaning |
|
|
112
126
|
| ---------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
113
|
-
| `pool` / `connectionString` / `executor` | required, exactly one | How to reach the database.
|
|
127
|
+
| `pool` / `connectionString` / `executor` | required, exactly one | How to reach the database. With `executor`, `pool` may be given too (see above). |
|
|
114
128
|
| `poolConfig` | none | Extra `pg` pool settings when `connectionString` is used. |
|
|
115
129
|
| `schema` | `'public'` | Schema of the tables. Created by `setup()` when it is not `public`. |
|
|
116
130
|
| `tablePrefix` | `'relaybus_'` | Tables are `<prefix>events`, `<prefix>deliveries`, `<prefix>key_sequences`, `<prefix>lanes`. |
|
|
@@ -274,11 +288,16 @@ tool, run it as one transaction with the workers stopped. Running 0.1.0 and the
|
|
|
274
288
|
that channel (`<schema>.<prefix>deliveries`, hashed when longer than 63 characters) on one connection of its own,
|
|
275
289
|
opened with the pool's settings outside the pool, so an event published by any process runs within milliseconds
|
|
276
290
|
instead of at the next poll. A lost listening connection is replaced after a second; polling remains the safety
|
|
277
|
-
net, so a missed notification only costs latency.
|
|
278
|
-
|
|
291
|
+
net, so a missed notification only costs latency. The notification of a publish in your own transaction is sent
|
|
292
|
+
by PostgreSQL at its commit, however late that is, so the handler starts within milliseconds of the commit
|
|
293
|
+
(`wake-after-commit.integration.test.ts`). A store built on an `executor` listens only when it is given `pool`
|
|
294
|
+
as well; without it, it relies on polling and on the wake-ups of its own process after a publish (see
|
|
295
|
+
`bus.wake()`). `wakeUp: 'poll'` sends and listens for nothing.
|
|
279
296
|
- **Prepared statements.** With `preparedStatements: true` (the default) the publish, claim, completion and renewal
|
|
280
297
|
statements are named prepared statements: each connection parses and plans them once. Only `PgExecutor` (the
|
|
281
|
-
default `pool` / `connectionString` path
|
|
298
|
+
default `pool` / `connectionString` path, and the `pool` given next to an `executor`) prepares; the library
|
|
299
|
+
adapters send the statements of your transactions unprepared (a dispatch batch on an ORM store parses 2 statements
|
|
300
|
+
without `pool`, 0 with it: `perf-design-orm-executors.integration.test.ts`). See
|
|
282
301
|
`docs/performance/publish-path.md` for what that saves.
|
|
283
302
|
- **Pooling.** Claim, complete, extend and purge need a free connection besides your transactions; size the pool for
|
|
284
303
|
`dispatcher.concurrency` plus publishers; the listening connection comes on top of the pool's `max`. Listening and
|
|
@@ -310,12 +329,12 @@ tool, run it as one transaction with the workers stopped. Running 0.1.0 and the
|
|
|
310
329
|
## Testing
|
|
311
330
|
|
|
312
331
|
Integration tests use Testcontainers against `postgres:17-alpine` and `postgres:13-alpine` (override with
|
|
313
|
-
`RELAYBUS_POSTGRES_IMAGES`). Every adapter
|
|
314
|
-
transaction tests against the real database.
|
|
332
|
+
`RELAYBUS_POSTGRES_IMAGES`). Every adapter (Kysely, Knex, TypeORM, Drizzle, Prisma with and without `pool`) runs the
|
|
333
|
+
full `@relaybus/testing` event store contract plus transaction tests against the real database.
|
|
315
334
|
|
|
316
335
|
## When to use @relaybus/postgres, and when not to
|
|
317
336
|
|
|
318
|
-
Use it when your data lives in PostgreSQL 13 or newer and events must commit or roll back with it. It is not for MySQL or MongoDB (see `@relaybus/mongodb` for MongoDB)
|
|
337
|
+
Use it when your data lives in PostgreSQL 13 or newer and events must commit or roll back with it. It is not for MySQL or MongoDB (see `@relaybus/mongodb` for MongoDB).
|
|
319
338
|
|
|
320
339
|
Documentation: <https://berkcansavur.github.io/relaybus/packages/postgres>
|
|
321
340
|
|
package/dist/drizzle.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SQL } from 'drizzle-orm';
|
|
2
|
-
import {
|
|
2
|
+
import { PostgresExecutor } from './index.cjs';
|
|
3
3
|
|
|
4
4
|
/** What a Drizzle database and a Drizzle transaction have in common. */
|
|
5
5
|
interface DrizzleQueryable {
|
|
@@ -17,4 +17,5 @@ interface DrizzleLike extends DrizzleQueryable {
|
|
|
17
17
|
*/
|
|
18
18
|
declare function createDrizzleExecutor(database: DrizzleLike): PostgresExecutor<DrizzleQueryable>;
|
|
19
19
|
|
|
20
|
-
export {
|
|
20
|
+
export { createDrizzleExecutor };
|
|
21
|
+
export type { DrizzleLike, DrizzleQueryable };
|
package/dist/drizzle.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SQL } from 'drizzle-orm';
|
|
2
|
-
import {
|
|
2
|
+
import { PostgresExecutor } from './index.js';
|
|
3
3
|
|
|
4
4
|
/** What a Drizzle database and a Drizzle transaction have in common. */
|
|
5
5
|
interface DrizzleQueryable {
|
|
@@ -17,4 +17,5 @@ interface DrizzleLike extends DrizzleQueryable {
|
|
|
17
17
|
*/
|
|
18
18
|
declare function createDrizzleExecutor(database: DrizzleLike): PostgresExecutor<DrizzleQueryable>;
|
|
19
19
|
|
|
20
|
-
export {
|
|
20
|
+
export { createDrizzleExecutor };
|
|
21
|
+
export type { DrizzleLike, DrizzleQueryable };
|
package/dist/index.cjs
CHANGED
|
@@ -493,6 +493,19 @@ var NotificationListener = class {
|
|
|
493
493
|
await drop?.();
|
|
494
494
|
}
|
|
495
495
|
};
|
|
496
|
+
|
|
497
|
+
// src/root-pool-executor.ts
|
|
498
|
+
function withRootPool(executor, root) {
|
|
499
|
+
return {
|
|
500
|
+
...executor.sessionTimeouts === void 0 ? {} : { sessionTimeouts: executor.sessionTimeouts },
|
|
501
|
+
transaction: (work) => executor.transaction(work),
|
|
502
|
+
...executor.transactionWithBudget === void 0 ? {} : {
|
|
503
|
+
transactionWithBudget: (work, timeoutMs) => executor.transactionWithBudget(work, timeoutMs)
|
|
504
|
+
},
|
|
505
|
+
// eslint-disable-next-line relaybus/max-two-params -- PostgresExecutor port signature
|
|
506
|
+
query: (sql, parameters, transaction, options) => transaction === void 0 ? root.query(sql, parameters, void 0, options) : executor.query(sql, parameters, transaction, options)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
496
509
|
function text(row, column) {
|
|
497
510
|
return row[column];
|
|
498
511
|
}
|
|
@@ -1436,7 +1449,7 @@ var PostgresMaintenance = class {
|
|
|
1436
1449
|
var WAKE_UPS = ["notify", "poll"];
|
|
1437
1450
|
var DEFAULT_PURGE_BATCH_SIZE = 1e3;
|
|
1438
1451
|
function resolveStoreSettings(options) {
|
|
1439
|
-
const sources = [options.
|
|
1452
|
+
const sources = [options.executor ?? options.pool, options.connectionString].filter(
|
|
1440
1453
|
(source) => source !== void 0
|
|
1441
1454
|
);
|
|
1442
1455
|
if (sources.length !== 1) {
|
|
@@ -1542,9 +1555,13 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1542
1555
|
RETURNING available_at
|
|
1543
1556
|
)`;
|
|
1544
1557
|
}
|
|
1545
|
-
/**
|
|
1558
|
+
/**
|
|
1559
|
+
* One wake-up notification on {@link PostgresNames.notificationChannel}, outside any publishing
|
|
1560
|
+
* transaction. Returns a boolean column rather than the `void` of `pg_notify`, which some libraries
|
|
1561
|
+
* cannot read (Prisma's raw queries reject a `void` column).
|
|
1562
|
+
*/
|
|
1546
1563
|
get notify() {
|
|
1547
|
-
return `SELECT pg_notify('${this.#names.notificationChannel}', '')`;
|
|
1564
|
+
return `SELECT true AS notified FROM pg_notify('${this.#names.notificationChannel}', '')`;
|
|
1548
1565
|
}
|
|
1549
1566
|
/**
|
|
1550
1567
|
* A CTE `woken` that sends one notification on {@link PostgresNames.notificationChannel} when a row of
|
|
@@ -2834,8 +2851,12 @@ var SetupStatements = class {
|
|
|
2834
2851
|
WHERE n.nspname = '${this.#names.schema}' AND c.relname = '${this.#names.lanesTableName}' AND d.description = $1::text
|
|
2835
2852
|
) AS current`;
|
|
2836
2853
|
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Takes the setup lock. Returns a boolean column rather than the `void` of the lock function, which
|
|
2856
|
+
* some libraries cannot read (Prisma's raw queries reject a `void` column).
|
|
2857
|
+
*/
|
|
2837
2858
|
get setupLock() {
|
|
2838
|
-
return "SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))";
|
|
2859
|
+
return "SELECT true AS locked FROM pg_advisory_xact_lock(hashtextextended($1::text, 0))";
|
|
2839
2860
|
}
|
|
2840
2861
|
};
|
|
2841
2862
|
|
|
@@ -3104,7 +3125,12 @@ var PostgresEventStore = class {
|
|
|
3104
3125
|
};
|
|
3105
3126
|
function connect(options, { logger, owned }) {
|
|
3106
3127
|
if (options.executor !== void 0) {
|
|
3107
|
-
|
|
3128
|
+
const { executor: executor2, pool: pool2 } = options;
|
|
3129
|
+
return {
|
|
3130
|
+
executor: pool2 === void 0 ? executor2 : withRootPool(executor2, new PgExecutor(pool2, { logger })),
|
|
3131
|
+
pool: pool2,
|
|
3132
|
+
ownedPool: void 0
|
|
3133
|
+
};
|
|
3108
3134
|
}
|
|
3109
3135
|
const pool = options.pool ?? new pg.Pool({
|
|
3110
3136
|
...options.poolConfig,
|