@sixb/sqlite 0.1.17 → 0.1.19
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/dist/action-run-storage.d.ts +4 -9
- package/dist/action-run-storage.d.ts.map +1 -1
- package/dist/index.js +248 -284
- package/dist/index.js.map +4 -4
- package/dist/migrations.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/action-run-storage.ts +104 -442
- package/src/migrations/060-terminal-action-runs.sql +149 -0
- package/src/migrations.ts +2 -0
|
@@ -5,31 +5,21 @@ import {
|
|
|
5
5
|
serializeActionRunFailure,
|
|
6
6
|
} from "@sixb/core/internal/action-run-storage"
|
|
7
7
|
import { assertActionRunExecution } from "@sixb/core/internal/action-run-storage-provider"
|
|
8
|
-
import {
|
|
9
|
-
actionRunPhaseRecordsEqual,
|
|
10
|
-
canRequeueActionRunAfterEnqueueFailure,
|
|
11
|
-
finishActionRunPhase,
|
|
12
|
-
} from "@sixb/core/internal/storage"
|
|
8
|
+
import { normalizeRecordActionRunInput, resolveActionRunEffects } from "@sixb/core/internal/storage"
|
|
13
9
|
import type {
|
|
14
10
|
ActionRunEffectsRecord,
|
|
15
|
-
ActionRunFailure,
|
|
16
|
-
ActionRunParams,
|
|
17
11
|
ActionRunPhase,
|
|
18
12
|
ActionRunRecord,
|
|
13
|
+
ActionRunStatus,
|
|
19
14
|
ActionRunStorage,
|
|
20
15
|
ActionRunWritebackRecord,
|
|
21
|
-
EnterActionRunPhaseInput,
|
|
22
16
|
ExecutionStorage,
|
|
23
|
-
FinishActionRunInput,
|
|
24
17
|
ListActionRunsInput,
|
|
25
18
|
ListActionRunsResult,
|
|
26
|
-
LockActionMaterializationRunInput,
|
|
27
|
-
QueueActionRunInput,
|
|
28
19
|
RecordActionEffectsInput,
|
|
29
|
-
|
|
30
|
-
StartActionRunInput,
|
|
20
|
+
RecordActionRunInput,
|
|
31
21
|
} from "@sixb/core/storage"
|
|
32
|
-
import { ActionRunError
|
|
22
|
+
import { ActionRunError } from "@sixb/core/storage"
|
|
33
23
|
import { installFreshSqliteSchema } from "./migrations"
|
|
34
24
|
import {
|
|
35
25
|
closeSqliteStoreConnection,
|
|
@@ -62,293 +52,84 @@ export class SqliteActionRunStorage implements ActionRunStorage {
|
|
|
62
52
|
}
|
|
63
53
|
}
|
|
64
54
|
|
|
65
|
-
async
|
|
66
|
-
const
|
|
67
|
-
.query(`SELECT action_id, status FROM action_runs WHERE project_id = ? AND id = ?`)
|
|
68
|
-
.get(input.projectId, input.runId) as {
|
|
69
|
-
readonly action_id: string
|
|
70
|
-
readonly status: string
|
|
71
|
-
} | null
|
|
72
|
-
if (!row) {
|
|
73
|
-
throw new ActionRunError(
|
|
74
|
-
`[SixbSqlite] Action run '${input.runId}' not found for project '${input.projectId}'.`
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
if (row.action_id !== input.actionId) {
|
|
78
|
-
throw new ActionRunError(
|
|
79
|
-
`[SixbSqlite] Action run '${input.runId}' does not belong to action '${input.actionId}'.`
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
if (row.status !== "running") {
|
|
83
|
-
throw new ActionRunError(
|
|
84
|
-
`[SixbSqlite] Action run '${input.runId}' cannot materialize from status '${row.status}'.`
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
const record = await this.getById({ projectId: input.projectId, id: input.runId })
|
|
88
|
-
if (!record) {
|
|
89
|
-
throw new ActionRunError(
|
|
90
|
-
`[SixbSqlite] Action run '${input.runId}' disappeared while locking materialization.`
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
return record
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async queue(input: QueueActionRunInput): Promise<ActionRunRecord> {
|
|
97
|
-
const queuedAt = input.queuedAt ?? new Date()
|
|
55
|
+
async record(input: RecordActionRunInput): Promise<ActionRunRecord> {
|
|
56
|
+
const record = normalizeRecordActionRunInput(input)
|
|
98
57
|
await assertActionRunExecution({
|
|
99
58
|
executions: this.executions,
|
|
100
|
-
projectId:
|
|
101
|
-
executionId:
|
|
102
|
-
runId:
|
|
103
|
-
actionId:
|
|
59
|
+
projectId: record.projectId,
|
|
60
|
+
executionId: record.executionId,
|
|
61
|
+
runId: record.id,
|
|
62
|
+
actionId: record.actionId,
|
|
104
63
|
})
|
|
105
64
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
.query(
|
|
109
|
-
`
|
|
110
|
-
INSERT INTO action_runs (
|
|
111
|
-
project_id,
|
|
112
|
-
id,
|
|
113
|
-
execution_id,
|
|
114
|
-
action_id,
|
|
115
|
-
subject_kind,
|
|
116
|
-
object_type_id,
|
|
117
|
-
primary_id,
|
|
118
|
-
status,
|
|
119
|
-
phase,
|
|
120
|
-
queued_at,
|
|
121
|
-
started_at,
|
|
122
|
-
finished_at,
|
|
123
|
-
params,
|
|
124
|
-
idempotency_key,
|
|
125
|
-
writeback_status,
|
|
126
|
-
writeback_completed_at,
|
|
127
|
-
writeback_result,
|
|
128
|
-
writeback_error,
|
|
129
|
-
effects_status,
|
|
130
|
-
effects_completed_at,
|
|
131
|
-
effects_error,
|
|
132
|
-
error
|
|
133
|
-
) VALUES (
|
|
134
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?,
|
|
135
|
-
NULL, NULL, NULL, NULL,
|
|
136
|
-
NULL, NULL, NULL,
|
|
137
|
-
NULL
|
|
138
|
-
)
|
|
65
|
+
const inserted = this.db
|
|
66
|
+
.query(
|
|
139
67
|
`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
68
|
+
INSERT INTO action_runs (
|
|
69
|
+
project_id,
|
|
70
|
+
id,
|
|
71
|
+
execution_id,
|
|
72
|
+
action_id,
|
|
73
|
+
subject_kind,
|
|
74
|
+
object_type_id,
|
|
75
|
+
primary_id,
|
|
76
|
+
status,
|
|
77
|
+
phase,
|
|
78
|
+
started_at,
|
|
79
|
+
finished_at,
|
|
80
|
+
params,
|
|
81
|
+
idempotency_key,
|
|
82
|
+
writeback_status,
|
|
83
|
+
writeback_completed_at,
|
|
84
|
+
writeback_result,
|
|
85
|
+
writeback_error,
|
|
86
|
+
error
|
|
87
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
88
|
+
ON CONFLICT DO NOTHING
|
|
89
|
+
`
|
|
90
|
+
)
|
|
91
|
+
.run(
|
|
92
|
+
record.projectId,
|
|
93
|
+
record.id,
|
|
94
|
+
record.executionId,
|
|
95
|
+
record.actionId,
|
|
96
|
+
record.subject.kind,
|
|
97
|
+
record.subject.kind === "object" ? record.subject.objectTypeId : null,
|
|
98
|
+
record.subject.kind === "object" ? record.subject.primaryId : null,
|
|
99
|
+
record.status,
|
|
100
|
+
record.phase,
|
|
101
|
+
record.startedAt.toISOString(),
|
|
102
|
+
record.finishedAt.toISOString(),
|
|
103
|
+
JSON.stringify(record.params),
|
|
104
|
+
record.idempotencyKey,
|
|
105
|
+
record.writeback?.status ?? null,
|
|
106
|
+
record.writeback?.completedAt.toISOString() ?? null,
|
|
107
|
+
record.writeback?.status === "succeeded" ? JSON.stringify(record.writeback.result) : null,
|
|
108
|
+
record.writeback?.status === "failed"
|
|
109
|
+
? serializeActionRunFailure(record.writeback.error, "writeback")
|
|
110
|
+
: null,
|
|
111
|
+
record.status === "failed" ? serializeActionRunFailure(record.error) : null
|
|
112
|
+
)
|
|
162
113
|
|
|
163
|
-
|
|
164
|
-
|
|
114
|
+
if (inserted.changes === 0) {
|
|
115
|
+
const conflict = this.selectRow(record.projectId, record.id)
|
|
165
116
|
throw new ActionRunError(
|
|
166
|
-
|
|
117
|
+
conflict
|
|
118
|
+
? `[SixbSqlite] Action run '${record.id}' is already recorded for project '${record.projectId}'.`
|
|
119
|
+
: `[SixbSqlite] Execution '${record.executionId}' already belongs to another Action run.`
|
|
167
120
|
)
|
|
168
121
|
}
|
|
169
|
-
|
|
170
|
-
return record
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private async requeueAfterEnqueueFailure(
|
|
174
|
-
input: QueueActionRunInput,
|
|
175
|
-
queuedAt: Date
|
|
176
|
-
): Promise<ActionRunRecord> {
|
|
177
|
-
return this.db.transaction(() => {
|
|
178
|
-
const existing = this.db
|
|
179
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
180
|
-
.get(input.projectId, input.id) as DatabaseRow | null
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
!existing ||
|
|
184
|
-
!canRequeueActionRunAfterEnqueueFailure(rowToActionRunRecord(existing), input)
|
|
185
|
-
) {
|
|
186
|
-
throw new ActionRunError(
|
|
187
|
-
`[SixbSqlite] Action run '${input.id}' already exists for project '${input.projectId}'.`
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
this.db
|
|
192
|
-
.query(
|
|
193
|
-
`
|
|
194
|
-
UPDATE action_runs
|
|
195
|
-
SET
|
|
196
|
-
status = ?,
|
|
197
|
-
phase = ?,
|
|
198
|
-
queued_at = ?,
|
|
199
|
-
started_at = NULL,
|
|
200
|
-
finished_at = NULL,
|
|
201
|
-
writeback_status = NULL,
|
|
202
|
-
writeback_completed_at = NULL,
|
|
203
|
-
writeback_result = NULL,
|
|
204
|
-
writeback_error = NULL,
|
|
205
|
-
effects_status = NULL,
|
|
206
|
-
effects_completed_at = NULL,
|
|
207
|
-
effects_error = NULL,
|
|
208
|
-
error = NULL
|
|
209
|
-
WHERE project_id = ? AND id = ?
|
|
210
|
-
`
|
|
211
|
-
)
|
|
212
|
-
.run("queued", "request", queuedAt.toISOString(), input.projectId, input.id)
|
|
213
|
-
|
|
214
|
-
const updated = this.db
|
|
215
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
216
|
-
.get(input.projectId, input.id) as DatabaseRow
|
|
217
|
-
|
|
218
|
-
return rowToActionRunRecord(updated)
|
|
219
|
-
})()
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async start(input: StartActionRunInput): Promise<ActionRunRecord> {
|
|
223
|
-
return this.db.transaction(() => {
|
|
224
|
-
const existing = this.db
|
|
225
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
226
|
-
.get(input.projectId, input.id) as DatabaseRow | null
|
|
227
|
-
|
|
228
|
-
if (!existing) {
|
|
229
|
-
throw new ActionRunError(
|
|
230
|
-
`[SixbSqlite] Action run '${input.id}' not found for project '${input.projectId}'.`
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (existing.status !== "queued") {
|
|
235
|
-
throw new ActionRunError(
|
|
236
|
-
`[SixbSqlite] Action run '${input.id}' cannot start from status '${existing.status}'.`
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
this.db
|
|
241
|
-
.query(
|
|
242
|
-
`
|
|
243
|
-
UPDATE action_runs
|
|
244
|
-
SET
|
|
245
|
-
status = ?,
|
|
246
|
-
phase = ?,
|
|
247
|
-
started_at = ?,
|
|
248
|
-
error = NULL
|
|
249
|
-
WHERE project_id = ? AND id = ?
|
|
250
|
-
`
|
|
251
|
-
)
|
|
252
|
-
.run(
|
|
253
|
-
"running",
|
|
254
|
-
input.phase ?? "validation",
|
|
255
|
-
(input.startedAt ?? new Date()).toISOString(),
|
|
256
|
-
input.projectId,
|
|
257
|
-
input.id
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
const updated = this.db
|
|
261
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
262
|
-
.get(input.projectId, input.id) as DatabaseRow
|
|
263
|
-
|
|
264
|
-
return rowToActionRunRecord(updated)
|
|
265
|
-
})()
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async enterPhase(input: EnterActionRunPhaseInput): Promise<ActionRunRecord> {
|
|
269
|
-
return this.db.transaction(() => {
|
|
270
|
-
this.requireRunningRun(input.projectId, input.id, "transition phase")
|
|
271
|
-
|
|
272
|
-
this.db
|
|
273
|
-
.query(
|
|
274
|
-
`
|
|
275
|
-
UPDATE action_runs
|
|
276
|
-
SET phase = ?
|
|
277
|
-
WHERE project_id = ? AND id = ?
|
|
278
|
-
`
|
|
279
|
-
)
|
|
280
|
-
.run(input.phase, input.projectId, input.id)
|
|
281
|
-
|
|
282
|
-
const updated = this.db
|
|
283
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
284
|
-
.get(input.projectId, input.id) as DatabaseRow
|
|
285
|
-
|
|
286
|
-
return rowToActionRunRecord(updated)
|
|
287
|
-
})()
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async recordWriteback(input: RecordActionWritebackInput): Promise<ActionRunRecord> {
|
|
291
|
-
return this.db.transaction(() => {
|
|
292
|
-
const existing = this.requireRunningRun(input.projectId, input.id, "record writeback")
|
|
293
|
-
const nextWriteback = toWritebackRecord(input, new Date(input.completedAt ?? new Date()))
|
|
294
|
-
const currentWriteback = toActionRunWritebackRecord(existing)
|
|
295
|
-
|
|
296
|
-
if (currentWriteback) {
|
|
297
|
-
if (actionRunPhaseRecordsEqual(currentWriteback, nextWriteback)) {
|
|
298
|
-
return rowToActionRunRecord(existing)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
throw new ActionRunError(
|
|
302
|
-
`[SixbSqlite] Action run '${input.id}' already has a different writeback record.`
|
|
303
|
-
)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
this.db
|
|
307
|
-
.query(
|
|
308
|
-
`
|
|
309
|
-
UPDATE action_runs
|
|
310
|
-
SET
|
|
311
|
-
phase = ?,
|
|
312
|
-
writeback_status = ?,
|
|
313
|
-
writeback_completed_at = ?,
|
|
314
|
-
writeback_result = ?,
|
|
315
|
-
writeback_error = ?
|
|
316
|
-
WHERE project_id = ? AND id = ?
|
|
317
|
-
`
|
|
318
|
-
)
|
|
319
|
-
.run(
|
|
320
|
-
"writeback",
|
|
321
|
-
input.status,
|
|
322
|
-
nextWriteback.completedAt.toISOString(),
|
|
323
|
-
input.status === "succeeded" ? serializeJsonValue(input.result) : null,
|
|
324
|
-
input.status === "failed" ? serializeActionRunFailure(input.error, "writeback") : null,
|
|
325
|
-
input.projectId,
|
|
326
|
-
input.id
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
const updated = this.db
|
|
330
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
331
|
-
.get(input.projectId, input.id) as DatabaseRow
|
|
332
|
-
|
|
333
|
-
return rowToActionRunRecord(updated)
|
|
334
|
-
})()
|
|
122
|
+
return rowToActionRunRecord(this.requireRow(record.projectId, record.id))
|
|
335
123
|
}
|
|
336
124
|
|
|
337
125
|
async recordEffects(input: RecordActionEffectsInput): Promise<ActionRunRecord> {
|
|
338
126
|
return this.db.transaction(() => {
|
|
339
|
-
const existing = this.
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return rowToActionRunRecord(existing)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
throw new ActionRunError(
|
|
349
|
-
`[SixbSqlite] Action run '${input.id}' already has a different effects record.`
|
|
350
|
-
)
|
|
351
|
-
}
|
|
127
|
+
const existing = this.selectRow(input.projectId, input.id)
|
|
128
|
+
const { run, effects } = resolveActionRunEffects(
|
|
129
|
+
existing ? rowToActionRunRecord(existing) : null,
|
|
130
|
+
input
|
|
131
|
+
)
|
|
132
|
+
if (!effects) return run
|
|
352
133
|
|
|
353
134
|
this.db
|
|
354
135
|
.query(
|
|
@@ -364,67 +145,13 @@ export class SqliteActionRunStorage implements ActionRunStorage {
|
|
|
364
145
|
)
|
|
365
146
|
.run(
|
|
366
147
|
"effects",
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
148
|
+
effects.status,
|
|
149
|
+
effects.completedAt.toISOString(),
|
|
150
|
+
effects.status === "failed" ? serializeActionRunFailure(effects.error, "effects") : null,
|
|
370
151
|
input.projectId,
|
|
371
152
|
input.id
|
|
372
153
|
)
|
|
373
|
-
|
|
374
|
-
const updated = this.db
|
|
375
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
376
|
-
.get(input.projectId, input.id) as DatabaseRow
|
|
377
|
-
|
|
378
|
-
return rowToActionRunRecord(updated)
|
|
379
|
-
})()
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async finish(input: FinishActionRunInput): Promise<ActionRunRecord> {
|
|
383
|
-
return this.db.transaction(() => {
|
|
384
|
-
const existing = this.db
|
|
385
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
386
|
-
.get(input.projectId, input.id) as DatabaseRow | null
|
|
387
|
-
|
|
388
|
-
if (!existing) {
|
|
389
|
-
throw new ActionRunError(
|
|
390
|
-
`[SixbSqlite] Action run '${input.id}' not found for project '${input.projectId}'.`
|
|
391
|
-
)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (isTerminalActionRun({ status: existing.status })) {
|
|
395
|
-
throw new ActionRunError(
|
|
396
|
-
`[SixbSqlite] Action run '${input.id}' cannot finish from terminal status '${existing.status}'.`
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const phase = finishActionRunPhase(input, existing.phase)
|
|
401
|
-
|
|
402
|
-
this.db
|
|
403
|
-
.query(
|
|
404
|
-
`
|
|
405
|
-
UPDATE action_runs
|
|
406
|
-
SET
|
|
407
|
-
status = ?,
|
|
408
|
-
phase = ?,
|
|
409
|
-
finished_at = ?,
|
|
410
|
-
error = ?
|
|
411
|
-
WHERE project_id = ? AND id = ?
|
|
412
|
-
`
|
|
413
|
-
)
|
|
414
|
-
.run(
|
|
415
|
-
input.status,
|
|
416
|
-
phase,
|
|
417
|
-
(input.finishedAt ?? new Date()).toISOString(),
|
|
418
|
-
input.status === "succeeded" ? null : serializeActionRunFailure(input.error),
|
|
419
|
-
input.projectId,
|
|
420
|
-
input.id
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
const updated = this.db
|
|
424
|
-
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
425
|
-
.get(input.projectId, input.id) as DatabaseRow
|
|
426
|
-
|
|
427
|
-
return rowToActionRunRecord(updated)
|
|
154
|
+
return rowToActionRunRecord(this.requireRow(input.projectId, input.id))
|
|
428
155
|
})()
|
|
429
156
|
}
|
|
430
157
|
|
|
@@ -503,12 +230,12 @@ export class SqliteActionRunStorage implements ActionRunStorage {
|
|
|
503
230
|
}
|
|
504
231
|
|
|
505
232
|
if (input.startedAfter) {
|
|
506
|
-
whereClauses.push("
|
|
233
|
+
whereClauses.push("started_at >= ?")
|
|
507
234
|
args.push(input.startedAfter.toISOString())
|
|
508
235
|
}
|
|
509
236
|
|
|
510
237
|
if (input.startedBefore) {
|
|
511
|
-
whereClauses.push("
|
|
238
|
+
whereClauses.push("started_at <= ?")
|
|
512
239
|
args.push(input.startedBefore.toISOString())
|
|
513
240
|
}
|
|
514
241
|
|
|
@@ -524,7 +251,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
|
|
|
524
251
|
let query = `
|
|
525
252
|
SELECT * FROM action_runs
|
|
526
253
|
${where}
|
|
527
|
-
ORDER BY
|
|
254
|
+
ORDER BY started_at ${order}, id ${order}
|
|
528
255
|
`
|
|
529
256
|
const queryArgs = [...args]
|
|
530
257
|
|
|
@@ -550,51 +277,29 @@ export class SqliteActionRunStorage implements ActionRunStorage {
|
|
|
550
277
|
closeSqliteStoreConnection(this.connection)
|
|
551
278
|
}
|
|
552
279
|
|
|
553
|
-
private
|
|
554
|
-
|
|
280
|
+
private selectRow(projectId: string, id: string): DatabaseRow | null {
|
|
281
|
+
return this.db
|
|
555
282
|
.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?")
|
|
556
283
|
.get(projectId, id) as DatabaseRow | null
|
|
284
|
+
}
|
|
557
285
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
)
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (existing.status !== "running") {
|
|
286
|
+
private requireRow(projectId: string, id: string): DatabaseRow {
|
|
287
|
+
const row = this.selectRow(projectId, id)
|
|
288
|
+
if (!row) {
|
|
565
289
|
throw new ActionRunError(
|
|
566
|
-
`[SixbSqlite] Action run '${id}'
|
|
290
|
+
`[SixbSqlite] Action run '${id}' disappeared from project '${projectId}' while recording it.`
|
|
567
291
|
)
|
|
568
292
|
}
|
|
569
|
-
|
|
570
|
-
return existing
|
|
293
|
+
return row
|
|
571
294
|
}
|
|
572
295
|
}
|
|
573
296
|
|
|
574
|
-
function serializeJsonValue(value: JsonValue): string {
|
|
575
|
-
return JSON.stringify(value)
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function toActionRunFailure(row: DatabaseRow): ActionRunFailure | undefined {
|
|
579
|
-
return row.error === null ? undefined : parseActionRunFailure(row.error)
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function parsePhaseFailure<TPhase extends Extract<ActionRunPhase, "writeback" | "effects">>(
|
|
583
|
-
value: string | null,
|
|
584
|
-
expectedPhase: TPhase
|
|
585
|
-
): ActionRunFailure<TPhase> {
|
|
586
|
-
return parseActionRunFailure(value, expectedPhase)
|
|
587
|
-
}
|
|
588
|
-
|
|
589
297
|
function toActionRunWritebackRecord(row: DatabaseRow): ActionRunWritebackRecord | undefined {
|
|
590
298
|
if (!row.writeback_status) {
|
|
591
299
|
return undefined
|
|
592
300
|
}
|
|
593
301
|
|
|
594
|
-
const completedAt = row.writeback_completed_at
|
|
595
|
-
? new Date(row.writeback_completed_at)
|
|
596
|
-
: new Date(row.queued_at)
|
|
597
|
-
|
|
302
|
+
const completedAt = new Date(row.writeback_completed_at ?? row.finished_at)
|
|
598
303
|
if (row.writeback_status === "succeeded") {
|
|
599
304
|
return {
|
|
600
305
|
status: "succeeded",
|
|
@@ -607,7 +312,7 @@ function toActionRunWritebackRecord(row: DatabaseRow): ActionRunWritebackRecord
|
|
|
607
312
|
return {
|
|
608
313
|
status: "failed",
|
|
609
314
|
completedAt,
|
|
610
|
-
error:
|
|
315
|
+
error: parseActionRunFailure(row.writeback_error, "writeback"),
|
|
611
316
|
}
|
|
612
317
|
}
|
|
613
318
|
|
|
@@ -616,10 +321,7 @@ function toActionRunEffectsRecord(row: DatabaseRow): ActionRunEffectsRecord | un
|
|
|
616
321
|
return undefined
|
|
617
322
|
}
|
|
618
323
|
|
|
619
|
-
const completedAt = row.effects_completed_at
|
|
620
|
-
? new Date(row.effects_completed_at)
|
|
621
|
-
: new Date(row.queued_at)
|
|
622
|
-
|
|
324
|
+
const completedAt = new Date(row.effects_completed_at ?? row.finished_at)
|
|
623
325
|
if (row.effects_status === "succeeded") {
|
|
624
326
|
return {
|
|
625
327
|
status: "succeeded",
|
|
@@ -630,65 +332,30 @@ function toActionRunEffectsRecord(row: DatabaseRow): ActionRunEffectsRecord | un
|
|
|
630
332
|
return {
|
|
631
333
|
status: "failed",
|
|
632
334
|
completedAt,
|
|
633
|
-
error:
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function toWritebackRecord(
|
|
638
|
-
input: RecordActionWritebackInput,
|
|
639
|
-
completedAt: Date
|
|
640
|
-
): ActionRunWritebackRecord {
|
|
641
|
-
if (input.status === "succeeded") {
|
|
642
|
-
return {
|
|
643
|
-
status: "succeeded",
|
|
644
|
-
completedAt,
|
|
645
|
-
result: input.result,
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return {
|
|
650
|
-
status: "failed",
|
|
651
|
-
completedAt,
|
|
652
|
-
error: input.error,
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function toEffectsRecord(
|
|
657
|
-
input: RecordActionEffectsInput,
|
|
658
|
-
completedAt: Date
|
|
659
|
-
): ActionRunEffectsRecord {
|
|
660
|
-
if (input.status === "succeeded") {
|
|
661
|
-
return {
|
|
662
|
-
status: "succeeded",
|
|
663
|
-
completedAt,
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return {
|
|
668
|
-
status: "failed",
|
|
669
|
-
completedAt,
|
|
670
|
-
error: input.error,
|
|
335
|
+
error: parseActionRunFailure(row.effects_error, "effects"),
|
|
671
336
|
}
|
|
672
337
|
}
|
|
673
338
|
|
|
674
339
|
function rowToActionRunRecord(row: DatabaseRow): ActionRunRecord {
|
|
675
|
-
|
|
340
|
+
const writeback = toActionRunWritebackRecord(row)
|
|
341
|
+
const effects = toActionRunEffectsRecord(row)
|
|
342
|
+
const fields = {
|
|
676
343
|
id: row.id,
|
|
677
344
|
projectId: row.project_id,
|
|
678
345
|
executionId: row.execution_id,
|
|
679
346
|
actionId: row.action_id,
|
|
680
347
|
subject: rowToActionSubject(row),
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
686
|
-
params: JSON.parse(row.params) as ActionRunParams,
|
|
348
|
+
phase: row.phase,
|
|
349
|
+
startedAt: new Date(row.started_at),
|
|
350
|
+
finishedAt: new Date(row.finished_at),
|
|
351
|
+
params: JSON.parse(row.params) as ActionRunRecord["params"],
|
|
687
352
|
idempotencyKey: row.idempotency_key,
|
|
688
|
-
writeback:
|
|
689
|
-
effects:
|
|
690
|
-
error: toActionRunFailure(row),
|
|
353
|
+
...(writeback === undefined ? {} : { writeback }),
|
|
354
|
+
...(effects === undefined ? {} : { effects }),
|
|
691
355
|
}
|
|
356
|
+
|
|
357
|
+
if (row.status === "succeeded") return { ...fields, status: "succeeded" }
|
|
358
|
+
return { ...fields, status: "failed", error: parseActionRunFailure(row.error) }
|
|
692
359
|
}
|
|
693
360
|
|
|
694
361
|
function rowToActionSubject(row: DatabaseRow): ActionSubject {
|
|
@@ -707,10 +374,6 @@ function rowToActionSubject(row: DatabaseRow): ActionSubject {
|
|
|
707
374
|
}
|
|
708
375
|
}
|
|
709
376
|
|
|
710
|
-
function isUniqueConstraintError(error: unknown): boolean {
|
|
711
|
-
return error instanceof Error && error.message.includes("UNIQUE constraint failed")
|
|
712
|
-
}
|
|
713
|
-
|
|
714
377
|
interface DatabaseRow {
|
|
715
378
|
project_id: string
|
|
716
379
|
id: string
|
|
@@ -719,11 +382,10 @@ interface DatabaseRow {
|
|
|
719
382
|
subject_kind: ActionSubject["kind"]
|
|
720
383
|
object_type_id: string | null
|
|
721
384
|
primary_id: string | null
|
|
722
|
-
status:
|
|
723
|
-
phase: ActionRunPhase
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
finished_at: string | null
|
|
385
|
+
status: ActionRunStatus
|
|
386
|
+
phase: ActionRunPhase
|
|
387
|
+
started_at: string
|
|
388
|
+
finished_at: string
|
|
727
389
|
params: string
|
|
728
390
|
idempotency_key: string
|
|
729
391
|
writeback_status: ActionRunWritebackRecord["status"] | null
|