@sixb/sqlite 0.0.1

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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/action-run-storage.d.ts +29 -0
  4. package/dist/action-run-storage.d.ts.map +1 -0
  5. package/dist/agents/agent-storage.d.ts +25 -0
  6. package/dist/agents/agent-storage.d.ts.map +1 -0
  7. package/dist/agents/index.d.ts +6 -0
  8. package/dist/agents/index.d.ts.map +1 -0
  9. package/dist/agents/messages.d.ts +14 -0
  10. package/dist/agents/messages.d.ts.map +1 -0
  11. package/dist/agents/rows.d.ts +77 -0
  12. package/dist/agents/rows.d.ts.map +1 -0
  13. package/dist/agents/runs.d.ts +26 -0
  14. package/dist/agents/runs.d.ts.map +1 -0
  15. package/dist/agents/threads.d.ts +14 -0
  16. package/dist/agents/threads.d.ts.map +1 -0
  17. package/dist/auth-storage/access-tokens.d.ts +33 -0
  18. package/dist/auth-storage/access-tokens.d.ts.map +1 -0
  19. package/dist/auth-storage/auth-storage.d.ts +46 -0
  20. package/dist/auth-storage/auth-storage.d.ts.map +1 -0
  21. package/dist/auth-storage/group-memberships.d.ts +21 -0
  22. package/dist/auth-storage/group-memberships.d.ts.map +1 -0
  23. package/dist/auth-storage/identities.d.ts +17 -0
  24. package/dist/auth-storage/identities.d.ts.map +1 -0
  25. package/dist/auth-storage/index.d.ts +3 -0
  26. package/dist/auth-storage/index.d.ts.map +1 -0
  27. package/dist/auth-storage/invitations.d.ts +28 -0
  28. package/dist/auth-storage/invitations.d.ts.map +1 -0
  29. package/dist/auth-storage/magic-links.d.ts +28 -0
  30. package/dist/auth-storage/magic-links.d.ts.map +1 -0
  31. package/dist/auth-storage/oidc-attempts.d.ts +18 -0
  32. package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
  33. package/dist/auth-storage/rows.d.ts +134 -0
  34. package/dist/auth-storage/rows.d.ts.map +1 -0
  35. package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
  36. package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
  37. package/dist/auth-storage/service-accounts.d.ts +14 -0
  38. package/dist/auth-storage/service-accounts.d.ts.map +1 -0
  39. package/dist/auth-storage/sessions.d.ts +55 -0
  40. package/dist/auth-storage/sessions.d.ts.map +1 -0
  41. package/dist/auth-storage/shared.d.ts +131 -0
  42. package/dist/auth-storage/shared.d.ts.map +1 -0
  43. package/dist/auth-storage/users.d.ts +19 -0
  44. package/dist/auth-storage/users.d.ts.map +1 -0
  45. package/dist/index.d.ts +71 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +10204 -0
  48. package/dist/index.js.map +54 -0
  49. package/dist/latest-run-query.d.ts +22 -0
  50. package/dist/latest-run-query.d.ts.map +1 -0
  51. package/dist/migrations.d.ts +18 -0
  52. package/dist/migrations.d.ts.map +1 -0
  53. package/dist/object-query-compiler.d.ts +44 -0
  54. package/dist/object-query-compiler.d.ts.map +1 -0
  55. package/dist/object-storage.d.ts +93 -0
  56. package/dist/object-storage.d.ts.map +1 -0
  57. package/dist/ontology-storage/commits.d.ts +14 -0
  58. package/dist/ontology-storage/commits.d.ts.map +1 -0
  59. package/dist/ontology-storage/index.d.ts +22 -0
  60. package/dist/ontology-storage/index.d.ts.map +1 -0
  61. package/dist/ontology-storage/materialization-session.d.ts +49 -0
  62. package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
  63. package/dist/ontology-storage/materialization-state.d.ts +63 -0
  64. package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
  65. package/dist/ontology-storage/materialization-writer.d.ts +15 -0
  66. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
  67. package/dist/ontology-storage/materializations.d.ts +32 -0
  68. package/dist/ontology-storage/materializations.d.ts.map +1 -0
  69. package/dist/ontology-storage/outbox.d.ts +14 -0
  70. package/dist/ontology-storage/outbox.d.ts.map +1 -0
  71. package/dist/ontology-storage/shared.d.ts +91 -0
  72. package/dist/ontology-storage/shared.d.ts.map +1 -0
  73. package/dist/ontology-storage/sources.d.ts +26 -0
  74. package/dist/ontology-storage/sources.d.ts.map +1 -0
  75. package/dist/pipeline-run-storage.d.ts +26 -0
  76. package/dist/pipeline-run-storage.d.ts.map +1 -0
  77. package/dist/projection-run-storage.d.ts +32 -0
  78. package/dist/projection-run-storage.d.ts.map +1 -0
  79. package/dist/rules-storage.d.ts +33 -0
  80. package/dist/rules-storage.d.ts.map +1 -0
  81. package/dist/run-list-query.d.ts +27 -0
  82. package/dist/run-list-query.d.ts.map +1 -0
  83. package/dist/storage-errors.d.ts +2 -0
  84. package/dist/storage-errors.d.ts.map +1 -0
  85. package/dist/sync-run-storage.d.ts +23 -0
  86. package/dist/sync-run-storage.d.ts.map +1 -0
  87. package/dist/timeseries-storage.d.ts +41 -0
  88. package/dist/timeseries-storage.d.ts.map +1 -0
  89. package/dist/transactions.d.ts +15 -0
  90. package/dist/transactions.d.ts.map +1 -0
  91. package/dist/tsconfig.json +5 -0
  92. package/dist/validation.d.ts +5 -0
  93. package/dist/validation.d.ts.map +1 -0
  94. package/dist/webhook-delivery-storage.d.ts +27 -0
  95. package/dist/webhook-delivery-storage.d.ts.map +1 -0
  96. package/dist/webhook-run-storage.d.ts +22 -0
  97. package/dist/webhook-run-storage.d.ts.map +1 -0
  98. package/dist/workflow-intervention-storage.d.ts +26 -0
  99. package/dist/workflow-intervention-storage.d.ts.map +1 -0
  100. package/dist/workflow-run-storage.d.ts +63 -0
  101. package/dist/workflow-run-storage.d.ts.map +1 -0
  102. package/package.json +58 -0
  103. package/src/action-run-storage.ts +761 -0
  104. package/src/agents/agent-storage.ts +46 -0
  105. package/src/agents/index.ts +5 -0
  106. package/src/agents/messages.ts +162 -0
  107. package/src/agents/rows.ts +205 -0
  108. package/src/agents/runs.ts +389 -0
  109. package/src/agents/threads.ts +133 -0
  110. package/src/auth-storage/access-tokens.ts +289 -0
  111. package/src/auth-storage/auth-storage.ts +784 -0
  112. package/src/auth-storage/group-memberships.ts +52 -0
  113. package/src/auth-storage/identities.ts +98 -0
  114. package/src/auth-storage/index.ts +2 -0
  115. package/src/auth-storage/invitations.ts +258 -0
  116. package/src/auth-storage/magic-links.ts +149 -0
  117. package/src/auth-storage/oidc-attempts.ts +109 -0
  118. package/src/auth-storage/rows.ts +338 -0
  119. package/src/auth-storage/service-account-group-memberships.ts +161 -0
  120. package/src/auth-storage/service-accounts.ts +168 -0
  121. package/src/auth-storage/sessions.ts +230 -0
  122. package/src/auth-storage/shared.ts +707 -0
  123. package/src/auth-storage/users.ts +226 -0
  124. package/src/index.ts +292 -0
  125. package/src/latest-run-query.ts +49 -0
  126. package/src/migrations/001-initial-schema.sql +1068 -0
  127. package/src/migrations.ts +245 -0
  128. package/src/object-query-compiler.ts +1198 -0
  129. package/src/object-storage.ts +605 -0
  130. package/src/ontology-storage/commits.ts +127 -0
  131. package/src/ontology-storage/index.ts +35 -0
  132. package/src/ontology-storage/materialization-session.ts +393 -0
  133. package/src/ontology-storage/materialization-state.ts +901 -0
  134. package/src/ontology-storage/materialization-writer.ts +394 -0
  135. package/src/ontology-storage/materializations.ts +838 -0
  136. package/src/ontology-storage/outbox.ts +237 -0
  137. package/src/ontology-storage/shared.ts +269 -0
  138. package/src/ontology-storage/sources.ts +653 -0
  139. package/src/pipeline-run-storage.ts +506 -0
  140. package/src/projection-run-storage.ts +592 -0
  141. package/src/rules-storage.ts +375 -0
  142. package/src/run-list-query.ts +98 -0
  143. package/src/sql.d.ts +4 -0
  144. package/src/storage-errors.ts +3 -0
  145. package/src/sync-run-storage.ts +299 -0
  146. package/src/timeseries-storage.ts +212 -0
  147. package/src/transactions.ts +96 -0
  148. package/src/validation.ts +30 -0
  149. package/src/webhook-delivery-storage.ts +272 -0
  150. package/src/webhook-run-storage.ts +240 -0
  151. package/src/workflow-intervention-storage.ts +398 -0
  152. package/src/workflow-run-storage.ts +1148 -0
