@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 +4 -0
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/kysely-database.d.ts +4 -0
- package/dist/cjs/kysely-instance-store.d.ts +4 -0
- package/dist/cjs/kysely-instance-store.js +37 -1
- package/dist/cjs/kysely-transaction-context.d.ts +5 -10
- package/dist/cjs/kysely-transaction-context.js +11 -3
- package/dist/cjs/kysely-transaction-runner.js +1 -1
- package/dist/index.js +3 -3
- package/dist/kysely-database.d.ts +4 -0
- package/dist/kysely-instance-store.d.ts +4 -0
- package/dist/kysely-instance-store.js +37 -1
- package/dist/kysely-transaction-context.d.ts +5 -10
- package/dist/kysely-transaction-context.js +11 -3
- package/dist/kysely-transaction-runner.js +2 -2
- package/package.json +4 -4
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
|
|
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.
|
|
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
|
-
.
|
|
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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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 {
|
|
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
|
|
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, () =>
|
|
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
|
-
.
|
|
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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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": "
|
|
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": "^
|
|
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": "
|
|
62
|
-
"@duraflows/pg": "
|
|
61
|
+
"@duraflows/core": "6.0.0",
|
|
62
|
+
"@duraflows/pg": "6.0.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsc --build"
|