@sixb/sqlite 0.1.19 → 0.1.21
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 -0
- package/dist/action-run-storage.d.ts.map +1 -1
- package/dist/activity-storage.d.ts +20 -0
- package/dist/activity-storage.d.ts.map +1 -0
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/threads.d.ts +4 -0
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/ai-limit-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/artifact-storage.d.ts +22 -0
- package/dist/artifact-storage.d.ts.map +1 -0
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/magic-links.d.ts.map +1 -1
- package/dist/auth-storage/oidc-attempts.d.ts.map +1 -1
- package/dist/auth-storage/rows.d.ts +0 -3
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/auth-storage/sessions.d.ts +2 -11
- package/dist/auth-storage/sessions.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +0 -2
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/execution-storage.d.ts +2 -1
- package/dist/execution-storage.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +933 -128
- package/dist/index.js.map +32 -30
- package/dist/migrations.d.ts +2 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +2 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pipeline-run-storage.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/rules-storage.d.ts.map +1 -1
- package/dist/share-grant-storage.d.ts +2 -1
- package/dist/share-grant-storage.d.ts.map +1 -1
- package/dist/share-session-storage.d.ts.map +1 -1
- package/dist/sync-run-storage.d.ts.map +1 -1
- package/dist/timeseries-storage.d.ts.map +1 -1
- package/dist/transactions.d.ts +0 -1
- package/dist/transactions.d.ts.map +1 -1
- package/dist/webhook-run-storage.d.ts.map +1 -1
- package/dist/workflow-intervention-storage.d.ts.map +1 -1
- package/dist/workflow-run-storage.d.ts +4 -0
- package/dist/workflow-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/action-run-storage.ts +27 -5
- package/src/activity-storage.ts +588 -0
- package/src/agents/agent-storage.ts +0 -5
- package/src/agents/threads.ts +27 -0
- package/src/ai-limit-storage.ts +0 -2
- package/src/ai-usage-storage.ts +0 -4
- package/src/artifact-storage.ts +356 -0
- package/src/auth-storage/auth-storage.ts +0 -23
- package/src/auth-storage/magic-links.ts +1 -6
- package/src/auth-storage/oidc-attempts.ts +1 -6
- package/src/auth-storage/rows.ts +0 -6
- package/src/auth-storage/sessions.ts +21 -42
- package/src/auth-storage/shared.ts +3 -20
- package/src/execution-storage.ts +56 -0
- package/src/index.ts +11 -10
- package/src/migrations/061-drop-auth-audience.sql +16 -0
- package/src/migrations/062-artifacts.sql +71 -0
- package/src/migrations/063-drop-output-diagnostics.sql +19 -0
- package/src/migrations/064-activity.sql +61 -0
- package/src/migrations/065-activity-timeline.sql +19 -0
- package/src/migrations/066-activity-interactions.sql +20 -0
- package/src/migrations.ts +36 -0
- package/src/objects/storage.ts +0 -5
- package/src/ontology-storage/sources.ts +41 -0
- package/src/pipeline-run-storage.ts +0 -5
- package/src/projection-run-storage.ts +0 -5
- package/src/rules-storage.ts +0 -5
- package/src/share-grant-storage.ts +25 -2
- package/src/share-session-storage.ts +0 -2
- package/src/sync-run-storage.ts +0 -5
- package/src/timeseries-storage.ts +0 -5
- package/src/transactions.ts +8 -8
- package/src/webhook-run-storage.ts +0 -3
- package/src/workflow-intervention-storage.ts +0 -5
- package/src/workflow-run-storage.ts +27 -5
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import type { Database, SQLQueryBindings } from "bun:sqlite"
|
|
2
|
+
import {
|
|
3
|
+
artifactStorageErrors,
|
|
4
|
+
parseArtifactFrameGrantRow,
|
|
5
|
+
parseArtifactRow,
|
|
6
|
+
parseArtifactVersionRow,
|
|
7
|
+
} from "@sixb/core/internal/storage"
|
|
8
|
+
import type {
|
|
9
|
+
ArtifactFrameGrantRecord,
|
|
10
|
+
ArtifactRecord,
|
|
11
|
+
ArtifactStorage,
|
|
12
|
+
ArtifactVersionInput,
|
|
13
|
+
ArtifactVersionRecord,
|
|
14
|
+
CreateArtifactInput,
|
|
15
|
+
DeleteArtifactInput,
|
|
16
|
+
DeleteArtifactVersionsByOriginInput,
|
|
17
|
+
DeleteExpiredArtifactFrameGrantsInput,
|
|
18
|
+
FindArtifactFrameGrantInput,
|
|
19
|
+
GetArtifactInput,
|
|
20
|
+
GetArtifactVersionInput,
|
|
21
|
+
ListArtifactsInput,
|
|
22
|
+
ListArtifactsResult,
|
|
23
|
+
ListArtifactVersionsInput,
|
|
24
|
+
ListArtifactVersionsResult,
|
|
25
|
+
ListReferencingVersionsInput,
|
|
26
|
+
PublishArtifactVersionInput,
|
|
27
|
+
PublishedArtifactVersion,
|
|
28
|
+
} from "@sixb/core/storage"
|
|
29
|
+
import { runImmediateTransaction, type SqliteStoreConnection } from "./transactions"
|
|
30
|
+
|
|
31
|
+
const ARTIFACT_COLUMNS = `
|
|
32
|
+
project_id AS projectId, id, kind, title,
|
|
33
|
+
owner_principal_type AS ownerPrincipalType, owner_principal_id AS ownerPrincipalId,
|
|
34
|
+
latest_version AS latestVersion, created_at AS createdAt, updated_at AS updatedAt
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
const VERSION_COLUMNS = `
|
|
38
|
+
project_id AS projectId, artifact_id AS artifactId, version, title, entry, files, outline, refs,
|
|
39
|
+
author_principal_type AS authorPrincipalType, author_principal_id AS authorPrincipalId,
|
|
40
|
+
origin_thread_id AS originThreadId, origin_run_id AS originRunId, created_at AS createdAt
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
const FRAME_GRANT_COLUMNS = `
|
|
44
|
+
project_id AS projectId, token_hash AS tokenHash, artifact_id AS artifactId, version, targets,
|
|
45
|
+
created_at AS createdAt, expires_at AS expiresAt
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
type Row = Record<string, unknown>
|
|
49
|
+
|
|
50
|
+
export class SqliteArtifactStorage implements ArtifactStorage {
|
|
51
|
+
private readonly db: Database
|
|
52
|
+
|
|
53
|
+
constructor(connection: SqliteStoreConnection) {
|
|
54
|
+
this.db = connection.db
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async create(input: CreateArtifactInput): Promise<PublishedArtifactVersion> {
|
|
58
|
+
return runImmediateTransaction(this.db, () => {
|
|
59
|
+
const createdAt = input.version.createdAt.toISOString()
|
|
60
|
+
const artifact = this.db
|
|
61
|
+
.query(
|
|
62
|
+
`INSERT INTO artifacts (
|
|
63
|
+
project_id, id, kind, title, owner_principal_type, owner_principal_id,
|
|
64
|
+
latest_version, next_version, created_at, updated_at
|
|
65
|
+
) VALUES (?, ?, ?, ?, ?, ?, 1, 2, ?, ?)
|
|
66
|
+
ON CONFLICT (project_id, id) DO NOTHING
|
|
67
|
+
RETURNING ${ARTIFACT_COLUMNS}`
|
|
68
|
+
)
|
|
69
|
+
.get(
|
|
70
|
+
input.projectId,
|
|
71
|
+
input.id,
|
|
72
|
+
input.kind,
|
|
73
|
+
input.version.title,
|
|
74
|
+
input.owner.type,
|
|
75
|
+
input.owner.id,
|
|
76
|
+
createdAt,
|
|
77
|
+
createdAt
|
|
78
|
+
) as Row | null
|
|
79
|
+
if (!artifact) throw artifactStorageErrors.exists(input.id)
|
|
80
|
+
return {
|
|
81
|
+
artifact: parseArtifactRow(artifact),
|
|
82
|
+
version: this.insertVersion(input.projectId, input.id, 1, input.version),
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async publish(input: PublishArtifactVersionInput): Promise<PublishedArtifactVersion> {
|
|
88
|
+
return runImmediateTransaction(this.db, () => {
|
|
89
|
+
const current = this.db
|
|
90
|
+
.query(
|
|
91
|
+
"SELECT kind, next_version AS nextVersion FROM artifacts WHERE project_id = ? AND id = ?"
|
|
92
|
+
)
|
|
93
|
+
.get(input.projectId, input.artifactId) as {
|
|
94
|
+
readonly kind: ArtifactRecord["kind"]
|
|
95
|
+
readonly nextVersion: number
|
|
96
|
+
} | null
|
|
97
|
+
if (!current) throw artifactStorageErrors.notFound(input.artifactId)
|
|
98
|
+
if (current.kind !== input.kind) {
|
|
99
|
+
throw artifactStorageErrors.kindMismatch(input.artifactId, current.kind, input.kind)
|
|
100
|
+
}
|
|
101
|
+
const version = this.insertVersion(
|
|
102
|
+
input.projectId,
|
|
103
|
+
input.artifactId,
|
|
104
|
+
current.nextVersion,
|
|
105
|
+
input.version
|
|
106
|
+
)
|
|
107
|
+
const artifact = this.db
|
|
108
|
+
.query(
|
|
109
|
+
`UPDATE artifacts
|
|
110
|
+
SET title = ?, latest_version = ?, next_version = ?, updated_at = ?
|
|
111
|
+
WHERE project_id = ? AND id = ?
|
|
112
|
+
RETURNING ${ARTIFACT_COLUMNS}`
|
|
113
|
+
)
|
|
114
|
+
.get(
|
|
115
|
+
input.version.title,
|
|
116
|
+
version.version,
|
|
117
|
+
version.version + 1,
|
|
118
|
+
input.version.createdAt.toISOString(),
|
|
119
|
+
input.projectId,
|
|
120
|
+
input.artifactId
|
|
121
|
+
) as Row
|
|
122
|
+
return { artifact: parseArtifactRow(artifact), version }
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async get(input: GetArtifactInput): Promise<ArtifactRecord | null> {
|
|
127
|
+
const row = this.db
|
|
128
|
+
.query(`SELECT ${ARTIFACT_COLUMNS} FROM artifacts WHERE project_id = ? AND id = ?`)
|
|
129
|
+
.get(input.projectId, input.id) as Row | null
|
|
130
|
+
return row ? parseArtifactRow(row) : null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async list(input: ListArtifactsInput): Promise<ListArtifactsResult> {
|
|
134
|
+
const where = ["a.project_id = ?"]
|
|
135
|
+
const params: SQLQueryBindings[] = [input.projectId]
|
|
136
|
+
if (input.owner) {
|
|
137
|
+
where.push("a.owner_principal_type = ? AND a.owner_principal_id = ?")
|
|
138
|
+
params.push(input.owner.type, input.owner.id)
|
|
139
|
+
}
|
|
140
|
+
if (input.originThreadId !== undefined) {
|
|
141
|
+
where.push(
|
|
142
|
+
`EXISTS (
|
|
143
|
+
SELECT 1 FROM artifact_versions v
|
|
144
|
+
WHERE v.project_id = a.project_id AND v.artifact_id = a.id AND v.origin_thread_id = ?
|
|
145
|
+
)`
|
|
146
|
+
)
|
|
147
|
+
params.push(input.originThreadId)
|
|
148
|
+
}
|
|
149
|
+
if (input.kind !== undefined) {
|
|
150
|
+
where.push("a.kind = ?")
|
|
151
|
+
params.push(input.kind)
|
|
152
|
+
}
|
|
153
|
+
const clause = where.join(" AND ")
|
|
154
|
+
const { total } = this.db
|
|
155
|
+
.query(`SELECT COUNT(*) AS total FROM artifacts a WHERE ${clause}`)
|
|
156
|
+
.get(...params) as { readonly total: number }
|
|
157
|
+
const offset = input.offset ?? 0
|
|
158
|
+
const rows = this.db
|
|
159
|
+
.query(
|
|
160
|
+
`SELECT ${ARTIFACT_COLUMNS} FROM artifacts a WHERE ${clause}
|
|
161
|
+
ORDER BY a.updated_at DESC, a.id ASC LIMIT ? OFFSET ?`
|
|
162
|
+
)
|
|
163
|
+
.all(...params, input.limit ?? -1, offset) as Row[]
|
|
164
|
+
return {
|
|
165
|
+
artifacts: rows.map(parseArtifactRow),
|
|
166
|
+
hasMore: offset + rows.length < total,
|
|
167
|
+
total,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getVersion(input: GetArtifactVersionInput): Promise<ArtifactVersionRecord | null> {
|
|
172
|
+
const row = this.db
|
|
173
|
+
.query(
|
|
174
|
+
`SELECT ${VERSION_COLUMNS} FROM artifact_versions
|
|
175
|
+
WHERE project_id = ? AND artifact_id = ? AND version = ?`
|
|
176
|
+
)
|
|
177
|
+
.get(input.projectId, input.artifactId, input.version) as Row | null
|
|
178
|
+
return row ? parseArtifactVersionRow(row) : null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async listVersions(input: ListArtifactVersionsInput): Promise<ListArtifactVersionsResult> {
|
|
182
|
+
const { total } = this.db
|
|
183
|
+
.query(
|
|
184
|
+
"SELECT COUNT(*) AS total FROM artifact_versions WHERE project_id = ? AND artifact_id = ?"
|
|
185
|
+
)
|
|
186
|
+
.get(input.projectId, input.artifactId) as { readonly total: number }
|
|
187
|
+
const offset = input.offset ?? 0
|
|
188
|
+
const rows = this.db
|
|
189
|
+
.query(
|
|
190
|
+
`SELECT ${VERSION_COLUMNS} FROM artifact_versions
|
|
191
|
+
WHERE project_id = ? AND artifact_id = ?
|
|
192
|
+
ORDER BY version DESC LIMIT ? OFFSET ?`
|
|
193
|
+
)
|
|
194
|
+
.all(input.projectId, input.artifactId, input.limit ?? -1, offset) as Row[]
|
|
195
|
+
return {
|
|
196
|
+
versions: rows.map(parseArtifactVersionRow),
|
|
197
|
+
hasMore: offset + rows.length < total,
|
|
198
|
+
total,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async listReferencingVersions(
|
|
203
|
+
input: ListReferencingVersionsInput
|
|
204
|
+
): Promise<ArtifactVersionRecord[]> {
|
|
205
|
+
// An address names the artifact when the id is followed by nothing, a version, a file, or a
|
|
206
|
+
// part.
|
|
207
|
+
const prefix = `artifact:${input.artifactId}`
|
|
208
|
+
const rows = this.db
|
|
209
|
+
.query(
|
|
210
|
+
`SELECT ${VERSION_COLUMNS} FROM artifact_versions v
|
|
211
|
+
WHERE (project_id, artifact_id, version) IN (
|
|
212
|
+
SELECT project_id, id, latest_version FROM artifacts WHERE project_id = ?
|
|
213
|
+
) AND EXISTS (
|
|
214
|
+
SELECT 1 FROM json_each(v.refs) r
|
|
215
|
+
WHERE substr(json_extract(r.value, '$.address'), 1, ?) = ?
|
|
216
|
+
AND substr(json_extract(r.value, '$.address'), ? + 1, 1) IN ('', '@', '/', '#')
|
|
217
|
+
)
|
|
218
|
+
ORDER BY artifact_id`
|
|
219
|
+
)
|
|
220
|
+
.all(input.projectId, prefix.length, prefix, prefix.length) as Row[]
|
|
221
|
+
return rows.map(parseArtifactVersionRow)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async createFrameGrant(record: ArtifactFrameGrantRecord): Promise<void> {
|
|
225
|
+
runImmediateTransaction(this.db, () => {
|
|
226
|
+
const exists = this.db
|
|
227
|
+
.query("SELECT 1 FROM artifacts WHERE project_id = ? AND id = ?")
|
|
228
|
+
.get(record.projectId, record.artifactId)
|
|
229
|
+
if (!exists) throw artifactStorageErrors.notFound(record.artifactId)
|
|
230
|
+
this.db
|
|
231
|
+
.query(
|
|
232
|
+
`INSERT INTO artifact_frame_grants
|
|
233
|
+
(project_id, token_hash, artifact_id, version, targets, created_at, expires_at)
|
|
234
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
235
|
+
)
|
|
236
|
+
.run(
|
|
237
|
+
record.projectId,
|
|
238
|
+
record.tokenHash,
|
|
239
|
+
record.artifactId,
|
|
240
|
+
record.version,
|
|
241
|
+
JSON.stringify(record.targets),
|
|
242
|
+
record.createdAt.toISOString(),
|
|
243
|
+
record.expiresAt.toISOString()
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async findFrameGrant(
|
|
249
|
+
input: FindArtifactFrameGrantInput
|
|
250
|
+
): Promise<ArtifactFrameGrantRecord | null> {
|
|
251
|
+
const row = this.db
|
|
252
|
+
.query(
|
|
253
|
+
`SELECT ${FRAME_GRANT_COLUMNS} FROM artifact_frame_grants
|
|
254
|
+
WHERE project_id = ? AND token_hash = ?`
|
|
255
|
+
)
|
|
256
|
+
.get(input.projectId, input.tokenHash) as Row | null
|
|
257
|
+
return row ? parseArtifactFrameGrantRow(row) : null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async deleteExpiredFrameGrants(input: DeleteExpiredArtifactFrameGrantsInput): Promise<number> {
|
|
261
|
+
return this.db
|
|
262
|
+
.query(
|
|
263
|
+
`DELETE FROM artifact_frame_grants WHERE rowid IN (
|
|
264
|
+
SELECT rowid FROM artifact_frame_grants
|
|
265
|
+
WHERE project_id = ? AND expires_at < ?
|
|
266
|
+
ORDER BY expires_at LIMIT ?
|
|
267
|
+
)`
|
|
268
|
+
)
|
|
269
|
+
.run(input.projectId, input.expiredBefore.toISOString(), input.limit).changes
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async delete(input: DeleteArtifactInput): Promise<boolean> {
|
|
273
|
+
// Versions and frame grants go with the artifact through ON DELETE CASCADE.
|
|
274
|
+
return (
|
|
275
|
+
this.db
|
|
276
|
+
.query("DELETE FROM artifacts WHERE project_id = ? AND id = ?")
|
|
277
|
+
.run(input.projectId, input.id).changes > 0
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async deleteVersionsByOrigin(input: DeleteArtifactVersionsByOriginInput): Promise<number> {
|
|
282
|
+
return runImmediateTransaction(this.db, () => {
|
|
283
|
+
const affected = this.db
|
|
284
|
+
.query(
|
|
285
|
+
`SELECT DISTINCT artifact_id AS artifactId FROM artifact_versions
|
|
286
|
+
WHERE project_id = ? AND origin_run_id = ?`
|
|
287
|
+
)
|
|
288
|
+
.all(input.projectId, input.runId) as { readonly artifactId: string }[]
|
|
289
|
+
const deleted = this.db
|
|
290
|
+
.query("DELETE FROM artifact_versions WHERE project_id = ? AND origin_run_id = ?")
|
|
291
|
+
.run(input.projectId, input.runId).changes
|
|
292
|
+
for (const { artifactId } of affected) {
|
|
293
|
+
this.repointLatest(input.projectId, artifactId)
|
|
294
|
+
}
|
|
295
|
+
return deleted
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Point an artifact at its newest remaining version, or delete it when none remains. */
|
|
300
|
+
private repointLatest(projectId: string, artifactId: string): void {
|
|
301
|
+
const latest = this.db
|
|
302
|
+
.query(
|
|
303
|
+
`SELECT version, title, created_at AS createdAt FROM artifact_versions
|
|
304
|
+
WHERE project_id = ? AND artifact_id = ? ORDER BY version DESC LIMIT 1`
|
|
305
|
+
)
|
|
306
|
+
.get(projectId, artifactId) as {
|
|
307
|
+
readonly version: number
|
|
308
|
+
readonly title: string
|
|
309
|
+
readonly createdAt: string
|
|
310
|
+
} | null
|
|
311
|
+
if (!latest) {
|
|
312
|
+
this.db
|
|
313
|
+
.query("DELETE FROM artifacts WHERE project_id = ? AND id = ?")
|
|
314
|
+
.run(projectId, artifactId)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
this.db
|
|
318
|
+
.query(
|
|
319
|
+
`UPDATE artifacts SET title = ?, latest_version = ?, updated_at = ?
|
|
320
|
+
WHERE project_id = ? AND id = ?`
|
|
321
|
+
)
|
|
322
|
+
.run(latest.title, latest.version, latest.createdAt, projectId, artifactId)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private insertVersion(
|
|
326
|
+
projectId: string,
|
|
327
|
+
artifactId: string,
|
|
328
|
+
version: number,
|
|
329
|
+
input: ArtifactVersionInput
|
|
330
|
+
): ArtifactVersionRecord {
|
|
331
|
+
const row = this.db
|
|
332
|
+
.query(
|
|
333
|
+
`INSERT INTO artifact_versions (
|
|
334
|
+
project_id, artifact_id, version, title, entry, files, outline, refs,
|
|
335
|
+
author_principal_type, author_principal_id, origin_thread_id, origin_run_id, created_at
|
|
336
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
337
|
+
RETURNING ${VERSION_COLUMNS}`
|
|
338
|
+
)
|
|
339
|
+
.get(
|
|
340
|
+
projectId,
|
|
341
|
+
artifactId,
|
|
342
|
+
version,
|
|
343
|
+
input.title,
|
|
344
|
+
input.entry,
|
|
345
|
+
JSON.stringify(input.files),
|
|
346
|
+
JSON.stringify(input.outline),
|
|
347
|
+
JSON.stringify(input.references),
|
|
348
|
+
input.author.type,
|
|
349
|
+
input.author.id,
|
|
350
|
+
input.origin?.threadId ?? null,
|
|
351
|
+
input.origin?.runId ?? null,
|
|
352
|
+
input.createdAt.toISOString()
|
|
353
|
+
) as Row
|
|
354
|
+
return parseArtifactVersionRow(row)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -16,7 +16,6 @@ import type {
|
|
|
16
16
|
UserRecord,
|
|
17
17
|
} from "@sixb/core/storage"
|
|
18
18
|
import { AuthStorageError } from "@sixb/core/storage"
|
|
19
|
-
import { installFreshSqliteSchema } from "../migrations"
|
|
20
19
|
import {
|
|
21
20
|
closeSqliteStoreConnection,
|
|
22
21
|
openSqliteStoreConnection,
|
|
@@ -90,10 +89,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
90
89
|
this.connection = openSqliteStoreConnection(options)
|
|
91
90
|
this.db = this.connection.db
|
|
92
91
|
|
|
93
|
-
if (this.connection.installFreshSchema) {
|
|
94
|
-
installFreshSqliteSchema(this.db)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
92
|
this.users = new SqliteAuthUserStore(this.db)
|
|
98
93
|
this.identities = new SqliteAuthUserIdentityStore(this.db)
|
|
99
94
|
this.serviceAccounts = new SqliteAuthServiceAccountStore(this.db)
|
|
@@ -202,7 +197,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
202
197
|
}
|
|
203
198
|
|
|
204
199
|
validateCompleteSessionInput(this.db, projectId, input.session)
|
|
205
|
-
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience)
|
|
206
200
|
|
|
207
201
|
let newUserId: string | undefined
|
|
208
202
|
if (shouldCreateUser) {
|
|
@@ -340,7 +334,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
340
334
|
}
|
|
341
335
|
|
|
342
336
|
validateCompleteSessionInput(this.db, projectId, input.session)
|
|
343
|
-
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience)
|
|
344
337
|
|
|
345
338
|
let newUserId: string | undefined
|
|
346
339
|
if (shouldCreateUser) {
|
|
@@ -700,7 +693,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
700
693
|
projectId: input.projectId,
|
|
701
694
|
userId: input.userId,
|
|
702
695
|
strategyId: input.strategyId,
|
|
703
|
-
audience: input.session.audience,
|
|
704
696
|
tokenHash: input.session.tokenHash,
|
|
705
697
|
createdAt: input.session.createdAt,
|
|
706
698
|
expiresAt: input.session.expiresAt,
|
|
@@ -773,21 +765,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
773
765
|
}
|
|
774
766
|
}
|
|
775
767
|
|
|
776
|
-
function assertSignInSessionAudience(
|
|
777
|
-
projectId: string,
|
|
778
|
-
sessionAudience: string,
|
|
779
|
-
storedAudience: string
|
|
780
|
-
): void {
|
|
781
|
-
if (sessionAudience === storedAudience) {
|
|
782
|
-
return
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
throw new AuthStorageError(
|
|
786
|
-
"invalid_input",
|
|
787
|
-
`[Sixb] Sign-in session audience '${sessionAudience}' does not match stored auth audience '${storedAudience}' for project '${projectId}'.`
|
|
788
|
-
)
|
|
789
|
-
}
|
|
790
|
-
|
|
791
768
|
function assertCompletableDeviceAuthorization(
|
|
792
769
|
authorization: DeviceAuthorizationRecord,
|
|
793
770
|
input: CompleteDeviceAuthorizationInput
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
-
import { resolveAuthSessionAudience } from "@sixb/core"
|
|
3
2
|
import type {
|
|
4
3
|
AuthMagicLinkStore,
|
|
5
4
|
CreateAuthMagicLinkInput,
|
|
@@ -28,7 +27,6 @@ export class SqliteAuthMagicLinkStore implements AuthMagicLinkStore {
|
|
|
28
27
|
const id = assertNonEmpty(input.id, "Magic link id")
|
|
29
28
|
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
30
29
|
const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
|
|
31
|
-
const audience = resolveAuthSessionAudience(input.audience)
|
|
32
30
|
const email = normalizeEmail(input.email)
|
|
33
31
|
const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash")
|
|
34
32
|
|
|
@@ -53,20 +51,18 @@ export class SqliteAuthMagicLinkStore implements AuthMagicLinkStore {
|
|
|
53
51
|
project_id,
|
|
54
52
|
id,
|
|
55
53
|
strategy_id,
|
|
56
|
-
audience,
|
|
57
54
|
email,
|
|
58
55
|
token_hash,
|
|
59
56
|
return_to,
|
|
60
57
|
created_at,
|
|
61
58
|
expires_at
|
|
62
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
59
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
63
60
|
`
|
|
64
61
|
)
|
|
65
62
|
.run(
|
|
66
63
|
projectId,
|
|
67
64
|
id,
|
|
68
65
|
strategyId,
|
|
69
|
-
audience,
|
|
70
66
|
email,
|
|
71
67
|
tokenHash,
|
|
72
68
|
input.returnTo ?? null,
|
|
@@ -85,7 +81,6 @@ export class SqliteAuthMagicLinkStore implements AuthMagicLinkStore {
|
|
|
85
81
|
id,
|
|
86
82
|
projectId,
|
|
87
83
|
strategyId,
|
|
88
|
-
audience,
|
|
89
84
|
email,
|
|
90
85
|
tokenHash,
|
|
91
86
|
returnTo: input.returnTo,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
-
import { resolveAuthSessionAudience } from "@sixb/core"
|
|
3
2
|
import type {
|
|
4
3
|
AuthOidcAuthorizationAttemptStore,
|
|
5
4
|
CreateOidcAuthorizationAttemptInput,
|
|
@@ -26,7 +25,6 @@ export class SqliteAuthOidcAuthorizationAttemptStore implements AuthOidcAuthoriz
|
|
|
26
25
|
const id = assertNonEmpty(input.id, "OIDC authorization attempt id")
|
|
27
26
|
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
28
27
|
const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
|
|
29
|
-
const audience = resolveAuthSessionAudience(input.audience)
|
|
30
28
|
const stateHash = assertNonEmpty(input.stateHash, "OIDC state hash")
|
|
31
29
|
const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash")
|
|
32
30
|
const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier")
|
|
@@ -46,21 +44,19 @@ export class SqliteAuthOidcAuthorizationAttemptStore implements AuthOidcAuthoriz
|
|
|
46
44
|
project_id,
|
|
47
45
|
id,
|
|
48
46
|
strategy_id,
|
|
49
|
-
audience,
|
|
50
47
|
state_hash,
|
|
51
48
|
nonce_hash,
|
|
52
49
|
code_verifier,
|
|
53
50
|
return_to,
|
|
54
51
|
created_at,
|
|
55
52
|
expires_at
|
|
56
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
53
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
57
54
|
`
|
|
58
55
|
)
|
|
59
56
|
.run(
|
|
60
57
|
projectId,
|
|
61
58
|
id,
|
|
62
59
|
strategyId,
|
|
63
|
-
audience,
|
|
64
60
|
stateHash,
|
|
65
61
|
nonceHash,
|
|
66
62
|
codeVerifier,
|
|
@@ -80,7 +76,6 @@ export class SqliteAuthOidcAuthorizationAttemptStore implements AuthOidcAuthoriz
|
|
|
80
76
|
id,
|
|
81
77
|
projectId,
|
|
82
78
|
strategyId,
|
|
83
|
-
audience,
|
|
84
79
|
stateHash,
|
|
85
80
|
nonceHash,
|
|
86
81
|
codeVerifier,
|
package/src/auth-storage/rows.ts
CHANGED
|
@@ -64,7 +64,6 @@ export interface SqliteAuthSessionRow {
|
|
|
64
64
|
readonly id: string
|
|
65
65
|
readonly user_id: string
|
|
66
66
|
readonly strategy_id: string
|
|
67
|
-
readonly audience: SessionRecord["audience"]
|
|
68
67
|
readonly token_hash: string
|
|
69
68
|
readonly created_at: string
|
|
70
69
|
readonly expires_at: string
|
|
@@ -127,7 +126,6 @@ export interface SqliteAuthMagicLinkRow {
|
|
|
127
126
|
readonly project_id: string
|
|
128
127
|
readonly id: string
|
|
129
128
|
readonly strategy_id: string
|
|
130
|
-
readonly audience: MagicLinkRecord["audience"]
|
|
131
129
|
readonly email: string
|
|
132
130
|
readonly token_hash: string
|
|
133
131
|
readonly return_to: string | null
|
|
@@ -141,7 +139,6 @@ export interface SqliteAuthOidcAttemptRow {
|
|
|
141
139
|
readonly project_id: string
|
|
142
140
|
readonly id: string
|
|
143
141
|
readonly strategy_id: string
|
|
144
|
-
readonly audience: OidcAuthorizationAttemptRecord["audience"]
|
|
145
142
|
readonly state_hash: string
|
|
146
143
|
readonly nonce_hash: string
|
|
147
144
|
readonly code_verifier: string
|
|
@@ -230,7 +227,6 @@ export function rowToSessionRecord(row: SqliteAuthSessionRow): SessionRecord {
|
|
|
230
227
|
projectId: row.project_id,
|
|
231
228
|
userId: row.user_id,
|
|
232
229
|
strategyId: row.strategy_id,
|
|
233
|
-
audience: row.audience,
|
|
234
230
|
tokenHash: row.token_hash,
|
|
235
231
|
createdAt: new Date(row.created_at),
|
|
236
232
|
expiresAt: new Date(row.expires_at),
|
|
@@ -321,7 +317,6 @@ export function rowToMagicLinkRecord(row: SqliteAuthMagicLinkRow): MagicLinkReco
|
|
|
321
317
|
id: row.id,
|
|
322
318
|
projectId: row.project_id,
|
|
323
319
|
strategyId: row.strategy_id,
|
|
324
|
-
audience: row.audience,
|
|
325
320
|
email: row.email,
|
|
326
321
|
tokenHash: row.token_hash,
|
|
327
322
|
returnTo: row.return_to ?? undefined,
|
|
@@ -339,7 +334,6 @@ export function rowToOidcAuthorizationAttemptRecord(
|
|
|
339
334
|
id: row.id,
|
|
340
335
|
projectId: row.project_id,
|
|
341
336
|
strategyId: row.strategy_id,
|
|
342
|
-
audience: row.audience,
|
|
343
337
|
stateHash: row.state_hash,
|
|
344
338
|
nonceHash: row.nonce_hash,
|
|
345
339
|
codeVerifier: row.code_verifier,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
-
import type { AuthSessionAudience } from "@sixb/core"
|
|
3
2
|
import type {
|
|
4
3
|
AuthSessionStore,
|
|
5
4
|
CreateAuthSessionInput,
|
|
5
|
+
ListAuthSessionsInput,
|
|
6
|
+
ListAuthSessionsResult,
|
|
6
7
|
RotateSessionRefreshTokenResult,
|
|
7
8
|
SessionRecord,
|
|
8
9
|
} from "@sixb/core/storage"
|
|
@@ -33,70 +34,62 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
33
34
|
return getSessionById(this.db, params)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async
|
|
37
|
+
async listActiveByUserId(params: {
|
|
37
38
|
readonly projectId: string
|
|
38
39
|
readonly userId: string
|
|
39
|
-
readonly audience: AuthSessionAudience
|
|
40
40
|
readonly now: Date
|
|
41
|
-
}): Promise<SessionRecord
|
|
42
|
-
const
|
|
41
|
+
}): Promise<readonly SessionRecord[]> {
|
|
42
|
+
const rows = this.db
|
|
43
43
|
.query(
|
|
44
44
|
`
|
|
45
45
|
SELECT *
|
|
46
46
|
FROM auth_sessions
|
|
47
47
|
WHERE project_id = ?
|
|
48
48
|
AND user_id = ?
|
|
49
|
-
AND audience = ?
|
|
50
49
|
AND revoked_at IS NULL
|
|
51
50
|
AND expires_at > ?
|
|
52
51
|
AND (absolute_expires_at IS NULL OR absolute_expires_at > ?)
|
|
53
|
-
ORDER BY created_at DESC, id DESC
|
|
54
|
-
LIMIT 1
|
|
52
|
+
ORDER BY COALESCE(last_seen_at, created_at) DESC, created_at DESC, id DESC
|
|
55
53
|
`
|
|
56
54
|
)
|
|
57
|
-
.
|
|
55
|
+
.all(
|
|
58
56
|
params.projectId,
|
|
59
57
|
params.userId,
|
|
60
|
-
params.audience,
|
|
61
58
|
toIso(params.now),
|
|
62
59
|
toIso(params.now)
|
|
63
|
-
) as SqliteAuthSessionRow
|
|
60
|
+
) as SqliteAuthSessionRow[]
|
|
64
61
|
|
|
65
|
-
return
|
|
62
|
+
return rows.map(rowToSessionRecord)
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
async
|
|
69
|
-
readonly projectId: string
|
|
70
|
-
readonly userId: string
|
|
71
|
-
readonly now: Date
|
|
72
|
-
}): Promise<readonly SessionRecord[]> {
|
|
65
|
+
async listByUserId(params: ListAuthSessionsInput): Promise<ListAuthSessionsResult> {
|
|
73
66
|
const rows = this.db
|
|
74
67
|
.query(
|
|
75
68
|
`
|
|
76
69
|
SELECT *
|
|
77
70
|
FROM auth_sessions
|
|
78
|
-
WHERE project_id = ?
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
AND expires_at > ?
|
|
82
|
-
AND (absolute_expires_at IS NULL OR absolute_expires_at > ?)
|
|
83
|
-
ORDER BY COALESCE(last_seen_at, created_at) DESC, created_at DESC, id DESC
|
|
71
|
+
WHERE project_id = ? AND user_id = ? AND created_at <= ?
|
|
72
|
+
ORDER BY created_at DESC, id DESC
|
|
73
|
+
LIMIT ? OFFSET ?
|
|
84
74
|
`
|
|
85
75
|
)
|
|
86
76
|
.all(
|
|
87
77
|
params.projectId,
|
|
88
78
|
params.userId,
|
|
89
|
-
toIso(params.
|
|
90
|
-
|
|
79
|
+
params.before ? toIso(params.before.createdAt) : "9999-12-31T23:59:59.999Z",
|
|
80
|
+
params.limit + 1,
|
|
81
|
+
params.before?.skip ?? 0
|
|
91
82
|
) as SqliteAuthSessionRow[]
|
|
92
83
|
|
|
93
|
-
return
|
|
84
|
+
return {
|
|
85
|
+
sessions: rows.slice(0, params.limit).map(rowToSessionRecord),
|
|
86
|
+
hasMore: rows.length > params.limit,
|
|
87
|
+
}
|
|
94
88
|
}
|
|
95
89
|
|
|
96
90
|
async findValidByTokenHash(params: {
|
|
97
91
|
readonly projectId: string
|
|
98
92
|
readonly id: string
|
|
99
|
-
readonly audience: AuthSessionAudience
|
|
100
93
|
readonly tokenHash: string
|
|
101
94
|
readonly now: Date
|
|
102
95
|
}): Promise<SessionRecord | null> {
|
|
@@ -105,7 +98,6 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
105
98
|
if (
|
|
106
99
|
!row ||
|
|
107
100
|
row.refresh_token_hash !== null ||
|
|
108
|
-
row.audience !== params.audience ||
|
|
109
101
|
row.token_hash !== params.tokenHash ||
|
|
110
102
|
!isLiveSessionRow(row, params.now)
|
|
111
103
|
) {
|
|
@@ -190,7 +182,6 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
190
182
|
async renewIfValid(params: {
|
|
191
183
|
readonly projectId: string
|
|
192
184
|
readonly id: string
|
|
193
|
-
readonly audience: AuthSessionAudience
|
|
194
185
|
readonly tokenHash: string
|
|
195
186
|
readonly now: Date
|
|
196
187
|
readonly expiresAt: Date
|
|
@@ -209,7 +200,6 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
209
200
|
last_seen_at = ?
|
|
210
201
|
WHERE project_id = ?
|
|
211
202
|
AND id = ?
|
|
212
|
-
AND audience = ?
|
|
213
203
|
AND token_hash = ?
|
|
214
204
|
AND refresh_token_hash IS NULL
|
|
215
205
|
AND revoked_at IS NULL
|
|
@@ -217,17 +207,7 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
217
207
|
AND (absolute_expires_at IS NULL OR absolute_expires_at > ?)
|
|
218
208
|
`
|
|
219
209
|
)
|
|
220
|
-
.run(
|
|
221
|
-
expiresAt,
|
|
222
|
-
expiresAt,
|
|
223
|
-
now,
|
|
224
|
-
params.projectId,
|
|
225
|
-
params.id,
|
|
226
|
-
params.audience,
|
|
227
|
-
params.tokenHash,
|
|
228
|
-
now,
|
|
229
|
-
now
|
|
230
|
-
)
|
|
210
|
+
.run(expiresAt, expiresAt, now, params.projectId, params.id, params.tokenHash, now, now)
|
|
231
211
|
|
|
232
212
|
return result.changes === 0 ? null : getSessionById(this.db, params)
|
|
233
213
|
})
|
|
@@ -269,7 +249,6 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
269
249
|
async revokeActiveForUser(params: {
|
|
270
250
|
readonly projectId: string
|
|
271
251
|
readonly userId: string
|
|
272
|
-
readonly audience?: AuthSessionAudience
|
|
273
252
|
readonly revokedAt: Date
|
|
274
253
|
}): Promise<readonly SessionRecord[]> {
|
|
275
254
|
return runImmediateTransaction(this.db, () => revokeActiveSessionsForUser(this.db, params))
|