@relaybus/postgres 0.4.1 → 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.
Files changed (48) hide show
  1. package/README.md +10 -0
  2. package/dist/chunk-5WWPAN5Q.js +26 -0
  3. package/dist/chunk-5WWPAN5Q.js.map +1 -0
  4. package/dist/chunk-FY65AUUF.js +40 -0
  5. package/dist/chunk-FY65AUUF.js.map +1 -0
  6. package/dist/chunk-UDBN4RRV.js +35 -0
  7. package/dist/chunk-UDBN4RRV.js.map +1 -0
  8. package/dist/drizzle.cjs +24 -0
  9. package/dist/drizzle.cjs.map +1 -1
  10. package/dist/drizzle.d.cts +1 -0
  11. package/dist/drizzle.d.ts +1 -0
  12. package/dist/drizzle.js +2 -1
  13. package/dist/drizzle.js.map +1 -1
  14. package/dist/{executor-CfJgktC6.d.cts → executor-Bt90w33Q.d.cts} +24 -1
  15. package/dist/{executor-CfJgktC6.d.ts → executor-Bt90w33Q.d.ts} +24 -1
  16. package/dist/index.cjs +103 -5
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +19 -3
  19. package/dist/index.d.ts +19 -3
  20. package/dist/index.js +71 -41
  21. package/dist/index.js.map +1 -1
  22. package/dist/knex.cjs +24 -0
  23. package/dist/knex.cjs.map +1 -1
  24. package/dist/knex.d.cts +1 -0
  25. package/dist/knex.d.ts +1 -0
  26. package/dist/knex.js +2 -1
  27. package/dist/knex.js.map +1 -1
  28. package/dist/kysely.cjs +24 -0
  29. package/dist/kysely.cjs.map +1 -1
  30. package/dist/kysely.d.cts +1 -0
  31. package/dist/kysely.d.ts +1 -0
  32. package/dist/kysely.js +2 -1
  33. package/dist/kysely.js.map +1 -1
  34. package/dist/prisma.cjs +24 -0
  35. package/dist/prisma.cjs.map +1 -1
  36. package/dist/prisma.d.cts +1 -0
  37. package/dist/prisma.d.ts +1 -0
  38. package/dist/prisma.js +2 -1
  39. package/dist/prisma.js.map +1 -1
  40. package/dist/typeorm.cjs +46 -0
  41. package/dist/typeorm.cjs.map +1 -1
  42. package/dist/typeorm.d.cts +1 -0
  43. package/dist/typeorm.d.ts +1 -0
  44. package/dist/typeorm.js +11 -1
  45. package/dist/typeorm.js.map +1 -1
  46. package/package.json +4 -4
  47. package/dist/chunk-NNZJR5AB.js +0 -12
  48. package/dist/chunk-NNZJR5AB.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as PostgresExecutor, a as PostgresParameter, b as PostgresQueryOptions, c as PostgresRow } from './executor-CfJgktC6.cjs';
2
- import { RelayBusLogger, EventStore, EventStoreTransactionOptions, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics, CloseOptions, RelayBusError } from '@relaybus/core';
1
+ import { P as PostgresExecutor, a as PostgresIsolatedTransactionOptions, b as PostgresParameter, c as PostgresQueryOptions, d as PostgresRow } from './executor-Bt90w33Q.cjs';
2
+ import { RelayBusLogger, EventStore, TransactionIsolationLevel, EventStoreTransactionOptions, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics, CloseOptions, RelayBusError } from '@relaybus/core';
3
3
  import { PoolClient, Pool, PoolConfig } from 'pg';
4
4
 
