@duraflows/kysely 5.2.1 → 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 +2 -0
- 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/kysely-database.d.ts +4 -0
- package/dist/kysely-instance-store.d.ts +4 -0
- package/dist/kysely-instance-store.js +37 -1
- 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:
|
|
@@ -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,
|
|
@@ -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,
|
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"
|