@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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { ReadonlyJsonObject } from "@sixb/core/storage"
|
|
2
|
+
import {
|
|
3
|
+
type AiModelCallUsageInput,
|
|
4
|
+
type AiModelCallUsageRecord,
|
|
5
|
+
type AiUsageExecutionSummary,
|
|
6
|
+
type AiUsageStorage,
|
|
7
|
+
AiUsageStorageError,
|
|
8
|
+
aggregateAiModelCallUsage,
|
|
9
|
+
assertAiUsageExecutionId,
|
|
10
|
+
normalizeAiModelCallRecord,
|
|
11
|
+
type RecordAiModelCallInput,
|
|
12
|
+
type RecordAiModelCallResult,
|
|
13
|
+
type SummarizeAiUsageExecutionInput,
|
|
14
|
+
type SummarizeAiUsageExecutionsInput,
|
|
15
|
+
} from "@sixb/core/storage"
|
|
16
|
+
import type { SQLClient } from "./pg-client"
|
|
17
|
+
import { type PgStoreClient, runPgTransaction } from "./transactions"
|
|
18
|
+
|
|
19
|
+
/** PostgreSQL-backed append-only model-call usage ledger. */
|
|
20
|
+
export class PgAiUsageStorage implements AiUsageStorage {
|
|
21
|
+
constructor(private readonly sql: PgStoreClient) {}
|
|
22
|
+
|
|
23
|
+
async recordModelCall(input: RecordAiModelCallInput): Promise<RecordAiModelCallResult> {
|
|
24
|
+
const record = normalizeAiModelCallRecord(input)
|
|
25
|
+
return runPgTransaction(this.sql, async (tx) => {
|
|
26
|
+
const existing = await this.findByIdempotencyKey(tx, record)
|
|
27
|
+
if (existing) return { record: existing, created: false }
|
|
28
|
+
await this.assertExecutionExists(tx, record.projectId, record.executionId)
|
|
29
|
+
|
|
30
|
+
const inserted = await tx<{ id: string }[]>`
|
|
31
|
+
INSERT INTO ai_model_call_usage (
|
|
32
|
+
project_id,
|
|
33
|
+
id,
|
|
34
|
+
execution_id,
|
|
35
|
+
attempt,
|
|
36
|
+
call_id,
|
|
37
|
+
provider_id,
|
|
38
|
+
requested_model_id,
|
|
39
|
+
response_model_id,
|
|
40
|
+
response_id,
|
|
41
|
+
input_tokens,
|
|
42
|
+
output_tokens,
|
|
43
|
+
total_tokens,
|
|
44
|
+
uncached_input_tokens,
|
|
45
|
+
cache_read_input_tokens,
|
|
46
|
+
cache_write_input_tokens,
|
|
47
|
+
text_output_tokens,
|
|
48
|
+
reasoning_output_tokens,
|
|
49
|
+
reporting_status,
|
|
50
|
+
raw_usage,
|
|
51
|
+
occurred_at,
|
|
52
|
+
recorded_at
|
|
53
|
+
) VALUES (
|
|
54
|
+
${record.projectId},
|
|
55
|
+
${record.id},
|
|
56
|
+
${record.executionId},
|
|
57
|
+
${record.attempt},
|
|
58
|
+
${record.callId},
|
|
59
|
+
${record.providerId},
|
|
60
|
+
${record.requestedModelId},
|
|
61
|
+
${record.responseModelId ?? null},
|
|
62
|
+
${record.responseId},
|
|
63
|
+
${record.usage.inputTokens ?? null},
|
|
64
|
+
${record.usage.outputTokens ?? null},
|
|
65
|
+
${record.usage.totalTokens ?? null},
|
|
66
|
+
${record.usage.uncachedInputTokens ?? null},
|
|
67
|
+
${record.usage.cacheReadInputTokens ?? null},
|
|
68
|
+
${record.usage.cacheWriteInputTokens ?? null},
|
|
69
|
+
${record.usage.textOutputTokens ?? null},
|
|
70
|
+
${record.usage.reasoningOutputTokens ?? null},
|
|
71
|
+
${record.usage.reportingStatus},
|
|
72
|
+
${record.rawUsage === undefined ? null : JSON.stringify(record.rawUsage)}::text::jsonb,
|
|
73
|
+
${record.occurredAt},
|
|
74
|
+
${record.recordedAt}
|
|
75
|
+
)
|
|
76
|
+
ON CONFLICT DO NOTHING
|
|
77
|
+
RETURNING id
|
|
78
|
+
`
|
|
79
|
+
|
|
80
|
+
if (inserted.length === 0) {
|
|
81
|
+
const concurrent = await this.findByIdempotencyKey(tx, record)
|
|
82
|
+
if (concurrent) return { record: concurrent, created: false }
|
|
83
|
+
throw new AiUsageStorageError(
|
|
84
|
+
"duplicate_id",
|
|
85
|
+
`[SixbPg] AI usage record '${record.id}' already exists for project '${record.projectId}'.`
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const groupId of record.requesterGroupIds) {
|
|
90
|
+
await tx`
|
|
91
|
+
INSERT INTO ai_model_call_usage_groups (
|
|
92
|
+
project_id, usage_record_id, group_id, occurred_at
|
|
93
|
+
) VALUES (
|
|
94
|
+
${record.projectId}, ${record.id}, ${groupId}, ${record.occurredAt}
|
|
95
|
+
)
|
|
96
|
+
`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { record: await this.requireById(tx, record.projectId, record.id), created: true }
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async summarizeExecution(
|
|
104
|
+
input: SummarizeAiUsageExecutionInput
|
|
105
|
+
): Promise<AiUsageExecutionSummary> {
|
|
106
|
+
const [summary] = await this.summarizeExecutions({
|
|
107
|
+
projectId: input.projectId,
|
|
108
|
+
executionIds: [input.executionId],
|
|
109
|
+
})
|
|
110
|
+
return summary!
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async summarizeExecutions(
|
|
114
|
+
input: SummarizeAiUsageExecutionsInput
|
|
115
|
+
): Promise<readonly AiUsageExecutionSummary[]> {
|
|
116
|
+
assertNonBlankProjectId(input.projectId)
|
|
117
|
+
for (const executionId of input.executionIds) assertAiUsageExecutionId(executionId)
|
|
118
|
+
if (input.executionIds.length === 0) return []
|
|
119
|
+
|
|
120
|
+
const rows = await this.executionRows(this.sql, input)
|
|
121
|
+
return aggregateExecutionRows(input.executionIds, rows)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async findByIdempotencyKey(
|
|
125
|
+
sql: PgStoreClient,
|
|
126
|
+
record: Pick<
|
|
127
|
+
AiModelCallUsageRecord,
|
|
128
|
+
"projectId" | "executionId" | "attempt" | "callId" | "responseId"
|
|
129
|
+
>
|
|
130
|
+
): Promise<AiModelCallUsageRecord | null> {
|
|
131
|
+
const rows = await sql<AiUsageRow[]>`
|
|
132
|
+
SELECT * FROM ai_model_call_usage
|
|
133
|
+
WHERE project_id = ${record.projectId}
|
|
134
|
+
AND execution_id = ${record.executionId}
|
|
135
|
+
AND attempt = ${record.attempt}
|
|
136
|
+
AND call_id = ${record.callId}
|
|
137
|
+
AND response_id = ${record.responseId}
|
|
138
|
+
`
|
|
139
|
+
return rows[0] ? this.rowToRecord(sql, rows[0]) : null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async requireById(
|
|
143
|
+
sql: SQLClient,
|
|
144
|
+
projectId: string,
|
|
145
|
+
id: string
|
|
146
|
+
): Promise<AiModelCallUsageRecord> {
|
|
147
|
+
const [row] = await sql<AiUsageRow[]>`
|
|
148
|
+
SELECT * FROM ai_model_call_usage WHERE project_id = ${projectId} AND id = ${id}
|
|
149
|
+
`
|
|
150
|
+
if (!row) throw new Error(`[SixbPg] AI usage record '${id}' disappeared after insert.`)
|
|
151
|
+
return this.rowToRecord(sql, row)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async rowToRecord(sql: PgStoreClient, row: AiUsageRow): Promise<AiModelCallUsageRecord> {
|
|
155
|
+
const groupRows = await sql<Array<{ group_id: string }>>`
|
|
156
|
+
SELECT group_id FROM ai_model_call_usage_groups
|
|
157
|
+
WHERE project_id = ${row.project_id} AND usage_record_id = ${row.id}
|
|
158
|
+
ORDER BY group_id
|
|
159
|
+
`
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
id: row.id,
|
|
163
|
+
projectId: row.project_id,
|
|
164
|
+
executionId: row.execution_id,
|
|
165
|
+
attempt: Number(row.attempt),
|
|
166
|
+
callId: row.call_id,
|
|
167
|
+
requesterGroupIds: groupRows.map((group) => group.group_id),
|
|
168
|
+
providerId: row.provider_id,
|
|
169
|
+
requestedModelId: row.requested_model_id,
|
|
170
|
+
...(row.response_model_id === null ? {} : { responseModelId: row.response_model_id }),
|
|
171
|
+
responseId: row.response_id,
|
|
172
|
+
usage: {
|
|
173
|
+
...usageFromRow(row),
|
|
174
|
+
...(row.total_tokens === null ? {} : { totalTokens: Number(row.total_tokens) }),
|
|
175
|
+
reportingStatus: row.reporting_status,
|
|
176
|
+
},
|
|
177
|
+
...(row.raw_usage === null ? {} : { rawUsage: rawUsageFromRow(row.raw_usage) }),
|
|
178
|
+
occurredAt: new Date(row.occurred_at),
|
|
179
|
+
recordedAt: new Date(row.recorded_at),
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private executionRows(
|
|
184
|
+
sql: PgStoreClient,
|
|
185
|
+
input: SummarizeAiUsageExecutionsInput
|
|
186
|
+
): Promise<AiUsageRow[]> {
|
|
187
|
+
const executionIds = [...new Set(input.executionIds)]
|
|
188
|
+
|
|
189
|
+
return sql<AiUsageRow[]>`
|
|
190
|
+
WITH requested(execution_id) AS (
|
|
191
|
+
SELECT * FROM unnest(${sql.array(executionIds)}::text[])
|
|
192
|
+
)
|
|
193
|
+
SELECT usage.* FROM requested
|
|
194
|
+
JOIN ai_model_call_usage AS usage
|
|
195
|
+
ON usage.project_id = ${input.projectId}
|
|
196
|
+
AND usage.execution_id = requested.execution_id
|
|
197
|
+
`
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async assertExecutionExists(
|
|
201
|
+
sql: PgStoreClient,
|
|
202
|
+
projectId: string,
|
|
203
|
+
executionId: string
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
const [execution] = await sql<{ exists: boolean }[]>`
|
|
206
|
+
SELECT EXISTS (
|
|
207
|
+
SELECT 1 FROM executions WHERE project_id = ${projectId} AND id = ${executionId}
|
|
208
|
+
) AS exists
|
|
209
|
+
`
|
|
210
|
+
if (execution?.exists) return
|
|
211
|
+
throw new AiUsageStorageError(
|
|
212
|
+
"missing_execution",
|
|
213
|
+
`[SixbPg] AI usage execution '${executionId}' does not exist in project '${projectId}'.`
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface AiUsageRow {
|
|
219
|
+
readonly project_id: string
|
|
220
|
+
readonly id: string
|
|
221
|
+
readonly execution_id: string
|
|
222
|
+
readonly attempt: number | string
|
|
223
|
+
readonly call_id: string
|
|
224
|
+
readonly provider_id: string
|
|
225
|
+
readonly requested_model_id: string
|
|
226
|
+
readonly response_model_id: string | null
|
|
227
|
+
readonly response_id: string
|
|
228
|
+
readonly input_tokens: number | string | null
|
|
229
|
+
readonly output_tokens: number | string | null
|
|
230
|
+
readonly total_tokens: number | string | null
|
|
231
|
+
readonly uncached_input_tokens: number | string | null
|
|
232
|
+
readonly cache_read_input_tokens: number | string | null
|
|
233
|
+
readonly cache_write_input_tokens: number | string | null
|
|
234
|
+
readonly text_output_tokens: number | string | null
|
|
235
|
+
readonly reasoning_output_tokens: number | string | null
|
|
236
|
+
readonly reporting_status: AiModelCallUsageRecord["usage"]["reportingStatus"]
|
|
237
|
+
readonly raw_usage: ReadonlyJsonObject | string | null
|
|
238
|
+
readonly occurred_at: Date | string
|
|
239
|
+
readonly recorded_at: Date | string
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function aggregateExecutionRows(
|
|
243
|
+
executionIds: readonly string[],
|
|
244
|
+
rows: readonly AiUsageRow[]
|
|
245
|
+
): readonly AiUsageExecutionSummary[] {
|
|
246
|
+
const usageByExecutionId = new Map<string, AiModelCallUsageInput[]>()
|
|
247
|
+
for (const executionId of executionIds) usageByExecutionId.set(executionId, [])
|
|
248
|
+
for (const row of rows) {
|
|
249
|
+
usageByExecutionId.get(row.execution_id)?.push(usageFromRow(row))
|
|
250
|
+
}
|
|
251
|
+
return executionIds.map((executionId) => {
|
|
252
|
+
const usages = usageByExecutionId.get(executionId) ?? []
|
|
253
|
+
return {
|
|
254
|
+
modelCallCount: usages.length,
|
|
255
|
+
usage: aggregateAiModelCallUsage(usages),
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function usageFromRow(row: AiUsageRow): AiModelCallUsageInput {
|
|
261
|
+
return {
|
|
262
|
+
...(row.input_tokens === null ? {} : { inputTokens: Number(row.input_tokens) }),
|
|
263
|
+
...(row.output_tokens === null ? {} : { outputTokens: Number(row.output_tokens) }),
|
|
264
|
+
...(row.uncached_input_tokens === null
|
|
265
|
+
? {}
|
|
266
|
+
: { uncachedInputTokens: Number(row.uncached_input_tokens) }),
|
|
267
|
+
...(row.cache_read_input_tokens === null
|
|
268
|
+
? {}
|
|
269
|
+
: { cacheReadInputTokens: Number(row.cache_read_input_tokens) }),
|
|
270
|
+
...(row.cache_write_input_tokens === null
|
|
271
|
+
? {}
|
|
272
|
+
: { cacheWriteInputTokens: Number(row.cache_write_input_tokens) }),
|
|
273
|
+
...(row.text_output_tokens === null
|
|
274
|
+
? {}
|
|
275
|
+
: { textOutputTokens: Number(row.text_output_tokens) }),
|
|
276
|
+
...(row.reasoning_output_tokens === null
|
|
277
|
+
? {}
|
|
278
|
+
: { reasoningOutputTokens: Number(row.reasoning_output_tokens) }),
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function rawUsageFromRow(value: Exclude<AiUsageRow["raw_usage"], null>): ReadonlyJsonObject {
|
|
283
|
+
return typeof value === "string" ? (JSON.parse(value) as ReadonlyJsonObject) : value
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function assertNonBlankProjectId(projectId: string): void {
|
|
287
|
+
if (typeof projectId !== "string" || projectId.trim().length === 0) {
|
|
288
|
+
throw new TypeError("[Sixb] AI usage projectId must be nonblank.")
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExecutionStorageRow,
|
|
3
|
+
executionRecordFromStorageRow,
|
|
4
|
+
executionRecordToStorageRow,
|
|
5
|
+
normalizeExecutionRecord,
|
|
6
|
+
validateExecutionRecordReferences,
|
|
7
|
+
} from "@sixb/core/internal/execution-storage"
|
|
8
|
+
import type { CreateExecutionInput, ExecutionRecord, ExecutionStorage } from "@sixb/core/storage"
|
|
9
|
+
import { ExecutionStorageError } from "@sixb/core/storage"
|
|
10
|
+
import type { PgAuthStorage } from "./auth-storage"
|
|
11
|
+
import { isUniqueViolation } from "./storage-errors"
|
|
12
|
+
import type { PgStoreClient } from "./transactions"
|
|
13
|
+
|
|
14
|
+
export class PgExecutionStorage implements ExecutionStorage {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly sql: PgStoreClient,
|
|
17
|
+
private readonly auth: PgAuthStorage
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async create(input: CreateExecutionInput): Promise<ExecutionRecord> {
|
|
21
|
+
const record = normalizeExecutionRecord(input)
|
|
22
|
+
await validateExecutionRecordReferences(record, {
|
|
23
|
+
auth: this.auth,
|
|
24
|
+
getExecution: (params) => this.getById(params),
|
|
25
|
+
})
|
|
26
|
+
const row = executionRecordToStorageRow(record)
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const [created] = await this.sql<PgExecutionRow[]>`
|
|
30
|
+
INSERT INTO executions (
|
|
31
|
+
project_id,
|
|
32
|
+
id,
|
|
33
|
+
executor_kind,
|
|
34
|
+
executor_id,
|
|
35
|
+
source_kind,
|
|
36
|
+
source_id,
|
|
37
|
+
requested_by_user_id,
|
|
38
|
+
requested_by_service_account_id,
|
|
39
|
+
correlation_id,
|
|
40
|
+
parent_execution_id,
|
|
41
|
+
authority_kind,
|
|
42
|
+
authority_user_id,
|
|
43
|
+
authority_service_account_id,
|
|
44
|
+
authority_session_id,
|
|
45
|
+
authority_access_token_id,
|
|
46
|
+
authority_primitive_kind,
|
|
47
|
+
authority_primitive_id,
|
|
48
|
+
authority_kernel_operation,
|
|
49
|
+
created_at
|
|
50
|
+
) VALUES (
|
|
51
|
+
${row.projectId},
|
|
52
|
+
${row.id},
|
|
53
|
+
${row.executorKind},
|
|
54
|
+
${row.executorId},
|
|
55
|
+
${row.sourceKind},
|
|
56
|
+
${row.sourceId},
|
|
57
|
+
${row.requestedByUserId},
|
|
58
|
+
${row.requestedByServiceAccountId},
|
|
59
|
+
${row.correlationId},
|
|
60
|
+
${row.parentExecutionId},
|
|
61
|
+
${row.authorityKind},
|
|
62
|
+
${row.authorityUserId},
|
|
63
|
+
${row.authorityServiceAccountId},
|
|
64
|
+
${row.authoritySessionId},
|
|
65
|
+
${row.authorityAccessTokenId},
|
|
66
|
+
${row.authorityPrimitiveKind},
|
|
67
|
+
${row.authorityPrimitiveId},
|
|
68
|
+
${row.authorityKernelOperation},
|
|
69
|
+
${row.createdAt}
|
|
70
|
+
)
|
|
71
|
+
RETURNING *
|
|
72
|
+
`
|
|
73
|
+
|
|
74
|
+
if (!created) {
|
|
75
|
+
throw new Error("[SixbPg] Execution insert returned no row.")
|
|
76
|
+
}
|
|
77
|
+
return executionRecordFromStorageRow(toStorageRow(created))
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (isUniqueViolation(error)) {
|
|
80
|
+
throw new ExecutionStorageError(
|
|
81
|
+
"duplicate_execution",
|
|
82
|
+
`[SixbPg] Execution '${record.id}' already exists in project '${record.projectId}'.`,
|
|
83
|
+
{ cause: error }
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
throw error
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async getById(params: {
|
|
91
|
+
readonly projectId: string
|
|
92
|
+
readonly id: string
|
|
93
|
+
}): Promise<ExecutionRecord | null> {
|
|
94
|
+
const [row] = await this.sql<PgExecutionRow[]>`
|
|
95
|
+
SELECT *
|
|
96
|
+
FROM executions
|
|
97
|
+
WHERE project_id = ${params.projectId} AND id = ${params.id}
|
|
98
|
+
`
|
|
99
|
+
return row ? executionRecordFromStorageRow(toStorageRow(row)) : null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toStorageRow(row: PgExecutionRow): ExecutionStorageRow {
|
|
104
|
+
return {
|
|
105
|
+
projectId: row.project_id,
|
|
106
|
+
id: row.id,
|
|
107
|
+
executorKind: row.executor_kind,
|
|
108
|
+
executorId: row.executor_id,
|
|
109
|
+
sourceKind: row.source_kind,
|
|
110
|
+
sourceId: row.source_id,
|
|
111
|
+
requestedByUserId: row.requested_by_user_id,
|
|
112
|
+
requestedByServiceAccountId: row.requested_by_service_account_id,
|
|
113
|
+
correlationId: row.correlation_id,
|
|
114
|
+
parentExecutionId: row.parent_execution_id,
|
|
115
|
+
authorityKind: row.authority_kind,
|
|
116
|
+
authorityUserId: row.authority_user_id,
|
|
117
|
+
authorityServiceAccountId: row.authority_service_account_id,
|
|
118
|
+
authoritySessionId: row.authority_session_id,
|
|
119
|
+
authorityAccessTokenId: row.authority_access_token_id,
|
|
120
|
+
authorityPrimitiveKind: row.authority_primitive_kind,
|
|
121
|
+
authorityPrimitiveId: row.authority_primitive_id,
|
|
122
|
+
authorityKernelOperation: row.authority_kernel_operation,
|
|
123
|
+
createdAt: new Date(row.created_at),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface PgExecutionRow {
|
|
128
|
+
readonly project_id: string
|
|
129
|
+
readonly id: string
|
|
130
|
+
readonly executor_kind: ExecutionStorageRow["executorKind"]
|
|
131
|
+
readonly executor_id: string
|
|
132
|
+
readonly source_kind: ExecutionStorageRow["sourceKind"]
|
|
133
|
+
readonly source_id: string
|
|
134
|
+
readonly requested_by_user_id: string | null
|
|
135
|
+
readonly requested_by_service_account_id: string | null
|
|
136
|
+
readonly correlation_id: string
|
|
137
|
+
readonly parent_execution_id: string | null
|
|
138
|
+
readonly authority_kind: ExecutionStorageRow["authorityKind"]
|
|
139
|
+
readonly authority_user_id: string | null
|
|
140
|
+
readonly authority_service_account_id: string | null
|
|
141
|
+
readonly authority_session_id: string | null
|
|
142
|
+
readonly authority_access_token_id: string | null
|
|
143
|
+
readonly authority_primitive_kind: ExecutionStorageRow["authorityPrimitiveKind"]
|
|
144
|
+
readonly authority_primitive_id: string | null
|
|
145
|
+
readonly authority_kernel_operation: ExecutionStorageRow["authorityKernelOperation"]
|
|
146
|
+
readonly created_at: Date | string
|
|
147
|
+
}
|