@pikku/kysely 0.12.11 → 0.12.13
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/CHANGELOG.md +20 -0
- package/dist/src/create-audited-kysely.d.ts +10 -0
- package/dist/src/create-audited-kysely.js +167 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/kysely-ai-agent-run-service.d.ts +1 -0
- package/dist/src/kysely-ai-agent-run-service.js +4 -1
- package/dist/src/kysely-channel-store.d.ts +3 -0
- package/dist/src/kysely-channel-store.js +25 -0
- package/dist/src/kysely-secret-service.d.ts +2 -3
- package/dist/src/kysely-secret-service.js +2 -7
- package/dist/src/kysely-tables.d.ts +2 -0
- package/dist/src/kysely-workflow-mirror.d.ts +60 -0
- package/dist/src/kysely-workflow-mirror.js +329 -0
- package/dist/src/kysely-workflow-service.d.ts +13 -13
- package/dist/src/kysely-workflow-service.js +13 -13
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/create-audited-kysely.test.ts +560 -0
- package/src/create-audited-kysely.ts +254 -0
- package/src/index.ts +5 -0
- package/src/kysely-ai-agent-run-service.ts +6 -1
- package/src/kysely-ai-run-state-service.ts +6 -2
- package/src/kysely-channel-store.ts +27 -0
- package/src/kysely-secret-service.ts +4 -17
- package/src/kysely-services.test.ts +2 -2
- package/src/kysely-session-store.ts +1 -3
- package/src/kysely-tables.ts +2 -0
- package/src/kysely-workflow-mirror.test.ts +246 -0
- package/src/kysely-workflow-mirror.ts +438 -0
- package/src/kysely-workflow-service.ts +25 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 0.12.13
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 909eb25: Add audit logging support for function invocations and database queries.
|
|
6
|
+
|
|
7
|
+
Introduces `AuditService` and `createAuditedKysely` — configurable audit capture with best-effort and transactional durability modes. Audit logs capture session metadata (user, org), RPC call details, and Kysely query operations (type, tables, changes). Audit context is scoped per-invocation so nested RPC calls are correctly attributed.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [909eb25]
|
|
10
|
+
- @pikku/core@0.12.26
|
|
11
|
+
|
|
12
|
+
## 0.12.12
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 55ba75a: Update the OSS runtime dependency set for newer Node support by bumping `uWebSockets.js` to `v20.68.0` and `better-sqlite3` to `v12.10.0`.
|
|
17
|
+
- Updated dependencies [c02275f]
|
|
18
|
+
- Updated dependencies [0bd0433]
|
|
19
|
+
- @pikku/core@0.12.24
|
|
20
|
+
|
|
1
21
|
## 0.12.11
|
|
2
22
|
|
|
3
23
|
### Patch Changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AuditLog } from '@pikku/core';
|
|
2
|
+
import type { Kysely } from 'kysely';
|
|
3
|
+
export interface CreateAuditedKyselyOptions {
|
|
4
|
+
audit: AuditLog;
|
|
5
|
+
auditReads?: boolean;
|
|
6
|
+
eventType?: string;
|
|
7
|
+
transactionId?: string | null;
|
|
8
|
+
queryIdPrefix?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function createAuditedKysely<DB>(db: Kysely<DB>, options: CreateAuditedKyselyOptions): Kysely<DB>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const QUERY_KIND_BY_NODE = {
|
|
2
|
+
SelectQueryNode: 'select',
|
|
3
|
+
InsertQueryNode: 'insert',
|
|
4
|
+
UpdateQueryNode: 'update',
|
|
5
|
+
DeleteQueryNode: 'delete',
|
|
6
|
+
};
|
|
7
|
+
function getIdentifierName(node) {
|
|
8
|
+
if (!node || typeof node !== 'object')
|
|
9
|
+
return undefined;
|
|
10
|
+
const candidate = node;
|
|
11
|
+
return (candidate.name ??
|
|
12
|
+
candidate.column?.name ??
|
|
13
|
+
candidate.identifier?.name ??
|
|
14
|
+
candidate.table?.identifier?.name);
|
|
15
|
+
}
|
|
16
|
+
function collectTableNames(node, out) {
|
|
17
|
+
if (!node || typeof node !== 'object')
|
|
18
|
+
return;
|
|
19
|
+
const candidate = node;
|
|
20
|
+
if (candidate.kind === 'TableNode' &&
|
|
21
|
+
typeof candidate.table?.identifier?.name === 'string') {
|
|
22
|
+
out.add(candidate.table.identifier.name);
|
|
23
|
+
}
|
|
24
|
+
for (const value of Object.values(node)) {
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
for (const item of value)
|
|
27
|
+
collectTableNames(item, out);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
collectTableNames(value, out);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function extractValue(node) {
|
|
34
|
+
if (!node || typeof node !== 'object')
|
|
35
|
+
return node;
|
|
36
|
+
const candidate = node;
|
|
37
|
+
switch (candidate.kind) {
|
|
38
|
+
case 'ValueNode':
|
|
39
|
+
return candidate.value;
|
|
40
|
+
case 'PrimitiveValueListNode':
|
|
41
|
+
case 'ValueListNode':
|
|
42
|
+
return (candidate.values ?? []).map(extractValue);
|
|
43
|
+
case 'DefaultInsertValueNode':
|
|
44
|
+
return '[default]';
|
|
45
|
+
case 'ColumnNode':
|
|
46
|
+
case 'ReferenceNode':
|
|
47
|
+
return `[${candidate.kind}]`;
|
|
48
|
+
case 'RawNode':
|
|
49
|
+
return '[raw]';
|
|
50
|
+
case 'SelectQueryNode':
|
|
51
|
+
return '[subquery]';
|
|
52
|
+
case 'ParensNode':
|
|
53
|
+
return extractValue(candidate.node);
|
|
54
|
+
case 'BinaryOperationNode':
|
|
55
|
+
return {
|
|
56
|
+
left: extractValue(candidate.left),
|
|
57
|
+
right: extractValue(candidate.right),
|
|
58
|
+
};
|
|
59
|
+
default:
|
|
60
|
+
return `[${candidate.kind ?? 'expression'}]`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function extractInsertChanges(node) {
|
|
64
|
+
const columns = Array.isArray(node.columns)
|
|
65
|
+
? node.columns
|
|
66
|
+
.map((column) => getIdentifierName(column))
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
: [];
|
|
69
|
+
const values = Array.isArray(node.values?.values) ? node.values.values : [];
|
|
70
|
+
if (columns.length === 0 || values.length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
changedColumns: columns.length > 0 ? columns : undefined,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const rows = values.map((row) => {
|
|
76
|
+
const rowValues = Array.isArray(row?.values)
|
|
77
|
+
? row.values.map(extractValue)
|
|
78
|
+
: [];
|
|
79
|
+
return Object.fromEntries(columns.map((column, index) => [column, rowValues[index]]));
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
changedColumns: columns,
|
|
83
|
+
changes: rows.length === 1 ? rows[0] : rows,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function extractUpdateChanges(node) {
|
|
87
|
+
const updates = Array.isArray(node.updates) ? node.updates : [];
|
|
88
|
+
const entries = updates
|
|
89
|
+
.map((update) => {
|
|
90
|
+
const column = getIdentifierName(update.column);
|
|
91
|
+
if (!column)
|
|
92
|
+
return null;
|
|
93
|
+
return [column, extractValue(update.value)];
|
|
94
|
+
})
|
|
95
|
+
.filter(Boolean);
|
|
96
|
+
if (entries.length === 0) {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
changedColumns: entries.map(([column]) => column),
|
|
101
|
+
changes: Object.fromEntries(entries),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function extractChangeSet(node, queryKind) {
|
|
105
|
+
if (queryKind === 'insert') {
|
|
106
|
+
return extractInsertChanges(node);
|
|
107
|
+
}
|
|
108
|
+
if (queryKind === 'update') {
|
|
109
|
+
return extractUpdateChanges(node);
|
|
110
|
+
}
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
class AuditedKyselyPlugin {
|
|
114
|
+
options;
|
|
115
|
+
pending = new Map();
|
|
116
|
+
queryCounter = 0;
|
|
117
|
+
constructor(options) {
|
|
118
|
+
this.options = options;
|
|
119
|
+
}
|
|
120
|
+
transformQuery(args) {
|
|
121
|
+
const queryKind = QUERY_KIND_BY_NODE[args.node.kind ?? ''];
|
|
122
|
+
if (!queryKind) {
|
|
123
|
+
return args.node;
|
|
124
|
+
}
|
|
125
|
+
if (queryKind === 'select' && this.options.auditReads === false) {
|
|
126
|
+
return args.node;
|
|
127
|
+
}
|
|
128
|
+
const tables = new Set();
|
|
129
|
+
collectTableNames(args.node, tables);
|
|
130
|
+
const queryId = `${this.options.queryIdPrefix ?? 'q'}-${++this.queryCounter}`;
|
|
131
|
+
this.pending.set(args.queryId, {
|
|
132
|
+
queryKind,
|
|
133
|
+
queryId,
|
|
134
|
+
tables: [...tables].sort(),
|
|
135
|
+
...extractChangeSet(args.node, queryKind),
|
|
136
|
+
});
|
|
137
|
+
return args.node;
|
|
138
|
+
}
|
|
139
|
+
async transformResult(args) {
|
|
140
|
+
const pending = this.pending.get(args.queryId);
|
|
141
|
+
if (!pending) {
|
|
142
|
+
return args.result;
|
|
143
|
+
}
|
|
144
|
+
this.pending.delete(args.queryId);
|
|
145
|
+
const rawResult = args.result;
|
|
146
|
+
const rowCount = rawResult.rows.length > 0
|
|
147
|
+
? rawResult.rows.length
|
|
148
|
+
: Number(rawResult.numUpdatedOrDeletedRows ?? rawResult.numAffectedRows ?? 0n);
|
|
149
|
+
await this.options.audit.write({
|
|
150
|
+
type: this.options.eventType ?? 'db.query',
|
|
151
|
+
source: 'auto',
|
|
152
|
+
transactionId: this.options.transactionId ?? null,
|
|
153
|
+
queryId: pending.queryId,
|
|
154
|
+
metadata: {
|
|
155
|
+
queryKind: pending.queryKind,
|
|
156
|
+
tables: pending.tables,
|
|
157
|
+
changedColumns: pending.changedColumns,
|
|
158
|
+
changes: pending.changes,
|
|
159
|
+
rowCount,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
return rawResult;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export function createAuditedKysely(db, options) {
|
|
166
|
+
return db.withPlugin(new AuditedKyselyPlugin(options));
|
|
167
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { KyselyChannelStore } from './kysely-channel-store.js';
|
|
|
2
2
|
export { KyselyEventHubStore } from './kysely-eventhub-store.js';
|
|
3
3
|
export { KyselyWorkflowService } from './kysely-workflow-service.js';
|
|
4
4
|
export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
|
|
5
|
+
export { KyselyWorkflowMirror } from './kysely-workflow-mirror.js';
|
|
5
6
|
export { KyselyDeploymentService } from './kysely-deployment-service.js';
|
|
6
7
|
export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
|
|
7
8
|
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
|
|
@@ -11,6 +12,7 @@ export type { KyselySecretServiceConfig } from './kysely-secret-service.js';
|
|
|
11
12
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
|
12
13
|
export type { KyselyCredentialServiceConfig } from './kysely-credential-service.js';
|
|
13
14
|
export { KyselySessionStore } from './kysely-session-store.js';
|
|
15
|
+
export { createAuditedKysely, type CreateAuditedKyselyOptions, } from './create-audited-kysely.js';
|
|
14
16
|
export type { KyselyPikkuDB } from './kysely-tables.js';
|
|
15
17
|
export type { WorkflowRunService } from '@pikku/core/workflow';
|
|
16
18
|
export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent';
|
package/dist/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { KyselyChannelStore } from './kysely-channel-store.js';
|
|
|
2
2
|
export { KyselyEventHubStore } from './kysely-eventhub-store.js';
|
|
3
3
|
export { KyselyWorkflowService } from './kysely-workflow-service.js';
|
|
4
4
|
export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
|
|
5
|
+
export { KyselyWorkflowMirror } from './kysely-workflow-mirror.js';
|
|
5
6
|
export { KyselyDeploymentService } from './kysely-deployment-service.js';
|
|
6
7
|
export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
|
|
7
8
|
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
|
|
@@ -9,3 +10,4 @@ export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js';
|
|
|
9
10
|
export { KyselySecretService } from './kysely-secret-service.js';
|
|
10
11
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
|
11
12
|
export { KyselySessionStore } from './kysely-session-store.js';
|
|
13
|
+
export { createAuditedKysely, } from './create-audited-kysely.js';
|
|
@@ -5,7 +5,7 @@ export class KyselyAgentRunService {
|
|
|
5
5
|
this.db = db;
|
|
6
6
|
}
|
|
7
7
|
async listThreads(options) {
|
|
8
|
-
const { agentName, limit = 50, offset = 0 } = options ?? {};
|
|
8
|
+
const { agentName, resourceId, limit = 50, offset = 0 } = options ?? {};
|
|
9
9
|
let query = this.db
|
|
10
10
|
.selectFrom('aiThreads as t')
|
|
11
11
|
.select([
|
|
@@ -23,6 +23,9 @@ export class KyselyAgentRunService {
|
|
|
23
23
|
.where('agentName', '=', agentName)
|
|
24
24
|
.distinct());
|
|
25
25
|
}
|
|
26
|
+
if (resourceId) {
|
|
27
|
+
query = query.where('t.resourceId', '=', resourceId);
|
|
28
|
+
}
|
|
26
29
|
const result = await query
|
|
27
30
|
.orderBy('t.updatedAt', 'desc')
|
|
28
31
|
.limit(limit)
|
|
@@ -13,5 +13,8 @@ export declare class KyselyChannelStore extends ChannelStore {
|
|
|
13
13
|
getChannel(channelId: string): Promise<Channel & {
|
|
14
14
|
pikkuUserId?: string;
|
|
15
15
|
}>;
|
|
16
|
+
setState(channelId: string, state: unknown): Promise<void>;
|
|
17
|
+
getState(channelId: string): Promise<unknown | undefined>;
|
|
18
|
+
clearState(channelId: string): Promise<void>;
|
|
16
19
|
close(): Promise<void>;
|
|
17
20
|
}
|
|
@@ -20,6 +20,7 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
20
20
|
.addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
21
21
|
.addColumn('opening_data', 'text', (col) => col.notNull().defaultTo('{}'))
|
|
22
22
|
.addColumn('pikku_user_id', 'text')
|
|
23
|
+
.addColumn('state', 'text')
|
|
23
24
|
.addColumn('last_wire', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
24
25
|
.execute();
|
|
25
26
|
await this.db.schema
|
|
@@ -76,5 +77,29 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
76
77
|
pikkuUserId: row.pikkuUserId ?? undefined,
|
|
77
78
|
};
|
|
78
79
|
}
|
|
80
|
+
async setState(channelId, state) {
|
|
81
|
+
await this.db
|
|
82
|
+
.updateTable('channels')
|
|
83
|
+
.set({ state: JSON.stringify(state ?? null) })
|
|
84
|
+
.where('channelId', '=', channelId)
|
|
85
|
+
.execute();
|
|
86
|
+
}
|
|
87
|
+
async getState(channelId) {
|
|
88
|
+
const row = await this.db
|
|
89
|
+
.selectFrom('channels')
|
|
90
|
+
.select(['state'])
|
|
91
|
+
.where('channelId', '=', channelId)
|
|
92
|
+
.executeTakeFirst();
|
|
93
|
+
if (!row || !row.state)
|
|
94
|
+
return undefined;
|
|
95
|
+
return parseJson(row.state) ?? undefined;
|
|
96
|
+
}
|
|
97
|
+
async clearState(channelId) {
|
|
98
|
+
await this.db
|
|
99
|
+
.updateTable('channels')
|
|
100
|
+
.set({ state: null })
|
|
101
|
+
.where('channelId', '=', channelId)
|
|
102
|
+
.execute();
|
|
103
|
+
}
|
|
79
104
|
async close() { }
|
|
80
105
|
}
|
|
@@ -20,10 +20,9 @@ export declare class KyselySecretService implements SecretService {
|
|
|
20
20
|
init(): Promise<void>;
|
|
21
21
|
private logAudit;
|
|
22
22
|
private getKEK;
|
|
23
|
-
getSecret(key: string): Promise<
|
|
24
|
-
getSecretJSON<R = {}>(key: string): Promise<R>;
|
|
23
|
+
getSecret<T = string>(key: string): Promise<T>;
|
|
25
24
|
hasSecret(key: string): Promise<boolean>;
|
|
26
|
-
|
|
25
|
+
setSecret(key: string, value: unknown): Promise<void>;
|
|
27
26
|
deleteSecret(key: string): Promise<void>;
|
|
28
27
|
rotateKEK(): Promise<number>;
|
|
29
28
|
}
|
|
@@ -76,10 +76,6 @@ export class KyselySecretService {
|
|
|
76
76
|
await this.logAudit(key, 'read');
|
|
77
77
|
return result;
|
|
78
78
|
}
|
|
79
|
-
async getSecretJSON(key) {
|
|
80
|
-
const raw = await this.getSecret(key);
|
|
81
|
-
return JSON.parse(raw);
|
|
82
|
-
}
|
|
83
79
|
async hasSecret(key) {
|
|
84
80
|
const row = await this.db
|
|
85
81
|
.selectFrom('secrets')
|
|
@@ -88,9 +84,8 @@ export class KyselySecretService {
|
|
|
88
84
|
.executeTakeFirst();
|
|
89
85
|
return !!row;
|
|
90
86
|
}
|
|
91
|
-
async
|
|
92
|
-
const
|
|
93
|
-
const { ciphertext, wrappedDEK } = await envelopeEncrypt(this.key, plaintext);
|
|
87
|
+
async setSecret(key, value) {
|
|
88
|
+
const { ciphertext, wrappedDEK } = await envelopeEncrypt(this.key, value);
|
|
94
89
|
const now = new Date().toISOString();
|
|
95
90
|
await this.db
|
|
96
91
|
.insertInto('secrets')
|
|
@@ -6,6 +6,8 @@ export interface ChannelsTable {
|
|
|
6
6
|
createdAt: Generated<Date>;
|
|
7
7
|
openingData: string;
|
|
8
8
|
pikkuUserId: string | null;
|
|
9
|
+
/** JSON-serialized per-socket channel state, keyed by channelId. */
|
|
10
|
+
state: string | null;
|
|
9
11
|
lastWire: Generated<Date>;
|
|
10
12
|
}
|
|
11
13
|
export interface ChannelSubscriptionsTable {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { SerializedError } from '@pikku/core';
|
|
2
|
+
import type { StepState, WorkflowRunMirror, WorkflowRunWire, WorkflowStatus, WorkflowPlannedStep, WorkflowVersionStatus } from '@pikku/core/workflow';
|
|
3
|
+
import type { Kysely } from 'kysely';
|
|
4
|
+
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
5
|
+
/**
|
|
6
|
+
* Kysely-backed `WorkflowRunMirror`.
|
|
7
|
+
*
|
|
8
|
+
* Forwards executor writes to the same `workflow_runs` / `workflow_step` /
|
|
9
|
+
* `workflow_step_history` / `workflow_versions` tables that
|
|
10
|
+
* `KyselyWorkflowService` uses, so a `KyselyWorkflowRunService` reading
|
|
11
|
+
* those tables will see runs driven by any executor (Cloudflare Durable
|
|
12
|
+
* Object, Redis, MongoDB, in-memory) — not just kysely-driven runs.
|
|
13
|
+
*
|
|
14
|
+
* Errors thrown here are caught by the executor's `safeMirror` wrapper
|
|
15
|
+
* and logged, so a mirror outage cannot break a running workflow.
|
|
16
|
+
*/
|
|
17
|
+
export declare class KyselyWorkflowMirror implements WorkflowRunMirror {
|
|
18
|
+
protected db: Kysely<KyselyPikkuDB>;
|
|
19
|
+
private initialized;
|
|
20
|
+
constructor(db: Kysely<KyselyPikkuDB>);
|
|
21
|
+
/**
|
|
22
|
+
* Create the underlying tables if they don't exist. Safe to call from
|
|
23
|
+
* either the mirror or `KyselyWorkflowService.init()` — schema is the
|
|
24
|
+
* same and uses `ifNotExists`.
|
|
25
|
+
*/
|
|
26
|
+
init(): Promise<void>;
|
|
27
|
+
createRun(runId: string, workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
|
|
28
|
+
deterministic?: boolean;
|
|
29
|
+
plannedSteps?: WorkflowPlannedStep[];
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
updateRunStatus(id: string, status: WorkflowStatus, output?: any, error?: SerializedError): Promise<void>;
|
|
32
|
+
insertStepState(runId: string, step: StepState & {
|
|
33
|
+
stepName: string;
|
|
34
|
+
rpcName: string | null;
|
|
35
|
+
data: any;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
setStepRunning(stepId: string): Promise<void>;
|
|
38
|
+
setStepScheduled(stepId: string): Promise<void>;
|
|
39
|
+
setStepResult(stepId: string, result: any): Promise<void>;
|
|
40
|
+
setStepChildRunId(stepId: string, childRunId: string): Promise<void>;
|
|
41
|
+
setStepError(stepId: string, error: SerializedError): Promise<void>;
|
|
42
|
+
createRetryAttempt(failedStepId: string, newStep: StepState & {
|
|
43
|
+
stepName: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
setBranchTaken(stepId: string, branchKey: string): Promise<void>;
|
|
46
|
+
updateRunState(runId: string, name: string, value: unknown): Promise<void>;
|
|
47
|
+
upsertWorkflowVersion(name: string, graphHash: string, graph: any, source: string, status?: WorkflowVersionStatus): Promise<void>;
|
|
48
|
+
updateWorkflowVersionStatus(name: string, graphHash: string, status: WorkflowVersionStatus): Promise<void>;
|
|
49
|
+
private updateStepStatus;
|
|
50
|
+
private insertHistoryRecord;
|
|
51
|
+
/**
|
|
52
|
+
* The executor pattern is: `insertStepState` writes a 'pending' history
|
|
53
|
+
* row, then later transitions UPDATE that latest row in place rather
|
|
54
|
+
* than appending a new one — except for retry attempts which append.
|
|
55
|
+
*
|
|
56
|
+
* We mirror the same shape so `KyselyWorkflowRunService.getRunHistory`
|
|
57
|
+
* returns the same sequence the executor would have produced.
|
|
58
|
+
*/
|
|
59
|
+
private appendOrUpdateLatestHistory;
|
|
60
|
+
}
|