@relaybus/postgres 0.4.0 → 0.4.1
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 +18 -15
- package/dist/index.cjs +124 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +124 -39
- package/dist/index.js.map +1 -1
- package/dist/prisma.cjs.map +1 -1
- package/dist/prisma.d.cts +1 -1
- package/dist/prisma.d.ts +1 -1
- package/dist/prisma.js.map +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -102,7 +102,8 @@ 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**
|
|
105
|
+
**Prisma** 5, 6 and 7 are supported (optional peer `@prisma/client` `^5.0.0 || ^6.0.0 || ^7.0.0`). It is
|
|
106
|
+
integration-tested with a generated Prisma Client 7.10 (and 6.19 and 5.22 in CI) on the `@prisma/adapter-pg`
|
|
106
107
|
driver adapter: the full event store contract, publishing in `prisma.$transaction`, rollback and concurrent
|
|
107
108
|
transactions. The adapter is typed structurally, so the package has no Prisma dependency. Prisma interactive
|
|
108
109
|
transactions default to a 5 s timeout, so pass `createPrismaExecutor(prisma, { timeout, maxWait })` if handlers run
|
|
@@ -155,14 +156,14 @@ import { createPostgresSchemaSql, createPostgresSchemaStatements } from '@relayb
|
|
|
155
156
|
const migration = createPostgresSchemaSql({ schema: 'app', tablePrefix: 'bus_' });
|
|
156
157
|
```
|
|
157
158
|
|
|
158
|
-
| Table | Purpose
|
|
159
|
-
| ---------------------- |
|
|
160
|
-
| `events` | One row per event: `id` (**text**, so any caller id works), `type`, `source`, `key`, `sequence`, `payload
|
|
161
|
-
| `deliveries` | One row per (event, handler) that is not finished: `lane`, `status` (`pending` or `dead`), `attempts`, `available_at`, `last_error jsonb`, key and sequence copied from the event, timestamps. `unique (event_id, handler)`, foreign key to events with `ON DELETE CASCADE`.
|
|
162
|
-
| `lanes` | 256 rows, one per lane: `epoch` (the fencing token), `lease_owner`, `lease_expires_at` and `in_flight` (ids of the deliveries the owner claimed). `fillfactor = 50` and no secondary index, so its updates are heap-only (HOT).
|
|
163
|
-
| `event_completions` | One row per event that had a delivery complete, with the time of its last completion. Retention of completed work (`retention.completed`) is measured from it, however old the event is; events that never had a delivery complete are measured from their creation.
|
|
164
|
-
| `key_sequences` | One counter row per ordering key.
|
|
165
|
-
| `completed_deliveries` | Only with `completionHistory`: one insert-only row per completed delivery.
|
|
159
|
+
| Table | Purpose |
|
|
160
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `events` | One row per event: `id` (**text**, so any caller id works), `type`, `source`, `key`, `sequence`, `payload json`, `headers json`, correlation and causation ids, `occurred_at`, `created_at`, and `handlers`, the handlers that ever got a delivery (it outlives the deliveries). |
|
|
162
|
+
| `deliveries` | One row per (event, handler) that is not finished: `lane`, `status` (`pending` or `dead`), `attempts`, `available_at`, `last_error jsonb`, key and sequence copied from the event, timestamps. `unique (event_id, handler)`, foreign key to events with `ON DELETE CASCADE`. |
|
|
163
|
+
| `lanes` | 256 rows, one per lane: `epoch` (the fencing token), `lease_owner`, `lease_expires_at` and `in_flight` (ids of the deliveries the owner claimed). `fillfactor = 50` and no secondary index, so its updates are heap-only (HOT). |
|
|
164
|
+
| `event_completions` | One row per event that had a delivery complete, with the time of its last completion. Retention of completed work (`retention.completed`) is measured from it, however old the event is; events that never had a delivery complete are measured from their creation. |
|
|
165
|
+
| `key_sequences` | One counter row per ordering key. |
|
|
166
|
+
| `completed_deliveries` | Only with `completionHistory`: one insert-only row per completed delivery. |
|
|
166
167
|
|
|
167
168
|
A delivery row is inserted once and deleted once when it completes. Claiming, renewing a lease and completing never
|
|
168
169
|
update it: they update the lane row (claim, renewal) or delete the delivery (completion), so the indexed columns of a
|
|
@@ -257,10 +258,12 @@ tool, run it as one transaction with the workers stopped. Running 0.1.0 and the
|
|
|
257
258
|
- **Key counters.** `key_sequences` keeps one row per key ever published and rows are never deleted; the table grows
|
|
258
259
|
with the number of distinct keys (a bigint counter each). Pick keys with bounded cardinality, or prune counters of keys
|
|
259
260
|
you know are retired once nothing for them is pending.
|
|
260
|
-
- **Stored JSON.** Payloads and headers are stored as `
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
- **Stored JSON.** Payloads and headers are stored as `json`, the text the producer serialized: an event read back
|
|
262
|
+
serializes byte for byte as it was published, key order included. (Before 0.4.1 they were `jsonb`, which re-orders
|
|
263
|
+
object keys; `setup()` converts the columns, and rows stored before the upgrade keep the order `jsonb` gave them. See
|
|
264
|
+
[Upgrading](https://berkcansavur.github.io/relaybus/upgrading).) A string containing `\u0000` (NUL) or a lone UTF-16
|
|
265
|
+
surrogate is rejected by PostgreSQL and fails the publish with a database error; strip or encode such text before
|
|
266
|
+
publishing.
|
|
264
267
|
- **Sequence gaps.** Republishing an existing event id consumes a sequence number. Gaps are normal; only order matters.
|
|
265
268
|
- **Vacuum.** Completing a delivery deletes its row, which leaves a dead tuple until autovacuum runs. The tables are
|
|
266
269
|
created with `autovacuum_vacuum_scale_factor = 0.02`; for sustained throughput above a few thousand deliveries per
|
|
@@ -315,8 +318,8 @@ tool, run it as one transaction with the workers stopped. Running 0.1.0 and the
|
|
|
315
318
|
`createPostgresSchemaSql()`.
|
|
316
319
|
- `permission denied for schema`: the role needs `CREATE` on the schema for `setup()`; with `setupStore: false` it needs
|
|
317
320
|
only DML rights.
|
|
318
|
-
- `invalid input syntax for type json` /
|
|
319
|
-
|
|
321
|
+
- `invalid input syntax for type json` / `unsupported Unicode escape sequence`: the payload holds a NUL character or a
|
|
322
|
+
lone UTF-16 surrogate, which PostgreSQL rejects.
|
|
320
323
|
- Foreign key error on append: the only known cause is republishing an event id in the same instant its last delivery
|
|
321
324
|
is purged. The publishing transaction rolls back and can simply be retried.
|
|
322
325
|
- A key looks stuck: a pending or processing head blocks it. Inspect `listDeliveries({ status: 'processing' })` and
|
package/dist/index.cjs
CHANGED
|
@@ -230,6 +230,7 @@ function resolvePostgresNames(options = {}) {
|
|
|
230
230
|
schema,
|
|
231
231
|
quotedSchema,
|
|
232
232
|
events: table("events"),
|
|
233
|
+
eventsTableName: name("events"),
|
|
233
234
|
deliveries: table("deliveries"),
|
|
234
235
|
keySequences: table("key_sequences"),
|
|
235
236
|
lanes: table("lanes"),
|
|
@@ -256,8 +257,17 @@ function laneExpression(keyExpression, eventIdExpression) {
|
|
|
256
257
|
|
|
257
258
|
// src/schema.ts
|
|
258
259
|
var SCHEMA_MARKER_DIGITS = 32;
|
|
260
|
+
var PAYLOAD_COLUMNS = ` payload json NOT NULL,
|
|
261
|
+
headers json NOT NULL DEFAULT '{}'::json,`;
|
|
262
|
+
var JSONB_PAYLOAD_COLUMNS = ` payload jsonb NOT NULL,
|
|
263
|
+
headers jsonb NOT NULL DEFAULT '{}'::jsonb,`;
|
|
259
264
|
function createPostgresSchemaStatements(options = {}) {
|
|
260
265
|
const names = resolvePostgresNames(options);
|
|
266
|
+
const statements = schemaBodyStatements(names);
|
|
267
|
+
statements.push(`COMMENT ON TABLE ${names.lanes} IS '${version040Marker(names, statements)}'`);
|
|
268
|
+
return statements;
|
|
269
|
+
}
|
|
270
|
+
function schemaBodyStatements(names) {
|
|
261
271
|
const statements = [...eventAndDeliveryStatements(names), ...laneAndCompletionStatements(names)];
|
|
262
272
|
if (names.completionHistory) {
|
|
263
273
|
statements.push(...completionHistoryStatements(names));
|
|
@@ -265,7 +275,6 @@ function createPostgresSchemaStatements(options = {}) {
|
|
|
265
275
|
if (names.schema !== "public") {
|
|
266
276
|
statements.unshift(`CREATE SCHEMA IF NOT EXISTS ${names.quotedSchema}`);
|
|
267
277
|
}
|
|
268
|
-
statements.push(`COMMENT ON TABLE ${names.lanes} IS '${postgresSchemaMarker(statements)}'`);
|
|
269
278
|
return statements;
|
|
270
279
|
}
|
|
271
280
|
function eventAndDeliveryStatements(names) {
|
|
@@ -279,14 +288,14 @@ function eventAndKeySequenceStatements(names) {
|
|
|
279
288
|
source text NOT NULL,
|
|
280
289
|
key text,
|
|
281
290
|
sequence bigint,
|
|
282
|
-
|
|
283
|
-
headers jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
291
|
+
${PAYLOAD_COLUMNS}
|
|
284
292
|
correlation_id text,
|
|
285
293
|
causation_id text,
|
|
286
294
|
occurred_at timestamptz NOT NULL,
|
|
287
295
|
created_at timestamptz NOT NULL,
|
|
288
296
|
handlers text[] NOT NULL DEFAULT '{}'
|
|
289
297
|
)`,
|
|
298
|
+
upgradePayloadToJson(names),
|
|
290
299
|
`CREATE INDEX IF NOT EXISTS ${names.indexes.eventCreated} ON ${names.events} (created_at, id)`,
|
|
291
300
|
`CREATE TABLE IF NOT EXISTS ${names.keySequences} (
|
|
292
301
|
key text PRIMARY KEY,
|
|
@@ -382,6 +391,33 @@ BEGIN
|
|
|
382
391
|
END
|
|
383
392
|
$relaybus_upgrade$`;
|
|
384
393
|
}
|
|
394
|
+
function upgradePayloadToJson(names) {
|
|
395
|
+
return `DO $relaybus_payload_json$
|
|
396
|
+
BEGIN
|
|
397
|
+
IF EXISTS (
|
|
398
|
+
SELECT 1 FROM pg_attribute
|
|
399
|
+
WHERE attrelid = to_regclass('${names.events}') AND attname IN ('payload', 'headers')
|
|
400
|
+
AND atttypid = 'jsonb'::regtype AND NOT attisdropped
|
|
401
|
+
) THEN
|
|
402
|
+
ALTER TABLE ${names.events}
|
|
403
|
+
ALTER COLUMN headers DROP DEFAULT,
|
|
404
|
+
ALTER COLUMN payload TYPE json USING payload::json,
|
|
405
|
+
ALTER COLUMN headers TYPE json USING headers::json,
|
|
406
|
+
ALTER COLUMN headers SET DEFAULT '{}'::json;
|
|
407
|
+
END IF;
|
|
408
|
+
END
|
|
409
|
+
$relaybus_payload_json$`;
|
|
410
|
+
}
|
|
411
|
+
function version040SchemaMarker(options = {}) {
|
|
412
|
+
const names = resolvePostgresNames(options);
|
|
413
|
+
return version040Marker(names, schemaBodyStatements(names));
|
|
414
|
+
}
|
|
415
|
+
function version040Marker(names, statements) {
|
|
416
|
+
const conversion = upgradePayloadToJson(names);
|
|
417
|
+
return postgresSchemaMarker(
|
|
418
|
+
statements.filter((statement) => statement !== conversion).map((statement) => statement.replace(PAYLOAD_COLUMNS, JSONB_PAYLOAD_COLUMNS))
|
|
419
|
+
);
|
|
420
|
+
}
|
|
385
421
|
function createPostgresSchemaSql(options = {}) {
|
|
386
422
|
return `${createPostgresSchemaStatements(options).join(";\n\n")};
|
|
387
423
|
`;
|
|
@@ -1446,6 +1482,67 @@ var PostgresMaintenance = class {
|
|
|
1446
1482
|
});
|
|
1447
1483
|
}
|
|
1448
1484
|
};
|
|
1485
|
+
|
|
1486
|
+
// src/postgres-setup.ts
|
|
1487
|
+
var PostgresSetup = class {
|
|
1488
|
+
#state;
|
|
1489
|
+
/** The conversion of `jsonb` payload columns. */
|
|
1490
|
+
#payloadUpgrade;
|
|
1491
|
+
constructor(state, options) {
|
|
1492
|
+
this.#state = state;
|
|
1493
|
+
this.#payloadUpgrade = upgradePayloadToJson(resolvePostgresNames(options));
|
|
1494
|
+
}
|
|
1495
|
+
/** Implements {@link PostgresEventStore.setup}. */
|
|
1496
|
+
async run() {
|
|
1497
|
+
if (isCurrent(await this.#schemaState())) {
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
await this.#underSetupLock(async (transaction) => {
|
|
1501
|
+
await this.#run(this.#payloadUpgrade, transaction);
|
|
1502
|
+
});
|
|
1503
|
+
await this.#underSetupLock(async (transaction, schema) => {
|
|
1504
|
+
if (schema.marked) {
|
|
1505
|
+
await this.#run(this.#payloadUpgrade, transaction);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
for (const statement of this.#state.schemaStatements) {
|
|
1509
|
+
await this.#run(statement, transaction);
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Runs `work` in a transaction under the setup lock, unless the schema became current. The
|
|
1515
|
+
* payload conversion goes first in a transaction of its own, so it holds no other table lock
|
|
1516
|
+
* while it waits for the event table (a completion locks deliveries before the event rows).
|
|
1517
|
+
*/
|
|
1518
|
+
async #underSetupLock(work) {
|
|
1519
|
+
await this.#state.executor.transaction(async (transaction) => {
|
|
1520
|
+
await this.#state.executor.query(
|
|
1521
|
+
this.#state.statements.setup.setupLock,
|
|
1522
|
+
[this.#state.setupLockKey],
|
|
1523
|
+
transaction
|
|
1524
|
+
);
|
|
1525
|
+
const schema = await this.#schemaState(transaction);
|
|
1526
|
+
if (!isCurrent(schema)) {
|
|
1527
|
+
await work(transaction, schema);
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
async #run(statement, transaction) {
|
|
1532
|
+
await this.#state.executor.query(statement, [], transaction);
|
|
1533
|
+
}
|
|
1534
|
+
async #schemaState(transaction) {
|
|
1535
|
+
const rows = await this.#state.executor.query(
|
|
1536
|
+
this.#state.statements.setup.schemaCurrent,
|
|
1537
|
+
[this.#state.schemaMarker],
|
|
1538
|
+
transaction
|
|
1539
|
+
);
|
|
1540
|
+
return { marked: rows[0]["marked"] === true, payloadJson: rows[0]["payload_json"] === true };
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
function isCurrent(schema) {
|
|
1544
|
+
return schema.marked && schema.payloadJson;
|
|
1545
|
+
}
|
|
1449
1546
|
var WAKE_UPS = ["notify", "poll"];
|
|
1450
1547
|
var DEFAULT_PURGE_BATCH_SIZE = 1e3;
|
|
1451
1548
|
function resolveStoreSettings(options) {
|
|
@@ -1520,8 +1617,8 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1520
1617
|
reserved.sequence - count(*) OVER (PARTITION BY incoming.key)
|
|
1521
1618
|
+ row_number() OVER (PARTITION BY incoming.key ORDER BY incoming.ordinal)
|
|
1522
1619
|
END AS sequence
|
|
1523
|
-
FROM
|
|
1524
|
-
ordinal int, id text, type text, source text, key text, payload
|
|
1620
|
+
FROM json_to_recordset($1::json) AS incoming (
|
|
1621
|
+
ordinal int, id text, type text, source text, key text, payload json, headers json,
|
|
1525
1622
|
correlation_id text, causation_id text, occurred_at timestamptz, handlers text[])
|
|
1526
1623
|
LEFT JOIN reserved ON reserved.key = incoming.key
|
|
1527
1624
|
)`;
|
|
@@ -1532,7 +1629,7 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1532
1629
|
INSERT INTO ${this.#names.events}
|
|
1533
1630
|
(id, type, source, key, sequence, payload, headers, correlation_id, causation_id, occurred_at, created_at, handlers)
|
|
1534
1631
|
SELECT incoming.id, incoming.type, incoming.source, incoming.key, incoming.sequence,
|
|
1535
|
-
COALESCE(incoming.payload, 'null'::
|
|
1632
|
+
COALESCE(incoming.payload, 'null'::json), COALESCE(incoming.headers, '{}'::json),
|
|
1536
1633
|
incoming.correlation_id, incoming.causation_id, incoming.occurred_at, $2::timestamptz,
|
|
1537
1634
|
COALESCE(incoming.handlers, '{}')
|
|
1538
1635
|
FROM incoming
|
|
@@ -1585,11 +1682,11 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1585
1682
|
return `INSERT INTO ${this.#names.events}
|
|
1586
1683
|
(id, type, source, key, sequence, payload, headers, correlation_id, causation_id, occurred_at, created_at, handlers)
|
|
1587
1684
|
SELECT incoming.id, incoming.type, incoming.source, incoming.key, incoming.sequence,
|
|
1588
|
-
COALESCE(incoming.payload, 'null'::
|
|
1685
|
+
COALESCE(incoming.payload, 'null'::json), COALESCE(incoming.headers, '{}'::json),
|
|
1589
1686
|
incoming.correlation_id, incoming.causation_id, incoming.occurred_at, $2::timestamptz,
|
|
1590
1687
|
COALESCE(incoming.handlers, '{}')
|
|
1591
|
-
FROM
|
|
1592
|
-
id text, type text, source text, key text, sequence bigint, payload
|
|
1688
|
+
FROM json_to_recordset($1::json) AS incoming (
|
|
1689
|
+
id text, type text, source text, key text, sequence bigint, payload json, headers json,
|
|
1593
1690
|
correlation_id text, causation_id text, occurred_at timestamptz, handlers text[])
|
|
1594
1691
|
ORDER BY incoming.id
|
|
1595
1692
|
ON CONFLICT (id) DO NOTHING
|
|
@@ -2839,8 +2936,10 @@ var SetupStatements = class {
|
|
|
2839
2936
|
return `relaybus:${this.#names.schema}`;
|
|
2840
2937
|
}
|
|
2841
2938
|
/**
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
2939
|
+
* The state of the schema, taking no table lock. Parameters: `[marker]`. Columns: `marked`, whether
|
|
2940
|
+
* the lane table carries `marker` as its comment (the schema statements that produce it already
|
|
2941
|
+
* ran), and `payload_json`, whether the payload and headers columns of the event table are both
|
|
2942
|
+
* `json` (the conversion of the `jsonb` columns of schemas before 0.4.1 ran).
|
|
2844
2943
|
*/
|
|
2845
2944
|
get schemaCurrent() {
|
|
2846
2945
|
return `SELECT EXISTS (
|
|
@@ -2849,7 +2948,14 @@ var SetupStatements = class {
|
|
|
2849
2948
|
JOIN pg_catalog.pg_description AS d
|
|
2850
2949
|
ON d.objoid = c.oid AND d.classoid = 'pg_catalog.pg_class'::regclass AND d.objsubid = 0
|
|
2851
2950
|
WHERE n.nspname = '${this.#names.schema}' AND c.relname = '${this.#names.lanesTableName}' AND d.description = $1::text
|
|
2852
|
-
) AS
|
|
2951
|
+
) AS marked, (
|
|
2952
|
+
SELECT count(*) FROM pg_catalog.pg_attribute AS a
|
|
2953
|
+
JOIN pg_catalog.pg_class AS c ON c.oid = a.attrelid
|
|
2954
|
+
JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
|
|
2955
|
+
WHERE n.nspname = '${this.#names.schema}' AND c.relname = '${this.#names.eventsTableName}'
|
|
2956
|
+
AND a.attname IN ('payload', 'headers') AND a.atttypid = 'pg_catalog.json'::pg_catalog.regtype
|
|
2957
|
+
AND NOT a.attisdropped
|
|
2958
|
+
) = 2 AS payload_json`;
|
|
2853
2959
|
}
|
|
2854
2960
|
/**
|
|
2855
2961
|
* Takes the setup lock. Returns a boolean column rather than the `void` of the lock function, which
|
|
@@ -2887,6 +2993,7 @@ var PostgresEventStore = class {
|
|
|
2887
2993
|
#claims;
|
|
2888
2994
|
#completion;
|
|
2889
2995
|
#maintenance;
|
|
2996
|
+
#setup;
|
|
2890
2997
|
#owned = new OwnedConnections();
|
|
2891
2998
|
constructor(options) {
|
|
2892
2999
|
const { purgeBatchSize, wakeUp, queryOptions } = resolveStoreSettings(options);
|
|
@@ -2900,7 +3007,7 @@ var PostgresEventStore = class {
|
|
|
2900
3007
|
statements,
|
|
2901
3008
|
schemaStatements,
|
|
2902
3009
|
setupLockKey: statements.setup.setupLockKey,
|
|
2903
|
-
schemaMarker:
|
|
3010
|
+
schemaMarker: version040SchemaMarker(options),
|
|
2904
3011
|
purgeBatchSize,
|
|
2905
3012
|
completionHistory: names.completionHistory,
|
|
2906
3013
|
logger,
|
|
@@ -2924,42 +3031,20 @@ var PostgresEventStore = class {
|
|
|
2924
3031
|
this.#claims = new PostgresClaims(state);
|
|
2925
3032
|
this.#completion = new PostgresCompletion(state);
|
|
2926
3033
|
this.#maintenance = new PostgresMaintenance(state);
|
|
3034
|
+
this.#setup = new PostgresSetup(state, options);
|
|
2927
3035
|
}
|
|
2928
3036
|
/**
|
|
2929
3037
|
* Creates the schema, tables and indexes if they do not exist. Safe to call
|
|
2930
3038
|
* from many processes at once: a transaction-scoped advisory lock serializes
|
|
2931
3039
|
* the DDL, so racing `CREATE TABLE IF NOT EXISTS` statements cannot collide.
|
|
2932
|
-
* When the lane table already carries the marker
|
|
2933
|
-
*
|
|
3040
|
+
* When the lane table already carries the schema marker ({@link version040SchemaMarker})
|
|
3041
|
+
* and the payload columns are `json`, nothing runs and no table is locked, so instances
|
|
2934
3042
|
* that start next to running dispatchers cannot deadlock with their claims.
|
|
2935
3043
|
* Skip it (`setupStore: false`) when a migration tool applies
|
|
2936
3044
|
* `createPostgresSchemaSql()` instead.
|
|
2937
3045
|
*/
|
|
2938
3046
|
async setup() {
|
|
2939
|
-
|
|
2940
|
-
return;
|
|
2941
|
-
}
|
|
2942
|
-
await this.#state.executor.transaction(async (transaction) => {
|
|
2943
|
-
await this.#state.executor.query(
|
|
2944
|
-
this.#state.statements.setup.setupLock,
|
|
2945
|
-
[this.#state.setupLockKey],
|
|
2946
|
-
transaction
|
|
2947
|
-
);
|
|
2948
|
-
if (await this.#schemaIsCurrent(transaction)) {
|
|
2949
|
-
return;
|
|
2950
|
-
}
|
|
2951
|
-
for (const statement of this.#state.schemaStatements) {
|
|
2952
|
-
await this.#state.executor.query(statement, [], transaction);
|
|
2953
|
-
}
|
|
2954
|
-
});
|
|
2955
|
-
}
|
|
2956
|
-
async #schemaIsCurrent(transaction) {
|
|
2957
|
-
const rows = await this.#state.executor.query(
|
|
2958
|
-
this.#state.statements.setup.schemaCurrent,
|
|
2959
|
-
[this.#state.schemaMarker],
|
|
2960
|
-
transaction
|
|
2961
|
-
);
|
|
2962
|
-
return rows[0]["current"] === true;
|
|
3047
|
+
await this.#setup.run();
|
|
2963
3048
|
}
|
|
2964
3049
|
/**
|
|
2965
3050
|
* Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
|