@duraflows/kysely 5.2.0 → 6.0.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 CHANGED
@@ -104,6 +104,8 @@ interface MyDatabase extends WorkflowDatabase {
104
104
  const db = new Kysely<MyDatabase>({ ... });
105
105
  ```
106
106
 
107
+ A database type declared as `interface AppDb extends WorkflowDatabase` picks up the `workflow_instances` columns automatically, including `timeout_attempts`, `timeout_retry_at`, `timeout_last_error` and `timeout_parked_at`. If you instead hand-write your own `workflow_instances` table type rather than extending `WorkflowDatabase`, it must declare all four: `timeout_attempts: Generated<number>`, `timeout_retry_at: Date | null`, `timeout_last_error: string | null`, `timeout_parked_at: Date | null`.
108
+
107
109
  ## Database Setup
108
110
 
109
111
  This package uses the same database schema as `@duraflows/pg`. Use the migration generator from `@duraflows/pg` or copy the reference migration:
@@ -144,6 +146,8 @@ Both are applied transaction-locally via `set_config(name, value, true)` — the
144
146
 
145
147
  Convenience factory for short-lived runtimes pre-bound to an existing transaction. Each workflow call runs in a savepoint on `trx`, so a failed call leaves no partial writes behind even if you catch its error. Observers fire when the call resolves, before your transaction commits, and duraflows calls they make join `trx`. For strictly post-commit observers, use `KyselyTransactionContext.transaction(db, …)` with the long-lived providers instead.
146
148
 
149
+ Don't mix these providers with the long-lived `kyselyWorkflowProviders(db)` (or `KyselyTransactionContext.transaction(db, …)`) inside one transaction. The two track their active transaction separately, one per `trx` and the other per `db`, so a call through one doesn't see a transaction opened through the other. It starts a second transaction on another connection instead, which can wait on row locks the first one holds. Use one style per transaction.
150
+
147
151
  ### `KyselyTransactionContext`
148
152
 
149
153
  The context is scoped per Kysely instance:
package/dist/cjs/index.js CHANGED
@@ -49,7 +49,7 @@ function kyselyWorkflowProvidersFromTransaction(trx) {
49
49
  // provider must never supersede it.
50
50
  const scope = kysely_transaction_context_js_1.kyselyTransactionScopes.current(narrowed);
51
51
  if (scope) {
52
- return kysely_transaction_context_js_1.kyselyTransactionScopes.runInSavepoint(narrowed, scope, callback, (sql) => (0, kysely_transaction_context_js_1.executeRawStatement)(scope.connection, sql));
52
+ return (0, kysely_transaction_context_js_1.runInKyselySavepoint)(narrowed, scope, callback);
53
53
  }
54
54
  // The caller owns `trx`, so even this outermost call is nested in their
55
55
  // transaction: run it in a savepoint so a failure leaves no partial
@@ -58,7 +58,7 @@ function kyselyWorkflowProvidersFromTransaction(trx) {
58
58
  // duraflows call they make joins `trx` as a savepoint, and the
59
59
  // callbacks it queues drain in the same pass.
60
60
  const root = kysely_transaction_context_js_1.kyselyTransactionScopes.createRoot(boundTrx);
61
- const result = await kysely_transaction_context_js_1.kyselyTransactionScopes.run(narrowed, root, () => kysely_transaction_context_js_1.kyselyTransactionScopes.runInSavepoint(narrowed, root, callback, (sql) => (0, kysely_transaction_context_js_1.executeRawStatement)(boundTrx, sql)));
61
+ const result = await kysely_transaction_context_js_1.kyselyTransactionScopes.run(narrowed, root, () => (0, kysely_transaction_context_js_1.runInKyselySavepoint)(narrowed, root, callback));
62
62
  await kysely_transaction_context_js_1.kyselyTransactionScopes.run(narrowed, root, () => (0, core_1.runAfterCommitCallbacks)(root.callbacks));
63
63
  return result;
64
64
  },
@@ -24,6 +24,10 @@ export interface WorkflowInstancesTable {
24
24
  metadata_json: JsonObjectColumn;
25
25
  created_at: Generated<Date>;
26
26
  updated_at: Generated<Date>;
27
+ timeout_attempts: Generated<number>;
28
+ timeout_retry_at: Date | null;
29
+ timeout_last_error: string | null;
30
+ timeout_parked_at: Date | null;
27
31
  }
28
32
  export interface WorkflowHistoryTable {
29
33
  uuid: Generated<string>;
@@ -10,6 +10,10 @@ export declare class KyselyWorkflowInstanceStore implements WorkflowInstanceStor
10
10
  lockByUuid(uuid: string): Promise<WorkflowInstance | null>;
11
11
  update(instance: WorkflowInstance): Promise<void>;
12
12
  findExpired(limit: number, now: Date): Promise<WorkflowInstance[]>;
13
+ findParkedTimeouts(options: {
14
+ limit: number;
15
+ workflowName?: string;
16
+ }): Promise<WorkflowInstance[]>;
13
17
  private mapRow;
14
18
  }
15
19
  //# sourceMappingURL=kysely-instance-store.d.ts.map
@@ -3,6 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KyselyWorkflowInstanceStore = void 0;
4
4
  const core_1 = require("@duraflows/core");
5
5
  const kysely_transaction_context_js_1 = require("./kysely-transaction-context.js");
6
+ /** The four timeout-retry column values for `retry` (`null` ⇒ never failed). */
7
+ function timeoutRetryColumns(retry) {
8
+ return {
9
+ timeout_attempts: retry?.attempts ?? 0,
10
+ timeout_retry_at: retry?.retryAt ?? null,
11
+ timeout_last_error: retry?.lastError ?? null,
12
+ timeout_parked_at: retry?.parkedAt ?? null,
13
+ };
14
+ }
6
15
  class KyselyWorkflowInstanceStore {
7
16
  db;
8
17
  constructor(db) {
@@ -27,6 +36,7 @@ class KyselyWorkflowInstanceStore {
27
36
  metadata_json: JSON.stringify(instance.metadata),
28
37
  created_at: instance.createdAt,
29
38
  updated_at: instance.updatedAt,
39
+ ...timeoutRetryColumns(instance.timeoutRetry),
30
40
  })
31
41
  .execute();
32
42
  }
@@ -65,6 +75,7 @@ class KyselyWorkflowInstanceStore {
65
75
  last_transition_at: instance.lastTransitionAt,
66
76
  context_json: JSON.stringify(instance.context),
67
77
  updated_at: instance.updatedAt,
78
+ ...timeoutRetryColumns(instance.timeoutRetry),
68
79
  })
69
80
  .where("uuid", "=", instance.uuid)
70
81
  .where("version", "=", expectedVersion)
@@ -84,13 +95,30 @@ class KyselyWorkflowInstanceStore {
84
95
  .selectAll()
85
96
  .where("expires_at", "is not", null)
86
97
  .where("expires_at", "<", now)
87
- .orderBy("expires_at")
98
+ .where("timeout_parked_at", "is", null)
99
+ .where((eb) => eb.or([eb("timeout_retry_at", "is", null), eb("timeout_retry_at", "<", now)]))
100
+ // Redundant (every due row already satisfies it); it gives the planner a
101
+ // range condition so workflow_instances_timeout_due_idx is range-scanned
102
+ // instead of walked in order with every entry filtered against the heap.
103
+ .where((eb) => eb(eb.fn.coalesce("timeout_retry_at", "expires_at"), "<", now))
104
+ .orderBy((eb) => eb.fn.coalesce("timeout_retry_at", "expires_at"))
88
105
  .forUpdate()
89
106
  .skipLocked()
90
107
  .limit(limit)
91
108
  .execute();
92
109
  return rows.map((row) => this.mapRow(row));
93
110
  }
111
+ async findParkedTimeouts(options) {
112
+ let query = this.getExecutor()
113
+ .selectFrom("workflow_instances")
114
+ .selectAll()
115
+ .where("timeout_parked_at", "is not", null);
116
+ if (options.workflowName !== undefined) {
117
+ query = query.where("workflow_name", "=", options.workflowName);
118
+ }
119
+ const rows = await query.orderBy("timeout_parked_at").orderBy("uuid").limit(options.limit).execute();
120
+ return rows.map((row) => this.mapRow(row));
121
+ }
94
122
  mapRow(row) {
95
123
  return {
96
124
  uuid: row.uuid,
@@ -99,6 +127,14 @@ class KyselyWorkflowInstanceStore {
99
127
  version: row.version,
100
128
  definitionVersion: row.definition_version ?? null,
101
129
  expiresAt: row.expires_at,
130
+ timeoutRetry: row.timeout_attempts > 0
131
+ ? {
132
+ attempts: row.timeout_attempts,
133
+ lastError: row.timeout_last_error ?? "",
134
+ retryAt: row.timeout_retry_at,
135
+ parkedAt: row.timeout_parked_at,
136
+ }
137
+ : null,
102
138
  lastTransitionAt: row.last_transition_at,
103
139
  context: row.context_json,
104
140
  metadata: row.metadata_json,
@@ -1,20 +1,15 @@
1
1
  import type { Kysely, Transaction } from "kysely";
2
- import { ScopedTransactionContext } from "@duraflows/core";
2
+ import { ScopedTransactionContext, type TransactionScope } from "@duraflows/core";
3
3
  import type { WorkflowDatabase } from "./kysely-database.js";
4
4
  type WorkflowTransaction = Transaction<WorkflowDatabase>;
5
5
  /** @internal Shared with the runners; not part of the public API. */
6
6
  export declare const kyselyTransactionScopes: ScopedTransactionContext<Kysely<WorkflowDatabase>, WorkflowTransaction>;
7
7
  /**
8
- * @internal Runs one fixed SQL statement (a savepoint command) on `trx`.
9
- *
10
- * `Transaction` has no savepoint API in kysely 0.29 (only `ControlledTransaction`
11
- * does), and this package imports kysely for types only, since kysely is
12
- * ESM-only and a runtime import would break the CommonJS build. So the
13
- * statement is handed to `executeQuery` as a compiled query built by hand,
14
- * shaped exactly like `CompiledQuery.raw(sql)`. `sql` is always an internally
15
- * generated `SAVEPOINT` / `RELEASE` / `ROLLBACK TO` statement, never user input.
8
+ * @internal Runs `callback` in a savepoint nested in `scope`, issuing the
9
+ * savepoint statements on that scope's own transaction. Every kysely entry
10
+ * point that nests (the runners and `transaction()`) goes through here.
16
11
  */
17
- export declare function executeRawStatement(trx: WorkflowTransaction, sql: string): Promise<unknown>;
12
+ export declare function runInKyselySavepoint<T>(owner: Kysely<WorkflowDatabase>, scope: TransactionScope<WorkflowTransaction>, callback: () => Promise<T>): Promise<T>;
18
13
  /**
19
14
  * @internal Runs `callback` as the root of a new kysely transaction on `db`
20
15
  * (after `setup`, e.g. transaction-local timeouts), and only once kysely has
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KyselyTransactionContext = exports.kyselyTransactionScopes = void 0;
4
- exports.executeRawStatement = executeRawStatement;
4
+ exports.runInKyselySavepoint = runInKyselySavepoint;
5
5
  exports.runOwnedKyselyTransaction = runOwnedKyselyTransaction;
6
6
  const node_crypto_1 = require("node:crypto");
7
7
  const core_1 = require("@duraflows/core");
@@ -10,7 +10,7 @@ const core_1 = require("@duraflows/core");
10
10
  /** @internal Shared with the runners; not part of the public API. */
11
11
  exports.kyselyTransactionScopes = new core_1.ScopedTransactionContext();
12
12
  /**
13
- * @internal Runs one fixed SQL statement (a savepoint command) on `trx`.
13
+ * Runs one fixed SQL statement (a savepoint command) on `trx`.
14
14
  *
15
15
  * `Transaction` has no savepoint API in kysely 0.29 (only `ControlledTransaction`
16
16
  * does), and this package imports kysely for types only, since kysely is
@@ -28,6 +28,14 @@ function executeRawStatement(trx, sql) {
28
28
  };
29
29
  return trx.executeQuery(query);
30
30
  }
31
+ /**
32
+ * @internal Runs `callback` in a savepoint nested in `scope`, issuing the
33
+ * savepoint statements on that scope's own transaction. Every kysely entry
34
+ * point that nests (the runners and `transaction()`) goes through here.
35
+ */
36
+ function runInKyselySavepoint(owner, scope, callback) {
37
+ return exports.kyselyTransactionScopes.runInSavepoint(owner, scope, callback, (sql) => executeRawStatement(scope.connection, sql));
38
+ }
31
39
  /**
32
40
  * @internal Runs `callback` as the root of a new kysely transaction on `db`
33
41
  * (after `setup`, e.g. transaction-local timeouts), and only once kysely has
@@ -84,7 +92,7 @@ exports.KyselyTransactionContext = {
84
92
  const owner = db;
85
93
  const scope = exports.kyselyTransactionScopes.current(owner);
86
94
  if (scope) {
87
- return exports.kyselyTransactionScopes.runInSavepoint(owner, scope, () => callback(scope.connection), (sql) => executeRawStatement(scope.connection, sql));
95
+ return runInKyselySavepoint(owner, scope, () => callback(scope.connection));
88
96
  }
89
97
  return runOwnedKyselyTransaction(owner, async () => { }, (trx) => callback(trx));
90
98
  },
@@ -40,7 +40,7 @@ class KyselyTransactionRunner {
40
40
  if (scope) {
41
41
  // Nested: a savepoint on the outer transaction, so a failure rolls back
42
42
  // only this call. The outer transaction's timeouts stay in force.
43
- return kysely_transaction_context_js_1.kyselyTransactionScopes.runInSavepoint(this.db, scope, callback, (sql) => (0, kysely_transaction_context_js_1.executeRawStatement)(scope.connection, sql));
43
+ return (0, kysely_transaction_context_js_1.runInKyselySavepoint)(this.db, scope, callback);
44
44
  }
45
45
  return (0, kysely_transaction_context_js_1.runOwnedKyselyTransaction)(this.db, (trx) => this.applyTimeouts(trx), () => callback());
46
46
  }
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { KyselyWorkflowInstanceStore } from "./kysely-instance-store.js";
3
3
  import { KyselyWorkflowHistoryStore } from "./kysely-history-store.js";
4
4
  import { KyselyWorkflowDefinitionStore } from "./kysely-definition-store.js";
5
5
  import { KyselyTransactionRunner } from "./kysely-transaction-runner.js";
6
- import { executeRawStatement, kyselyTransactionScopes } from "./kysely-transaction-context.js";
6
+ import { kyselyTransactionScopes, runInKyselySavepoint } from "./kysely-transaction-context.js";
7
7
  export { KyselyTransactionContext } from "./kysely-transaction-context.js";
8
8
  export { KyselyTransactionRunner } from "./kysely-transaction-runner.js";
9
9
  export { KyselyWorkflowInstanceStore } from "./kysely-instance-store.js";
@@ -39,7 +39,7 @@ export function kyselyWorkflowProvidersFromTransaction(trx) {
39
39
  // provider must never supersede it.
40
40
  const scope = kyselyTransactionScopes.current(narrowed);
41
41
  if (scope) {
42
- return kyselyTransactionScopes.runInSavepoint(narrowed, scope, callback, (sql) => executeRawStatement(scope.connection, sql));
42
+ return runInKyselySavepoint(narrowed, scope, callback);
43
43
  }
44
44
  // The caller owns `trx`, so even this outermost call is nested in their
45
45
  // transaction: run it in a savepoint so a failure leaves no partial
@@ -48,7 +48,7 @@ export function kyselyWorkflowProvidersFromTransaction(trx) {
48
48
  // duraflows call they make joins `trx` as a savepoint, and the
49
49
  // callbacks it queues drain in the same pass.
50
50
  const root = kyselyTransactionScopes.createRoot(boundTrx);
51
- const result = await kyselyTransactionScopes.run(narrowed, root, () => kyselyTransactionScopes.runInSavepoint(narrowed, root, callback, (sql) => executeRawStatement(boundTrx, sql)));
51
+ const result = await kyselyTransactionScopes.run(narrowed, root, () => runInKyselySavepoint(narrowed, root, callback));
52
52
  await kyselyTransactionScopes.run(narrowed, root, () => runAfterCommitCallbacks(root.callbacks));
53
53
  return result;
54
54
  },
@@ -24,6 +24,10 @@ export interface WorkflowInstancesTable {
24
24
  metadata_json: JsonObjectColumn;
25
25
  created_at: Generated<Date>;
26
26
  updated_at: Generated<Date>;
27
+ timeout_attempts: Generated<number>;
28
+ timeout_retry_at: Date | null;
29
+ timeout_last_error: string | null;
30
+ timeout_parked_at: Date | null;
27
31
  }
28
32
  export interface WorkflowHistoryTable {
29
33
  uuid: Generated<string>;
@@ -10,6 +10,10 @@ export declare class KyselyWorkflowInstanceStore implements WorkflowInstanceStor
10
10
  lockByUuid(uuid: string): Promise<WorkflowInstance | null>;
11
11
  update(instance: WorkflowInstance): Promise<void>;
12
12
  findExpired(limit: number, now: Date): Promise<WorkflowInstance[]>;
13
+ findParkedTimeouts(options: {
14
+ limit: number;
15
+ workflowName?: string;
16
+ }): Promise<WorkflowInstance[]>;
13
17
  private mapRow;
14
18
  }
15
19
  //# sourceMappingURL=kysely-instance-store.d.ts.map
@@ -1,5 +1,14 @@
1
1
  import { WorkflowError } from "@duraflows/core";
2
2
  import { KyselyTransactionContext } from "./kysely-transaction-context.js";
3
+ /** The four timeout-retry column values for `retry` (`null` ⇒ never failed). */
4
+ function timeoutRetryColumns(retry) {
5
+ return {
6
+ timeout_attempts: retry?.attempts ?? 0,
7
+ timeout_retry_at: retry?.retryAt ?? null,
8
+ timeout_last_error: retry?.lastError ?? null,
9
+ timeout_parked_at: retry?.parkedAt ?? null,
10
+ };
11
+ }
3
12
  export class KyselyWorkflowInstanceStore {
4
13
  db;
5
14
  constructor(db) {
@@ -24,6 +33,7 @@ export class KyselyWorkflowInstanceStore {
24
33
  metadata_json: JSON.stringify(instance.metadata),
25
34
  created_at: instance.createdAt,
26
35
  updated_at: instance.updatedAt,
36
+ ...timeoutRetryColumns(instance.timeoutRetry),
27
37
  })
28
38
  .execute();
29
39
  }
@@ -62,6 +72,7 @@ export class KyselyWorkflowInstanceStore {
62
72
  last_transition_at: instance.lastTransitionAt,
63
73
  context_json: JSON.stringify(instance.context),
64
74
  updated_at: instance.updatedAt,
75
+ ...timeoutRetryColumns(instance.timeoutRetry),
65
76
  })
66
77
  .where("uuid", "=", instance.uuid)
67
78
  .where("version", "=", expectedVersion)
@@ -81,13 +92,30 @@ export class KyselyWorkflowInstanceStore {
81
92
  .selectAll()
82
93
  .where("expires_at", "is not", null)
83
94
  .where("expires_at", "<", now)
84
- .orderBy("expires_at")
95
+ .where("timeout_parked_at", "is", null)
96
+ .where((eb) => eb.or([eb("timeout_retry_at", "is", null), eb("timeout_retry_at", "<", now)]))
97
+ // Redundant (every due row already satisfies it); it gives the planner a
98
+ // range condition so workflow_instances_timeout_due_idx is range-scanned
99
+ // instead of walked in order with every entry filtered against the heap.
100
+ .where((eb) => eb(eb.fn.coalesce("timeout_retry_at", "expires_at"), "<", now))
101
+ .orderBy((eb) => eb.fn.coalesce("timeout_retry_at", "expires_at"))
85
102
  .forUpdate()
86
103
  .skipLocked()
87
104
  .limit(limit)
88
105
  .execute();
89
106
  return rows.map((row) => this.mapRow(row));
90
107
  }
108
+ async findParkedTimeouts(options) {
109
+ let query = this.getExecutor()
110
+ .selectFrom("workflow_instances")
111
+ .selectAll()
112
+ .where("timeout_parked_at", "is not", null);
113
+ if (options.workflowName !== undefined) {
114
+ query = query.where("workflow_name", "=", options.workflowName);
115
+ }
116
+ const rows = await query.orderBy("timeout_parked_at").orderBy("uuid").limit(options.limit).execute();
117
+ return rows.map((row) => this.mapRow(row));
118
+ }
91
119
  mapRow(row) {
92
120
  return {
93
121
  uuid: row.uuid,
@@ -96,6 +124,14 @@ export class KyselyWorkflowInstanceStore {
96
124
  version: row.version,
97
125
  definitionVersion: row.definition_version ?? null,
98
126
  expiresAt: row.expires_at,
127
+ timeoutRetry: row.timeout_attempts > 0
128
+ ? {
129
+ attempts: row.timeout_attempts,
130
+ lastError: row.timeout_last_error ?? "",
131
+ retryAt: row.timeout_retry_at,
132
+ parkedAt: row.timeout_parked_at,
133
+ }
134
+ : null,
99
135
  lastTransitionAt: row.last_transition_at,
100
136
  context: row.context_json,
101
137
  metadata: row.metadata_json,
@@ -1,20 +1,15 @@
1
1
  import type { Kysely, Transaction } from "kysely";
2
- import { ScopedTransactionContext } from "@duraflows/core";
2
+ import { ScopedTransactionContext, type TransactionScope } from "@duraflows/core";
3
3
  import type { WorkflowDatabase } from "./kysely-database.js";
4
4
  type WorkflowTransaction = Transaction<WorkflowDatabase>;
5
5
  /** @internal Shared with the runners; not part of the public API. */
6
6
  export declare const kyselyTransactionScopes: ScopedTransactionContext<Kysely<WorkflowDatabase>, WorkflowTransaction>;
7
7
  /**
8
- * @internal Runs one fixed SQL statement (a savepoint command) on `trx`.
9
- *
10
- * `Transaction` has no savepoint API in kysely 0.29 (only `ControlledTransaction`
11
- * does), and this package imports kysely for types only, since kysely is
12
- * ESM-only and a runtime import would break the CommonJS build. So the
13
- * statement is handed to `executeQuery` as a compiled query built by hand,
14
- * shaped exactly like `CompiledQuery.raw(sql)`. `sql` is always an internally
15
- * generated `SAVEPOINT` / `RELEASE` / `ROLLBACK TO` statement, never user input.
8
+ * @internal Runs `callback` in a savepoint nested in `scope`, issuing the
9
+ * savepoint statements on that scope's own transaction. Every kysely entry
10
+ * point that nests (the runners and `transaction()`) goes through here.
16
11
  */
17
- export declare function executeRawStatement(trx: WorkflowTransaction, sql: string): Promise<unknown>;
12
+ export declare function runInKyselySavepoint<T>(owner: Kysely<WorkflowDatabase>, scope: TransactionScope<WorkflowTransaction>, callback: () => Promise<T>): Promise<T>;
18
13
  /**
19
14
  * @internal Runs `callback` as the root of a new kysely transaction on `db`
20
15
  * (after `setup`, e.g. transaction-local timeouts), and only once kysely has
@@ -5,7 +5,7 @@ import { ScopedTransactionContext, runAfterCommitCallbacks } from "@duraflows/co
5
5
  /** @internal Shared with the runners; not part of the public API. */
6
6
  export const kyselyTransactionScopes = new ScopedTransactionContext();
7
7
  /**
8
- * @internal Runs one fixed SQL statement (a savepoint command) on `trx`.
8
+ * Runs one fixed SQL statement (a savepoint command) on `trx`.
9
9
  *
10
10
  * `Transaction` has no savepoint API in kysely 0.29 (only `ControlledTransaction`
11
11
  * does), and this package imports kysely for types only, since kysely is
@@ -14,7 +14,7 @@ export const kyselyTransactionScopes = new ScopedTransactionContext();
14
14
  * shaped exactly like `CompiledQuery.raw(sql)`. `sql` is always an internally
15
15
  * generated `SAVEPOINT` / `RELEASE` / `ROLLBACK TO` statement, never user input.
16
16
  */
17
- export function executeRawStatement(trx, sql) {
17
+ function executeRawStatement(trx, sql) {
18
18
  const query = {
19
19
  sql,
20
20
  parameters: [],
@@ -23,6 +23,14 @@ export function executeRawStatement(trx, sql) {
23
23
  };
24
24
  return trx.executeQuery(query);
25
25
  }
26
+ /**
27
+ * @internal Runs `callback` in a savepoint nested in `scope`, issuing the
28
+ * savepoint statements on that scope's own transaction. Every kysely entry
29
+ * point that nests (the runners and `transaction()`) goes through here.
30
+ */
31
+ export function runInKyselySavepoint(owner, scope, callback) {
32
+ return kyselyTransactionScopes.runInSavepoint(owner, scope, callback, (sql) => executeRawStatement(scope.connection, sql));
33
+ }
26
34
  /**
27
35
  * @internal Runs `callback` as the root of a new kysely transaction on `db`
28
36
  * (after `setup`, e.g. transaction-local timeouts), and only once kysely has
@@ -79,7 +87,7 @@ export const KyselyTransactionContext = {
79
87
  const owner = db;
80
88
  const scope = kyselyTransactionScopes.current(owner);
81
89
  if (scope) {
82
- return kyselyTransactionScopes.runInSavepoint(owner, scope, () => callback(scope.connection), (sql) => executeRawStatement(scope.connection, sql));
90
+ return runInKyselySavepoint(owner, scope, () => callback(scope.connection));
83
91
  }
84
92
  return runOwnedKyselyTransaction(owner, async () => { }, (trx) => callback(trx));
85
93
  },
@@ -1,5 +1,5 @@
1
1
  import { WorkflowError } from "@duraflows/core";
2
- import { executeRawStatement, kyselyTransactionScopes, runOwnedKyselyTransaction, } from "./kysely-transaction-context.js";
2
+ import { kyselyTransactionScopes, runInKyselySavepoint, runOwnedKyselyTransaction, } from "./kysely-transaction-context.js";
3
3
  /**
4
4
  * Rejects anything that is not a finite, non-negative integer. Called at
5
5
  * construction time so an invalid value can never reach the database.
@@ -37,7 +37,7 @@ export class KyselyTransactionRunner {
37
37
  if (scope) {
38
38
  // Nested: a savepoint on the outer transaction, so a failure rolls back
39
39
  // only this call. The outer transaction's timeouts stay in force.
40
- return kyselyTransactionScopes.runInSavepoint(this.db, scope, callback, (sql) => executeRawStatement(scope.connection, sql));
40
+ return runInKyselySavepoint(this.db, scope, callback);
41
41
  }
42
42
  return runOwnedKyselyTransaction(this.db, (trx) => this.applyTimeouts(trx), () => callback());
43
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duraflows/kysely",
3
- "version": "5.2.0",
3
+ "version": "6.0.0",
4
4
  "description": "PostgreSQL persistence adapter for duraflows using `kysely`. Integrates with kysely's type-safe query builder and AsyncLocalStorage-based transaction context.",
5
5
  "keywords": [
6
6
  "duraflows",
@@ -51,15 +51,15 @@
51
51
  "access": "public"
52
52
  },
53
53
  "peerDependencies": {
54
- "@duraflows/core": "^5.2.0",
54
+ "@duraflows/core": "^6.0.0",
55
55
  "kysely": "^0.29.6"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/pg": "^8.23.1",
59
59
  "kysely": "^0.29.6",
60
60
  "pg": "^8.23.0",
61
- "@duraflows/core": "5.2.0",
62
- "@duraflows/pg": "5.2.0"
61
+ "@duraflows/core": "6.0.0",
62
+ "@duraflows/pg": "6.0.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsc --build"