@sixb/pg 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 +73 -0
  3. package/dist/agents/agent-storage.d.ts +20 -0
  4. package/dist/agents/agent-storage.d.ts.map +1 -0
  5. package/dist/agents/index.d.ts +6 -0
  6. package/dist/agents/index.d.ts.map +1 -0
  7. package/dist/agents/messages.d.ts +13 -0
  8. package/dist/agents/messages.d.ts.map +1 -0
  9. package/dist/agents/rows.d.ts +77 -0
  10. package/dist/agents/rows.d.ts.map +1 -0
  11. package/dist/agents/runs.d.ts +25 -0
  12. package/dist/agents/runs.d.ts.map +1 -0
  13. package/dist/agents/threads.d.ts +13 -0
  14. package/dist/agents/threads.d.ts.map +1 -0
  15. package/dist/auth-storage/access-tokens.d.ts +33 -0
  16. package/dist/auth-storage/access-tokens.d.ts.map +1 -0
  17. package/dist/auth-storage/auth-storage.d.ts +41 -0
  18. package/dist/auth-storage/auth-storage.d.ts.map +1 -0
  19. package/dist/auth-storage/group-memberships.d.ts +21 -0
  20. package/dist/auth-storage/group-memberships.d.ts.map +1 -0
  21. package/dist/auth-storage/identities.d.ts +17 -0
  22. package/dist/auth-storage/identities.d.ts.map +1 -0
  23. package/dist/auth-storage/index.d.ts +3 -0
  24. package/dist/auth-storage/index.d.ts.map +1 -0
  25. package/dist/auth-storage/invitations.d.ts +29 -0
  26. package/dist/auth-storage/invitations.d.ts.map +1 -0
  27. package/dist/auth-storage/magic-links.d.ts +28 -0
  28. package/dist/auth-storage/magic-links.d.ts.map +1 -0
  29. package/dist/auth-storage/oidc-attempts.d.ts +18 -0
  30. package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
  31. package/dist/auth-storage/rows.d.ts +138 -0
  32. package/dist/auth-storage/rows.d.ts.map +1 -0
  33. package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
  34. package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
  35. package/dist/auth-storage/service-accounts.d.ts +14 -0
  36. package/dist/auth-storage/service-accounts.d.ts.map +1 -0
  37. package/dist/auth-storage/sessions.d.ts +55 -0
  38. package/dist/auth-storage/sessions.d.ts.map +1 -0
  39. package/dist/auth-storage/shared.d.ts +159 -0
  40. package/dist/auth-storage/shared.d.ts.map +1 -0
  41. package/dist/auth-storage/users.d.ts +19 -0
  42. package/dist/auth-storage/users.d.ts.map +1 -0
  43. package/dist/index.d.ts +127 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +11957 -0
  46. package/dist/index.js.map +55 -0
  47. package/dist/latest-run-query.d.ts +24 -0
  48. package/dist/latest-run-query.d.ts.map +1 -0
  49. package/dist/migrations.d.ts +20 -0
  50. package/dist/migrations.d.ts.map +1 -0
  51. package/dist/ontology-storage/commits.d.ts +11 -0
  52. package/dist/ontology-storage/commits.d.ts.map +1 -0
  53. package/dist/ontology-storage/index.d.ts +22 -0
  54. package/dist/ontology-storage/index.d.ts.map +1 -0
  55. package/dist/ontology-storage/materialization-session.d.ts +63 -0
  56. package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
  57. package/dist/ontology-storage/materialization-state.d.ts +69 -0
  58. package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
  59. package/dist/ontology-storage/materialization-writer.d.ts +15 -0
  60. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
  61. package/dist/ontology-storage/materializations.d.ts +33 -0
  62. package/dist/ontology-storage/materializations.d.ts.map +1 -0
  63. package/dist/ontology-storage/outbox.d.ts +12 -0
  64. package/dist/ontology-storage/outbox.d.ts.map +1 -0
  65. package/dist/ontology-storage/shared.d.ts +95 -0
  66. package/dist/ontology-storage/shared.d.ts.map +1 -0
  67. package/dist/ontology-storage/sources.d.ts +25 -0
  68. package/dist/ontology-storage/sources.d.ts.map +1 -0
  69. package/dist/pg-action-run-storage.d.ts +21 -0
  70. package/dist/pg-action-run-storage.d.ts.map +1 -0
  71. package/dist/pg-client.d.ts +58 -0
  72. package/dist/pg-client.d.ts.map +1 -0
  73. package/dist/pg-object-query-compiler.d.ts +55 -0
  74. package/dist/pg-object-query-compiler.d.ts.map +1 -0
  75. package/dist/pg-object-storage.d.ts +90 -0
  76. package/dist/pg-object-storage.d.ts.map +1 -0
  77. package/dist/pg-pipeline-run-storage.d.ts +18 -0
  78. package/dist/pg-pipeline-run-storage.d.ts.map +1 -0
  79. package/dist/pg-projection-run-storage.d.ts +19 -0
  80. package/dist/pg-projection-run-storage.d.ts.map +1 -0
  81. package/dist/pg-rules-storage.d.ts +25 -0
  82. package/dist/pg-rules-storage.d.ts.map +1 -0
  83. package/dist/pg-sync-run-storage.d.ts +15 -0
  84. package/dist/pg-sync-run-storage.d.ts.map +1 -0
  85. package/dist/pg-timeseries-storage.d.ts +32 -0
  86. package/dist/pg-timeseries-storage.d.ts.map +1 -0
  87. package/dist/pg-webhook-delivery-storage.d.ts +17 -0
  88. package/dist/pg-webhook-delivery-storage.d.ts.map +1 -0
  89. package/dist/pg-webhook-run-storage.d.ts +14 -0
  90. package/dist/pg-webhook-run-storage.d.ts.map +1 -0
  91. package/dist/pg-workflow-intervention-storage.d.ts +16 -0
  92. package/dist/pg-workflow-intervention-storage.d.ts.map +1 -0
  93. package/dist/pg-workflow-run-storage.d.ts +49 -0
  94. package/dist/pg-workflow-run-storage.d.ts.map +1 -0
  95. package/dist/run-list-query.d.ts +30 -0
  96. package/dist/run-list-query.d.ts.map +1 -0
  97. package/dist/storage-errors.d.ts +16 -0
  98. package/dist/storage-errors.d.ts.map +1 -0
  99. package/dist/transactions.d.ts +10 -0
  100. package/dist/transactions.d.ts.map +1 -0
  101. package/dist/tsconfig.json +5 -0
  102. package/package.json +60 -0
  103. package/src/agents/agent-storage.ts +26 -0
  104. package/src/agents/index.ts +5 -0
  105. package/src/agents/messages.ts +144 -0
  106. package/src/agents/rows.ts +227 -0
  107. package/src/agents/runs.ts +330 -0
  108. package/src/agents/threads.ts +116 -0
  109. package/src/auth-storage/access-tokens.ts +254 -0
  110. package/src/auth-storage/auth-storage.ts +798 -0
  111. package/src/auth-storage/group-memberships.ts +47 -0
  112. package/src/auth-storage/identities.ts +85 -0
  113. package/src/auth-storage/index.ts +2 -0
  114. package/src/auth-storage/invitations.ts +280 -0
  115. package/src/auth-storage/magic-links.ts +140 -0
  116. package/src/auth-storage/oidc-attempts.ts +93 -0
  117. package/src/auth-storage/rows.ts +352 -0
  118. package/src/auth-storage/service-account-group-memberships.ts +150 -0
  119. package/src/auth-storage/service-accounts.ts +154 -0
  120. package/src/auth-storage/sessions.ts +193 -0
  121. package/src/auth-storage/shared.ts +799 -0
  122. package/src/auth-storage/users.ts +196 -0
  123. package/src/index.ts +367 -0
  124. package/src/latest-run-query.ts +42 -0
  125. package/src/migrations/001-initial-schema.sql +1195 -0
  126. package/src/migrations.ts +280 -0
  127. package/src/ontology-storage/commits.ts +115 -0
  128. package/src/ontology-storage/index.ts +35 -0
  129. package/src/ontology-storage/materialization-session.ts +426 -0
  130. package/src/ontology-storage/materialization-state.ts +1109 -0
  131. package/src/ontology-storage/materialization-writer.ts +509 -0
  132. package/src/ontology-storage/materializations.ts +939 -0
  133. package/src/ontology-storage/outbox.ts +216 -0
  134. package/src/ontology-storage/shared.ts +293 -0
  135. package/src/ontology-storage/sources.ts +658 -0
  136. package/src/pg-action-run-storage.ts +657 -0
  137. package/src/pg-client.ts +124 -0
  138. package/src/pg-object-query-compiler.ts +1545 -0
  139. package/src/pg-object-storage.ts +638 -0
  140. package/src/pg-pipeline-run-storage.ts +457 -0
  141. package/src/pg-projection-run-storage.ts +526 -0
  142. package/src/pg-rules-storage.ts +308 -0
  143. package/src/pg-sync-run-storage.ts +263 -0
  144. package/src/pg-timeseries-storage.ts +180 -0
  145. package/src/pg-webhook-delivery-storage.ts +180 -0
  146. package/src/pg-webhook-run-storage.ts +185 -0
  147. package/src/pg-workflow-intervention-storage.ts +339 -0
  148. package/src/pg-workflow-run-storage.ts +1065 -0
  149. package/src/run-list-query.ts +108 -0
  150. package/src/sql.d.ts +4 -0
  151. package/src/storage-errors.ts +72 -0
  152. package/src/transactions.ts +58 -0