@@ -0,0 +1,52 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type {
3
+ AuthGroupMembershipStore,
4
+ GroupMembershipRecord,
5
+ UpsertAuthGroupMembershipInput,
6
+ } from "@sixb/core/storage"
7
+ import { runImmediateTransaction } from "../transactions"
8
+ import type { SqliteAuthGroupMembershipRow } from "./rows"
9
+ import { rowToGroupMembershipRecord } from "./rows"
10
+ import { listMembershipsForUser, removeGroupMembership, upsertGroupMembership } from "./shared"
11
+
12
+ export class SqliteAuthGroupMembershipStore implements AuthGroupMembershipStore {
13
+ constructor(private readonly db: Database) {}
14
+
15
+ async upsert(input: UpsertAuthGroupMembershipInput): Promise<GroupMembershipRecord> {
16
+ return runImmediateTransaction(this.db, () => upsertGroupMembership(this.db, input))
17
+ }
18
+
19
+ async remove(params: {
20
+ readonly projectId: string
21
+ readonly userId: string
22
+ readonly groupId: string
23
+ }): Promise<GroupMembershipRecord | null> {
24
+ return runImmediateTransaction(this.db, () => removeGroupMembership(this.db, params))
25
+ }
26
+
27
+ async listForUser(params: {
28
+ readonly projectId: string
29
+ readonly userId: string
30
+ }): Promise<readonly GroupMembershipRecord[]> {
31
+ return listMembershipsForUser(this.db, params)
32
+ }
33
+
34
+ async listForGroup(params: {
35
+ readonly projectId: string
36
+ readonly groupId: string
37
+ }): Promise<readonly GroupMembershipRecord[]> {
38
+ const rows = this.db
39
+ .query(
40
+ `
41
+ SELECT *
42
+ FROM auth_group_memberships
43
+ WHERE project_id = ?
44
+ AND group_id = ?
45
+ ORDER BY user_id ASC
46
+ `
47
+ )
48
+ .all(params.projectId, params.groupId) as SqliteAuthGroupMembershipRow[]
49
+
50
+ return rows.map(rowToGroupMembershipRecord)
51
+ }
52
+ }
@@ -0,0 +1,98 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type {
3
+ AuthUserIdentityStore,
4
+ UpsertAuthUserIdentityInput,
5
+ UserIdentityRecord,
6
+ } from "@sixb/core/storage"
7
+ import type { SqliteAuthUserIdentityRow } from "./rows"
8
+ import { rowToIdentityRecord, serializeOptionalRecord } from "./rows"
9
+ import { assertNonEmpty, dateOrNow, getIdentityRowBySubject, toIso } from "./shared"
10
+
11
+ export class SqliteAuthUserIdentityStore implements AuthUserIdentityStore {
12
+ constructor(private readonly db: Database) {}
13
+
14
+ async upsert(input: UpsertAuthUserIdentityInput): Promise<UserIdentityRecord> {
15
+ const projectId = assertNonEmpty(input.projectId, "Project id")
16
+ const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
17
+ const subject = assertNonEmpty(input.subject, "Subject")
18
+ const userId = assertNonEmpty(input.userId, "User id")
19
+ const existing = getIdentityRowBySubject(this.db, {
20
+ projectId,
21
+ strategyId,
22
+ subject,
23
+ })
24
+ const createdAt = existing ? new Date(existing.created_at) : dateOrNow(input.createdAt)
25
+ const updatedAt = input.updatedAt
26
+ ? new Date(input.updatedAt)
27
+ : existing
28
+ ? new Date()
29
+ : createdAt
30
+
31
+ this.db
32
+ .query(
33
+ `
34
+ INSERT INTO auth_user_identities (
35
+ project_id,
36
+ strategy_id,
37
+ subject,
38
+ user_id,
39
+ claims,
40
+ created_at,
41
+ updated_at
42
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
43
+ ON CONFLICT(project_id, strategy_id, subject)
44
+ DO UPDATE SET
45
+ user_id = excluded.user_id,
46
+ claims = excluded.claims,
47
+ updated_at = excluded.updated_at
48
+ `
49
+ )
50
+ .run(
51
+ projectId,
52
+ strategyId,
53
+ subject,
54
+ userId,
55
+ serializeOptionalRecord(input.claims),
56
+ toIso(createdAt),
57
+ toIso(updatedAt)
58
+ )
59
+
60
+ return {
61
+ projectId,
62
+ strategyId,
63
+ subject,
64
+ userId,
65
+ claims: input.claims ? structuredClone(input.claims) : undefined,
66
+ createdAt,
67
+ updatedAt,
68
+ }
69
+ }
70
+
71
+ async getBySubject(params: {
72
+ readonly projectId: string
73
+ readonly strategyId: string
74
+ readonly subject: string
75
+ }): Promise<UserIdentityRecord | null> {
76
+ const row = getIdentityRowBySubject(this.db, params)
77
+ return row ? rowToIdentityRecord(row) : null
78
+ }
79
+
80
+ async listForUser(params: {
81
+ readonly projectId: string
82
+ readonly userId: string
83
+ }): Promise<readonly UserIdentityRecord[]> {
84
+ const rows = this.db
85
+ .query(
86
+ `
87
+ SELECT *
88
+ FROM auth_user_identities
89
+ WHERE project_id = ?
90
+ AND user_id = ?
91
+ ORDER BY created_at ASC, strategy_id ASC, subject ASC
92
+ `
93
+ )
94
+ .all(params.projectId, params.userId) as SqliteAuthUserIdentityRow[]
95
+
96
+ return rows.map(rowToIdentityRecord)
97
+ }
98
+ }
@@ -0,0 +1,2 @@
1
+ export type { SqliteAuthStorageOptions } from "./auth-storage"
2
+ export { SqliteAuthStorage } from "./auth-storage"
@@ -0,0 +1,258 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type {
3
+ AuthInvitationStore,
4
+ CreateOrUpdateAuthInvitationInput,
5
+ InvitationRecord,
6
+ ListAuthInvitationsInput,
7
+ ListAuthInvitationsResult,
8
+ } from "@sixb/core/storage"
9
+ import { AuthStorageError } from "@sixb/core/storage"
10
+ import { runImmediateTransaction } from "../transactions"
11
+ import type { SqliteAuthInvitationRow } from "./rows"
12
+ import { rowToInvitationRecord } from "./rows"
13
+ import {
14
+ appendPagination,
15
+ assertNonEmpty,
16
+ dateOrNow,
17
+ getActiveInvitationByEmail,
18
+ getInvitationById,
19
+ getInvitationGroupIds,
20
+ hasEmptyFilter,
21
+ mapUniqueConstraintError,
22
+ normalizeEmail,
23
+ normalizeGroupIds,
24
+ replaceInvitationGroups,
25
+ requireInvitationById,
26
+ type SqliteValue,
27
+ toIso,
28
+ } from "./shared"
29
+
30
+ export class SqliteAuthInvitationStore implements AuthInvitationStore {
31
+ constructor(private readonly db: Database) {}
32
+
33
+ async createOrUpdateActive(input: CreateOrUpdateAuthInvitationInput): Promise<InvitationRecord> {
34
+ return runImmediateTransaction(this.db, () => {
35
+ const id = assertNonEmpty(input.id, "Invitation id")
36
+ const projectId = assertNonEmpty(input.projectId, "Project id")
37
+ const email = normalizeEmail(input.email)
38
+ const groupIds = normalizeGroupIds(input.groupIds)
39
+ const now = dateOrNow(input.updatedAt ?? input.createdAt)
40
+ const existingActive = getActiveInvitationByEmail(this.db, {
41
+ projectId,
42
+ email,
43
+ now,
44
+ })
45
+
46
+ if (existingActive) {
47
+ this.db
48
+ .query(
49
+ `
50
+ UPDATE auth_invitations
51
+ SET created_by_principal_type = ?,
52
+ created_by_principal_id = ?,
53
+ created_by_session_id = ?,
54
+ expires_at = ?,
55
+ updated_at = ?
56
+ WHERE project_id = ?
57
+ AND id = ?
58
+ `
59
+ )
60
+ .run(
61
+ input.createdByPrincipal?.type ?? null,
62
+ input.createdByPrincipal?.id ?? null,
63
+ input.createdBySessionId ?? null,
64
+ toIso(input.expiresAt),
65
+ toIso(now),
66
+ projectId,
67
+ existingActive.id
68
+ )
69
+ replaceInvitationGroups(this.db, {
70
+ projectId,
71
+ invitationId: existingActive.id,
72
+ groupIds,
73
+ })
74
+
75
+ return requireInvitationById(this.db, { projectId, id: existingActive.id })
76
+ }
77
+
78
+ if (getInvitationById(this.db, { projectId, id })) {
79
+ throw new AuthStorageError(
80
+ "duplicate_invitation",
81
+ `[Sixb] Invitation '${id}' already exists but is not active for project '${projectId}'.`
82
+ )
83
+ }
84
+
85
+ const createdAt = dateOrNow(input.createdAt)
86
+ try {
87
+ this.db
88
+ .query(
89
+ `
90
+ INSERT INTO auth_invitations (
91
+ project_id,
92
+ id,
93
+ email,
94
+ status,
95
+ created_by_principal_type,
96
+ created_by_principal_id,
97
+ created_by_session_id,
98
+ created_at,
99
+ updated_at,
100
+ expires_at
101
+ ) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?)
102
+ `
103
+ )
104
+ .run(
105
+ projectId,
106
+ id,
107
+ email,
108
+ input.createdByPrincipal?.type ?? null,
109
+ input.createdByPrincipal?.id ?? null,
110
+ input.createdBySessionId ?? null,
111
+ toIso(createdAt),
112
+ toIso(now),
113
+ toIso(input.expiresAt)
114
+ )
115
+ } catch (error) {
116
+ mapUniqueConstraintError(
117
+ error,
118
+ "duplicate_invitation",
119
+ `[Sixb] Invitation '${id}' already exists for project '${projectId}'.`
120
+ )
121
+ }
122
+
123
+ replaceInvitationGroups(this.db, { projectId, invitationId: id, groupIds })
124
+ return requireInvitationById(this.db, { projectId, id })
125
+ })
126
+ }
127
+
128
+ async getById(params: {
129
+ readonly projectId: string
130
+ readonly id: string
131
+ }): Promise<InvitationRecord | null> {
132
+ return getInvitationById(this.db, params)
133
+ }
134
+
135
+ async getActiveByEmail(params: {
136
+ readonly projectId: string
137
+ readonly email: string
138
+ readonly now: Date
139
+ }): Promise<InvitationRecord | null> {
140
+ return getActiveInvitationByEmail(this.db, params)
141
+ }
142
+
143
+ async list(input: ListAuthInvitationsInput): Promise<ListAuthInvitationsResult> {
144
+ if (hasEmptyFilter(input.statuses) || hasEmptyFilter(input.groupIds)) {
145
+ return { invitations: [], hasMore: false, total: 0 }
146
+ }
147
+
148
+ const whereClauses = ["project_id = ?"]
149
+ const args: SqliteValue[] = [input.projectId]
150
+
151
+ if (input.email) {
152
+ whereClauses.push("email = ?")
153
+ args.push(normalizeEmail(input.email))
154
+ }
155
+
156
+ if (input.statuses) {
157
+ whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
158
+ args.push(...input.statuses)
159
+ }
160
+
161
+ const groupIds = input.groupIds ? normalizeGroupIds(input.groupIds) : null
162
+ if (groupIds) {
163
+ whereClauses.push(`
164
+ EXISTS (
165
+ SELECT 1
166
+ FROM auth_invitation_groups invitation_groups
167
+ WHERE invitation_groups.project_id = auth_invitations.project_id
168
+ AND invitation_groups.invitation_id = auth_invitations.id
169
+ AND invitation_groups.group_id IN (${groupIds.map(() => "?").join(", ")})
170
+ )
171
+ `)
172
+ args.push(...groupIds)
173
+ }
174
+
175
+ const where = `WHERE ${whereClauses.join(" AND ")}`
176
+ const order = input.order === "asc" ? "ASC" : "DESC"
177
+ const totalRow = this.db
178
+ .query(`SELECT COUNT(*) AS count FROM auth_invitations ${where}`)
179
+ .get(...args) as { readonly count: number }
180
+
181
+ const queryArgs = [...args]
182
+ const query = appendPagination(
183
+ `
184
+ SELECT *
185
+ FROM auth_invitations
186
+ ${where}
187
+ ORDER BY created_at ${order}, id ${order}
188
+ `,
189
+ queryArgs,
190
+ input
191
+ )
192
+ const rows = this.db.query(query).all(...queryArgs) as SqliteAuthInvitationRow[]
193
+
194
+ return {
195
+ invitations: rows.map((row) =>
196
+ rowToInvitationRecord(
197
+ row,
198
+ getInvitationGroupIds(this.db, {
199
+ projectId: row.project_id,
200
+ invitationId: row.id,
201
+ })
202
+ )
203
+ ),
204
+ hasMore: (input.offset ?? 0) + rows.length < totalRow.count,
205
+ total: totalRow.count,
206
+ }
207
+ }
208
+
209
+ async accept(params: {
210
+ readonly projectId: string
211
+ readonly id: string
212
+ readonly acceptedAt: Date
213
+ }): Promise<InvitationRecord> {
214
+ return runImmediateTransaction(this.db, () => {
215
+ requireInvitationById(this.db, params)
216
+
217
+ this.db
218
+ .query(
219
+ `
220
+ UPDATE auth_invitations
221
+ SET status = 'accepted',
222
+ accepted_at = ?,
223
+ updated_at = ?
224
+ WHERE project_id = ?
225
+ AND id = ?
226
+ `
227
+ )
228
+ .run(toIso(params.acceptedAt), toIso(params.acceptedAt), params.projectId, params.id)
229
+
230
+ return requireInvitationById(this.db, params)
231
+ })
232
+ }
233
+
234
+ async revoke(params: {
235
+ readonly projectId: string
236
+ readonly id: string
237
+ readonly revokedAt: Date
238
+ }): Promise<InvitationRecord> {
239
+ return runImmediateTransaction(this.db, () => {
240
+ requireInvitationById(this.db, params)
241
+
242
+ this.db
243
+ .query(
244
+ `
245
+ UPDATE auth_invitations
246
+ SET status = 'revoked',
247
+ revoked_at = ?,
248
+ updated_at = ?
249
+ WHERE project_id = ?
250
+ AND id = ?
251
+ `
252
+ )
253
+ .run(toIso(params.revokedAt), toIso(params.revokedAt), params.projectId, params.id)
254
+
255
+ return requireInvitationById(this.db, params)
256
+ })
257
+ }
258
+ }
@@ -0,0 +1,149 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import { resolveAuthSessionAudience } from "@sixb/core"
3
+ import type {
4
+ AuthMagicLinkStore,
5
+ CreateAuthMagicLinkInput,
6
+ MagicLinkRecord,
7
+ } from "@sixb/core/storage"
8
+ import { AuthStorageError } from "@sixb/core/storage"
9
+ import { runImmediateTransaction } from "../transactions"
10
+ import type { SqliteAuthMagicLinkRow } from "./rows"
11
+ import { rowToMagicLinkRecord } from "./rows"
12
+ import {
13
+ assertNonEmpty,
14
+ consumeMagicLink,
15
+ getMagicLinkById,
16
+ getMagicLinkRowById,
17
+ mapUniqueConstraintError,
18
+ normalizeEmail,
19
+ revokeActiveMagicLinksForEmail,
20
+ toIso,
21
+ } from "./shared"
22
+
23
+ export class SqliteAuthMagicLinkStore implements AuthMagicLinkStore {
24
+ constructor(private readonly db: Database) {}
25
+
26
+ async create(input: CreateAuthMagicLinkInput): Promise<MagicLinkRecord> {
27
+ return runImmediateTransaction(this.db, () => {
28
+ const id = assertNonEmpty(input.id, "Magic link id")
29
+ const projectId = assertNonEmpty(input.projectId, "Project id")
30
+ const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
31
+ const audience = resolveAuthSessionAudience(input.audience)
32
+ const email = normalizeEmail(input.email)
33
+ const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash")
34
+
35
+ if (getMagicLinkRowById(this.db, { projectId, id })) {
36
+ throw new AuthStorageError(
37
+ "duplicate_magic_link",
38
+ `[Sixb] Magic link '${id}' already exists for project '${projectId}'.`
39
+ )
40
+ }
41
+
42
+ revokeActiveMagicLinksForEmail(this.db, {
43
+ projectId,
44
+ email,
45
+ revokedAt: input.createdAt,
46
+ })
47
+
48
+ try {
49
+ this.db
50
+ .query(
51
+ `
52
+ INSERT INTO auth_magic_links (
53
+ project_id,
54
+ id,
55
+ strategy_id,
56
+ audience,
57
+ email,
58
+ token_hash,
59
+ return_to,
60
+ created_at,
61
+ expires_at
62
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
63
+ `
64
+ )
65
+ .run(
66
+ projectId,
67
+ id,
68
+ strategyId,
69
+ audience,
70
+ email,
71
+ tokenHash,
72
+ input.returnTo ?? null,
73
+ toIso(input.createdAt),
74
+ toIso(input.expiresAt)
75
+ )
76
+ } catch (error) {
77
+ mapUniqueConstraintError(
78
+ error,
79
+ "duplicate_magic_link",
80
+ `[Sixb] Magic link '${id}' already exists for project '${projectId}'.`
81
+ )
82
+ }
83
+
84
+ return {
85
+ id,
86
+ projectId,
87
+ strategyId,
88
+ audience,
89
+ email,
90
+ tokenHash,
91
+ returnTo: input.returnTo,
92
+ createdAt: new Date(input.createdAt),
93
+ expiresAt: new Date(input.expiresAt),
94
+ }
95
+ })
96
+ }
97
+
98
+ async getById(params: {
99
+ readonly projectId: string
100
+ readonly id: string
101
+ }): Promise<MagicLinkRecord | null> {
102
+ return getMagicLinkById(this.db, params)
103
+ }
104
+
105
+ async getActiveByEmail(params: {
106
+ readonly projectId: string
107
+ readonly email: string
108
+ readonly now: Date
109
+ }): Promise<MagicLinkRecord | null> {
110
+ const row = this.db
111
+ .query(
112
+ `
113
+ SELECT *
114
+ FROM auth_magic_links
115
+ WHERE project_id = ?
116
+ AND email = ?
117
+ AND consumed_at IS NULL
118
+ AND revoked_at IS NULL
119
+ AND expires_at > ?
120
+ ORDER BY created_at DESC, id DESC
121
+ LIMIT 1
122
+ `
123
+ )
124
+ .get(
125
+ params.projectId,
126
+ normalizeEmail(params.email),
127
+ toIso(params.now)
128
+ ) as SqliteAuthMagicLinkRow | null
129
+
130
+ return row ? rowToMagicLinkRecord(row) : null
131
+ }
132
+
133
+ async consume(params: {
134
+ readonly projectId: string
135
+ readonly id: string
136
+ readonly tokenHash: string
137
+ readonly consumedAt: Date
138
+ }): Promise<MagicLinkRecord> {
139
+ return runImmediateTransaction(this.db, () => consumeMagicLink(this.db, params))
140
+ }
141
+
142
+ async revokeActiveForEmail(params: {
143
+ readonly projectId: string
144
+ readonly email: string
145
+ readonly revokedAt: Date
146
+ }): Promise<readonly MagicLinkRecord[]> {
147
+ return runImmediateTransaction(this.db, () => revokeActiveMagicLinksForEmail(this.db, params))
148
+ }
149
+ }
@@ -0,0 +1,109 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import { resolveAuthSessionAudience } from "@sixb/core"
3
+ import type {
4
+ AuthOidcAuthorizationAttemptStore,
5
+ CreateOidcAuthorizationAttemptInput,
6
+ OidcAuthorizationAttemptRecord,
7
+ } from "@sixb/core/storage"
8
+ import { AuthStorageError } from "@sixb/core/storage"
9
+ import { runImmediateTransaction } from "../transactions"
10
+ import {
11
+ assertNonEmpty,
12
+ consumeOidcAttempt,
13
+ getOidcAttemptById,
14
+ getOidcAttemptRowById,
15
+ mapUniqueConstraintError,
16
+ toIso,
17
+ } from "./shared"
18
+
19
+ export class SqliteAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizationAttemptStore {
20
+ constructor(private readonly db: Database) {}
21
+
22
+ async create(
23
+ input: CreateOidcAuthorizationAttemptInput
24
+ ): Promise<OidcAuthorizationAttemptRecord> {
25
+ return runImmediateTransaction(this.db, () => {
26
+ const id = assertNonEmpty(input.id, "OIDC authorization attempt id")
27
+ const projectId = assertNonEmpty(input.projectId, "Project id")
28
+ const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
29
+ const audience = resolveAuthSessionAudience(input.audience)
30
+ const stateHash = assertNonEmpty(input.stateHash, "OIDC state hash")
31
+ const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash")
32
+ const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier")
33
+
34
+ if (getOidcAttemptRowById(this.db, { projectId, id })) {
35
+ throw new AuthStorageError(
36
+ "duplicate_oidc_attempt",
37
+ `[Sixb] OIDC authorization attempt '${id}' already exists for project '${projectId}'.`
38
+ )
39
+ }
40
+
41
+ try {
42
+ this.db
43
+ .query(
44
+ `
45
+ INSERT INTO auth_oidc_authorization_attempts (
46
+ project_id,
47
+ id,
48
+ strategy_id,
49
+ audience,
50
+ state_hash,
51
+ nonce_hash,
52
+ code_verifier,
53
+ return_to,
54
+ created_at,
55
+ expires_at
56
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
57
+ `
58
+ )
59
+ .run(
60
+ projectId,
61
+ id,
62
+ strategyId,
63
+ audience,
64
+ stateHash,
65
+ nonceHash,
66
+ codeVerifier,
67
+ input.returnTo ?? null,
68
+ toIso(input.createdAt),
69
+ toIso(input.expiresAt)
70
+ )
71
+ } catch (error) {
72
+ mapUniqueConstraintError(
73
+ error,
74
+ "duplicate_oidc_attempt",
75
+ `[Sixb] OIDC authorization attempt '${id}' already exists for project '${projectId}'.`
76
+ )
77
+ }
78
+
79
+ return {
80
+ id,
81
+ projectId,
82
+ strategyId,
83
+ audience,
84
+ stateHash,
85
+ nonceHash,
86
+ codeVerifier,
87
+ returnTo: input.returnTo,
88
+ createdAt: new Date(input.createdAt),
89
+ expiresAt: new Date(input.expiresAt),
90
+ }
91
+ })
92
+ }
93
+
94
+ async getById(params: {
95
+ readonly projectId: string
96
+ readonly id: string
97
+ }): Promise<OidcAuthorizationAttemptRecord | null> {
98
+ return getOidcAttemptById(this.db, params)
99
+ }
100
+
101
+ async consume(params: {
102
+ readonly projectId: string
103
+ readonly id: string
104
+ readonly stateHash: string
105
+ readonly consumedAt: Date
106
+ }): Promise<OidcAuthorizationAttemptRecord> {
107
+ return runImmediateTransaction(this.db, () => consumeOidcAttempt(this.db, params))
108
+ }
109
+ }