@sixb/pg 0.1.1 → 0.1.3
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 +7 -6
- package/dist/agents/agent-storage.d.ts +7 -4
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/checkpoints.d.ts +12 -0
- package/dist/agents/checkpoints.d.ts.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/messages.d.ts +2 -1
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +22 -12
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -2
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/auth-storage/service-accounts.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +0 -4
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/connector-connection-storage.d.ts +9 -0
- package/dist/connector-connection-storage.d.ts.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5010 -1837
- package/dist/index.js.map +31 -26
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +0 -2
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +3 -6
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/pg-action-run-storage.d.ts +3 -2
- package/dist/pg-action-run-storage.d.ts.map +1 -1
- package/dist/pg-ai-cost-storage.d.ts +12 -0
- package/dist/pg-ai-cost-storage.d.ts.map +1 -0
- package/dist/pg-ai-usage-storage.d.ts +16 -0
- package/dist/pg-ai-usage-storage.d.ts.map +1 -0
- package/dist/pg-execution-storage.d.ts +14 -0
- package/dist/pg-execution-storage.d.ts.map +1 -0
- package/dist/pg-pipeline-run-storage.d.ts +5 -2
- package/dist/pg-pipeline-run-storage.d.ts.map +1 -1
- package/dist/pg-projection-run-storage.d.ts +5 -2
- package/dist/pg-projection-run-storage.d.ts.map +1 -1
- package/dist/pg-sync-run-storage.d.ts +5 -2
- package/dist/pg-sync-run-storage.d.ts.map +1 -1
- package/dist/pg-webhook-run-storage.d.ts +5 -2
- package/dist/pg-webhook-run-storage.d.ts.map +1 -1
- package/dist/pg-workflow-run-storage.d.ts +5 -3
- package/dist/pg-workflow-run-storage.d.ts.map +1 -1
- package/dist/run-list-query.d.ts +1 -1
- package/dist/run-list-query.d.ts.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/agents/agent-storage.ts +9 -5
- package/src/agents/checkpoints.ts +175 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/messages.ts +71 -13
- package/src/agents/rows.ts +57 -43
- package/src/agents/runs.ts +29 -15
- package/src/auth-storage/service-accounts.ts +14 -15
- package/src/auth-storage/shared.ts +0 -8
- package/src/connector-connection-storage.ts +760 -0
- package/src/index.ts +38 -15
- package/src/latest-run-query.ts +7 -1
- package/src/migrations/004-executions.sql +140 -0
- package/src/migrations/005-workflow-executions.sql +10 -0
- package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
- package/src/migrations/007-split-overrides.sql +153 -0
- package/src/migrations/008-action-executions.sql +7 -0
- package/src/migrations/009-agent-executions.sql +21 -0
- package/src/migrations/010-ai-usage-accounting-foundation.sql +76 -0
- package/src/migrations/011-sync-failure-record.sql +23 -0
- package/src/migrations/012-pipeline-failure-record.sql +47 -0
- package/src/migrations/013-workflow-failure-record.sql +45 -0
- package/src/migrations/014-agent-failure-record.sql +56 -0
- package/src/migrations/015-projection-failure-record.sql +20 -0
- package/src/migrations/016-webhook-run-failure-record.sql +19 -0
- package/src/migrations/017-action-failure-record.sql +65 -0
- package/src/migrations/018-ontology-outbox-failure-record.sql +29 -0
- package/src/migrations/019-webhook-delivery-failure-record.sql +27 -0
- package/src/migrations/020-drop-run-usage-projections.sql +8 -0
- package/src/migrations/021-sync-pipeline-executions.sql +50 -0
- package/src/migrations/022-projection-executions.sql +115 -0
- package/src/migrations/023-webhook-executions.sql +30 -0
- package/src/migrations/024-ontology-commit-executions.sql +12 -0
- package/src/migrations/025-connector-connections.sql +231 -0
- package/src/migrations/026-ai-cost-accounting.sql +53 -0
- package/src/migrations/027-agent-context-checkpoints.sql +34 -0
- package/src/migrations/028-object-override-edit-times.sql +3 -0
- package/src/migrations.ts +82 -0
- package/src/ontology-storage/materialization-session.ts +25 -28
- package/src/ontology-storage/materialization-state.ts +161 -43
- package/src/ontology-storage/materialization-writer.ts +149 -76
- package/src/ontology-storage/materializations.ts +3 -1
- package/src/ontology-storage/outbox.ts +9 -2
- package/src/ontology-storage/shared.ts +10 -20
- package/src/pg-action-run-storage.ts +43 -67
- package/src/pg-ai-cost-storage.ts +707 -0
- package/src/pg-ai-usage-storage.ts +290 -0
- package/src/pg-execution-storage.ts +147 -0
- package/src/pg-pipeline-run-storage.ts +107 -41
- package/src/pg-projection-run-storage.ts +159 -91
- package/src/pg-sync-run-storage.ts +100 -28
- package/src/pg-webhook-run-storage.ts +89 -57
- package/src/pg-workflow-run-storage.ts +97 -130
- package/src/run-list-query.ts +9 -4
- package/src/testing.ts +18 -0
- package/dist/pg-webhook-delivery-storage.d.ts +0 -17
- package/dist/pg-webhook-delivery-storage.d.ts.map +0 -1
- package/src/pg-webhook-delivery-storage.ts +0 -180
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import type { JsonValue } from "@sixb/core"
|
|
2
|
+
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
3
|
+
import { assertSyncRunExecution } from "@sixb/core/internal/sync-run-storage-provider"
|
|
2
4
|
import type {
|
|
5
|
+
ExecutionStorage,
|
|
3
6
|
FinishSyncRunInput,
|
|
4
7
|
ListLatestSyncRunsInput,
|
|
5
8
|
ListLatestSyncRunsResult,
|
|
6
9
|
ListSyncRunsInput,
|
|
7
10
|
ListSyncRunsResult,
|
|
11
|
+
QueueSyncRunInput,
|
|
8
12
|
StartSyncRunInput,
|
|
9
|
-
SyncRunFailure,
|
|
10
13
|
SyncRunRecord,
|
|
11
14
|
SyncRunStorage,
|
|
12
15
|
} from "@sixb/core/storage"
|
|
13
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
canRequeueSyncRunAfterEnqueueFailure,
|
|
18
|
+
SYNC_RUN_FAILURE_CODES,
|
|
19
|
+
SyncRunError,
|
|
20
|
+
} from "@sixb/core/storage"
|
|
14
21
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
15
22
|
import type { SqlParameter } from "./pg-client"
|
|
16
23
|
import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "./run-list-query"
|
|
@@ -18,29 +25,43 @@ import { isUniqueViolation } from "./storage-errors"
|
|
|
18
25
|
import { type PgStoreClient, runPgTransaction } from "./transactions"
|
|
19
26
|
|
|
20
27
|
export class PgSyncRunStorage implements SyncRunStorage {
|
|
21
|
-
constructor(
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly sql: PgStoreClient,
|
|
30
|
+
private readonly executions: ExecutionStorage
|
|
31
|
+
) {}
|
|
22
32
|
|
|
23
|
-
async
|
|
33
|
+
async queue(input: QueueSyncRunInput): Promise<SyncRunRecord> {
|
|
34
|
+
await assertSyncRunExecution({
|
|
35
|
+
executions: this.executions,
|
|
36
|
+
projectId: input.projectId,
|
|
37
|
+
executionId: input.executionId,
|
|
38
|
+
runId: input.id,
|
|
39
|
+
syncId: input.syncId,
|
|
40
|
+
})
|
|
24
41
|
try {
|
|
25
42
|
const [row] = await this.sql<DatabaseRow[]>`
|
|
26
43
|
INSERT INTO sync_runs (
|
|
27
44
|
project_id,
|
|
28
45
|
id,
|
|
46
|
+
execution_id,
|
|
29
47
|
sync_id,
|
|
30
48
|
dataset_id,
|
|
31
49
|
mode,
|
|
32
50
|
status,
|
|
51
|
+
queued_at,
|
|
33
52
|
started_at,
|
|
34
53
|
expected_latest_version_id,
|
|
35
54
|
commit_message
|
|
36
55
|
) VALUES (
|
|
37
56
|
${input.projectId},
|
|
38
57
|
${input.id},
|
|
58
|
+
${input.executionId},
|
|
39
59
|
${input.syncId},
|
|
40
60
|
${input.datasetId},
|
|
41
61
|
${input.mode},
|
|
42
|
-
${"
|
|
43
|
-
${input.
|
|
62
|
+
${"queued"},
|
|
63
|
+
${input.queuedAt ?? new Date()},
|
|
64
|
+
${null},
|
|
44
65
|
${input.expectedLatestVersionId ?? null},
|
|
45
66
|
${input.commitMessage ?? null}
|
|
46
67
|
)
|
|
@@ -50,13 +71,57 @@ export class PgSyncRunStorage implements SyncRunStorage {
|
|
|
50
71
|
return rowToSyncRunRecord(row)
|
|
51
72
|
} catch (error) {
|
|
52
73
|
if (isUniqueViolation(error)) {
|
|
74
|
+
return this.requeueAfterEnqueueFailure(input)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw error
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async requeueAfterEnqueueFailure(input: QueueSyncRunInput): Promise<SyncRunRecord> {
|
|
82
|
+
return runPgTransaction(this.sql, async (tx) => {
|
|
83
|
+
const [existing] = await tx<DatabaseRow[]>`
|
|
84
|
+
SELECT *, checkpoint IS NOT NULL AS checkpoint_present FROM sync_runs
|
|
85
|
+
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
86
|
+
FOR UPDATE
|
|
87
|
+
`
|
|
88
|
+
if (!existing || !canRequeueSyncRunAfterEnqueueFailure(rowToSyncRunRecord(existing), input)) {
|
|
53
89
|
throw new SyncRunError(
|
|
54
90
|
`[SixbPg] Sync run '${input.id}' already exists for project '${input.projectId}'.`
|
|
55
91
|
)
|
|
56
92
|
}
|
|
57
93
|
|
|
58
|
-
|
|
94
|
+
const [updated] = await tx<DatabaseRow[]>`
|
|
95
|
+
UPDATE sync_runs
|
|
96
|
+
SET status = ${"queued"}, queued_at = ${input.queuedAt ?? new Date()},
|
|
97
|
+
started_at = ${null}, finished_at = ${null}, rows_read = ${null},
|
|
98
|
+
output_version_id = ${null}, error = ${null}, checkpoint = ${null}
|
|
99
|
+
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
100
|
+
RETURNING *, checkpoint IS NOT NULL AS checkpoint_present
|
|
101
|
+
`
|
|
102
|
+
return rowToSyncRunRecord(updated)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async start(input: StartSyncRunInput): Promise<SyncRunRecord> {
|
|
107
|
+
const [updated] = await this.sql<DatabaseRow[]>`
|
|
108
|
+
UPDATE sync_runs
|
|
109
|
+
SET status = ${"running"}, started_at = ${input.startedAt ?? new Date()},
|
|
110
|
+
error = ${null}
|
|
111
|
+
WHERE project_id = ${input.projectId} AND id = ${input.id} AND status = ${"queued"}
|
|
112
|
+
RETURNING *, checkpoint IS NOT NULL AS checkpoint_present
|
|
113
|
+
`
|
|
114
|
+
if (updated) return rowToSyncRunRecord(updated)
|
|
115
|
+
|
|
116
|
+
const existing = await this.getById({ projectId: input.projectId, id: input.id })
|
|
117
|
+
if (!existing) {
|
|
118
|
+
throw new SyncRunError(
|
|
119
|
+
`[SixbPg] Sync run '${input.id}' not found for project '${input.projectId}'.`
|
|
120
|
+
)
|
|
59
121
|
}
|
|
122
|
+
throw new SyncRunError(
|
|
123
|
+
`[SixbPg] Sync run '${input.id}' cannot start from status '${existing.status}'.`
|
|
124
|
+
)
|
|
60
125
|
}
|
|
61
126
|
|
|
62
127
|
async finish(input: FinishSyncRunInput): Promise<SyncRunRecord> {
|
|
@@ -72,6 +137,17 @@ export class PgSyncRunStorage implements SyncRunStorage {
|
|
|
72
137
|
)
|
|
73
138
|
}
|
|
74
139
|
|
|
140
|
+
const enqueueFailure =
|
|
141
|
+
input.status === "failed" && input.error.code === "queue.enqueue_failed"
|
|
142
|
+
if (
|
|
143
|
+
(enqueueFailure && existing.status !== "queued") ||
|
|
144
|
+
(!enqueueFailure && existing.status !== "running")
|
|
145
|
+
) {
|
|
146
|
+
throw new SyncRunError(
|
|
147
|
+
`[SixbPg] Sync run '${input.id}' cannot finish from status '${existing.status}'.`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
75
151
|
if (input.status === "succeeded") {
|
|
76
152
|
if (input.output && input.output.datasetId !== existing.dataset_id) {
|
|
77
153
|
throw new SyncRunError(
|
|
@@ -94,8 +170,7 @@ export class PgSyncRunStorage implements SyncRunStorage {
|
|
|
94
170
|
finished_at = ${input.finishedAt ?? new Date()},
|
|
95
171
|
rows_read = ${input.rowsRead},
|
|
96
172
|
output_version_id = ${input.output?.versionId ?? null},
|
|
97
|
-
|
|
98
|
-
error_message = ${null},
|
|
173
|
+
error = ${null},
|
|
99
174
|
checkpoint = ${serializeCheckpoint(input.checkpoint)}::text::jsonb
|
|
100
175
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
101
176
|
RETURNING *, checkpoint IS NOT NULL AS checkpoint_present
|
|
@@ -107,8 +182,7 @@ export class PgSyncRunStorage implements SyncRunStorage {
|
|
|
107
182
|
finished_at = ${input.finishedAt ?? new Date()},
|
|
108
183
|
rows_read = ${input.rowsRead ?? existing.rows_read ?? null},
|
|
109
184
|
output_version_id = ${null},
|
|
110
|
-
|
|
111
|
-
error_message = ${input.error?.message ?? null},
|
|
185
|
+
error = ${input.error === undefined ? null : serializeSixbFailure(input.error, SYNC_RUN_FAILURE_CODES)}::text::jsonb,
|
|
112
186
|
checkpoint = ${null}
|
|
113
187
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
114
188
|
RETURNING *, checkpoint IS NOT NULL AS checkpoint_present
|
|
@@ -156,7 +230,13 @@ export class PgSyncRunStorage implements SyncRunStorage {
|
|
|
156
230
|
params.push(input.datasetId)
|
|
157
231
|
}
|
|
158
232
|
|
|
159
|
-
index = appendRunListFilters(
|
|
233
|
+
index = appendRunListFilters(
|
|
234
|
+
whereClauses,
|
|
235
|
+
params,
|
|
236
|
+
index,
|
|
237
|
+
input,
|
|
238
|
+
"COALESCE(started_at, queued_at)"
|
|
239
|
+
)
|
|
160
240
|
|
|
161
241
|
const { rows, total, hasMore } = await queryRunList<DatabaseRow>({
|
|
162
242
|
sql: this.sql,
|
|
@@ -198,26 +278,17 @@ function serializeCheckpoint(checkpoint: JsonValue | undefined): string | null {
|
|
|
198
278
|
return checkpoint !== undefined ? JSON.stringify(checkpoint) : null
|
|
199
279
|
}
|
|
200
280
|
|
|
201
|
-
function toSyncRunFailure(row: DatabaseRow): SyncRunFailure | undefined {
|
|
202
|
-
if (!row.error_message) {
|
|
203
|
-
return undefined
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
name: row.error_name ?? undefined,
|
|
208
|
-
message: row.error_message,
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
281
|
function rowToSyncRunRecord(row: DatabaseRow): SyncRunRecord {
|
|
213
282
|
return {
|
|
214
283
|
id: row.id,
|
|
215
284
|
projectId: row.project_id,
|
|
285
|
+
executionId: row.execution_id,
|
|
216
286
|
syncId: row.sync_id,
|
|
217
287
|
datasetId: row.dataset_id,
|
|
218
288
|
mode: row.mode,
|
|
219
289
|
status: row.status,
|
|
220
|
-
|
|
290
|
+
queuedAt: new Date(row.queued_at),
|
|
291
|
+
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
221
292
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
222
293
|
rowsRead: row.rows_read != null ? Number(row.rows_read) : undefined,
|
|
223
294
|
output: row.output_version_id
|
|
@@ -228,7 +299,7 @@ function rowToSyncRunRecord(row: DatabaseRow): SyncRunRecord {
|
|
|
228
299
|
: undefined,
|
|
229
300
|
expectedLatestVersionId: row.expected_latest_version_id ?? undefined,
|
|
230
301
|
commitMessage: row.commit_message ?? undefined,
|
|
231
|
-
error:
|
|
302
|
+
error: row.error === null ? undefined : parseSixbFailure(row.error, SYNC_RUN_FAILURE_CODES),
|
|
232
303
|
checkpoint: hasCheckpoint(row) ? row.checkpoint : undefined,
|
|
233
304
|
}
|
|
234
305
|
}
|
|
@@ -246,18 +317,19 @@ function hasCheckpoint(row: DatabaseRow): boolean {
|
|
|
246
317
|
interface DatabaseRow {
|
|
247
318
|
project_id: string
|
|
248
319
|
id: string
|
|
320
|
+
execution_id: string
|
|
249
321
|
sync_id: string
|
|
250
322
|
dataset_id: string
|
|
251
323
|
mode: SyncRunRecord["mode"]
|
|
252
324
|
status: SyncRunRecord["status"]
|
|
253
|
-
|
|
325
|
+
queued_at: Date | string
|
|
326
|
+
started_at: Date | string | null
|
|
254
327
|
finished_at: Date | string | null
|
|
255
328
|
rows_read: number | string | null
|
|
256
329
|
output_version_id: string | null
|
|
257
330
|
expected_latest_version_id: string | null
|
|
258
331
|
commit_message: string | null
|
|
259
|
-
|
|
260
|
-
error_message: string | null
|
|
332
|
+
error: JsonValue | null
|
|
261
333
|
checkpoint: JsonValue | null
|
|
262
334
|
checkpoint_present: boolean | number | string
|
|
263
335
|
}
|
|
@@ -1,91 +1,126 @@
|
|
|
1
|
+
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
2
|
+
import { assertWebhookRunExecution } from "@sixb/core/internal/webhook-run-storage-provider"
|
|
1
3
|
import type {
|
|
4
|
+
ExecutionStorage,
|
|
2
5
|
FinishWebhookRunInput,
|
|
6
|
+
GetWebhookRunByDeliveryInput,
|
|
3
7
|
ListWebhookRunsInput,
|
|
4
8
|
ListWebhookRunsResult,
|
|
9
|
+
RestartWebhookRunInput,
|
|
5
10
|
StartWebhookRunInput,
|
|
6
11
|
WebhookRunRecord,
|
|
7
12
|
WebhookRunStorage,
|
|
8
13
|
} from "@sixb/core/storage"
|
|
9
|
-
import { WebhookRunError } from "@sixb/core/storage"
|
|
14
|
+
import { canRetryWebhookRun, WEBHOOK_RUN_FAILURE_CODES, WebhookRunError } from "@sixb/core/storage"
|
|
10
15
|
import type { SqlParameter } from "./pg-client"
|
|
11
16
|
import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "./run-list-query"
|
|
12
17
|
import { isUniqueViolation } from "./storage-errors"
|
|
13
18
|
import { type PgStoreClient, runPgTransaction } from "./transactions"
|
|
14
19
|
|
|
15
20
|
export class PgWebhookRunStorage implements WebhookRunStorage {
|
|
16
|
-
constructor(
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly sql: PgStoreClient,
|
|
23
|
+
private readonly executions: ExecutionStorage
|
|
24
|
+
) {}
|
|
17
25
|
|
|
18
26
|
async start(input: StartWebhookRunInput): Promise<WebhookRunRecord> {
|
|
27
|
+
await assertWebhookRunExecution({
|
|
28
|
+
executions: this.executions,
|
|
29
|
+
projectId: input.projectId,
|
|
30
|
+
executionId: input.executionId,
|
|
31
|
+
runId: input.id,
|
|
32
|
+
route: input.route,
|
|
33
|
+
})
|
|
19
34
|
try {
|
|
20
35
|
const [row] = await this.sql<WebhookRunDatabaseRow[]>`
|
|
21
36
|
INSERT INTO webhook_runs (
|
|
22
|
-
project_id,
|
|
23
|
-
|
|
24
|
-
connector_id,
|
|
25
|
-
webhook_id,
|
|
26
|
-
status,
|
|
27
|
-
method,
|
|
28
|
-
route,
|
|
29
|
-
started_at
|
|
37
|
+
project_id, id, execution_id, connector_id, webhook_id, status, method, route,
|
|
38
|
+
started_at, request_body_bytes, request_body_sha256, idempotency_key
|
|
30
39
|
) VALUES (
|
|
31
|
-
${input.projectId},
|
|
32
|
-
${input.
|
|
33
|
-
${input.
|
|
34
|
-
${input.
|
|
35
|
-
${"running"},
|
|
36
|
-
${input.method},
|
|
37
|
-
${input.route},
|
|
38
|
-
${input.startedAt ?? new Date()}
|
|
40
|
+
${input.projectId}, ${input.id}, ${input.executionId}, ${input.connectorId},
|
|
41
|
+
${input.webhookId}, ${"running"}, ${input.method}, ${input.route},
|
|
42
|
+
${input.startedAt ?? new Date()}, ${input.requestBodyBytes},
|
|
43
|
+
${input.requestBodySha256}, ${input.idempotencyKey ?? null}
|
|
39
44
|
)
|
|
40
45
|
RETURNING *
|
|
41
46
|
`
|
|
42
|
-
|
|
43
47
|
return rowToWebhookRunRecord(row)
|
|
44
48
|
} catch (error) {
|
|
45
49
|
if (isUniqueViolation(error)) {
|
|
46
50
|
throw new WebhookRunError(
|
|
47
|
-
`[SixbPg] Webhook run '${input.id}' already exists for project '${input.projectId}'
|
|
51
|
+
`[SixbPg] Webhook run '${input.id}' or delivery key already exists for project '${input.projectId}'.`,
|
|
52
|
+
"duplicate_run"
|
|
48
53
|
)
|
|
49
54
|
}
|
|
50
|
-
|
|
51
55
|
throw error
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
async
|
|
59
|
+
async restart(input: RestartWebhookRunInput): Promise<WebhookRunRecord> {
|
|
56
60
|
return runPgTransaction(this.sql, async (tx) => {
|
|
57
61
|
const [existing] = await tx<WebhookRunDatabaseRow[]>`
|
|
58
62
|
SELECT * FROM webhook_runs
|
|
59
63
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
60
64
|
FOR UPDATE
|
|
61
65
|
`
|
|
62
|
-
|
|
63
66
|
if (!existing) {
|
|
64
67
|
throw new WebhookRunError(
|
|
65
|
-
`[SixbPg] Webhook run '${input.id}' not found for project '${input.projectId}'
|
|
68
|
+
`[SixbPg] Webhook run '${input.id}' not found for project '${input.projectId}'.`,
|
|
69
|
+
"not_found"
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
if (!canRetryWebhookRun(rowToWebhookRunRecord(existing))) {
|
|
73
|
+
throw new WebhookRunError(
|
|
74
|
+
`[SixbPg] Webhook run '${input.id}' cannot restart from status '${existing.status}'.`,
|
|
75
|
+
"invalid_transition"
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const [updated] = await tx<WebhookRunDatabaseRow[]>`
|
|
80
|
+
UPDATE webhook_runs
|
|
81
|
+
SET status = ${"running"}, started_at = ${input.startedAt ?? new Date()},
|
|
82
|
+
finished_at = ${null}, response_status = ${null}, error = ${null}
|
|
83
|
+
WHERE project_id = ${input.projectId} AND id = ${input.id} AND status = ${"failed"}
|
|
84
|
+
RETURNING *
|
|
85
|
+
`
|
|
86
|
+
if (!updated) {
|
|
87
|
+
throw new WebhookRunError(
|
|
88
|
+
`[SixbPg] Webhook run '${input.id}' changed while restarting.`,
|
|
89
|
+
"invalid_transition"
|
|
66
90
|
)
|
|
67
91
|
}
|
|
92
|
+
return rowToWebhookRunRecord(updated)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
68
95
|
|
|
96
|
+
async finish(input: FinishWebhookRunInput): Promise<WebhookRunRecord> {
|
|
97
|
+
return runPgTransaction(this.sql, async (tx) => {
|
|
98
|
+
const [existing] = await tx<WebhookRunDatabaseRow[]>`
|
|
99
|
+
SELECT * FROM webhook_runs
|
|
100
|
+
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
101
|
+
FOR UPDATE
|
|
102
|
+
`
|
|
103
|
+
if (!existing) {
|
|
104
|
+
throw new WebhookRunError(
|
|
105
|
+
`[SixbPg] Webhook run '${input.id}' not found for project '${input.projectId}'.`,
|
|
106
|
+
"not_found"
|
|
107
|
+
)
|
|
108
|
+
}
|
|
69
109
|
if (existing.status !== "running") {
|
|
70
110
|
throw new WebhookRunError(
|
|
71
|
-
`[SixbPg] Webhook run '${input.id}' for project '${input.projectId}' is already terminal
|
|
111
|
+
`[SixbPg] Webhook run '${input.id}' for project '${input.projectId}' is already terminal.`,
|
|
112
|
+
"invalid_transition"
|
|
72
113
|
)
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
const [updated] = await tx<WebhookRunDatabaseRow[]>`
|
|
76
117
|
UPDATE webhook_runs
|
|
77
|
-
SET
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
request_body_bytes = ${input.requestBodyBytes ?? null},
|
|
81
|
-
response_status = ${input.responseStatus ?? null},
|
|
82
|
-
idempotency_key = ${input.idempotencyKey ?? null},
|
|
83
|
-
delivery_claim_result = ${input.deliveryClaimResult ?? null},
|
|
84
|
-
error = ${input.status === "succeeded" ? null : (input.error ?? null)}
|
|
118
|
+
SET status = ${input.status}, finished_at = ${input.finishedAt ?? new Date()},
|
|
119
|
+
response_status = ${input.responseStatus ?? null},
|
|
120
|
+
error = ${input.status === "succeeded" ? null : serializeSixbFailure(input.error, WEBHOOK_RUN_FAILURE_CODES)}::text::jsonb
|
|
85
121
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
86
122
|
RETURNING *
|
|
87
123
|
`
|
|
88
|
-
|
|
89
124
|
return rowToWebhookRunRecord(updated)
|
|
90
125
|
})
|
|
91
126
|
}
|
|
@@ -95,38 +130,38 @@ export class PgWebhookRunStorage implements WebhookRunStorage {
|
|
|
95
130
|
SELECT * FROM webhook_runs
|
|
96
131
|
WHERE project_id = ${params.projectId} AND id = ${params.id}
|
|
97
132
|
`
|
|
133
|
+
return row ? rowToWebhookRunRecord(row) : null
|
|
134
|
+
}
|
|
98
135
|
|
|
136
|
+
async getByDelivery(input: GetWebhookRunByDeliveryInput): Promise<WebhookRunRecord | null> {
|
|
137
|
+
const [row] = await this.sql<WebhookRunDatabaseRow[]>`
|
|
138
|
+
SELECT * FROM webhook_runs
|
|
139
|
+
WHERE project_id = ${input.projectId}
|
|
140
|
+
AND connector_id = ${input.connectorId}
|
|
141
|
+
AND webhook_id = ${input.webhookId}
|
|
142
|
+
AND idempotency_key = ${input.idempotencyKey}
|
|
143
|
+
`
|
|
99
144
|
return row ? rowToWebhookRunRecord(row) : null
|
|
100
145
|
}
|
|
101
146
|
|
|
102
147
|
async list(input: ListWebhookRunsInput): Promise<ListWebhookRunsResult> {
|
|
103
|
-
if (hasEmptyStatuses(input)) {
|
|
104
|
-
return {
|
|
105
|
-
runs: [],
|
|
106
|
-
hasMore: false,
|
|
107
|
-
total: 0,
|
|
108
|
-
}
|
|
109
|
-
}
|
|
148
|
+
if (hasEmptyStatuses(input)) return { runs: [], hasMore: false, total: 0 }
|
|
110
149
|
|
|
111
150
|
const whereClauses = ["project_id = $1"]
|
|
112
151
|
const params: SqlParameter[] = [input.projectId]
|
|
113
152
|
let index = 2
|
|
114
|
-
|
|
115
153
|
if (input.connectorId) {
|
|
116
154
|
whereClauses.push(`connector_id = $${index++}`)
|
|
117
155
|
params.push(input.connectorId)
|
|
118
156
|
}
|
|
119
|
-
|
|
120
157
|
if (input.webhookId) {
|
|
121
158
|
whereClauses.push(`webhook_id = $${index++}`)
|
|
122
159
|
params.push(input.webhookId)
|
|
123
160
|
}
|
|
124
|
-
|
|
125
161
|
if (input.idempotencyKey) {
|
|
126
162
|
whereClauses.push(`idempotency_key = $${index++}`)
|
|
127
163
|
params.push(input.idempotencyKey)
|
|
128
164
|
}
|
|
129
|
-
|
|
130
165
|
index = appendRunListFilters(whereClauses, params, index, input)
|
|
131
166
|
|
|
132
167
|
const { rows, total, hasMore } = await queryRunList<WebhookRunDatabaseRow>({
|
|
@@ -139,12 +174,7 @@ export class PgWebhookRunStorage implements WebhookRunStorage {
|
|
|
139
174
|
limit: input.limit,
|
|
140
175
|
offset: input.offset,
|
|
141
176
|
})
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
runs: rows.map(rowToWebhookRunRecord),
|
|
145
|
-
hasMore,
|
|
146
|
-
total,
|
|
147
|
-
}
|
|
177
|
+
return { runs: rows.map(rowToWebhookRunRecord), hasMore, total }
|
|
148
178
|
}
|
|
149
179
|
}
|
|
150
180
|
|
|
@@ -152,6 +182,7 @@ function rowToWebhookRunRecord(row: WebhookRunDatabaseRow): WebhookRunRecord {
|
|
|
152
182
|
return {
|
|
153
183
|
id: row.id,
|
|
154
184
|
projectId: row.project_id,
|
|
185
|
+
executionId: row.execution_id,
|
|
155
186
|
connectorId: row.connector_id,
|
|
156
187
|
webhookId: row.webhook_id,
|
|
157
188
|
status: row.status,
|
|
@@ -159,17 +190,18 @@ function rowToWebhookRunRecord(row: WebhookRunDatabaseRow): WebhookRunRecord {
|
|
|
159
190
|
route: row.route,
|
|
160
191
|
startedAt: new Date(row.started_at),
|
|
161
192
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
162
|
-
requestBodyBytes:
|
|
193
|
+
requestBodyBytes: Number(row.request_body_bytes),
|
|
194
|
+
requestBodySha256: row.request_body_sha256,
|
|
163
195
|
responseStatus: row.response_status != null ? Number(row.response_status) : undefined,
|
|
164
196
|
idempotencyKey: row.idempotency_key ?? undefined,
|
|
165
|
-
|
|
166
|
-
error: row.error ?? undefined,
|
|
197
|
+
error: row.error === null ? undefined : parseSixbFailure(row.error, WEBHOOK_RUN_FAILURE_CODES),
|
|
167
198
|
}
|
|
168
199
|
}
|
|
169
200
|
|
|
170
201
|
interface WebhookRunDatabaseRow {
|
|
171
202
|
readonly project_id: string
|
|
172
203
|
readonly id: string
|
|
204
|
+
readonly execution_id: string
|
|
173
205
|
readonly connector_id: string
|
|
174
206
|
readonly webhook_id: string
|
|
175
207
|
readonly status: WebhookRunRecord["status"]
|
|
@@ -177,9 +209,9 @@ interface WebhookRunDatabaseRow {
|
|
|
177
209
|
readonly route: string
|
|
178
210
|
readonly started_at: Date | string
|
|
179
211
|
readonly finished_at: Date | string | null
|
|
180
|
-
readonly request_body_bytes: number | string
|
|
212
|
+
readonly request_body_bytes: number | string
|
|
213
|
+
readonly request_body_sha256: string
|
|
181
214
|
readonly response_status: number | string | null
|
|
182
215
|
readonly idempotency_key: string | null
|
|
183
|
-
readonly
|
|
184
|
-
readonly error: string | null
|
|
216
|
+
readonly error: unknown | null
|
|
185
217
|
}
|