5
5
  interface PgExecutorOptions {
@@ -43,6 +43,11 @@ declare class PgExecutor implements PostgresExecutor<PoolClient> {
43
43
  * connection that already keeps this budget only gets `BEGIN`.
44
44
  */
45
45
  transactionWithBudget<T>(work: (transaction: PoolClient) => Promise<T>, timeoutMs: number): Promise<T>;
46
+ /**
47
+ * Like {@link PgExecutor.transactionWithBudget} with `BEGIN ISOLATION LEVEL ...`: the level is set
48
+ * with the `BEGIN` of the same simple query, before the budget's first statement.
49
+ */
50
+ transactionWithIsolation<T>(work: (transaction: PoolClient) => Promise<T>, { isolationLevel, timeoutMs }: PostgresIsolatedTransactionOptions): Promise<T>;
46
51
  query(sql: string, parameters: readonly PostgresParameter[], transaction?: PoolClient, options?: PostgresQueryOptions): Promise<readonly PostgresRow[]>;
47
52
  }
48
53
 
@@ -259,6 +264,14 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
259
264
  * `createPostgresSchemaSql()` instead.
260
265
  */
261
266
  setup(): Promise<void>;
267
+ /** Every level PostgreSQL has (it runs `read uncommitted` as `read committed`, which the bus does not offer). */
268
+ readonly isolationLevels: readonly TransactionIsolationLevel[];
269
+ /**
270
+ * True for a serialization failure (SQLSTATE `40001`) raised through any executor, including
271
+ * Prisma's `P2034` and the wrappers of TypeORM and Drizzle; a custom executor can add its own
272
+ * test with {@link PostgresExecutor.isSerializationFailure}.
273
+ */
274
+ isSerializationFailure(error: unknown): boolean;
262
275
  /**
263
276
  * Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
264
277
  * the server holds the transaction to the budget: `statement_timeout`, and for executors with
@@ -266,6 +279,9 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
266
279
  * `transaction_timeout`, all scoped to the transaction. An executor with `transactionWithBudget`
267
280
  * (`PgExecutor`) sets them with `BEGIN`, so the budget costs no round trip; on a pool the store
268
281
  * created they are kept per connection and sent only when the connection does not have them yet.
282
+ * With `options.isolationLevel` the transaction runs at that level: `PgExecutor` sends it with
283
+ * `BEGIN ISOLATION LEVEL ...` (no extra round trip), any other executor as `SET TRANSACTION
284
+ * ISOLATION LEVEL ...` before the budget statement.
269
285
  */
270
286
  transaction<T>(work: (transaction: TTransaction) => Promise<T>, options?: EventStoreTransactionOptions): Promise<T>;
271
287
  /**
@@ -343,5 +359,5 @@ declare class ReconcileFailedError extends RelayBusError {
343
359
  readonly code: string;
344
360
  }
345
361
 
346
- export { PgExecutor, PostgresEventStore, PostgresExecutor, PostgresParameter, PostgresQueryOptions, PostgresRow, ReconcileFailedError, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
362
+ export { PgExecutor, PostgresEventStore, PostgresExecutor, PostgresIsolatedTransactionOptions, PostgresParameter, PostgresQueryOptions, PostgresRow, ReconcileFailedError, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
347
363
  export type { PgExecutorOptions, PostgresConnectionOptions, PostgresEventStoreOptions, PostgresEventStoreSettings, PostgresExecutorOptions, PostgresNames, PostgresPoolOptions, PostgresSchemaOptions, PostgresWakeUp };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as PostgresExecutor, a as PostgresParameter, b as PostgresQueryOptions, c as PostgresRow } from './executor-CfJgktC6.js';
2
- import { RelayBusLogger, EventStore, EventStoreTransactionOptions, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics, CloseOptions, RelayBusError } from '@relaybus/core';
1
+ import { P as PostgresExecutor, a as PostgresIsolatedTransactionOptions, b as PostgresParameter, c as PostgresQueryOptions, d as PostgresRow } from './executor-Bt90w33Q.js';
2
+ import { RelayBusLogger, EventStore, TransactionIsolationLevel, EventStoreTransactionOptions, AppendEntry, AppendResult, ClaimRequest, ClaimedDelivery, LeaseReference, ErrorInfo, DeliveryQuery, DeliveryRecord, PurgeRequest, PurgeResult, EventStoreStatistics, CloseOptions, RelayBusError } from '@relaybus/core';
3
3
  import { PoolClient, Pool, PoolConfig } from 'pg';
4
4
 
5
5
  interface PgExecutorOptions {
@@ -43,6 +43,11 @@ declare class PgExecutor implements PostgresExecutor<PoolClient> {
43
43
  * connection that already keeps this budget only gets `BEGIN`.
44
44
  */
45
45
  transactionWithBudget<T>(work: (transaction: PoolClient) => Promise<T>, timeoutMs: number): Promise<T>;
46
+ /**
47
+ * Like {@link PgExecutor.transactionWithBudget} with `BEGIN ISOLATION LEVEL ...`: the level is set
48
+ * with the `BEGIN` of the same simple query, before the budget's first statement.
49
+ */
50
+ transactionWithIsolation<T>(work: (transaction: PoolClient) => Promise<T>, { isolationLevel, timeoutMs }: PostgresIsolatedTransactionOptions): Promise<T>;
46
51
  query(sql: string, parameters: readonly PostgresParameter[], transaction?: PoolClient, options?: PostgresQueryOptions): Promise<readonly PostgresRow[]>;
47
52
  }