@@ -0,0 +1,352 @@
1
+ import type { Principal } from "@sixb/core"
2
+ import type {
3
+ AccessTokenRecord,
4
+ GroupMembershipRecord,
5
+ GroupMembershipSource,
6
+ InvitationRecord,
7
+ InvitationStatus,
8
+ MagicLinkRecord,
9
+ OidcAuthorizationAttemptRecord,
10
+ ServiceAccountGroupMembershipRecord,
11
+ ServiceAccountRecord,
12
+ ServiceAccountStatus,
13
+ SessionRecord,
14
+ UserIdentityRecord,
15
+ UserRecord,
16
+ UserStatus,
17
+ } from "@sixb/core/storage"
18
+
19
+ type PgDate = Date | string
20
+
21
+ export interface PgAuthUserRow {
22
+ readonly project_id: string
23
+ readonly id: string
24
+ readonly email: string
25
+ readonly display_name: string | null
26
+ readonly avatar_url: string | null
27
+ readonly status: UserStatus
28
+ readonly created_at: PgDate
29
+ readonly updated_at: PgDate
30
+ }
31
+
32
+ export interface PgAuthUserIdentityRow {
33
+ readonly project_id: string
34
+ readonly strategy_id: string
35
+ readonly subject: string
36
+ readonly user_id: string
37
+ readonly claims: Readonly<Record<string, unknown>> | string | null
38
+ readonly created_at: PgDate
39
+ readonly updated_at: PgDate
40
+ }
41
+
42
+ export interface PgAuthServiceAccountRow {
43
+ readonly project_id: string
44
+ readonly id: string
45
+ readonly name: string
46
+ readonly description: string | null
47
+ readonly status: ServiceAccountStatus
48
+ readonly created_by_user_id: string | null
49
+ readonly created_by_service_account_id: string | null
50
+ readonly created_by_system_id: string | null
51
+ readonly created_by_session_id: string | null
52
+ readonly created_at: PgDate
53
+ readonly updated_at: PgDate
54
+ }
55
+
56
+ export interface PgAuthServiceAccountGroupMembershipRow {
57
+ readonly project_id: string
58
+ readonly service_account_id: string
59
+ readonly group_id: string
60
+ readonly source: GroupMembershipSource
61
+ readonly created_at: PgDate
62
+ }
63
+
64
+ export interface PgAuthSessionRow {
65
+ readonly project_id: string
66
+ readonly id: string
67
+ readonly user_id: string
68
+ readonly strategy_id: string
69
+ readonly audience: SessionRecord["audience"]
70
+ readonly token_hash: string
71
+ readonly created_at: PgDate
72
+ readonly expires_at: PgDate
73
+ readonly absolute_expires_at: PgDate | null
74
+ readonly revoked_at: PgDate | null
75
+ readonly last_seen_at: PgDate | null
76
+ readonly user_agent: string | null
77
+ readonly ip_address: string | null
78
+ }
79
+
80
+ export interface PgAuthAccessTokenRow {
81
+ readonly project_id: string
82
+ readonly id: string
83
+ readonly name: string
84
+ readonly kind: AccessTokenRecord["kind"]
85
+ readonly subject_type: AccessTokenRecord["subjectType"]
86
+ readonly subject_id: string
87
+ readonly token_hash: string
88
+ readonly group_ids: readonly string[] | string | null
89
+ readonly created_by_user_id: string | null
90
+ readonly created_by_service_account_id: string | null
91
+ readonly created_by_system_id: string | null
92
+ readonly created_by_session_id: string | null
93
+ readonly created_at: PgDate
94
+ readonly expires_at: PgDate
95
+ readonly revoked_at: PgDate | null
96
+ readonly last_used_at: PgDate | null
97
+ readonly last_used_user_agent: string | null
98
+ readonly last_used_ip_address: string | null
99
+ }
100
+
101
+ export interface PgAuthInvitationRow {
102
+ readonly project_id: string
103
+ readonly id: string
104
+ readonly email: string
105
+ readonly status: InvitationStatus
106
+ readonly created_by_user_id: string | null
107
+ readonly created_by_service_account_id: string | null
108
+ readonly created_by_system_id: string | null
109
+ readonly created_by_session_id: string | null
110
+ readonly created_at: PgDate
111
+ readonly updated_at: PgDate
112
+ readonly expires_at: PgDate
113
+ readonly accepted_at: PgDate | null
114
+ readonly revoked_at: PgDate | null
115
+ }
116
+
117
+ export interface PgAuthGroupMembershipRow {
118
+ readonly project_id: string
119
+ readonly user_id: string
120
+ readonly group_id: string
121
+ readonly source: GroupMembershipSource
122
+ readonly created_at: PgDate
123
+ }
124
+
125
+ export interface PgAuthMagicLinkRow {
126
+ readonly project_id: string
127
+ readonly id: string
128
+ readonly strategy_id: string
129
+ readonly audience: MagicLinkRecord["audience"]
130
+ readonly email: string
131
+ readonly token_hash: string
132
+ readonly return_to: string | null
133
+ readonly created_at: PgDate
134
+ readonly expires_at: PgDate
135
+ readonly consumed_at: PgDate | null
136
+ readonly revoked_at: PgDate | null
137
+ }
138
+
139
+ export interface PgAuthOidcAttemptRow {
140
+ readonly project_id: string
141
+ readonly id: string
142
+ readonly strategy_id: string
143
+ readonly audience: OidcAuthorizationAttemptRecord["audience"]
144
+ readonly state_hash: string
145
+ readonly nonce_hash: string
146
+ readonly code_verifier: string
147
+ readonly return_to: string | null
148
+ readonly created_at: PgDate
149
+ readonly expires_at: PgDate
150
+ readonly consumed_at: PgDate | null
151
+ }
152
+
153
+ export function rowToUserRecord(row: PgAuthUserRow): UserRecord {
154
+ return {
155
+ id: row.id,
156
+ projectId: row.project_id,
157
+ email: row.email,
158
+ displayName: row.display_name ?? undefined,
159
+ avatarUrl: row.avatar_url ?? undefined,
160
+ status: row.status,
161
+ createdAt: toDate(row.created_at),
162
+ updatedAt: toDate(row.updated_at),
163
+ }
164
+ }
165
+
166
+ export function rowToIdentityRecord(row: PgAuthUserIdentityRow): UserIdentityRecord {
167
+ return {
168
+ projectId: row.project_id,
169
+ strategyId: row.strategy_id,
170
+ subject: row.subject,
171
+ userId: row.user_id,
172
+ claims: parseOptionalRecord(row.claims),
173
+ createdAt: toDate(row.created_at),
174
+ updatedAt: toDate(row.updated_at),
175
+ }
176
+ }
177
+
178
+ export function rowToServiceAccountRecord(row: PgAuthServiceAccountRow): ServiceAccountRecord {
179
+ return {
180
+ id: row.id,
181
+ projectId: row.project_id,
182
+ name: row.name,
183
+ description: row.description ?? undefined,
184
+ status: row.status,
185
+ createdByPrincipal: principalFromColumns(row),
186
+ createdBySessionId: row.created_by_session_id ?? undefined,
187
+ createdAt: toDate(row.created_at),
188
+ updatedAt: toDate(row.updated_at),
189
+ }
190
+ }
191
+
192
+ export function rowToServiceAccountGroupMembershipRecord(
193
+ row: PgAuthServiceAccountGroupMembershipRow
194
+ ): ServiceAccountGroupMembershipRecord {
195
+ return {
196
+ projectId: row.project_id,
197
+ serviceAccountId: row.service_account_id,
198
+ groupId: row.group_id,
199
+ source: row.source,
200
+ createdAt: toDate(row.created_at),
201
+ }
202
+ }
203
+
204
+ export function rowToSessionRecord(row: PgAuthSessionRow): SessionRecord {
205
+ return {
206
+ id: row.id,
207
+ projectId: row.project_id,
208
+ userId: row.user_id,
209
+ strategyId: row.strategy_id,
210
+ audience: row.audience,
211
+ tokenHash: row.token_hash,
212
+ createdAt: toDate(row.created_at),
213
+ expiresAt: toDate(row.expires_at),
214
+ absoluteExpiresAt: row.absolute_expires_at ? toDate(row.absolute_expires_at) : undefined,
215
+ revokedAt: row.revoked_at ? toDate(row.revoked_at) : undefined,
216
+ lastSeenAt: row.last_seen_at ? toDate(row.last_seen_at) : undefined,
217
+ userAgent: row.user_agent ?? undefined,
218
+ ipAddress: row.ip_address ?? undefined,
219
+ }
220
+ }
221
+
222
+ export function rowToAccessTokenRecord(row: PgAuthAccessTokenRow): AccessTokenRecord {
223
+ return {
224
+ id: row.id,
225
+ projectId: row.project_id,
226
+ name: row.name,
227
+ kind: row.kind,
228
+ subjectType: row.subject_type,
229
+ subjectId: row.subject_id,
230
+ tokenHash: row.token_hash,
231
+ groupIds: parseOptionalStringArray(row.group_ids),
232
+ createdByPrincipal: principalFromColumns(row),
233
+ createdBySessionId: row.created_by_session_id ?? undefined,
234
+ createdAt: toDate(row.created_at),
235
+ expiresAt: toDate(row.expires_at),
236
+ revokedAt: row.revoked_at ? toDate(row.revoked_at) : undefined,
237
+ lastUsedAt: row.last_used_at ? toDate(row.last_used_at) : undefined,
238
+ lastUsedUserAgent: row.last_used_user_agent ?? undefined,
239
+ lastUsedIpAddress: row.last_used_ip_address ?? undefined,
240
+ }
241
+ }
242
+
243
+ export function rowToInvitationRecord(
244
+ row: PgAuthInvitationRow,
245
+ groupIds: readonly string[]
246
+ ): InvitationRecord {
247
+ return {
248
+ id: row.id,
249
+ projectId: row.project_id,
250
+ email: row.email,
251
+ groupIds,
252
+ status: row.status,
253
+ createdByPrincipal: invitationCreatorFromRow(row),
254
+ createdBySessionId: row.created_by_session_id ?? undefined,
255
+ createdAt: toDate(row.created_at),
256
+ updatedAt: toDate(row.updated_at),
257
+ expiresAt: toDate(row.expires_at),
258
+ acceptedAt: row.accepted_at ? toDate(row.accepted_at) : undefined,
259
+ revokedAt: row.revoked_at ? toDate(row.revoked_at) : undefined,
260
+ }
261
+ }
262
+
263
+ export function rowToGroupMembershipRecord(row: PgAuthGroupMembershipRow): GroupMembershipRecord {
264
+ return {
265
+ projectId: row.project_id,
266
+ userId: row.user_id,
267
+ groupId: row.group_id,
268
+ source: row.source,
269
+ createdAt: toDate(row.created_at),
270
+ }
271
+ }
272
+
273
+ export function rowToMagicLinkRecord(row: PgAuthMagicLinkRow): MagicLinkRecord {
274
+ return {
275
+ id: row.id,
276
+ projectId: row.project_id,
277
+ strategyId: row.strategy_id,
278
+ audience: row.audience,
279
+ email: row.email,
280
+ tokenHash: row.token_hash,
281
+ returnTo: row.return_to ?? undefined,
282
+ createdAt: toDate(row.created_at),
283
+ expiresAt: toDate(row.expires_at),
284
+ consumedAt: row.consumed_at ? toDate(row.consumed_at) : undefined,
285
+ revokedAt: row.revoked_at ? toDate(row.revoked_at) : undefined,
286
+ }
287
+ }
288
+
289
+ export function rowToOidcAuthorizationAttemptRecord(
290
+ row: PgAuthOidcAttemptRow
291
+ ): OidcAuthorizationAttemptRecord {
292
+ return {
293
+ id: row.id,
294
+ projectId: row.project_id,
295
+ strategyId: row.strategy_id,
296
+ audience: row.audience,
297
+ stateHash: row.state_hash,
298
+ nonceHash: row.nonce_hash,
299
+ codeVerifier: row.code_verifier,
300
+ returnTo: row.return_to ?? undefined,
301
+ createdAt: toDate(row.created_at),
302
+ expiresAt: toDate(row.expires_at),
303
+ consumedAt: row.consumed_at ? toDate(row.consumed_at) : undefined,
304
+ }
305
+ }
306
+
307
+ export function serializeOptionalRecord(
308
+ value: Readonly<Record<string, unknown>> | undefined
309
+ ): string | null {
310
+ return value ? JSON.stringify(value) : null
311
+ }
312
+
313
+ export function serializeOptionalStringArray(value: readonly string[] | undefined): string | null {
314
+ return value === undefined ? null : JSON.stringify(value)
315
+ }
316
+
317
+ function principalFromColumns(row: {
318
+ readonly created_by_user_id: string | null
319
+ readonly created_by_service_account_id: string | null
320
+ readonly created_by_system_id: string | null
321
+ }): Principal | undefined {
322
+ if (row.created_by_user_id) return { type: "user", id: row.created_by_user_id }
323
+ if (row.created_by_service_account_id) {
324
+ return { type: "serviceAccount", id: row.created_by_service_account_id }
325
+ }
326
+ if (row.created_by_system_id) return { type: "system", id: row.created_by_system_id }
327
+ return undefined
328
+ }
329
+
330
+ function invitationCreatorFromRow(row: PgAuthInvitationRow): Principal | undefined {
331
+ return principalFromColumns(row)
332
+ }
333
+
334
+ function parseOptionalRecord(
335
+ value: Readonly<Record<string, unknown>> | string | null
336
+ ): Readonly<Record<string, unknown>> | undefined {
337
+ if (!value) return undefined
338
+ if (typeof value === "string") return JSON.parse(value) as Readonly<Record<string, unknown>>
339
+ return structuredClone(value)
340
+ }
341
+
342
+ function parseOptionalStringArray(
343
+ value: readonly string[] | string | null
344
+ ): readonly string[] | undefined {
345
+ if (value === null) return undefined
346
+ if (typeof value === "string") return JSON.parse(value) as readonly string[]
347
+ return [...value]
348
+ }
349
+
350
+ function toDate(value: PgDate): Date {
351
+ return value instanceof Date ? new Date(value) : new Date(value)
352
+ }
@@ -0,0 +1,150 @@
1
+ import type {
2
+ AuthServiceAccountGroupMembershipStore,
3
+ ReconcileAuthServiceAccountGroupMembershipsInput,
4
+ ServiceAccountGroupMembershipRecord,
5
+ UpsertAuthServiceAccountGroupMembershipInput,
6
+ } from "@sixb/core/storage"
7
+ import { AuthStorageError } from "@sixb/core/storage"
8
+ import type { PgStoreClient } from "../transactions"
9
+ import type { PgAuthServiceAccountGroupMembershipRow, PgAuthServiceAccountRow } from "./rows"
10
+ import { rowToServiceAccountGroupMembershipRecord } from "./rows"
11
+ import { assertNonEmpty, dateOrNow } from "./shared"
12
+
13
+ export class PgAuthServiceAccountGroupMembershipStore
14
+ implements AuthServiceAccountGroupMembershipStore
15
+ {
16
+ constructor(private readonly sql: PgStoreClient) {}
17
+
18
+ async upsert(
19
+ input: UpsertAuthServiceAccountGroupMembershipInput
20
+ ): Promise<ServiceAccountGroupMembershipRecord> {
21
+ const projectId = assertNonEmpty(input.projectId, "Project id")
22
+ const serviceAccountId = assertNonEmpty(input.serviceAccountId, "Service account id")
23
+ const groupId = assertNonEmpty(input.groupId, "Group id")
24
+ const [serviceAccount] = await this.sql<PgAuthServiceAccountRow[]>`
25
+ SELECT *
26
+ FROM auth_service_accounts
27
+ WHERE project_id = ${projectId}
28
+ AND id = ${serviceAccountId}
29
+ `
30
+ if (!serviceAccount) {
31
+ throw new AuthStorageError(
32
+ "missing_service_account",
33
+ `[Sixb] Service account '${serviceAccountId}' not found for project '${projectId}'.`
34
+ )
35
+ }
36
+
37
+ const [row] = await this.sql<PgAuthServiceAccountGroupMembershipRow[]>`
38
+ INSERT INTO auth_service_account_group_memberships (
39
+ project_id,
40
+ service_account_id,
41
+ group_id,
42
+ source,
43
+ created_at
44
+ ) VALUES (
45
+ ${projectId},
46
+ ${serviceAccountId},
47
+ ${groupId},
48
+ ${input.source},
49
+ ${dateOrNow(input.createdAt)}
50
+ )
51
+ ON CONFLICT (project_id, service_account_id, group_id) DO UPDATE
52
+ SET source = auth_service_account_group_memberships.source
53
+ RETURNING *
54
+ `
55
+
56
+ return rowToServiceAccountGroupMembershipRecord(row)
57
+ }
58
+
59
+ async reconcileForServiceAccount(
60
+ input: ReconcileAuthServiceAccountGroupMembershipsInput
61
+ ): Promise<readonly ServiceAccountGroupMembershipRecord[]> {
62
+ const projectId = assertNonEmpty(input.projectId, "Project id")
63
+ const serviceAccountId = assertNonEmpty(input.serviceAccountId, "Service account id")
64
+ const groupIds = [
65
+ ...new Set(input.groupIds.map((groupId) => assertNonEmpty(groupId, "Group id"))),
66
+ ]
67
+ const [serviceAccount] = await this.sql<PgAuthServiceAccountRow[]>`
68
+ SELECT *
69
+ FROM auth_service_accounts
70
+ WHERE project_id = ${projectId}
71
+ AND id = ${serviceAccountId}
72
+ `
73
+ if (!serviceAccount) {
74
+ throw new AuthStorageError(
75
+ "missing_service_account",
76
+ `[Sixb] Service account '${serviceAccountId}' not found for project '${projectId}'.`
77
+ )
78
+ }
79
+
80
+ if (groupIds.length === 0) {
81
+ await this.sql`
82
+ DELETE FROM auth_service_account_group_memberships
83
+ WHERE project_id = ${projectId}
84
+ AND service_account_id = ${serviceAccountId}
85
+ AND source = ${input.source}
86
+ `
87
+ } else {
88
+ await this.sql`
89
+ DELETE FROM auth_service_account_group_memberships
90
+ WHERE project_id = ${projectId}
91
+ AND service_account_id = ${serviceAccountId}
92
+ AND source = ${input.source}
93
+ AND group_id NOT IN ${this.sql(groupIds)}
94
+ `
95
+ }
96
+
97
+ const updatedAt = dateOrNow(input.updatedAt)
98
+ for (const groupId of groupIds) {
99
+ await this.sql<PgAuthServiceAccountGroupMembershipRow[]>`
100
+ INSERT INTO auth_service_account_group_memberships (
101
+ project_id,
102
+ service_account_id,
103
+ group_id,
104
+ source,
105
+ created_at
106
+ ) VALUES (
107
+ ${projectId},
108
+ ${serviceAccountId},
109
+ ${groupId},
110
+ ${input.source},
111
+ ${updatedAt}
112
+ )
113
+ ON CONFLICT (project_id, service_account_id, group_id) DO UPDATE
114
+ SET source = EXCLUDED.source
115
+ `
116
+ }
117
+
118
+ return this.listForServiceAccount({ projectId, serviceAccountId })
119
+ }
120
+
121
+ async listForServiceAccount(params: {
122
+ readonly projectId: string
123
+ readonly serviceAccountId: string
124
+ }): Promise<readonly ServiceAccountGroupMembershipRecord[]> {
125
+ const rows = await this.sql<PgAuthServiceAccountGroupMembershipRow[]>`
126
+ SELECT *
127
+ FROM auth_service_account_group_memberships
128
+ WHERE project_id = ${params.projectId}
129
+ AND service_account_id = ${params.serviceAccountId}
130
+ ORDER BY group_id ASC
131
+ `
132
+
133
+ return rows.map(rowToServiceAccountGroupMembershipRecord)
134
+ }
135
+
136
+ async listForGroup(params: {
137
+ readonly projectId: string
138
+ readonly groupId: string
139
+ }): Promise<readonly ServiceAccountGroupMembershipRecord[]> {
140
+ const rows = await this.sql<PgAuthServiceAccountGroupMembershipRow[]>`
141
+ SELECT *
142
+ FROM auth_service_account_group_memberships
143
+ WHERE project_id = ${params.projectId}
144
+ AND group_id = ${params.groupId}
145
+ ORDER BY service_account_id ASC
146
+ `
147
+
148
+ return rows.map(rowToServiceAccountGroupMembershipRecord)
149
+ }
150
+ }
@@ -0,0 +1,154 @@
1
+ import type {
2
+ AuthServiceAccountStore,
3
+ CreateAuthServiceAccountInput,
4
+ ListAuthServiceAccountsInput,
5
+ ListAuthServiceAccountsResult,
6
+ ServiceAccountRecord,
7
+ UpdateAuthServiceAccountInput,
8
+ } from "@sixb/core/storage"
9
+ import { AuthStorageError } from "@sixb/core/storage"
10
+ import type { PgStoreClient } from "../transactions"
11
+ import type { PgAuthServiceAccountRow } from "./rows"
12
+ import { rowToServiceAccountRecord } from "./rows"
13
+ import {
14
+ appendPagination,
15
+ assertNonEmpty,
16
+ dateOrNow,
17
+ hasEmptyFilter,
18
+ mapUniqueConstraintError,
19
+ type PgValue,
20
+ } from "./shared"
21
+
22
+ export class PgAuthServiceAccountStore implements AuthServiceAccountStore {
23
+ constructor(private readonly sql: PgStoreClient) {}
24
+
25
+ async create(input: CreateAuthServiceAccountInput): Promise<ServiceAccountRecord> {
26
+ const id = assertNonEmpty(input.id, "Service account id")
27
+ const projectId = assertNonEmpty(input.projectId, "Project id")
28
+ const name = assertNonEmpty(input.name, "Service account name")
29
+ const createdAt = dateOrNow(input.createdAt)
30
+ const updatedAt = input.updatedAt ? new Date(input.updatedAt) : createdAt
31
+
32
+ try {
33
+ const [row] = await this.sql<PgAuthServiceAccountRow[]>`
34
+ INSERT INTO auth_service_accounts (
35
+ project_id,
36
+ id,
37
+ name,
38
+ description,
39
+ status,
40
+ created_by_user_id,
41
+ created_by_service_account_id,
42
+ created_by_system_id,
43
+ created_by_session_id,
44
+ created_at,
45
+ updated_at
46
+ ) VALUES (
47
+ ${projectId},
48
+ ${id},
49
+ ${name},
50
+ ${input.description ?? null},
51
+ ${input.status ?? "active"},
52
+ ${input.createdByPrincipal?.type === "user" ? input.createdByPrincipal.id : null},
53
+ ${
54
+ input.createdByPrincipal?.type === "serviceAccount" ? input.createdByPrincipal.id : null
55
+ },
56
+ ${input.createdByPrincipal?.type === "system" ? input.createdByPrincipal.id : null},
57
+ ${input.createdBySessionId ?? null},
58
+ ${createdAt},
59
+ ${updatedAt}
60
+ )
61
+ RETURNING *
62
+ `
63
+
64
+ return rowToServiceAccountRecord(row)
65
+ } catch (error) {
66
+ mapUniqueConstraintError(
67
+ error,
68
+ "duplicate_service_account",
69
+ `[Sixb] Service account '${id}' already exists for project '${projectId}'.`
70
+ )
71
+ }
72
+ }
73
+
74
+ async getById(params: {
75
+ readonly projectId: string
76
+ readonly id: string
77
+ }): Promise<ServiceAccountRecord | null> {
78
+ const [row] = await this.sql<PgAuthServiceAccountRow[]>`
79
+ SELECT *
80
+ FROM auth_service_accounts
81
+ WHERE project_id = ${params.projectId}
82
+ AND id = ${params.id}
83
+ `
84
+ return row ? rowToServiceAccountRecord(row) : null
85
+ }
86
+
87
+ async update(input: UpdateAuthServiceAccountInput): Promise<ServiceAccountRecord> {
88
+ const existing = await this.getById(input)
89
+ if (!existing) {
90
+ throw new AuthStorageError(
91
+ "missing_service_account",
92
+ `[Sixb] Service account '${input.id}' not found for project '${input.projectId}'.`
93
+ )
94
+ }
95
+
96
+ const updatedAt = dateOrNow(input.updatedAt)
97
+ const name =
98
+ input.name === undefined ? existing.name : assertNonEmpty(input.name, "Service account name")
99
+ const description = input.description === undefined ? existing.description : input.description
100
+ const [row] = await this.sql<PgAuthServiceAccountRow[]>`
101
+ UPDATE auth_service_accounts
102
+ SET name = ${name},
103
+ description = ${description ?? null},
104
+ status = ${input.status ?? existing.status},
105
+ updated_at = ${updatedAt}
106
+ WHERE project_id = ${input.projectId}
107
+ AND id = ${input.id}
108
+ RETURNING *
109
+ `
110
+
111
+ return rowToServiceAccountRecord(row)
112
+ }
113
+
114
+ async list(input: ListAuthServiceAccountsInput): Promise<ListAuthServiceAccountsResult> {
115
+ if (hasEmptyFilter(input.statuses)) {
116
+ return { serviceAccounts: [], hasMore: false, total: 0 }
117
+ }
118
+
119
+ const whereClauses = ["project_id = $1"]
120
+ const params: PgValue[] = [input.projectId]
121
+ let nextIndex = 2
122
+ if (input.statuses) {
123
+ whereClauses.push(`status IN (${input.statuses.map(() => `$${nextIndex++}`).join(", ")})`)
124
+ params.push(...input.statuses)
125
+ }
126
+
127
+ const where = `WHERE ${whereClauses.join(" AND ")}`
128
+ const order = input.order === "desc" ? "DESC" : "ASC"
129
+ const [totalRow] = await this.sql.unsafe<{ readonly count: string | number }[]>(
130
+ `SELECT COUNT(*)::bigint AS count FROM auth_service_accounts ${where}`,
131
+ [...params]
132
+ )
133
+ const queryParams = [...params]
134
+ const query = appendPagination(
135
+ `
136
+ SELECT *
137
+ FROM auth_service_accounts
138
+ ${where}
139
+ ORDER BY created_at ${order}, id ${order}
140
+ `,
141
+ queryParams,
142
+ nextIndex,
143
+ input
144
+ )
145
+ const rows = await this.sql.unsafe<PgAuthServiceAccountRow[]>(query, queryParams)
146
+ const total = Number(totalRow?.count ?? 0)
147
+
148
+ return {
149
+ serviceAccounts: rows.map(rowToServiceAccountRecord),
150
+ hasMore: input.limit === undefined ? false : (input.offset ?? 0) + input.limit < total,
151
+ total,
152
+ }
153
+ }
154
+ }