@sixb/sqlite 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 +18 -13
- package/dist/action-run-storage.d.ts +5 -2
- package/dist/action-run-storage.d.ts.map +1 -1
- package/dist/agents/agent-storage.d.ts +9 -5
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/checkpoints.d.ts +14 -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 +21 -11
- 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/ai-cost-storage.d.ts +15 -0
- package/dist/ai-cost-storage.d.ts.map +1 -0
- package/dist/ai-usage-storage.d.ts +22 -0
- package/dist/ai-usage-storage.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +1 -10
- 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/execution-storage.d.ts +14 -0
- package/dist/execution-storage.d.ts.map +1 -0
- package/dist/index.d.ts +14 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4633 -1066
- package/dist/index.js.map +33 -28
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +50 -8
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +4 -5
- 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 +0 -2
- 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 -3
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pipeline-run-storage.d.ts +7 -2
- package/dist/pipeline-run-storage.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts +9 -2
- package/dist/projection-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/sync-run-storage.d.ts +7 -2
- package/dist/sync-run-storage.d.ts.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/transactions.d.ts +3 -0
- package/dist/transactions.d.ts.map +1 -1
- package/dist/webhook-run-storage.d.ts +9 -2
- package/dist/webhook-run-storage.d.ts.map +1 -1
- package/dist/workflow-run-storage.d.ts +7 -3
- package/dist/workflow-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/action-run-storage.ts +51 -74
- package/src/agents/agent-storage.ts +11 -6
- package/src/agents/checkpoints.ts +175 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/messages.ts +59 -0
- package/src/agents/rows.ts +52 -40
- package/src/agents/runs.ts +27 -21
- package/src/ai-cost-storage.ts +744 -0
- package/src/ai-usage-storage.ts +324 -0
- package/src/auth-storage/service-accounts.ts +14 -21
- package/src/auth-storage/shared.ts +0 -22
- package/src/connector-connection-storage.ts +784 -0
- package/src/execution-storage.ts +158 -0
- package/src/index.ts +69 -22
- package/src/latest-run-query.ts +7 -1
- package/src/migrations/004-executions.sql +142 -0
- package/src/migrations/005-workflow-executions.sql +7 -0
- package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
- package/src/migrations/007-split-overrides.sql +154 -0
- package/src/migrations/008-action-executions.sql +4 -0
- package/src/migrations/009-agent-executions.sql +115 -0
- package/src/migrations/010-ai-usage-accounting-foundation.sql +74 -0
- package/src/migrations/011-sync-failure-record.sql +22 -0
- package/src/migrations/012-pipeline-failure-record.sql +45 -0
- package/src/migrations/013-workflow-failure-record.sql +45 -0
- package/src/migrations/014-agent-failure-record.sql +59 -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 +80 -0
- package/src/migrations/019-webhook-delivery-failure-record.sql +62 -0
- package/src/migrations/020-drop-run-usage-projections.sql +7 -0
- package/src/migrations/021-sync-pipeline-executions.sql +101 -0
- package/src/migrations/022-projection-executions.sql +244 -0
- package/src/migrations/023-webhook-executions.sql +51 -0
- package/src/migrations/024-ontology-commit-executions.sql +58 -0
- package/src/migrations/025-connector-connections.sql +257 -0
- package/src/migrations/026-ai-cost-accounting.sql +57 -0
- package/src/migrations/027-agent-context-checkpoints.sql +28 -0
- package/src/migrations/028-object-override-edit-times.sql +3 -0
- package/src/migrations.ts +129 -4
- package/src/ontology-storage/materialization-session.ts +317 -143
- package/src/ontology-storage/materialization-state.ts +323 -112
- package/src/ontology-storage/materialization-writer.ts +145 -67
- package/src/ontology-storage/materializations.ts +67 -141
- package/src/ontology-storage/outbox.ts +10 -3
- package/src/ontology-storage/shared.ts +10 -4
- package/src/ontology-storage/sources.ts +4 -1
- package/src/pipeline-run-storage.ts +128 -43
- package/src/projection-run-storage.ts +209 -97
- package/src/run-list-query.ts +9 -4
- package/src/sync-run-storage.ts +118 -32
- package/src/testing.ts +20 -0
- package/src/transactions.ts +10 -3
- package/src/webhook-run-storage.ts +118 -74
- package/src/workflow-run-storage.ts +117 -131
- package/dist/validation.d.ts +0 -5
- package/dist/validation.d.ts.map +0 -1
- package/dist/webhook-delivery-storage.d.ts +0 -27
- package/dist/webhook-delivery-storage.d.ts.map +0 -1
- package/src/validation.ts +0 -30
- package/src/webhook-delivery-storage.ts +0 -272
|
@@ -0,0 +1,324 @@
|
|
|
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 { installFreshSqliteSchema } from "./migrations"
|
|
17
|
+
import { isUniqueConstraintError } from "./storage-errors"
|
|
18
|
+
import {
|
|
19
|
+
closeSqliteStoreConnection,
|
|
20
|
+
openSqliteStoreConnection,
|
|
21
|
+
runImmediateTransaction,
|
|
22
|
+
type SqliteStoreConnection,
|
|
23
|
+
} from "./transactions"
|
|
24
|
+
|
|
25
|
+
export interface SqliteAiUsageStorageOptions {
|
|
26
|
+
readonly path?: string
|
|
27
|
+
readonly connection?: SqliteStoreConnection
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** SQLite-backed append-only model-call usage ledger. */
|
|
31
|
+
export class SqliteAiUsageStorage implements AiUsageStorage {
|
|
32
|
+
private readonly connection: SqliteStoreConnection
|
|
33
|
+
|
|
34
|
+
constructor(options: SqliteAiUsageStorageOptions = {}) {
|
|
35
|
+
this.connection = openSqliteStoreConnection(options)
|
|
36
|
+
if (this.connection.installFreshSchema) {
|
|
37
|
+
installFreshSqliteSchema(this.connection.db)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async recordModelCall(input: RecordAiModelCallInput): Promise<RecordAiModelCallResult> {
|
|
42
|
+
const record = normalizeAiModelCallRecord(input)
|
|
43
|
+
return runImmediateTransaction(this.connection.db, () => {
|
|
44
|
+
const existing = this.findByIdempotencyKey(record)
|
|
45
|
+
if (existing) return { record: existing, created: false }
|
|
46
|
+
this.assertExecutionExists(record.projectId, record.executionId)
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
this.insertRecord(record)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (!isUniqueConstraintError(error)) throw error
|
|
52
|
+
|
|
53
|
+
const concurrent = this.findByIdempotencyKey(record)
|
|
54
|
+
if (concurrent) return { record: concurrent, created: false }
|
|
55
|
+
throw new AiUsageStorageError(
|
|
56
|
+
"duplicate_id",
|
|
57
|
+
`[SixbSqlite] AI usage record '${record.id}' already exists for project '${record.projectId}'.`
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const groupId of record.requesterGroupIds) {
|
|
62
|
+
this.connection.db
|
|
63
|
+
.query(
|
|
64
|
+
`
|
|
65
|
+
INSERT INTO ai_model_call_usage_groups (
|
|
66
|
+
project_id, usage_record_id, group_id, occurred_at
|
|
67
|
+
) VALUES (?, ?, ?, ?)
|
|
68
|
+
`
|
|
69
|
+
)
|
|
70
|
+
.run(record.projectId, record.id, groupId, record.occurredAt.toISOString())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { record: this.requireById(record.projectId, record.id), created: true }
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async summarizeExecution(
|
|
78
|
+
input: SummarizeAiUsageExecutionInput
|
|
79
|
+
): Promise<AiUsageExecutionSummary> {
|
|
80
|
+
const [summary] = await this.summarizeExecutions({
|
|
81
|
+
projectId: input.projectId,
|
|
82
|
+
executionIds: [input.executionId],
|
|
83
|
+
})
|
|
84
|
+
return summary!
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async summarizeExecutions(
|
|
88
|
+
input: SummarizeAiUsageExecutionsInput
|
|
89
|
+
): Promise<readonly AiUsageExecutionSummary[]> {
|
|
90
|
+
assertNonBlankProjectId(input.projectId)
|
|
91
|
+
for (const executionId of input.executionIds) assertAiUsageExecutionId(executionId)
|
|
92
|
+
if (input.executionIds.length === 0) return []
|
|
93
|
+
|
|
94
|
+
const rows = this.executionRows(input)
|
|
95
|
+
return aggregateExecutionRows(input.executionIds, rows)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
close(): void {
|
|
99
|
+
closeSqliteStoreConnection(this.connection)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private insertRecord(record: AiModelCallUsageRecord): void {
|
|
103
|
+
this.connection.db
|
|
104
|
+
.query(
|
|
105
|
+
`
|
|
106
|
+
INSERT INTO ai_model_call_usage (
|
|
107
|
+
project_id,
|
|
108
|
+
id,
|
|
109
|
+
execution_id,
|
|
110
|
+
attempt,
|
|
111
|
+
call_id,
|
|
112
|
+
provider_id,
|
|
113
|
+
requested_model_id,
|
|
114
|
+
response_model_id,
|
|
115
|
+
response_id,
|
|
116
|
+
input_tokens,
|
|
117
|
+
output_tokens,
|
|
118
|
+
total_tokens,
|
|
119
|
+
uncached_input_tokens,
|
|
120
|
+
cache_read_input_tokens,
|
|
121
|
+
cache_write_input_tokens,
|
|
122
|
+
text_output_tokens,
|
|
123
|
+
reasoning_output_tokens,
|
|
124
|
+
reporting_status,
|
|
125
|
+
raw_usage,
|
|
126
|
+
occurred_at,
|
|
127
|
+
recorded_at
|
|
128
|
+
) VALUES (
|
|
129
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
130
|
+
)
|
|
131
|
+
`
|
|
132
|
+
)
|
|
133
|
+
.run(
|
|
134
|
+
record.projectId,
|
|
135
|
+
record.id,
|
|
136
|
+
record.executionId,
|
|
137
|
+
record.attempt,
|
|
138
|
+
record.callId,
|
|
139
|
+
record.providerId,
|
|
140
|
+
record.requestedModelId,
|
|
141
|
+
record.responseModelId ?? null,
|
|
142
|
+
record.responseId,
|
|
143
|
+
record.usage.inputTokens ?? null,
|
|
144
|
+
record.usage.outputTokens ?? null,
|
|
145
|
+
record.usage.totalTokens ?? null,
|
|
146
|
+
record.usage.uncachedInputTokens ?? null,
|
|
147
|
+
record.usage.cacheReadInputTokens ?? null,
|
|
148
|
+
record.usage.cacheWriteInputTokens ?? null,
|
|
149
|
+
record.usage.textOutputTokens ?? null,
|
|
150
|
+
record.usage.reasoningOutputTokens ?? null,
|
|
151
|
+
record.usage.reportingStatus,
|
|
152
|
+
record.rawUsage === undefined ? null : JSON.stringify(record.rawUsage),
|
|
153
|
+
record.occurredAt.toISOString(),
|
|
154
|
+
record.recordedAt.toISOString()
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private findByIdempotencyKey(
|
|
159
|
+
record: Pick<
|
|
160
|
+
AiModelCallUsageRecord,
|
|
161
|
+
"projectId" | "executionId" | "attempt" | "callId" | "responseId"
|
|
162
|
+
>
|
|
163
|
+
): AiModelCallUsageRecord | null {
|
|
164
|
+
const row = this.connection.db
|
|
165
|
+
.query(
|
|
166
|
+
`
|
|
167
|
+
SELECT * FROM ai_model_call_usage
|
|
168
|
+
WHERE project_id = ?
|
|
169
|
+
AND execution_id = ?
|
|
170
|
+
AND attempt = ?
|
|
171
|
+
AND call_id = ?
|
|
172
|
+
AND response_id = ?
|
|
173
|
+
`
|
|
174
|
+
)
|
|
175
|
+
.get(
|
|
176
|
+
record.projectId,
|
|
177
|
+
record.executionId,
|
|
178
|
+
record.attempt,
|
|
179
|
+
record.callId,
|
|
180
|
+
record.responseId
|
|
181
|
+
) as AiUsageRow | null
|
|
182
|
+
return row ? this.rowToRecord(row) : null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private requireById(projectId: string, id: string): AiModelCallUsageRecord {
|
|
186
|
+
const row = this.connection.db
|
|
187
|
+
.query("SELECT * FROM ai_model_call_usage WHERE project_id = ? AND id = ?")
|
|
188
|
+
.get(projectId, id) as AiUsageRow | null
|
|
189
|
+
if (!row) throw new Error(`[SixbSqlite] AI usage record '${id}' disappeared after insert.`)
|
|
190
|
+
return this.rowToRecord(row)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private rowToRecord(row: AiUsageRow): AiModelCallUsageRecord {
|
|
194
|
+
const groupRows = this.connection.db
|
|
195
|
+
.query(
|
|
196
|
+
`
|
|
197
|
+
SELECT group_id FROM ai_model_call_usage_groups
|
|
198
|
+
WHERE project_id = ? AND usage_record_id = ?
|
|
199
|
+
ORDER BY group_id
|
|
200
|
+
`
|
|
201
|
+
)
|
|
202
|
+
.all(row.project_id, row.id) as Array<{ group_id: string }>
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
id: row.id,
|
|
206
|
+
projectId: row.project_id,
|
|
207
|
+
executionId: row.execution_id,
|
|
208
|
+
attempt: row.attempt,
|
|
209
|
+
callId: row.call_id,
|
|
210
|
+
requesterGroupIds: groupRows.map((group) => group.group_id),
|
|
211
|
+
providerId: row.provider_id,
|
|
212
|
+
requestedModelId: row.requested_model_id,
|
|
213
|
+
...(row.response_model_id === null ? {} : { responseModelId: row.response_model_id }),
|
|
214
|
+
responseId: row.response_id,
|
|
215
|
+
usage: {
|
|
216
|
+
...usageFromRow(row),
|
|
217
|
+
...(row.total_tokens === null ? {} : { totalTokens: row.total_tokens }),
|
|
218
|
+
reportingStatus: row.reporting_status,
|
|
219
|
+
},
|
|
220
|
+
...(row.raw_usage === null
|
|
221
|
+
? {}
|
|
222
|
+
: { rawUsage: JSON.parse(row.raw_usage) as ReadonlyJsonObject }),
|
|
223
|
+
occurredAt: new Date(row.occurred_at),
|
|
224
|
+
recordedAt: new Date(row.recorded_at),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private executionRows(input: SummarizeAiUsageExecutionsInput): AiUsageRow[] {
|
|
229
|
+
const requested = JSON.stringify([...new Set(input.executionIds)])
|
|
230
|
+
return this.connection.db
|
|
231
|
+
.query(
|
|
232
|
+
`
|
|
233
|
+
WITH requested AS (
|
|
234
|
+
SELECT DISTINCT value AS execution_id
|
|
235
|
+
FROM json_each(?)
|
|
236
|
+
)
|
|
237
|
+
SELECT usage.* FROM requested
|
|
238
|
+
JOIN ai_model_call_usage AS usage
|
|
239
|
+
ON usage.project_id = ?
|
|
240
|
+
AND usage.execution_id = requested.execution_id
|
|
241
|
+
`
|
|
242
|
+
)
|
|
243
|
+
.all(requested, input.projectId) as AiUsageRow[]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private assertExecutionExists(projectId: string, executionId: string): void {
|
|
247
|
+
const execution = this.connection.db
|
|
248
|
+
.query("SELECT 1 FROM executions WHERE project_id = ? AND id = ?")
|
|
249
|
+
.get(projectId, executionId)
|
|
250
|
+
if (execution) return
|
|
251
|
+
throw new AiUsageStorageError(
|
|
252
|
+
"missing_execution",
|
|
253
|
+
`[SixbSqlite] AI usage execution '${executionId}' does not exist in project '${projectId}'.`
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
interface AiUsageRow {
|
|
259
|
+
readonly project_id: string
|
|
260
|
+
readonly id: string
|
|
261
|
+
readonly execution_id: string
|
|
262
|
+
readonly attempt: number
|
|
263
|
+
readonly call_id: string
|
|
264
|
+
readonly provider_id: string
|
|
265
|
+
readonly requested_model_id: string
|
|
266
|
+
readonly response_model_id: string | null
|
|
267
|
+
readonly response_id: string
|
|
268
|
+
readonly input_tokens: number | null
|
|
269
|
+
readonly output_tokens: number | null
|
|
270
|
+
readonly total_tokens: number | null
|
|
271
|
+
readonly uncached_input_tokens: number | null
|
|
272
|
+
readonly cache_read_input_tokens: number | null
|
|
273
|
+
readonly cache_write_input_tokens: number | null
|
|
274
|
+
readonly text_output_tokens: number | null
|
|
275
|
+
readonly reasoning_output_tokens: number | null
|
|
276
|
+
readonly reporting_status: AiModelCallUsageRecord["usage"]["reportingStatus"]
|
|
277
|
+
readonly raw_usage: string | null
|
|
278
|
+
readonly occurred_at: string
|
|
279
|
+
readonly recorded_at: string
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function aggregateExecutionRows(
|
|
283
|
+
executionIds: readonly string[],
|
|
284
|
+
rows: readonly AiUsageRow[]
|
|
285
|
+
): readonly AiUsageExecutionSummary[] {
|
|
286
|
+
const usageByExecutionId = new Map<string, AiModelCallUsageInput[]>()
|
|
287
|
+
for (const executionId of executionIds) usageByExecutionId.set(executionId, [])
|
|
288
|
+
for (const row of rows) {
|
|
289
|
+
usageByExecutionId.get(row.execution_id)?.push(usageFromRow(row))
|
|
290
|
+
}
|
|
291
|
+
return executionIds.map((executionId) => {
|
|
292
|
+
const usages = usageByExecutionId.get(executionId) ?? []
|
|
293
|
+
return {
|
|
294
|
+
modelCallCount: usages.length,
|
|
295
|
+
usage: aggregateAiModelCallUsage(usages),
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function usageFromRow(row: AiUsageRow): AiModelCallUsageInput {
|
|
301
|
+
return {
|
|
302
|
+
...(row.input_tokens === null ? {} : { inputTokens: row.input_tokens }),
|
|
303
|
+
...(row.output_tokens === null ? {} : { outputTokens: row.output_tokens }),
|
|
304
|
+
...(row.uncached_input_tokens === null
|
|
305
|
+
? {}
|
|
306
|
+
: { uncachedInputTokens: row.uncached_input_tokens }),
|
|
307
|
+
...(row.cache_read_input_tokens === null
|
|
308
|
+
? {}
|
|
309
|
+
: { cacheReadInputTokens: row.cache_read_input_tokens }),
|
|
310
|
+
...(row.cache_write_input_tokens === null
|
|
311
|
+
? {}
|
|
312
|
+
: { cacheWriteInputTokens: row.cache_write_input_tokens }),
|
|
313
|
+
...(row.text_output_tokens === null ? {} : { textOutputTokens: row.text_output_tokens }),
|
|
314
|
+
...(row.reasoning_output_tokens === null
|
|
315
|
+
? {}
|
|
316
|
+
: { reasoningOutputTokens: row.reasoning_output_tokens }),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function assertNonBlankProjectId(projectId: string): void {
|
|
321
|
+
if (typeof projectId !== "string" || projectId.trim().length === 0) {
|
|
322
|
+
throw new TypeError("[Sixb] AI usage projectId must be nonblank.")
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -92,40 +92,33 @@ export class SqliteAuthServiceAccountStore implements AuthServiceAccountStore {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async update(input: UpdateAuthServiceAccountInput): Promise<ServiceAccountRecord> {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
throw new AuthStorageError(
|
|
98
|
-
"missing_service_account",
|
|
99
|
-
`[Sixb] Service account '${input.id}' not found for project '${input.projectId}'.`
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
95
|
+
const id = assertNonEmpty(input.id, "Service account id")
|
|
96
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
103
97
|
const updatedAt = dateOrNow(input.updatedAt)
|
|
104
98
|
const name =
|
|
105
|
-
input.name === undefined ?
|
|
106
|
-
const description = input.description === undefined ? existing.description : input.description
|
|
107
|
-
const status = input.status ?? existing.status
|
|
99
|
+
input.name === undefined ? null : assertNonEmpty(input.name, "Service account name")
|
|
108
100
|
this.db
|
|
109
101
|
.query(
|
|
110
102
|
`
|
|
111
103
|
UPDATE auth_service_accounts
|
|
112
|
-
SET name = ?,
|
|
113
|
-
description = ?,
|
|
114
|
-
status = ?,
|
|
104
|
+
SET name = COALESCE(?, name),
|
|
105
|
+
description = COALESCE(?, description),
|
|
106
|
+
status = COALESCE(?, status),
|
|
115
107
|
updated_at = ?
|
|
116
108
|
WHERE project_id = ?
|
|
117
109
|
AND id = ?
|
|
118
110
|
`
|
|
119
111
|
)
|
|
120
|
-
.run(name, description ?? null, status, toIso(updatedAt),
|
|
112
|
+
.run(name, input.description ?? null, input.status ?? null, toIso(updatedAt), projectId, id)
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
const updated = await this.getById({ projectId, id })
|
|
115
|
+
if (!updated) {
|
|
116
|
+
throw new AuthStorageError(
|
|
117
|
+
"missing_service_account",
|
|
118
|
+
`[Sixb] Service account '${id}' not found for project '${projectId}'.`
|
|
119
|
+
)
|
|
128
120
|
}
|
|
121
|
+
return updated
|
|
129
122
|
}
|
|
130
123
|
|
|
131
124
|
async list(input: ListAuthServiceAccountsInput): Promise<ListAuthServiceAccountsResult> {
|
|
@@ -8,7 +8,6 @@ import type {
|
|
|
8
8
|
MagicLinkRecord,
|
|
9
9
|
OidcAuthorizationAttemptRecord,
|
|
10
10
|
UpsertAuthGroupMembershipInput,
|
|
11
|
-
UserRecord,
|
|
12
11
|
} from "@sixb/core/storage"
|
|
13
12
|
import { AuthStorageError } from "@sixb/core/storage"
|
|
14
13
|
import { isUniqueConstraintError } from "../storage-errors"
|
|
@@ -27,7 +26,6 @@ import {
|
|
|
27
26
|
rowToMagicLinkRecord,
|
|
28
27
|
rowToOidcAuthorizationAttemptRecord,
|
|
29
28
|
rowToSessionRecord,
|
|
30
|
-
rowToUserRecord,
|
|
31
29
|
} from "./rows"
|
|
32
30
|
|
|
33
31
|
export type SqliteValue = string | number | null
|
|
@@ -48,10 +46,6 @@ export function normalizeGroupIds(groupIds: readonly string[] | undefined): read
|
|
|
48
46
|
return [...new Set((groupIds ?? []).map((groupId) => assertNonEmpty(groupId, "Group id")))]
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
export function cloneRecord<T>(record: T): T {
|
|
52
|
-
return structuredClone(record)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
49
|
export function dateOrNow(value: Date | undefined): Date {
|
|
56
50
|
return value ? new Date(value) : new Date()
|
|
57
51
|
}
|
|
@@ -93,14 +87,6 @@ export function getUserRowById(
|
|
|
93
87
|
.get(params.projectId, params.id) as SqliteAuthUserRow | null
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
export function getUserById(
|
|
97
|
-
db: Database,
|
|
98
|
-
params: { readonly projectId: string; readonly id: string }
|
|
99
|
-
): UserRecord | null {
|
|
100
|
-
const row = getUserRowById(db, params)
|
|
101
|
-
return row ? rowToUserRecord(row) : null
|
|
102
|
-
}
|
|
103
|
-
|
|
104
90
|
export function getUserRowByEmail(
|
|
105
91
|
db: Database,
|
|
106
92
|
params: { readonly projectId: string; readonly email: string }
|
|
@@ -110,14 +96,6 @@ export function getUserRowByEmail(
|
|
|
110
96
|
.get(params.projectId, normalizeEmail(params.email)) as SqliteAuthUserRow | null
|
|
111
97
|
}
|
|
112
98
|
|
|
113
|
-
export function getUserByEmail(
|
|
114
|
-
db: Database,
|
|
115
|
-
params: { readonly projectId: string; readonly email: string }
|
|
116
|
-
): UserRecord | null {
|
|
117
|
-
const row = getUserRowByEmail(db, params)
|
|
118
|
-
return row ? rowToUserRecord(row) : null
|
|
119
|
-
}
|
|
120
|
-
|
|
121
99
|
export function getIdentityRowBySubject(
|
|
122
100
|
db: Database,
|
|
123
101
|
params: {
|