48
53
 
@@ -259,6 +264,14 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
259
264
  * `createPostgresSchemaSql()` instead.
260
265
  */
261
266
  setup(): Promise<void>;
267
+ /** Every level PostgreSQL has (it runs `read uncommitted` as `read committed`, which the bus does not offer). */
268
+ readonly isolationLevels: readonly TransactionIsolationLevel[];
269
+ /**
270
+ * True for a serialization failure (SQLSTATE `40001`) raised through any executor, including
271
+ * Prisma's `P2034` and the wrappers of TypeORM and Drizzle; a custom executor can add its own
272
+ * test with {@link PostgresExecutor.isSerializationFailure}.
273
+ */
274
+ isSerializationFailure(error: unknown): boolean;
262
275
  /**
263
276
  * Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
264
277
  * the server holds the transaction to the budget: `statement_timeout`, and for executors with
@@ -266,6 +279,9 @@ declare class PostgresEventStore<TTransaction = PoolClient> implements EventStor
266
279
  * `transaction_timeout`, all scoped to the transaction. An executor with `transactionWithBudget`
267
280
  * (`PgExecutor`) sets them with `BEGIN`, so the budget costs no round trip; on a pool the store
268
281
  * created they are kept per connection and sent only when the connection does not have them yet.
282
+ * With `options.isolationLevel` the transaction runs at that level: `PgExecutor` sends it with
283
+ * `BEGIN ISOLATION LEVEL ...` (no extra round trip), any other executor as `SET TRANSACTION
284
+ * ISOLATION LEVEL ...` before the budget statement.
269
285
  */
270
286
  transaction<T>(work: (transaction: TTransaction) => Promise<T>, options?: EventStoreTransactionOptions): Promise<T>;
271
287
  /**
@@ -343,5 +359,5 @@ declare class ReconcileFailedError extends RelayBusError {
343
359
  readonly code: string;
344
360
  }
345
361
 
346
- export { PgExecutor, PostgresEventStore, PostgresExecutor, PostgresParameter, PostgresQueryOptions, PostgresRow, ReconcileFailedError, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
362
+ export { PgExecutor, PostgresEventStore, PostgresExecutor, PostgresIsolatedTransactionOptions, PostgresParameter, PostgresQueryOptions, PostgresRow, ReconcileFailedError, createPostgresSchemaSql, createPostgresSchemaStatements, resolvePostgresNames };
347
363
  export type { PgExecutorOptions, PostgresConnectionOptions, PostgresEventStoreOptions, PostgresEventStoreSettings, PostgresExecutorOptions, PostgresNames, PostgresPoolOptions, PostgresSchemaOptions, PostgresWakeUp };
package/dist/index.js CHANGED
@@ -1,43 +1,10 @@
1
+ import { isolationKeywords, inIsolatedTransaction } from './chunk-FY65AUUF.js';
2
+ import { SESSION_DEFAULTS_SQL, budgetValue, inBudgetedTransaction, budgetSettingsSql } from './chunk-UDBN4RRV.js';
1
3
  import { createHash } from 'crypto';
2
- import { ConfigurationError, RelayBusError, systemClock } from '@relaybus/core';
4
+ import { ConfigurationError, RelayBusError, TRANSACTION_ISOLATION_LEVELS, systemClock } from '@relaybus/core';
3
5
  import { LaneLeaseTokenFormat, HandedOutLedger, PositiveInteger, schedule, OwnerSerializedClaims, LeaseSettlement, CompositeKey, DeliveryLeaseIdentity } from '@relaybus/core/internal';
4
6
  import { Client, Pool } from 'pg';
5
7
 
6
- // src/pg-executor.ts
7
-
8
- // src/transaction-timeouts.ts
9
- function budgetSettingsSql({ value, sessionTimeouts, scope }) {
10
- const local = scope === "transaction";
11
- const statementTimeout = `SELECT set_config('statement_timeout', ${value}, ${local})`;
12
- if (!sessionTimeouts) {
13
- return statementTimeout;
14
- }
15
- return `${statementTimeout}, set_config('idle_in_transaction_session_timeout', ${value}, ${local}), CASE WHEN current_setting('server_version_num')::int >= 170000 THEN set_config('transaction_timeout', ${value}, ${local}) END`;
16
- }
17
- var SESSION_DEFAULTS_SQL = "SELECT set_config(name, reset_val, false) FROM pg_settings WHERE name IN ('statement_timeout', 'idle_in_transaction_session_timeout', 'transaction_timeout')";
18
- function budgetValue(timeoutMs) {
19
- return `${Math.ceil(timeoutMs)}ms`;
20
- }
21
- function inBudgetedTransaction(state, {
22
- work,
23
- timeoutMs
24
- }) {
25
- const { executor } = state;
26
- if (executor.transactionWithBudget !== void 0) {
27
- return executor.transactionWithBudget(work, timeoutMs);
28
- }
29
- const sql = budgetSettingsSql({
30
- value: "$1",
31
- sessionTimeouts: executor.sessionTimeouts === true,
32
- scope: "transaction"
33
- });
34
- return executor.transaction(async (transaction) => {
35
- await state.query({ sql, parameters: [budgetValue(timeoutMs)], transaction });
36
- return work(transaction);
37
- });
38
- }
39
-
40
- // src/pg-executor.ts
41
8
  var FOREIGN_SESSION_ERRORS = /* @__PURE__ */ new Set(["26000", "42P05"]);
