@sixb/sqlite 0.1.20 → 0.1.22
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 +8 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/approvals.d.ts +51 -0
- package/dist/agents/approvals.d.ts.map +1 -0
- package/dist/agents/checkpoints.d.ts +1 -0
- package/dist/agents/checkpoints.d.ts.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/memory.d.ts +21 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +15 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +14 -1
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/agents/settings.d.ts +29 -0
- package/dist/agents/settings.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +16 -1
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/ai-cost-groups.d.ts.map +1 -1
- package/dist/ai-cost-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +4 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/artifact-storage.d.ts +31 -0
- package/dist/artifact-storage.d.ts.map +1 -0
- package/dist/auth-storage/auth-storage.d.ts +3 -2
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/sessions.d.ts +2 -1
- package/dist/auth-storage/sessions.d.ts.map +1 -1
- package/dist/auth-storage/users.d.ts +7 -3
- package/dist/auth-storage/users.d.ts.map +1 -1
- package/dist/execution-storage.d.ts +3 -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 +5216 -2517
- package/dist/index.js.map +39 -31
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/conditional-reader.d.ts +8 -0
- package/dist/objects/conditional-reader.d.ts.map +1 -0
- package/dist/objects/query-compiler.d.ts +26 -1
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +10 -1
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +2 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/scoped-reader.d.ts +15 -0
- package/dist/objects/scoped-reader.d.ts.map +1 -0
- package/dist/objects/selected-reader.d.ts.map +1 -1
- package/dist/objects/storage.d.ts +6 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +2 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +2 -0
- 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/sources.d.ts +2 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/ontology-storage/unique-values.d.ts +42 -0
- package/dist/ontology-storage/unique-values.d.ts.map +1 -0
- package/dist/share-grant-storage.d.ts +2 -1
- package/dist/share-grant-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 -0
- package/src/activity-storage.ts +588 -0
- package/src/agents/agent-storage.ts +11 -2
- package/src/agents/approvals.ts +456 -0
- package/src/agents/checkpoints.ts +9 -4
- package/src/agents/index.ts +1 -0
- package/src/agents/memory.ts +195 -0
- package/src/agents/messages.ts +21 -18
- package/src/agents/rows.ts +46 -1
- package/src/agents/runs.ts +202 -21
- package/src/agents/settings.ts +112 -0
- package/src/agents/threads.ts +412 -1
- package/src/ai-cost-groups.ts +11 -1
- package/src/ai-cost-storage.ts +54 -12
- package/src/ai-usage-storage.ts +79 -2
- package/src/artifact-storage.ts +553 -0
- package/src/auth-storage/auth-storage.ts +58 -26
- package/src/auth-storage/sessions.ts +27 -0
- package/src/auth-storage/users.ts +106 -60
- package/src/execution-storage.ts +95 -0
- package/src/index.ts +10 -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/067-artifact-kinds.sql +32 -0
- package/src/migrations/068-artifact-state.sql +15 -0
- package/src/migrations/069-agent-thread-participants.sql +16 -0
- package/src/migrations/070-forked-context-checkpoints.sql +45 -0
- package/src/migrations/071-unique-values.sql +22 -0
- package/src/migrations/072-generated-media-usage.sql +32 -0
- package/src/migrations/073-model-call-files.sql +11 -0
- package/src/migrations/074-agent-memory.sql +32 -0
- package/src/migrations/075-artifact-threads.sql +21 -0
- package/src/migrations/076-agent-approvals.sql +59 -0
- package/src/migrations/077-agent-approval-resume.sql +20 -0
- package/src/migrations/078-subagent-threads.sql +241 -0
- package/src/migrations/079-subagent-questions.sql +6 -0
- package/src/migrations.ts +89 -1
- package/src/objects/README.md +7 -0
- package/src/objects/conditional-reader.ts +27 -0
- package/src/objects/query-compiler.ts +36 -6
- package/src/objects/read-scope.ts +140 -23
- package/src/objects/reader.ts +47 -14
- package/src/objects/scoped-reader.ts +103 -0
- package/src/objects/selected-reader.ts +12 -75
- package/src/objects/storage.ts +16 -0
- package/src/ontology-storage/index.ts +3 -0
- package/src/ontology-storage/materialization-writer.ts +14 -0
- package/src/ontology-storage/materializations.ts +16 -1
- package/src/ontology-storage/sources.ts +41 -0
- package/src/ontology-storage/unique-values.ts +187 -0
- package/src/share-grant-storage.ts +25 -0
- package/src/transactions.ts +1 -1
- package/src/workflow-run-storage.ts +27 -0
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
InvitationRecord,
|
|
13
13
|
SessionRecord,
|
|
14
14
|
SuspendUserAndRevokeSessionsInput,
|
|
15
|
+
UpdateAuthUserResult,
|
|
15
16
|
UserIdentityRecord,
|
|
16
17
|
UserRecord,
|
|
17
18
|
} from "@sixb/core/storage"
|
|
@@ -32,7 +33,7 @@ import { SqliteAuthUserIdentityStore } from "./identities"
|
|
|
32
33
|
import { SqliteAuthInvitationStore } from "./invitations"
|
|
33
34
|
import { SqliteAuthMagicLinkStore } from "./magic-links"
|
|
34
35
|
import { SqliteAuthOidcAuthorizationAttemptStore } from "./oidc-attempts"
|
|
35
|
-
import type { SqliteAuthMagicLinkRow, SqliteAuthOidcAttemptRow } from "./rows"
|
|
36
|
+
import type { SqliteAuthMagicLinkRow, SqliteAuthOidcAttemptRow, SqliteAuthUserRow } from "./rows"
|
|
36
37
|
import { rowToIdentityRecord, rowToUserRecord, serializeOptionalRecord } from "./rows"
|
|
37
38
|
import { SqliteAuthServiceAccountGroupMembershipStore } from "./service-account-group-memberships"
|
|
38
39
|
import { SqliteAuthServiceAccountStore } from "./service-accounts"
|
|
@@ -216,16 +217,17 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
216
217
|
consumedAt: completedAt,
|
|
217
218
|
})
|
|
218
219
|
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
220
|
+
const previousUser = existingUserRow ? rowToUserRecord(existingUserRow) : null
|
|
221
|
+
const user =
|
|
222
|
+
previousUser ??
|
|
223
|
+
this.insertUser({
|
|
224
|
+
id: newUserId ?? assertNonEmpty(input.newUserId, "User id"),
|
|
225
|
+
projectId,
|
|
226
|
+
email: magicLink.email,
|
|
227
|
+
displayName: input.newUserDisplayName,
|
|
228
|
+
avatarUrl: input.newUserAvatarUrl,
|
|
229
|
+
createdAt: completedAt,
|
|
230
|
+
})
|
|
229
231
|
|
|
230
232
|
const invitation = this.acceptInvitationAndApplyGroups({
|
|
231
233
|
activeInvitation,
|
|
@@ -253,6 +255,7 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
253
255
|
session,
|
|
254
256
|
invitation: invitation.invitation,
|
|
255
257
|
groupMemberships,
|
|
258
|
+
previousUser,
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
)
|
|
@@ -297,7 +300,7 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
297
300
|
email,
|
|
298
301
|
now: completedAt,
|
|
299
302
|
})
|
|
300
|
-
|
|
303
|
+
const userRow = identity
|
|
301
304
|
? getUserRowById(this.db, { projectId, id: identity.user_id })
|
|
302
305
|
: getUserRowByEmail(this.db, { projectId, email })
|
|
303
306
|
const shouldCreateUser = !identity && !userRow
|
|
@@ -348,8 +351,10 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
348
351
|
|
|
349
352
|
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
350
353
|
|
|
354
|
+
const previousUser = userRow ? rowToUserRecord(userRow) : null
|
|
355
|
+
let user: UserRecord
|
|
351
356
|
if (!userRow) {
|
|
352
|
-
|
|
357
|
+
user = this.insertUser({
|
|
353
358
|
id: newUserId ?? assertNonEmpty(input.newUserId, "User id"),
|
|
354
359
|
projectId,
|
|
355
360
|
email,
|
|
@@ -357,19 +362,10 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
357
362
|
avatarUrl: input.avatarUrl,
|
|
358
363
|
createdAt: completedAt,
|
|
359
364
|
})
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
id: user.id,
|
|
363
|
-
email: user.email,
|
|
364
|
-
display_name: user.displayName ?? null,
|
|
365
|
-
avatar_url: user.avatarUrl ?? null,
|
|
366
|
-
status: user.status,
|
|
367
|
-
created_at: toIso(user.createdAt),
|
|
368
|
-
updated_at: toIso(user.updatedAt),
|
|
369
|
-
}
|
|
365
|
+
} else {
|
|
366
|
+
user = this.refreshProviderProfile(userRow, input, completedAt)
|
|
370
367
|
}
|
|
371
368
|
|
|
372
|
-
const user = rowToUserRecord(userRow)
|
|
373
369
|
const invitation = this.acceptInvitationAndApplyGroups({
|
|
374
370
|
activeInvitation,
|
|
375
371
|
completedAt,
|
|
@@ -412,6 +408,7 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
412
408
|
identity: nextIdentity,
|
|
413
409
|
invitation: invitation.invitation,
|
|
414
410
|
groupMemberships,
|
|
411
|
+
previousUser,
|
|
415
412
|
}
|
|
416
413
|
}
|
|
417
414
|
)
|
|
@@ -425,7 +422,7 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
425
422
|
|
|
426
423
|
async suspendUserAndRevokeSessions(
|
|
427
424
|
input: SuspendUserAndRevokeSessionsInput
|
|
428
|
-
): Promise<
|
|
425
|
+
): Promise<UpdateAuthUserResult> {
|
|
429
426
|
return runImmediateTransaction(this.db, () => {
|
|
430
427
|
const existing = getUserRowById(this.db, {
|
|
431
428
|
projectId: input.projectId,
|
|
@@ -456,11 +453,12 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
456
453
|
revokedAt: input.suspendedAt,
|
|
457
454
|
})
|
|
458
455
|
|
|
459
|
-
|
|
456
|
+
const user = rowToUserRecord({
|
|
460
457
|
...existing,
|
|
461
458
|
status: "suspended",
|
|
462
459
|
updated_at: toIso(input.suspendedAt),
|
|
463
460
|
})
|
|
461
|
+
return { user, previousUser: rowToUserRecord(existing) }
|
|
464
462
|
})
|
|
465
463
|
}
|
|
466
464
|
|
|
@@ -526,6 +524,40 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
526
524
|
}
|
|
527
525
|
}
|
|
528
526
|
|
|
527
|
+
// The identity provider owns the name and picture it sends: every sign-in overwrites them, and
|
|
528
|
+
// one it leaves out keeps the stored value.
|
|
529
|
+
private refreshProviderProfile(
|
|
530
|
+
row: SqliteAuthUserRow,
|
|
531
|
+
input: CompleteOidcSignInInput,
|
|
532
|
+
updatedAt: Date
|
|
533
|
+
): UserRecord {
|
|
534
|
+
const displayName = input.displayName ?? row.display_name
|
|
535
|
+
const avatarUrl = input.avatarUrl ?? row.avatar_url
|
|
536
|
+
if (displayName === row.display_name && avatarUrl === row.avatar_url) {
|
|
537
|
+
return rowToUserRecord(row)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
this.db
|
|
541
|
+
.query(
|
|
542
|
+
`
|
|
543
|
+
UPDATE auth_users
|
|
544
|
+
SET display_name = ?,
|
|
545
|
+
avatar_url = ?,
|
|
546
|
+
updated_at = ?
|
|
547
|
+
WHERE project_id = ?
|
|
548
|
+
AND id = ?
|
|
549
|
+
`
|
|
550
|
+
)
|
|
551
|
+
.run(displayName, avatarUrl, toIso(updatedAt), row.project_id, row.id)
|
|
552
|
+
|
|
553
|
+
return rowToUserRecord({
|
|
554
|
+
...row,
|
|
555
|
+
display_name: displayName,
|
|
556
|
+
avatar_url: avatarUrl,
|
|
557
|
+
updated_at: toIso(updatedAt),
|
|
558
|
+
})
|
|
559
|
+
}
|
|
560
|
+
|
|
529
561
|
private insertUser(input: {
|
|
530
562
|
readonly id: string
|
|
531
563
|
readonly projectId: string
|
|
@@ -2,6 +2,8 @@ import type { Database } from "bun:sqlite"
|
|
|
2
2
|
import type {
|
|
3
3
|
AuthSessionStore,
|
|
4
4
|
CreateAuthSessionInput,
|
|
5
|
+
ListAuthSessionsInput,
|
|
6
|
+
ListAuthSessionsResult,
|
|
5
7
|
RotateSessionRefreshTokenResult,
|
|
6
8
|
SessionRecord,
|
|
7
9
|
} from "@sixb/core/storage"
|
|
@@ -60,6 +62,31 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
|
|
|
60
62
|
return rows.map(rowToSessionRecord)
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
async listByUserId(params: ListAuthSessionsInput): Promise<ListAuthSessionsResult> {
|
|
66
|
+
const rows = this.db
|
|
67
|
+
.query(
|
|
68
|
+
`
|
|
69
|
+
SELECT *
|
|
70
|
+
FROM auth_sessions
|
|
71
|
+
WHERE project_id = ? AND user_id = ? AND created_at <= ?
|
|
72
|
+
ORDER BY created_at DESC, id DESC
|
|
73
|
+
LIMIT ? OFFSET ?
|
|
74
|
+
`
|
|
75
|
+
)
|
|
76
|
+
.all(
|
|
77
|
+
params.projectId,
|
|
78
|
+
params.userId,
|
|
79
|
+
params.before ? toIso(params.before.createdAt) : "9999-12-31T23:59:59.999Z",
|
|
80
|
+
params.limit + 1,
|
|
81
|
+
params.before?.skip ?? 0
|
|
82
|
+
) as SqliteAuthSessionRow[]
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
sessions: rows.slice(0, params.limit).map(rowToSessionRecord),
|
|
86
|
+
hasMore: rows.length > params.limit,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
async findValidByTokenHash(params: {
|
|
64
91
|
readonly projectId: string
|
|
65
92
|
readonly id: string
|
|
@@ -5,10 +5,12 @@ import type {
|
|
|
5
5
|
ListAuthUsersInput,
|
|
6
6
|
ListAuthUsersResult,
|
|
7
7
|
UpdateAuthUserProfileInput,
|
|
8
|
+
UpdateAuthUserResult,
|
|
8
9
|
UpdateAuthUserStatusInput,
|
|
9
10
|
UserRecord,
|
|
10
11
|
} from "@sixb/core/storage"
|
|
11
12
|
import { AuthStorageError } from "@sixb/core/storage"
|
|
13
|
+
import { runImmediateTransaction } from "../transactions"
|
|
12
14
|
import type { SqliteAuthUserRow } from "./rows"
|
|
13
15
|
import { rowToUserRecord } from "./rows"
|
|
14
16
|
import {
|
|
@@ -103,6 +105,22 @@ export class SqliteAuthUserStore implements AuthUserStore {
|
|
|
103
105
|
return row ? rowToUserRecord(row) : null
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
async getByIds(params: {
|
|
109
|
+
readonly projectId: string
|
|
110
|
+
readonly ids: readonly string[]
|
|
111
|
+
}): Promise<readonly UserRecord[]> {
|
|
112
|
+
const ids = [...new Set(params.ids)]
|
|
113
|
+
if (ids.length === 0) return []
|
|
114
|
+
|
|
115
|
+
const rows = this.db
|
|
116
|
+
.query(
|
|
117
|
+
`SELECT * FROM auth_users WHERE project_id = ? AND id IN (${ids.map(() => "?").join(", ")})`
|
|
118
|
+
)
|
|
119
|
+
.all(params.projectId, ...ids) as SqliteAuthUserRow[]
|
|
120
|
+
|
|
121
|
+
return rows.map(rowToUserRecord)
|
|
122
|
+
}
|
|
123
|
+
|
|
106
124
|
async getByEmail(params: {
|
|
107
125
|
readonly projectId: string
|
|
108
126
|
readonly email: string
|
|
@@ -111,77 +129,87 @@ export class SqliteAuthUserStore implements AuthUserStore {
|
|
|
111
129
|
return row ? rowToUserRecord(row) : null
|
|
112
130
|
}
|
|
113
131
|
|
|
114
|
-
async updateProfile(input: UpdateAuthUserProfileInput): Promise<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
async updateProfile(input: UpdateAuthUserProfileInput): Promise<UpdateAuthUserResult> {
|
|
133
|
+
// One transaction, so the user it returns as previous is the one it changed.
|
|
134
|
+
return runImmediateTransaction(this.db, () => {
|
|
135
|
+
const existing = getUserRowById(this.db, {
|
|
136
|
+
projectId: input.projectId,
|
|
137
|
+
id: input.id,
|
|
138
|
+
})
|
|
119
139
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
if (!existing) {
|
|
141
|
+
throw new AuthStorageError(
|
|
142
|
+
"missing_user",
|
|
143
|
+
`[Sixb] User '${input.id}' not found for project '${input.projectId}'.`
|
|
144
|
+
)
|
|
145
|
+
}
|
|
126
146
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
const previousUser = rowToUserRecord(existing)
|
|
148
|
+
if (!changesProfile(previousUser, input)) {
|
|
149
|
+
return { user: previousUser, previousUser }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const updatedAt = dateOrNow(input.updatedAt)
|
|
153
|
+
const row = this.db
|
|
154
|
+
.query(
|
|
155
|
+
`
|
|
156
|
+
UPDATE auth_users
|
|
157
|
+
SET display_name = CASE WHEN ? THEN ? ELSE display_name END,
|
|
158
|
+
avatar_url = CASE WHEN ? THEN ? ELSE avatar_url END,
|
|
159
|
+
updated_at = ?
|
|
160
|
+
WHERE project_id = ?
|
|
161
|
+
AND id = ?
|
|
162
|
+
RETURNING *
|
|
130
163
|
`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
input.avatarUrl ?? null,
|
|
142
|
-
toIso(updatedAt),
|
|
143
|
-
input.projectId,
|
|
144
|
-
input.id
|
|
145
|
-
)
|
|
164
|
+
)
|
|
165
|
+
.get(
|
|
166
|
+
input.displayName === undefined ? 0 : 1,
|
|
167
|
+
input.displayName ?? null,
|
|
168
|
+
input.avatarUrl === undefined ? 0 : 1,
|
|
169
|
+
input.avatarUrl ?? null,
|
|
170
|
+
toIso(updatedAt),
|
|
171
|
+
input.projectId,
|
|
172
|
+
input.id
|
|
173
|
+
) as SqliteAuthUserRow
|
|
146
174
|
|
|
147
|
-
|
|
148
|
-
...existing,
|
|
149
|
-
display_name: input.displayName ?? null,
|
|
150
|
-
avatar_url: input.avatarUrl ?? null,
|
|
151
|
-
updated_at: toIso(updatedAt),
|
|
175
|
+
return { user: rowToUserRecord(row), previousUser }
|
|
152
176
|
})
|
|
153
177
|
}
|
|
154
178
|
|
|
155
|
-
async updateStatus(input: UpdateAuthUserStatusInput): Promise<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
async updateStatus(input: UpdateAuthUserStatusInput): Promise<UpdateAuthUserResult> {
|
|
180
|
+
// One transaction, so two status changes cannot both report the same previous one.
|
|
181
|
+
return runImmediateTransaction(this.db, () => {
|
|
182
|
+
const existing = getUserRowById(this.db, {
|
|
183
|
+
projectId: input.projectId,
|
|
184
|
+
id: input.id,
|
|
185
|
+
})
|
|
160
186
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
if (!existing) {
|
|
188
|
+
throw new AuthStorageError(
|
|
189
|
+
"missing_user",
|
|
190
|
+
`[Sixb] User '${input.id}' not found for project '${input.projectId}'.`
|
|
191
|
+
)
|
|
192
|
+
}
|
|
167
193
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
const updatedAt = dateOrNow(input.updatedAt)
|
|
195
|
+
this.db
|
|
196
|
+
.query(
|
|
197
|
+
`
|
|
198
|
+
UPDATE auth_users
|
|
199
|
+
SET status = ?,
|
|
200
|
+
updated_at = ?
|
|
201
|
+
WHERE project_id = ?
|
|
202
|
+
AND id = ?
|
|
171
203
|
`
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
updated_at = ?
|
|
175
|
-
WHERE project_id = ?
|
|
176
|
-
AND id = ?
|
|
177
|
-
`
|
|
178
|
-
)
|
|
179
|
-
.run(input.status, toIso(updatedAt), input.projectId, input.id)
|
|
204
|
+
)
|
|
205
|
+
.run(input.status, toIso(updatedAt), input.projectId, input.id)
|
|
180
206
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
207
|
+
const user = rowToUserRecord({
|
|
208
|
+
...existing,
|
|
209
|
+
status: input.status,
|
|
210
|
+
updated_at: toIso(updatedAt),
|
|
211
|
+
})
|
|
212
|
+
return { user, previousUser: rowToUserRecord(existing) }
|
|
185
213
|
})
|
|
186
214
|
}
|
|
187
215
|
|
|
@@ -198,6 +226,15 @@ export class SqliteAuthUserStore implements AuthUserStore {
|
|
|
198
226
|
args.push(...input.statuses)
|
|
199
227
|
}
|
|
200
228
|
|
|
229
|
+
// `instr` matches literally, so `%` and `_` need no escaping. Both sides fold through the same
|
|
230
|
+
// `lower()`, which ignores case for ASCII letters only: "émilie" does not find "Émilie" here.
|
|
231
|
+
if (input.search !== undefined) {
|
|
232
|
+
whereClauses.push(
|
|
233
|
+
"(instr(lower(coalesce(display_name, '')), lower(?)) > 0 OR instr(lower(email), lower(?)) > 0)"
|
|
234
|
+
)
|
|
235
|
+
args.push(input.search, input.search)
|
|
236
|
+
}
|
|
237
|
+
|
|
201
238
|
const where = `WHERE ${whereClauses.join(" AND ")}`
|
|
202
239
|
const order = input.order === "asc" ? "ASC" : "DESC"
|
|
203
240
|
const totalRow = this.db
|
|
@@ -224,3 +261,12 @@ export class SqliteAuthUserStore implements AuthUserStore {
|
|
|
224
261
|
}
|
|
225
262
|
}
|
|
226
263
|
}
|
|
264
|
+
|
|
265
|
+
// Omitted keeps the stored value, and `null` clears it.
|
|
266
|
+
function changesProfile(user: UserRecord, input: UpdateAuthUserProfileInput): boolean {
|
|
267
|
+
const displayName =
|
|
268
|
+
input.displayName === undefined ? user.displayName : (input.displayName ?? undefined)
|
|
269
|
+
const avatarUrl = input.avatarUrl === undefined ? user.avatarUrl : (input.avatarUrl ?? undefined)
|
|
270
|
+
|
|
271
|
+
return displayName !== user.displayName || avatarUrl !== user.avatarUrl
|
|
272
|
+
}
|
package/src/execution-storage.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import {
|
|
3
|
+
compareExecutionCreation,
|
|
3
4
|
type ExecutionStorageRow,
|
|
4
5
|
executionRecordFromStorageRow,
|
|
5
6
|
executionRecordToStorageRow,
|
|
@@ -7,8 +8,12 @@ import {
|
|
|
7
8
|
} from "@sixb/core/internal/execution-storage"
|
|
8
9
|
import type {
|
|
9
10
|
CreateExecutionInput,
|
|
11
|
+
ExecutionDescendant,
|
|
10
12
|
ExecutionRecord,
|
|
11
13
|
ExecutionStorage,
|
|
14
|
+
ListExecutionDescendantsInput,
|
|
15
|
+
ListSessionExecutionsInput,
|
|
16
|
+
SessionExecutions,
|
|
12
17
|
ShareSessionStorage,
|
|
13
18
|
} from "@sixb/core/storage"
|
|
14
19
|
import { ExecutionStorageError } from "@sixb/core/storage"
|
|
@@ -115,6 +120,96 @@ export class SqliteExecutionStorage implements ExecutionStorage {
|
|
|
115
120
|
|
|
116
121
|
return row ? sqliteExecutionRecord(row) : null
|
|
117
122
|
}
|
|
123
|
+
|
|
124
|
+
async listBySession(input: ListSessionExecutionsInput): Promise<SessionExecutions> {
|
|
125
|
+
const sessionColumn =
|
|
126
|
+
input.session.type === "user" ? "authority_session_id" : "authority_delegation_session_id"
|
|
127
|
+
|
|
128
|
+
// The queue is ordered by depth, so the walk takes roots, then their children level by level,
|
|
129
|
+
// the earliest first, and the limit stops it: a large tree costs no more than the rows
|
|
130
|
+
// returned. CROSS JOIN keeps the walk driven by the rows found: statistics taken on a small
|
|
131
|
+
// ledger choose to scan every child.
|
|
132
|
+
const rows = this.db
|
|
133
|
+
.query(
|
|
134
|
+
`
|
|
135
|
+
WITH RECURSIVE tree AS (
|
|
136
|
+
SELECT roots.*, 0 AS depth
|
|
137
|
+
FROM (
|
|
138
|
+
SELECT *
|
|
139
|
+
FROM executions
|
|
140
|
+
WHERE project_id = ?
|
|
141
|
+
AND ${sessionColumn} = ?
|
|
142
|
+
AND parent_execution_id IS NULL
|
|
143
|
+
AND created_at >= ?
|
|
144
|
+
AND created_at < ?
|
|
145
|
+
ORDER BY created_at, id
|
|
146
|
+
LIMIT ?
|
|
147
|
+
) roots
|
|
148
|
+
UNION ALL
|
|
149
|
+
SELECT child.*, tree.depth + 1
|
|
150
|
+
FROM tree
|
|
151
|
+
CROSS JOIN executions child
|
|
152
|
+
ON child.project_id = tree.project_id
|
|
153
|
+
AND child.parent_execution_id = tree.id
|
|
154
|
+
ORDER BY depth, created_at, id
|
|
155
|
+
LIMIT ?
|
|
156
|
+
)
|
|
157
|
+
SELECT * FROM tree
|
|
158
|
+
`
|
|
159
|
+
)
|
|
160
|
+
.all(
|
|
161
|
+
input.projectId,
|
|
162
|
+
input.session.id,
|
|
163
|
+
input.from.toISOString(),
|
|
164
|
+
input.to.toISOString(),
|
|
165
|
+
input.limit + 1,
|
|
166
|
+
input.limit + 1
|
|
167
|
+
) as SqliteExecutionRow[]
|
|
168
|
+
|
|
169
|
+
// Rows come out in walk order, so the first `limit` are the ones the contract keeps.
|
|
170
|
+
const kept = rows.slice(0, input.limit).map(sqliteExecutionRecord)
|
|
171
|
+
return {
|
|
172
|
+
executions: kept.sort(compareExecutionCreation),
|
|
173
|
+
truncated: rows.length > input.limit,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async listDescendants(
|
|
178
|
+
input: ListExecutionDescendantsInput
|
|
179
|
+
): Promise<readonly ExecutionDescendant[]> {
|
|
180
|
+
if (input.executionIds.length === 0) return []
|
|
181
|
+
const ids = [...new Set(input.executionIds)]
|
|
182
|
+
|
|
183
|
+
// UNION, not UNION ALL: an execution asked about may sit below another one asked about, and
|
|
184
|
+
// the walk would otherwise reach what it caused twice.
|
|
185
|
+
const rows = this.db
|
|
186
|
+
.query(
|
|
187
|
+
`
|
|
188
|
+
WITH RECURSIVE tree(id, parent_id) AS (
|
|
189
|
+
SELECT child.id, child.parent_execution_id
|
|
190
|
+
FROM executions child
|
|
191
|
+
WHERE child.project_id = ?
|
|
192
|
+
AND child.parent_execution_id IN (SELECT value FROM json_each(?))
|
|
193
|
+
UNION
|
|
194
|
+
SELECT child.id, child.parent_execution_id
|
|
195
|
+
FROM tree
|
|
196
|
+
CROSS JOIN executions child
|
|
197
|
+
ON child.project_id = ?
|
|
198
|
+
AND child.parent_execution_id = tree.id
|
|
199
|
+
)
|
|
200
|
+
SELECT id, parent_id
|
|
201
|
+
FROM tree
|
|
202
|
+
WHERE id NOT IN (SELECT value FROM json_each(?))
|
|
203
|
+
ORDER BY id
|
|
204
|
+
`
|
|
205
|
+
)
|
|
206
|
+
.all(input.projectId, JSON.stringify(ids), input.projectId, JSON.stringify(ids)) as {
|
|
207
|
+
id: string
|
|
208
|
+
parent_id: string
|
|
209
|
+
}[]
|
|
210
|
+
|
|
211
|
+
return rows.map((row) => ({ id: row.id, parentId: row.parent_id }))
|
|
212
|
+
}
|
|
118
213
|
}
|
|
119
214
|
|
|
120
215
|
/** An `executions` row as its record, for any store that reads executions alongside its own rows. */
|
package/src/index.ts
CHANGED
|
@@ -32,10 +32,12 @@ import {
|
|
|
32
32
|
StorageTransactionError,
|
|
33
33
|
} from "@sixb/core/storage"
|
|
34
34
|
import { SqliteActionRunStorage } from "./action-run-storage"
|
|
35
|
+
import { SqliteActivityStorage } from "./activity-storage"
|
|
35
36
|
import { SqliteAgentStorage } from "./agents"
|
|
36
37
|
import { SqliteAiCostStorage } from "./ai-cost-storage"
|
|
37
38
|
import { SqliteAiLimitStorage } from "./ai-limit-storage"
|
|
38
39
|
import { SqliteAiUsageStorage } from "./ai-usage-storage"
|
|
40
|
+
import { SqliteArtifactStorage } from "./artifact-storage"
|
|
39
41
|
import { SqliteAuthStorage } from "./auth-storage"
|
|
40
42
|
import { SqliteConnectorConnectionStorage } from "./connector-connection-storage"
|
|
41
43
|
import { SqliteExecutionStorage } from "./execution-storage"
|
|
@@ -89,6 +91,7 @@ export class SqliteStorage implements MigrationCapableStorage {
|
|
|
89
91
|
readonly auth: SqliteAuthStorage
|
|
90
92
|
readonly executions: SqliteExecutionStorage
|
|
91
93
|
readonly agents: SqliteAgentStorage
|
|
94
|
+
readonly artifacts: SqliteArtifactStorage
|
|
92
95
|
readonly aiUsage: AiUsageStorage
|
|
93
96
|
readonly aiCosts: AiCostStorage
|
|
94
97
|
readonly aiLimits: AiLimitStorage
|
|
@@ -106,6 +109,7 @@ export class SqliteStorage implements MigrationCapableStorage {
|
|
|
106
109
|
readonly connectorConnections: SqliteConnectorConnectionStorage
|
|
107
110
|
readonly fileUploadSessions: SqliteFileUploadSessionStorage
|
|
108
111
|
readonly fileDownloadGrants: SqliteFileDownloadGrantStorage
|
|
112
|
+
readonly activity: SqliteActivityStorage
|
|
109
113
|
readonly migrators: readonly StorageMigrator[]
|
|
110
114
|
|
|
111
115
|
private readonly connection: SqliteStoreConnection
|
|
@@ -153,6 +157,7 @@ export class SqliteStorage implements MigrationCapableStorage {
|
|
|
153
157
|
this.auth = createAuthOperationScope(stores.auth, scope)
|
|
154
158
|
this.executions = createOperationScopedFacade(stores.executions, scope)
|
|
155
159
|
this.agents = createAgentOperationScope(stores.agents, scope)
|
|
160
|
+
this.artifacts = createOperationScopedFacade(stores.artifacts, scope)
|
|
156
161
|
this.aiUsage = createOperationScopedFacade(stores.aiUsage, scope)
|
|
157
162
|
this.aiCosts = createOperationScopedFacade(stores.aiCosts, scope)
|
|
158
163
|
this.aiLimits = createOperationScopedFacade(stores.aiLimits, scope)
|
|
@@ -170,6 +175,7 @@ export class SqliteStorage implements MigrationCapableStorage {
|
|
|
170
175
|
this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope)
|
|
171
176
|
this.fileUploadSessions = createOperationScopedFacade(stores.fileUploadSessions, scope)
|
|
172
177
|
this.fileDownloadGrants = createOperationScopedFacade(stores.fileDownloadGrants, scope)
|
|
178
|
+
this.activity = createOperationScopedFacade(stores.activity, scope)
|
|
173
179
|
this.migrators = options.path ? createSqliteStorageMigrators(options.path) : []
|
|
174
180
|
registerSqliteStorageTestingAdapter(this, {
|
|
175
181
|
advanceConnectorConnectionTime: (durationMs) =>
|
|
@@ -317,6 +323,7 @@ function createSqliteStores(
|
|
|
317
323
|
auth,
|
|
318
324
|
executions,
|
|
319
325
|
agents: new SqliteAgentStorage({ connection, executions }),
|
|
326
|
+
artifacts: new SqliteArtifactStorage(connection),
|
|
320
327
|
aiUsage: new SqliteAiUsageStorage({ connection }),
|
|
321
328
|
aiCosts: new SqliteAiCostStorage(connection),
|
|
322
329
|
aiLimits: new SqliteAiLimitStorage({ connection }),
|
|
@@ -334,6 +341,7 @@ function createSqliteStores(
|
|
|
334
341
|
connectorConnections: new SqliteConnectorConnectionStorage(connection),
|
|
335
342
|
fileUploadSessions: new SqliteFileUploadSessionStorage(connection),
|
|
336
343
|
fileDownloadGrants: new SqliteFileDownloadGrantStorage(connection),
|
|
344
|
+
activity: new SqliteActivityStorage(connection),
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
347
|
|
|
@@ -343,6 +351,7 @@ interface SqliteStoreSet {
|
|
|
343
351
|
readonly auth: SqliteAuthStorage
|
|
344
352
|
readonly executions: SqliteExecutionStorage
|
|
345
353
|
readonly agents: SqliteAgentStorage
|
|
354
|
+
readonly artifacts: SqliteArtifactStorage
|
|
346
355
|
readonly aiUsage: SqliteAiUsageStorage
|
|
347
356
|
readonly aiCosts: SqliteAiCostStorage
|
|
348
357
|
readonly aiLimits: SqliteAiLimitStorage
|
|
@@ -360,6 +369,7 @@ interface SqliteStoreSet {
|
|
|
360
369
|
readonly connectorConnections: SqliteConnectorConnectionStorage
|
|
361
370
|
readonly fileUploadSessions: SqliteFileUploadSessionStorage
|
|
362
371
|
readonly fileDownloadGrants: SqliteFileDownloadGrantStorage
|
|
372
|
+
readonly activity: SqliteActivityStorage
|
|
363
373
|
}
|
|
364
374
|
|
|
365
375
|
export { migrateSqliteStorage } from "./migrations"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
-- Artifacts: versioned documents, sheets, decks, and files. An artifact's kind never
|
|
2
|
+
-- changes. Versions take `next_version`, which only grows, so a number is never reused after its
|
|
3
|
+
-- version is deleted. Timestamps are ISO-8601 UTC text and compare lexicographically.
|
|
4
|
+
CREATE TABLE artifacts (
|
|
5
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
6
|
+
id TEXT NOT NULL CHECK (length(trim(id)) > 0),
|
|
7
|
+
kind TEXT NOT NULL CHECK (kind IN ('document', 'sheet', 'slides', 'file')),
|
|
8
|
+
title TEXT NOT NULL CHECK (length(trim(title)) > 0),
|
|
9
|
+
owner_principal_type TEXT NOT NULL
|
|
10
|
+
CHECK (owner_principal_type IN ('user', 'serviceAccount', 'system')),
|
|
11
|
+
owner_principal_id TEXT NOT NULL CHECK (length(owner_principal_id) > 0),
|
|
12
|
+
latest_version INTEGER NOT NULL CHECK (latest_version > 0),
|
|
13
|
+
next_version INTEGER NOT NULL CHECK (next_version > latest_version),
|
|
14
|
+
created_at TEXT NOT NULL,
|
|
15
|
+
updated_at TEXT NOT NULL,
|
|
16
|
+
PRIMARY KEY (project_id, id)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX idx_artifacts_project_updated ON artifacts (project_id, updated_at DESC, id);
|
|
20
|
+
CREATE INDEX idx_artifacts_owner_updated
|
|
21
|
+
ON artifacts (project_id, owner_principal_type, owner_principal_id, updated_at DESC, id);
|
|
22
|
+
|
|
23
|
+
-- `refs`, not `references`: that word is reserved in SQL.
|
|
24
|
+
CREATE TABLE artifact_versions (
|
|
25
|
+
project_id TEXT NOT NULL,
|
|
26
|
+
artifact_id TEXT NOT NULL,
|
|
27
|
+
version INTEGER NOT NULL CHECK (version > 0),
|
|
28
|
+
title TEXT NOT NULL CHECK (length(trim(title)) > 0),
|
|
29
|
+
entry TEXT NOT NULL CHECK (length(entry) > 0),
|
|
30
|
+
files TEXT NOT NULL CHECK (json_type(files) = 'array'),
|
|
31
|
+
outline TEXT NOT NULL CHECK (json_type(outline) = 'object'),
|
|
32
|
+
refs TEXT NOT NULL CHECK (json_type(refs) = 'array'),
|
|
33
|
+
author_principal_type TEXT NOT NULL
|
|
34
|
+
CHECK (author_principal_type IN ('user', 'serviceAccount', 'system')),
|
|
35
|
+
author_principal_id TEXT NOT NULL CHECK (length(author_principal_id) > 0),
|
|
36
|
+
origin_thread_id TEXT,
|
|
37
|
+
origin_run_id TEXT,
|
|
38
|
+
created_at TEXT NOT NULL,
|
|
39
|
+
PRIMARY KEY (project_id, artifact_id, version),
|
|
40
|
+
FOREIGN KEY (project_id, artifact_id) REFERENCES artifacts (project_id, id) ON DELETE CASCADE,
|
|
41
|
+
CHECK ((origin_thread_id IS NULL) = (origin_run_id IS NULL))
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE INDEX idx_artifact_versions_origin_run
|
|
45
|
+
ON artifact_versions (project_id, origin_run_id) WHERE origin_run_id IS NOT NULL;
|
|
46
|
+
CREATE INDEX idx_artifact_versions_origin_thread
|
|
47
|
+
ON artifact_versions (project_id, origin_thread_id, artifact_id) WHERE origin_thread_id IS NOT NULL;
|
|
48
|
+
|
|
49
|
+
-- Short-lived links that a sandboxed frame loads a deck's files through. Only the SHA-256 of each
|
|
50
|
+
-- link's token is stored. `targets` is what the frame may show from other artifacts, decided for
|
|
51
|
+
-- its reader when the link was issued: a JSON array of { address, artifactId, version }.
|
|
52
|
+
CREATE TABLE artifact_frame_grants (
|
|
53
|
+
project_id TEXT NOT NULL,
|
|
54
|
+
token_hash TEXT NOT NULL CHECK (
|
|
55
|
+
length(token_hash) = 64
|
|
56
|
+
AND token_hash = lower(token_hash)
|
|
57
|
+
AND token_hash NOT GLOB '*[^0-9a-f]*'
|
|
58
|
+
),
|
|
59
|
+
artifact_id TEXT NOT NULL,
|
|
60
|
+
version INTEGER NOT NULL CHECK (version > 0),
|
|
61
|
+
targets TEXT NOT NULL CHECK (json_type(targets) = 'array'),
|
|
62
|
+
created_at TEXT NOT NULL,
|
|
63
|
+
expires_at TEXT NOT NULL,
|
|
64
|
+
PRIMARY KEY (project_id, token_hash),
|
|
65
|
+
FOREIGN KEY (project_id, artifact_id) REFERENCES artifacts (project_id, id) ON DELETE CASCADE,
|
|
66
|
+
CHECK (expires_at > created_at)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX idx_artifact_frame_grants_expiry ON artifact_frame_grants (project_id, expires_at);
|
|
70
|
+
-- Deleting an artifact cascades here.
|
|
71
|
+
CREATE INDEX idx_artifact_frame_grants_artifact ON artifact_frame_grants (project_id, artifact_id);
|