@relaybus/postgres 0.4.0 → 0.5.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 +28 -15
- package/dist/chunk-5WWPAN5Q.js +26 -0
- package/dist/chunk-5WWPAN5Q.js.map +1 -0
- package/dist/chunk-FY65AUUF.js +40 -0
- package/dist/chunk-FY65AUUF.js.map +1 -0
- package/dist/chunk-UDBN4RRV.js +35 -0
- package/dist/chunk-UDBN4RRV.js.map +1 -0
- package/dist/drizzle.cjs +24 -0
- package/dist/drizzle.cjs.map +1 -1
- package/dist/drizzle.d.cts +1 -0
- package/dist/drizzle.d.ts +1 -0
- package/dist/drizzle.js +2 -1
- package/dist/drizzle.js.map +1 -1
- package/dist/{executor-CfJgktC6.d.cts → executor-Bt90w33Q.d.cts} +24 -1
- package/dist/{executor-CfJgktC6.d.ts → executor-Bt90w33Q.d.ts} +24 -1
- package/dist/index.cjs +226 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -7
- package/dist/index.d.ts +25 -7
- package/dist/index.js +194 -79
- package/dist/index.js.map +1 -1
- package/dist/knex.cjs +24 -0
- package/dist/knex.cjs.map +1 -1
- package/dist/knex.d.cts +1 -0
- package/dist/knex.d.ts +1 -0
- package/dist/knex.js +2 -1
- package/dist/knex.js.map +1 -1
- package/dist/kysely.cjs +24 -0
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +1 -0
- package/dist/kysely.d.ts +1 -0
- package/dist/kysely.js +2 -1
- package/dist/kysely.js.map +1 -1
- package/dist/prisma.cjs +24 -0
- package/dist/prisma.cjs.map +1 -1
- package/dist/prisma.d.cts +2 -1
- package/dist/prisma.d.ts +2 -1
- package/dist/prisma.js +2 -1
- package/dist/prisma.js.map +1 -1
- package/dist/typeorm.cjs +46 -0
- package/dist/typeorm.cjs.map +1 -1
- package/dist/typeorm.d.cts +1 -0
- package/dist/typeorm.d.ts +1 -0
- package/dist/typeorm.js +11 -1
- package/dist/typeorm.js.map +1 -1
- package/package.json +6 -5
- package/dist/chunk-NNZJR5AB.js +0 -12
- package/dist/chunk-NNZJR5AB.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,41 @@ function inBudgetedTransaction(state, {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// src/transaction-isolation.ts
|
|
43
|
+
var ISOLATION_KEYWORDS = {
|
|
44
|
+
"read committed": "READ COMMITTED",
|
|
45
|
+
"repeatable read": "REPEATABLE READ",
|
|
46
|
+
serializable: "SERIALIZABLE"
|
|
47
|
+
};
|
|
48
|
+
function isolationKeywords(isolationLevel) {
|
|
49
|
+
const keywords = Object.hasOwn(ISOLATION_KEYWORDS, isolationLevel) ? ISOLATION_KEYWORDS[isolationLevel] : void 0;
|
|
50
|
+
if (keywords === void 0) {
|
|
51
|
+
throw new core.ConfigurationError(`Unknown isolation level ${JSON.stringify(isolationLevel)}`);
|
|
52
|
+
}
|
|
53
|
+
return keywords;
|
|
54
|
+
}
|
|
55
|
+
function inIsolatedTransaction(state, {
|
|
56
|
+
work,
|
|
57
|
+
timeoutMs,
|
|
58
|
+
isolationLevel
|
|
59
|
+
}) {
|
|
60
|
+
const { executor } = state;
|
|
61
|
+
if (executor.transactionWithIsolation !== void 0) {
|
|
62
|
+
return executor.transactionWithIsolation(work, { isolationLevel, timeoutMs });
|
|
63
|
+
}
|
|
64
|
+
const keywords = isolationKeywords(isolationLevel);
|
|
65
|
+
const budget = budgetSettingsSql({
|
|
66
|
+
value: "$1",
|
|
67
|
+
sessionTimeouts: executor.sessionTimeouts === true,
|
|
68
|
+
scope: "transaction"
|
|
69
|
+
});
|
|
70
|
+
return executor.transaction(async (transaction) => {
|
|
71
|
+
await executor.query(`SET TRANSACTION ISOLATION LEVEL ${keywords}`, [], transaction);
|
|
72
|
+
await state.query({ sql: budget, parameters: [budgetValue(timeoutMs)], transaction });
|
|
73
|
+
return work(transaction);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
// src/pg-executor.ts
|
|
43
78
|
var FOREIGN_SESSION_ERRORS = /* @__PURE__ */ new Set(["26000", "42P05"]);
|
|
44
79
|
var STATEMENT_NAME_DIGITS = 24;
|
|
@@ -69,14 +104,28 @@ var PgExecutor = class {
|
|
|
69
104
|
* connection that already keeps this budget only gets `BEGIN`.
|
|
70
105
|
*/
|
|
71
106
|
transactionWithBudget(work, timeoutMs) {
|
|
107
|
+
return this.#transaction(work, this.#budgetStart(timeoutMs, "BEGIN"));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Like {@link PgExecutor.transactionWithBudget} with `BEGIN ISOLATION LEVEL ...`: the level is set
|
|
111
|
+
* with the `BEGIN` of the same simple query, before the budget's first statement.
|
|
112
|
+
*/
|
|
113
|
+
transactionWithIsolation(work, { isolationLevel, timeoutMs }) {
|
|
114
|
+
return this.#transaction(
|
|
115
|
+
work,
|
|
116
|
+
this.#budgetStart(timeoutMs, `BEGIN ISOLATION LEVEL ${isolationKeywords(isolationLevel)}`)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
/** How a transaction starts on a connection when it is held to `timeoutMs`; `begin` is the `BEGIN` statement. */
|
|
120
|
+
#budgetStart(timeoutMs, begin) {
|
|
72
121
|
const value = budgetValue(timeoutMs);
|
|
73
122
|
const settings = (scope) => budgetSettingsSql({ value: `'${value}'`, sessionTimeouts: true, scope });
|
|
74
|
-
return
|
|
123
|
+
return (client) => {
|
|
75
124
|
if (!this.#keepsSessionBudget()) {
|
|
76
|
-
return { begin:
|
|
125
|
+
return { begin: `${begin}; ${settings("transaction")}` };
|
|
77
126
|
}
|
|
78
|
-
return this.#sessionValues.get(client) === value ? { begin
|
|
79
|
-
}
|
|
127
|
+
return this.#sessionValues.get(client) === value ? { begin } : { begin: `${begin}; ${settings("session")}`, sessionValue: value };
|
|
128
|
+
};
|
|
80
129
|
}
|
|
81
130
|
#recordSession(client, sessionValue) {
|
|
82
131
|
if (sessionValue === null) {
|
|
@@ -230,6 +279,7 @@ function resolvePostgresNames(options = {}) {
|
|
|
230
279
|
schema,
|
|
231
280
|
quotedSchema,
|
|
232
281
|
events: table("events"),
|
|
282
|
+
eventsTableName: name("events"),
|
|
233
283
|
deliveries: table("deliveries"),
|
|
234
284
|
keySequences: table("key_sequences"),
|
|
235
285
|
lanes: table("lanes"),
|
|
@@ -256,8 +306,17 @@ function laneExpression(keyExpression, eventIdExpression) {
|
|
|
256
306
|
|
|
257
307
|
// src/schema.ts
|
|
258
308
|
var SCHEMA_MARKER_DIGITS = 32;
|
|
309
|
+
var PAYLOAD_COLUMNS = ` payload json NOT NULL,
|
|
310
|
+
headers json NOT NULL DEFAULT '{}'::json,`;
|
|
311
|
+
var JSONB_PAYLOAD_COLUMNS = ` payload jsonb NOT NULL,
|
|
312
|
+
headers jsonb NOT NULL DEFAULT '{}'::jsonb,`;
|
|
259
313
|
function createPostgresSchemaStatements(options = {}) {
|
|
260
314
|
const names = resolvePostgresNames(options);
|
|
315
|
+
const statements = schemaBodyStatements(names);
|
|
316
|
+
statements.push(`COMMENT ON TABLE ${names.lanes} IS '${version040Marker(names, statements)}'`);
|
|
317
|
+
return statements;
|
|
318
|
+
}
|
|
319
|
+
function schemaBodyStatements(names) {
|
|
261
320
|
const statements = [...eventAndDeliveryStatements(names), ...laneAndCompletionStatements(names)];
|
|
262
321
|
if (names.completionHistory) {
|
|
263
322
|
statements.push(...completionHistoryStatements(names));
|
|
@@ -265,7 +324,6 @@ function createPostgresSchemaStatements(options = {}) {
|
|
|
265
324
|
if (names.schema !== "public") {
|
|
266
325
|
statements.unshift(`CREATE SCHEMA IF NOT EXISTS ${names.quotedSchema}`);
|
|
267
326
|
}
|
|
268
|
-
statements.push(`COMMENT ON TABLE ${names.lanes} IS '${postgresSchemaMarker(statements)}'`);
|
|
269
327
|
return statements;
|
|
270
328
|
}
|
|
271
329
|
function eventAndDeliveryStatements(names) {
|
|
@@ -279,14 +337,14 @@ function eventAndKeySequenceStatements(names) {
|
|
|
279
337
|
source text NOT NULL,
|
|
280
338
|
key text,
|
|
281
339
|
sequence bigint,
|
|
282
|
-
|
|
283
|
-
headers jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
340
|
+
${PAYLOAD_COLUMNS}
|
|
284
341
|
correlation_id text,
|
|
285
342
|
causation_id text,
|
|
286
343
|
occurred_at timestamptz NOT NULL,
|
|
287
344
|
created_at timestamptz NOT NULL,
|
|
288
345
|
handlers text[] NOT NULL DEFAULT '{}'
|
|
289
346
|
)`,
|
|
347
|
+
upgradePayloadToJson(names),
|
|
290
348
|
`CREATE INDEX IF NOT EXISTS ${names.indexes.eventCreated} ON ${names.events} (created_at, id)`,
|
|
291
349
|
`CREATE TABLE IF NOT EXISTS ${names.keySequences} (
|
|
292
350
|
key text PRIMARY KEY,
|
|
@@ -382,6 +440,33 @@ BEGIN
|
|
|
382
440
|
END
|
|
383
441
|
$relaybus_upgrade$`;
|
|
384
442
|
}
|
|
443
|
+
function upgradePayloadToJson(names) {
|
|
444
|
+
return `DO $relaybus_payload_json$
|
|
445
|
+
BEGIN
|
|
446
|
+
IF EXISTS (
|
|
447
|
+
SELECT 1 FROM pg_attribute
|
|
448
|
+
WHERE attrelid = to_regclass('${names.events}') AND attname IN ('payload', 'headers')
|
|
449
|
+
AND atttypid = 'jsonb'::regtype AND NOT attisdropped
|
|
450
|
+
) THEN
|
|
451
|
+
ALTER TABLE ${names.events}
|
|
452
|
+
ALTER COLUMN headers DROP DEFAULT,
|
|
453
|
+
ALTER COLUMN payload TYPE json USING payload::json,
|
|
454
|
+
ALTER COLUMN headers TYPE json USING headers::json,
|
|
455
|
+
ALTER COLUMN headers SET DEFAULT '{}'::json;
|
|
456
|
+
END IF;
|
|
457
|
+
END
|
|
458
|
+
$relaybus_payload_json$`;
|
|
459
|
+
}
|
|
460
|
+
function version040SchemaMarker(options = {}) {
|
|
461
|
+
const names = resolvePostgresNames(options);
|
|
462
|
+
return version040Marker(names, schemaBodyStatements(names));
|
|
463
|
+
}
|
|
464
|
+
function version040Marker(names, statements) {
|
|
465
|
+
const conversion = upgradePayloadToJson(names);
|
|
466
|
+
return postgresSchemaMarker(
|
|
467
|
+
statements.filter((statement) => statement !== conversion).map((statement) => statement.replace(PAYLOAD_COLUMNS, JSONB_PAYLOAD_COLUMNS))
|
|
468
|
+
);
|
|
469
|
+
}
|
|
385
470
|
function createPostgresSchemaSql(options = {}) {
|
|
386
471
|
return `${createPostgresSchemaStatements(options).join(";\n\n")};
|
|
387
472
|
`;
|
|
@@ -502,10 +587,38 @@ function withRootPool(executor, root) {
|
|
|
502
587
|
...executor.transactionWithBudget === void 0 ? {} : {
|
|
503
588
|
transactionWithBudget: (work, timeoutMs) => executor.transactionWithBudget(work, timeoutMs)
|
|
504
589
|
},
|
|
590
|
+
...executor.transactionWithIsolation === void 0 ? {} : {
|
|
591
|
+
transactionWithIsolation: (work, options) => executor.transactionWithIsolation(work, options)
|
|
592
|
+
},
|
|
593
|
+
...executor.isSerializationFailure === void 0 ? {} : { isSerializationFailure: (error) => executor.isSerializationFailure(error) },
|
|
505
594
|
// eslint-disable-next-line relaybus/max-two-params -- PostgresExecutor port signature
|
|
506
595
|
query: (sql, parameters, transaction, options) => transaction === void 0 ? root.query(sql, parameters, void 0, options) : executor.query(sql, parameters, transaction, options)
|
|
507
596
|
};
|
|
508
597
|
}
|
|
598
|
+
|
|
599
|
+
// src/serialization-failure.ts
|
|
600
|
+
var SERIALIZATION_FAILURE = "40001";
|
|
601
|
+
var PRISMA_WRITE_CONFLICT = "P2034";
|
|
602
|
+
var CONFLICT_CODES = /* @__PURE__ */ new Set([
|
|
603
|
+
SERIALIZATION_FAILURE,
|
|
604
|
+
PRISMA_WRITE_CONFLICT
|
|
605
|
+
]);
|
|
606
|
+
var MAXIMUM_DEPTH = 6;
|
|
607
|
+
var WRAPPED_ERROR_KEYS = ["cause", "driverError", "originalError"];
|
|
608
|
+
function isPostgresSerializationFailure(error, depth = 0) {
|
|
609
|
+
if (typeof error !== "object" || error === null || depth > MAXIMUM_DEPTH) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
const shape = error;
|
|
613
|
+
if (CONFLICT_CODES.has(shape.code) || shape.originalCode === SERIALIZATION_FAILURE) {
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
const meta = shape.meta;
|
|
617
|
+
if (isPostgresSerializationFailure(meta, depth + 1) || isPostgresSerializationFailure(meta?.driverAdapterError, depth + 1)) {
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
return WRAPPED_ERROR_KEYS.some((key) => isPostgresSerializationFailure(shape[key], depth + 1));
|
|
621
|
+
}
|
|
509
622
|
function text(row, column) {
|
|
510
623
|
return row[column];
|
|
511
624
|
}
|
|
@@ -1446,6 +1559,67 @@ var PostgresMaintenance = class {
|
|
|
1446
1559
|
});
|
|
1447
1560
|
}
|
|
1448
1561
|
};
|
|
1562
|
+
|
|
1563
|
+
// src/postgres-setup.ts
|
|
1564
|
+
var PostgresSetup = class {
|
|
1565
|
+
#state;
|
|
1566
|
+
/** The conversion of `jsonb` payload columns. */
|
|
1567
|
+
#payloadUpgrade;
|
|
1568
|
+
constructor(state, options) {
|
|
1569
|
+
this.#state = state;
|
|
1570
|
+
this.#payloadUpgrade = upgradePayloadToJson(resolvePostgresNames(options));
|
|
1571
|
+
}
|
|
1572
|
+
/** Implements {@link PostgresEventStore.setup}. */
|
|
1573
|
+
async run() {
|
|
1574
|
+
if (isCurrent(await this.#schemaState())) {
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
await this.#underSetupLock(async (transaction) => {
|
|
1578
|
+
await this.#run(this.#payloadUpgrade, transaction);
|
|
1579
|
+
});
|
|
1580
|
+
await this.#underSetupLock(async (transaction, schema) => {
|
|
1581
|
+
if (schema.marked) {
|
|
1582
|
+
await this.#run(this.#payloadUpgrade, transaction);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
for (const statement of this.#state.schemaStatements) {
|
|
1586
|
+
await this.#run(statement, transaction);
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Runs `work` in a transaction under the setup lock, unless the schema became current. The
|
|
1592
|
+
* payload conversion goes first in a transaction of its own, so it holds no other table lock
|
|
1593
|
+
* while it waits for the event table (a completion locks deliveries before the event rows).
|
|
1594
|
+
*/
|
|
1595
|
+
async #underSetupLock(work) {
|
|
1596
|
+
await this.#state.executor.transaction(async (transaction) => {
|
|
1597
|
+
await this.#state.executor.query(
|
|
1598
|
+
this.#state.statements.setup.setupLock,
|
|
1599
|
+
[this.#state.setupLockKey],
|
|
1600
|
+
transaction
|
|
1601
|
+
);
|
|
1602
|
+
const schema = await this.#schemaState(transaction);
|
|
1603
|
+
if (!isCurrent(schema)) {
|
|
1604
|
+
await work(transaction, schema);
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
async #run(statement, transaction) {
|
|
1609
|
+
await this.#state.executor.query(statement, [], transaction);
|
|
1610
|
+
}
|
|
1611
|
+
async #schemaState(transaction) {
|
|
1612
|
+
const rows = await this.#state.executor.query(
|
|
1613
|
+
this.#state.statements.setup.schemaCurrent,
|
|
1614
|
+
[this.#state.schemaMarker],
|
|
1615
|
+
transaction
|
|
1616
|
+
);
|
|
1617
|
+
return { marked: rows[0]["marked"] === true, payloadJson: rows[0]["payload_json"] === true };
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
function isCurrent(schema) {
|
|
1621
|
+
return schema.marked && schema.payloadJson;
|
|
1622
|
+
}
|
|
1449
1623
|
var WAKE_UPS = ["notify", "poll"];
|
|
1450
1624
|
var DEFAULT_PURGE_BATCH_SIZE = 1e3;
|
|
1451
1625
|
function resolveStoreSettings(options) {
|
|
@@ -1520,8 +1694,8 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1520
1694
|
reserved.sequence - count(*) OVER (PARTITION BY incoming.key)
|
|
1521
1695
|
+ row_number() OVER (PARTITION BY incoming.key ORDER BY incoming.ordinal)
|
|
1522
1696
|
END AS sequence
|
|
1523
|
-
FROM
|
|
1524
|
-
ordinal int, id text, type text, source text, key text, payload
|
|
1697
|
+
FROM json_to_recordset($1::json) AS incoming (
|
|
1698
|
+
ordinal int, id text, type text, source text, key text, payload json, headers json,
|
|
1525
1699
|
correlation_id text, causation_id text, occurred_at timestamptz, handlers text[])
|
|
1526
1700
|
LEFT JOIN reserved ON reserved.key = incoming.key
|
|
1527
1701
|
)`;
|
|
@@ -1532,7 +1706,7 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1532
1706
|
INSERT INTO ${this.#names.events}
|
|
1533
1707
|
(id, type, source, key, sequence, payload, headers, correlation_id, causation_id, occurred_at, created_at, handlers)
|
|
1534
1708
|
SELECT incoming.id, incoming.type, incoming.source, incoming.key, incoming.sequence,
|
|
1535
|
-
COALESCE(incoming.payload, 'null'::
|
|
1709
|
+
COALESCE(incoming.payload, 'null'::json), COALESCE(incoming.headers, '{}'::json),
|
|
1536
1710
|
incoming.correlation_id, incoming.causation_id, incoming.occurred_at, $2::timestamptz,
|
|
1537
1711
|
COALESCE(incoming.handlers, '{}')
|
|
1538
1712
|
FROM incoming
|
|
@@ -1585,11 +1759,11 @@ WHERE (SELECT count(*) FROM woken) >= 0`;
|
|
|
1585
1759
|
return `INSERT INTO ${this.#names.events}
|
|
1586
1760
|
(id, type, source, key, sequence, payload, headers, correlation_id, causation_id, occurred_at, created_at, handlers)
|
|
1587
1761
|
SELECT incoming.id, incoming.type, incoming.source, incoming.key, incoming.sequence,
|
|
1588
|
-
COALESCE(incoming.payload, 'null'::
|
|
1762
|
+
COALESCE(incoming.payload, 'null'::json), COALESCE(incoming.headers, '{}'::json),
|
|
1589
1763
|
incoming.correlation_id, incoming.causation_id, incoming.occurred_at, $2::timestamptz,
|
|
1590
1764
|
COALESCE(incoming.handlers, '{}')
|
|
1591
|
-
FROM
|
|
1592
|
-
id text, type text, source text, key text, sequence bigint, payload
|
|
1765
|
+
FROM json_to_recordset($1::json) AS incoming (
|
|
1766
|
+
id text, type text, source text, key text, sequence bigint, payload json, headers json,
|
|
1593
1767
|
correlation_id text, causation_id text, occurred_at timestamptz, handlers text[])
|
|
1594
1768
|
ORDER BY incoming.id
|
|
1595
1769
|
ON CONFLICT (id) DO NOTHING
|
|
@@ -2839,8 +3013,10 @@ var SetupStatements = class {
|
|
|
2839
3013
|
return `relaybus:${this.#names.schema}`;
|
|
2840
3014
|
}
|
|
2841
3015
|
/**
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
3016
|
+
* The state of the schema, taking no table lock. Parameters: `[marker]`. Columns: `marked`, whether
|
|
3017
|
+
* the lane table carries `marker` as its comment (the schema statements that produce it already
|
|
3018
|
+
* ran), and `payload_json`, whether the payload and headers columns of the event table are both
|
|
3019
|
+
* `json` (the conversion of the `jsonb` columns of schemas before 0.4.1 ran).
|
|
2844
3020
|
*/
|
|
2845
3021
|
get schemaCurrent() {
|
|
2846
3022
|
return `SELECT EXISTS (
|
|
@@ -2849,7 +3025,14 @@ var SetupStatements = class {
|
|
|
2849
3025
|
JOIN pg_catalog.pg_description AS d
|
|
2850
3026
|
ON d.objoid = c.oid AND d.classoid = 'pg_catalog.pg_class'::regclass AND d.objsubid = 0
|
|
2851
3027
|
WHERE n.nspname = '${this.#names.schema}' AND c.relname = '${this.#names.lanesTableName}' AND d.description = $1::text
|
|
2852
|
-
) AS
|
|
3028
|
+
) AS marked, (
|
|
3029
|
+
SELECT count(*) FROM pg_catalog.pg_attribute AS a
|
|
3030
|
+
JOIN pg_catalog.pg_class AS c ON c.oid = a.attrelid
|
|
3031
|
+
JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
|
|
3032
|
+
WHERE n.nspname = '${this.#names.schema}' AND c.relname = '${this.#names.eventsTableName}'
|
|
3033
|
+
AND a.attname IN ('payload', 'headers') AND a.atttypid = 'pg_catalog.json'::pg_catalog.regtype
|
|
3034
|
+
AND NOT a.attisdropped
|
|
3035
|
+
) = 2 AS payload_json`;
|
|
2853
3036
|
}
|
|
2854
3037
|
/**
|
|
2855
3038
|
* Takes the setup lock. Returns a boolean column rather than the `void` of the lock function, which
|
|
@@ -2887,6 +3070,7 @@ var PostgresEventStore = class {
|
|
|
2887
3070
|
#claims;
|
|
2888
3071
|
#completion;
|
|
2889
3072
|
#maintenance;
|
|
3073
|
+
#setup;
|
|
2890
3074
|
#owned = new OwnedConnections();
|
|
2891
3075
|
constructor(options) {
|
|
2892
3076
|
const { purgeBatchSize, wakeUp, queryOptions } = resolveStoreSettings(options);
|
|
@@ -2900,7 +3084,7 @@ var PostgresEventStore = class {
|
|
|
2900
3084
|
statements,
|
|
2901
3085
|
schemaStatements,
|
|
2902
3086
|
setupLockKey: statements.setup.setupLockKey,
|
|
2903
|
-
schemaMarker:
|
|
3087
|
+
schemaMarker: version040SchemaMarker(options),
|
|
2904
3088
|
purgeBatchSize,
|
|
2905
3089
|
completionHistory: names.completionHistory,
|
|
2906
3090
|
logger,
|
|
@@ -2924,42 +3108,30 @@ var PostgresEventStore = class {
|
|
|
2924
3108
|
this.#claims = new PostgresClaims(state);
|
|
2925
3109
|
this.#completion = new PostgresCompletion(state);
|
|
2926
3110
|
this.#maintenance = new PostgresMaintenance(state);
|
|
3111
|
+
this.#setup = new PostgresSetup(state, options);
|
|
2927
3112
|
}
|
|
2928
3113
|
/**
|
|
2929
3114
|
* Creates the schema, tables and indexes if they do not exist. Safe to call
|
|
2930
3115
|
* from many processes at once: a transaction-scoped advisory lock serializes
|
|
2931
3116
|
* the DDL, so racing `CREATE TABLE IF NOT EXISTS` statements cannot collide.
|
|
2932
|
-
* When the lane table already carries the marker
|
|
2933
|
-
*
|
|
3117
|
+
* When the lane table already carries the schema marker ({@link version040SchemaMarker})
|
|
3118
|
+
* and the payload columns are `json`, nothing runs and no table is locked, so instances
|
|
2934
3119
|
* that start next to running dispatchers cannot deadlock with their claims.
|
|
2935
3120
|
* Skip it (`setupStore: false`) when a migration tool applies
|
|
2936
3121
|
* `createPostgresSchemaSql()` instead.
|
|
2937
3122
|
*/
|
|
2938
3123
|
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
|
-
});
|
|
3124
|
+
await this.#setup.run();
|
|
2955
3125
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
3126
|
+
/** Every level PostgreSQL has (it runs `read uncommitted` as `read committed`, which the bus does not offer). */
|
|
3127
|
+
isolationLevels = core.TRANSACTION_ISOLATION_LEVELS;
|
|
3128
|
+
/**
|
|
3129
|
+
* True for a serialization failure (SQLSTATE `40001`) raised through any executor, including
|
|
3130
|
+
* Prisma's `P2034` and the wrappers of TypeORM and Drizzle; a custom executor can add its own
|
|
3131
|
+
* test with {@link PostgresExecutor.isSerializationFailure}.
|
|
3132
|
+
*/
|
|
3133
|
+
isSerializationFailure(error) {
|
|
3134
|
+
return isPostgresSerializationFailure(error) || this.#state.executor.isSerializationFailure?.(error) === true;
|
|
2963
3135
|
}
|
|
2964
3136
|
/**
|
|
2965
3137
|
* Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
|
|
@@ -2968,6 +3140,9 @@ var PostgresEventStore = class {
|
|
|
2968
3140
|
* `transaction_timeout`, all scoped to the transaction. An executor with `transactionWithBudget`
|
|
2969
3141
|
* (`PgExecutor`) sets them with `BEGIN`, so the budget costs no round trip; on a pool the store
|
|
2970
3142
|
* created they are kept per connection and sent only when the connection does not have them yet.
|
|
3143
|
+
* With `options.isolationLevel` the transaction runs at that level: `PgExecutor` sends it with
|
|
3144
|
+
* `BEGIN ISOLATION LEVEL ...` (no extra round trip), any other executor as `SET TRANSACTION
|
|
3145
|
+
* ISOLATION LEVEL ...` before the budget statement.
|
|
2971
3146
|
*/
|
|
2972
3147
|
async transaction(work, options) {
|
|
2973
3148
|
const own = { wake: false };
|
|
@@ -2979,12 +3154,20 @@ var PostgresEventStore = class {
|
|
|
2979
3154
|
this.#state.ownTransactions.delete(transaction);
|
|
2980
3155
|
}
|
|
2981
3156
|
};
|
|
2982
|
-
const result = await
|
|
3157
|
+
const result = await this.#open(tracked, options);
|
|
2983
3158
|
if (own.wake) {
|
|
2984
3159
|
this.#appender.wakeAfterCommit();
|
|
2985
3160
|
}
|
|
2986
3161
|
return result;
|
|
2987
3162
|
}
|
|
3163
|
+
/** Opens the transaction `work` runs in: plain, held to the budget, or at an isolation level too. */
|
|
3164
|
+
#open(work, options) {
|
|
3165
|
+
if (options === void 0) {
|
|
3166
|
+
return this.#state.executor.transaction(work);
|
|
3167
|
+
}
|
|
3168
|
+
const { timeoutMs, isolationLevel } = options;
|
|
3169
|
+
return isolationLevel === void 0 ? inBudgetedTransaction(this.#state, { work, timeoutMs }) : inIsolatedTransaction(this.#state, { work, timeoutMs, isolationLevel });
|
|
3170
|
+
}
|
|
2988
3171
|
/**
|
|
2989
3172
|
* Writes the entries in `transaction`, normally with one statement
|
|
2990
3173
|
* (`AppendStatements.insertFreshEvents`): keyed events reserve their sequence numbers (locking
|