42
9
  var STATEMENT_NAME_DIGITS = 24;
43
10
  var PgExecutor = class {
@@ -67,14 +34,28 @@ var PgExecutor = class {
67
34
  * connection that already keeps this budget only gets `BEGIN`.
68
35
  */
69
36
  transactionWithBudget(work, timeoutMs) {
37
+ return this.#transaction(work, this.#budgetStart(timeoutMs, "BEGIN"));
38
+ }
39
+ /**
40
+ * Like {@link PgExecutor.transactionWithBudget} with `BEGIN ISOLATION LEVEL ...`: the level is set
41
+ * with the `BEGIN` of the same simple query, before the budget's first statement.
42
+ */
43
+ transactionWithIsolation(work, { isolationLevel, timeoutMs }) {
44
+ return this.#transaction(
45
+ work,
46
+ this.#budgetStart(timeoutMs, `BEGIN ISOLATION LEVEL ${isolationKeywords(isolationLevel)}`)
47
+ );
48
+ }
49
+ /** How a transaction starts on a connection when it is held to `timeoutMs`; `begin` is the `BEGIN` statement. */
50
+ #budgetStart(timeoutMs, begin) {
70
51
  const value = budgetValue(timeoutMs);
71
52
  const settings = (scope) => budgetSettingsSql({ value: `'${value}'`, sessionTimeouts: true, scope });
72
- return this.#transaction(work, (client) => {
53
+ return (client) => {
73
54
  if (!this.#keepsSessionBudget()) {
74
- return { begin: `BEGIN; ${settings("transaction")}` };
55
+ return { begin: `${begin}; ${settings("transaction")}` };
75
56
  }
76
- return this.#sessionValues.get(client) === value ? { begin: "BEGIN" } : { begin: `BEGIN; ${settings("session")}`, sessionValue: value };
77
- });
57
+ return this.#sessionValues.get(client) === value ? { begin } : { begin: `${begin}; ${settings("session")}`, sessionValue: value };
58
+ };
78
59
  }
79
60
  #recordSession(client, sessionValue) {
80
61
  if (sessionValue === null) {
@@ -536,10 +517,38 @@ function withRootPool(executor, root) {
536
517
  ...executor.transactionWithBudget === void 0 ? {} : {
537
518
  transactionWithBudget: (work, timeoutMs) => executor.transactionWithBudget(work, timeoutMs)
538
519
  },
520
+ ...executor.transactionWithIsolation === void 0 ? {} : {
521
+ transactionWithIsolation: (work, options) => executor.transactionWithIsolation(work, options)
522
+ },
523
+ ...executor.isSerializationFailure === void 0 ? {} : { isSerializationFailure: (error) => executor.isSerializationFailure(error) },
539
524
  // eslint-disable-next-line relaybus/max-two-params -- PostgresExecutor port signature
540
525
  query: (sql, parameters, transaction, options) => transaction === void 0 ? root.query(sql, parameters, void 0, options) : executor.query(sql, parameters, transaction, options)
541
526
  };
542
527
  }
528
+
529
+ // src/serialization-failure.ts
530
+ var SERIALIZATION_FAILURE = "40001";
531
+ var PRISMA_WRITE_CONFLICT = "P2034";
532
+ var CONFLICT_CODES = /* @__PURE__ */ new Set([
533
+ SERIALIZATION_FAILURE,
534
+ PRISMA_WRITE_CONFLICT
535
+ ]);
536
+ var MAXIMUM_DEPTH = 6;
537
+ var WRAPPED_ERROR_KEYS = ["cause", "driverError", "originalError"];
538
+ function isPostgresSerializationFailure(error, depth = 0) {
539
+ if (typeof error !== "object" || error === null || depth > MAXIMUM_DEPTH) {
540
+ return false;
541
+ }
542
+ const shape = error;
543
+ if (CONFLICT_CODES.has(shape.code) || shape.originalCode === SERIALIZATION_FAILURE) {
544
+ return true;
545
+ }
546
+ const meta = shape.meta;
547
+ if (isPostgresSerializationFailure(meta, depth + 1) || isPostgresSerializationFailure(meta?.driverAdapterError, depth + 1)) {
548
+ return true;
549
+ }
550
+ return WRAPPED_ERROR_KEYS.some((key) => isPostgresSerializationFailure(shape[key], depth + 1));
551
+ }
543
552
  function text(row, column) {
544
553
  return row[column];
545
554
  }
@@ -3044,6 +3053,16 @@ var PostgresEventStore = class {
3044
3053
  async setup() {
3045
3054
  await this.#setup.run();
3046
3055
  }
3056
+ /** Every level PostgreSQL has (it runs `read uncommitted` as `read committed`, which the bus does not offer). */
3057
+ isolationLevels = TRANSACTION_ISOLATION_LEVELS;
3058
+ /**
3059
+ * True for a serialization failure (SQLSTATE `40001`) raised through any executor, including
3060
+ * Prisma's `P2034` and the wrappers of TypeORM and Drizzle; a custom executor can add its own
3061
+ * test with {@link PostgresExecutor.isSerializationFailure}.
3062
+ */
3063
+ isSerializationFailure(error) {
3064
+ return isPostgresSerializationFailure(error) || this.#state.executor.isSerializationFailure?.(error) === true;
3065
+ }
3047
3066
  /**
3048
3067
  * Runs `work` in a transaction of the executor. With `options.timeoutMs` (from `bus.transaction`)
3049
3068
  * the server holds the transaction to the budget: `statement_timeout`, and for executors with
@@ -3051,6 +3070,9 @@ var PostgresEventStore = class {
3051
3070
  * `transaction_timeout`, all scoped to the transaction. An executor with `transactionWithBudget`
3052
3071
  * (`PgExecutor`) sets them with `BEGIN`, so the budget costs no round trip; on a pool the store
3053
3072
  * created they are kept per connection and sent only when the connection does not have them yet.
3073
+ * With `options.isolationLevel` the transaction runs at that level: `PgExecutor` sends it with
3074
+ * `BEGIN ISOLATION LEVEL ...` (no extra round trip), any other executor as `SET TRANSACTION
3075
+ * ISOLATION LEVEL ...` before the budget statement.
3054
3076
  */
3055
3077
  async transaction(work, options) {
3056
3078
  const own = { wake: false };
@@ -3062,12 +3084,20 @@ var PostgresEventStore = class {
3062
3084
  this.#state.ownTransactions.delete(transaction);
3063
3085
  }
3064
3086
  };
3065
- const result = await (options === void 0 ? this.#state.executor.transaction(tracked) : inBudgetedTransaction(this.#state, { work: tracked, timeoutMs: options.timeoutMs }));
3087
+ const result = await this.#open(tracked, options);
3066
3088
  if (own.wake) {
3067
3089
  this.#appender.wakeAfterCommit();
3068
3090
  }
3069
3091
  return result;
3070
3092
  }
3093
+ /** Opens the transaction `work` runs in: plain, held to the budget, or at an isolation level too. */
3094
+ #open(work, options) {
3095
+ if (options === void 0) {
3096
+ return this.#state.executor.transaction(work);
3097
+ }
3098
+ const { timeoutMs, isolationLevel } = options;
3099
+ return isolationLevel === void 0 ? inBudgetedTransaction(this.#state, { work, timeoutMs }) : inIsolatedTransaction(this.#state, { work, timeoutMs, isolationLevel });
3100
+ }
3071
3101
  /**
3072
3102
  * Writes the entries in `transaction`, normally with one statement
3073
3103
  * (`AppendStatements.insertFreshEvents`): keyed events reserve their sequence numbers (locking