@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,280 @@
1
+ import { createHash } from "node:crypto"
2
+ import type { StorageMigrator } from "@sixb/core"
3
+ import type {
4
+ MigrationHistoryStore,
5
+ MigrationRecord,
6
+ MigrationSet,
7
+ MigrationStep,
8
+ MigrationStepOptions,
9
+ } from "@sixb/core/storage"
10
+ import {
11
+ defineMigrations,
12
+ describeMigrationHistory,
13
+ runMigrationSet,
14
+ step,
15
+ } from "@sixb/core/storage"
16
+ import initialSchemaSql from "./migrations/001-initial-schema.sql" with { type: "text" }
17
+ import type { SQL, SQLClient } from "./pg-client"
18
+
19
+ export interface PostgresMigrationContext {
20
+ exec(sqlText: string): Promise<void>
21
+ }
22
+
23
+ export const POSTGRES_STORAGE_ADAPTER_ID = "SixbPostgresStorage"
24
+
25
+ export function pgSql(id: string, sqlText: string): MigrationStep<PostgresMigrationContext> {
26
+ return pgStep(id, (context) => context.exec(sqlText), { checksum: checksum(sqlText) })
27
+ }
28
+
29
+ export function pgStep(
30
+ id: string,
31
+ up: (context: PostgresMigrationContext) => void | Promise<void>,
32
+ options: MigrationStepOptions = {}
33
+ ): MigrationStep<PostgresMigrationContext> {
34
+ return step<PostgresMigrationContext>(id, up, options)
35
+ }
36
+
37
+ export function createPostgresStorageMigrators(
38
+ sql: SQL,
39
+ schemaName: string
40
+ ): readonly StorageMigrator[] {
41
+ return [
42
+ createPostgresMigrator({
43
+ sql,
44
+ schemaName,
45
+ migrations: postgresStorageMigrations,
46
+ }),
47
+ ]
48
+ }
49
+
50
+ export function createPostgresMigrator(params: {
51
+ readonly sql: SQL
52
+ readonly schemaName: string
53
+ readonly migrations: MigrationSet<PostgresMigrationContext>
54
+ }): StorageMigrator {
55
+ return {
56
+ adapterId: params.migrations.adapterId,
57
+ latestVersion: params.migrations.latestVersion,
58
+ async status() {
59
+ return describeMigrationHistory({
60
+ migrations: params.migrations,
61
+ rows: await readPostgresHistory(params.sql, params.schemaName, params.migrations.adapterId),
62
+ })
63
+ },
64
+ migrate() {
65
+ return withPostgresMigrationLock(params, async (sql) => {
66
+ const session = postgresMigrationSession(sql, params.schemaName)
67
+ return runMigrationSet({
68
+ context: session.context,
69
+ migrations: params.migrations,
70
+ state: session.state,
71
+ })
72
+ })
73
+ },
74
+ }
75
+ }
76
+
77
+ export async function migratePostgresStorage(sql: SQL, schemaName: string): Promise<void> {
78
+ await createPostgresMigrator({
79
+ sql,
80
+ schemaName,
81
+ migrations: postgresStorageMigrations,
82
+ }).migrate()
83
+ }
84
+
85
+ export async function dropSchema(sql: SQL, schemaName: string): Promise<void> {
86
+ await sql.unsafe(`DROP SCHEMA IF EXISTS ${quoteIdent(schemaName)} CASCADE`)
87
+ }
88
+
89
+ export function quoteIdent(identifier: string): string {
90
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
91
+ throw new Error(`[SixbPg] Invalid identifier: ${identifier}`)
92
+ }
93
+ return `"${identifier}"`
94
+ }
95
+
96
+ function postgresMigrationSession(
97
+ sql: SQL,
98
+ schemaName: string
99
+ ): { context: PostgresMigrationContext; state: MigrationHistoryStore } {
100
+ const schema = quoteIdent(schemaName)
101
+ const migrationsTable = `${schema}.sixb_migrations`
102
+ // Migration steps receive a stable context; route exec() through the active transaction.
103
+ let active: SQLClient | null = null
104
+ const connection = (): SQLClient => active ?? sql
105
+
106
+ return {
107
+ context: {
108
+ async exec(sqlText) {
109
+ await connection().unsafe(sqlText)
110
+ },
111
+ },
112
+ state: {
113
+ async ensure() {
114
+ await sql.unsafe(`
115
+ CREATE SCHEMA IF NOT EXISTS ${schema};
116
+
117
+ CREATE TABLE IF NOT EXISTS ${migrationsTable} (
118
+ adapter_id TEXT NOT NULL,
119
+ version INTEGER NOT NULL,
120
+ id TEXT NOT NULL,
121
+ checksum TEXT,
122
+ status TEXT NOT NULL CHECK (status IN ('started', 'applied')),
123
+ started_at TIMESTAMPTZ NOT NULL,
124
+ finished_at TIMESTAMPTZ,
125
+ PRIMARY KEY (adapter_id, version)
126
+ );
127
+ `)
128
+ },
129
+ async readHistory(adapterId) {
130
+ const rows = await sql.unsafe<PostgresMigrationRow[]>(
131
+ `SELECT * FROM ${migrationsTable} WHERE adapter_id = $1 ORDER BY version`,
132
+ [adapterId]
133
+ )
134
+ return rows.map(rowToMigrationRecord)
135
+ },
136
+ async markStarted(adapterId, migration, at) {
137
+ await connection().unsafe(
138
+ `
139
+ INSERT INTO ${migrationsTable} (
140
+ adapter_id, version, id, checksum, status, started_at, finished_at
141
+ ) VALUES ($1, $2, $3, $4, 'started', $5, NULL)
142
+ ON CONFLICT(adapter_id, version) DO UPDATE SET
143
+ id = excluded.id,
144
+ checksum = excluded.checksum,
145
+ status = excluded.status,
146
+ started_at = excluded.started_at,
147
+ finished_at = NULL
148
+ `,
149
+ [adapterId, migration.version, migration.id, migration.checksum ?? null, at]
150
+ )
151
+ },
152
+ async markApplied(adapterId, migration, at) {
153
+ await connection().unsafe(
154
+ `
155
+ UPDATE ${migrationsTable}
156
+ SET status = 'applied', finished_at = $1
157
+ WHERE adapter_id = $2 AND version = $3
158
+ `,
159
+ [at, adapterId, migration.version]
160
+ )
161
+ },
162
+ async transaction(run) {
163
+ // `sql` is the reserved connection holding the migration advisory lock. porsager's
164
+ // reserved connection has no `.begin`, so drive the transaction manually on it and
165
+ // route exec()/markStarted()/markApplied() through the same connection via `active`.
166
+ const previous = active
167
+ active = sql
168
+ await sql.unsafe("BEGIN")
169
+ try {
170
+ await sql.unsafe(`SET LOCAL search_path TO ${schema}`)
171
+ const result = await run()
172
+ await sql.unsafe("COMMIT")
173
+ return result
174
+ } catch (error) {
175
+ await sql.unsafe("ROLLBACK")
176
+ throw error
177
+ } finally {
178
+ active = previous
179
+ }
180
+ },
181
+ },
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Reads migration history without DDL and without the advisory lock. `null` means the
187
+ * history table does not exist, which is a state and not a failure.
188
+ *
189
+ * `migrate()` cannot be used for this: it calls `ensure()` first, so it runs
190
+ * `CREATE SCHEMA`/`CREATE TABLE` and reserves a connection to hold
191
+ * `pg_advisory_lock`. A probe must need no DDL grant — `/ready` is public and
192
+ * unauthenticated — and must not serialize N replicas behind one lock.
193
+ *
194
+ * `to_regclass` returns NULL for a missing relation (including a missing schema)
195
+ * instead of raising, and needs no catalog privileges.
196
+ */
197
+ async function readPostgresHistory(
198
+ sql: SQL,
199
+ schemaName: string,
200
+ adapterId: string
201
+ ): Promise<readonly MigrationRecord[] | null> {
202
+ const schema = quoteIdent(schemaName)
203
+ const probe = await sql.unsafe<{ oid: string | null }[]>(`SELECT to_regclass($1) AS oid`, [
204
+ `${schema}.sixb_migrations`,
205
+ ])
206
+ if (!probe[0]?.oid) {
207
+ return null
208
+ }
209
+
210
+ const rows = await sql.unsafe<PostgresMigrationRow[]>(
211
+ `SELECT * FROM ${schema}.sixb_migrations WHERE adapter_id = $1 ORDER BY version`,
212
+ [adapterId]
213
+ )
214
+ return rows.map(rowToMigrationRecord)
215
+ }
216
+
217
+ async function withPostgresMigrationLock<T>(
218
+ params: {
219
+ readonly sql: SQL
220
+ readonly schemaName: string
221
+ readonly migrations: MigrationSet<PostgresMigrationContext>
222
+ },
223
+ run: (sql: SQL) => Promise<T>
224
+ ): Promise<T> {
225
+ const sql = await params.sql.reserve()
226
+ const [first, second] = advisoryLockParts(
227
+ `storage:migration:${params.schemaName}:${params.migrations.adapterId}`
228
+ )
229
+
230
+ try {
231
+ await sql`SELECT pg_advisory_lock(${first}, ${second})`
232
+ try {
233
+ return await run(sql)
234
+ } finally {
235
+ await sql`SELECT pg_advisory_unlock(${first}, ${second})`
236
+ }
237
+ } finally {
238
+ sql.release()
239
+ }
240
+ }
241
+
242
+ function advisoryLockParts(key: string): readonly [number, number] {
243
+ const hash = createHash("sha256").update(key).digest()
244
+ return [hash.readInt32BE(0), hash.readInt32BE(4)]
245
+ }
246
+
247
+ function rowToMigrationRecord(row: PostgresMigrationRow): MigrationRecord {
248
+ return {
249
+ adapterId: row.adapter_id,
250
+ version: row.version,
251
+ id: row.id,
252
+ checksum: row.checksum ?? undefined,
253
+ status: row.status,
254
+ startedAt: toIsoString(row.started_at),
255
+ finishedAt: row.finished_at ? toIsoString(row.finished_at) : undefined,
256
+ }
257
+ }
258
+
259
+ function toIsoString(value: Date | string): string {
260
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString()
261
+ }
262
+
263
+ function checksum(value: string): string {
264
+ return createHash("sha256").update(value).digest("hex")
265
+ }
266
+
267
+ export const postgresStorageMigrations = defineMigrations<PostgresMigrationContext>({
268
+ adapterId: POSTGRES_STORAGE_ADAPTER_ID,
269
+ steps: [pgSql("001-initial-schema", initialSchemaSql)],
270
+ })
271
+
272
+ interface PostgresMigrationRow {
273
+ readonly adapter_id: string
274
+ readonly version: number
275
+ readonly id: string
276
+ readonly checksum: string | null
277
+ readonly status: MigrationRecord["status"]
278
+ readonly started_at: Date | string
279
+ readonly finished_at: Date | string | null
280
+ }
@@ -0,0 +1,115 @@
1
+ import type {
2
+ GetOntologyCommitByIdempotencyKeyInput,
3
+ GetOntologyCommitByIdInput,
4
+ GetOntologyCommitByOriginInput,
5
+ ListOntologyCommitsInput,
6
+ ListOntologyCommitsResult,
7
+ OntologyCommitRecord,
8
+ OntologyCommitStorage,
9
+ } from "@sixb/core/storage"
10
+ import {
11
+ assertNonblank,
12
+ assertNonnegativeInteger,
13
+ commitRecord,
14
+ originWhere,
15
+ type PgOntologyCommitRow,
16
+ type PgRootOperation,
17
+ } from "./shared"
18
+
19
+ export class PgOntologyCommitStorage implements OntologyCommitStorage {
20
+ constructor(private readonly runRootOperation: PgRootOperation) {}
21
+
22
+ async getByIdempotencyKey(
23
+ input: GetOntologyCommitByIdempotencyKeyInput
24
+ ): Promise<OntologyCommitRecord | null> {
25
+ return this.runRootOperation(async (sql) => {
26
+ assertNonblank(input.projectId, "Ontology commit project id")
27
+ assertNonblank(input.idempotencyKey, "Ontology commit idempotency key")
28
+ const [row] = await sql<PgOntologyCommitRow[]>`
29
+ SELECT * FROM ontology_commits
30
+ WHERE project_id = ${input.projectId} AND idempotency_key = ${input.idempotencyKey}
31
+ `
32
+ return row ? commitRecord(row) : null
33
+ })
34
+ }
35
+
36
+ async getById(input: GetOntologyCommitByIdInput): Promise<OntologyCommitRecord | null> {
37
+ return this.runRootOperation(async (sql) => {
38
+ assertNonblank(input.projectId, "Ontology commit project id")
39
+ assertNonblank(input.id, "Ontology commit id")
40
+ const [row] = await sql<PgOntologyCommitRow[]>`
41
+ SELECT * FROM ontology_commits
42
+ WHERE project_id = ${input.projectId} AND id = ${input.id}
43
+ `
44
+ return row ? commitRecord(row) : null
45
+ })
46
+ }
47
+
48
+ async getByOrigin(input: GetOntologyCommitByOriginInput): Promise<OntologyCommitRecord | null> {
49
+ return this.runRootOperation(async (sql) => {
50
+ assertNonblank(input.projectId, "Ontology commit project id")
51
+ const origin = originWhere(input.origin)
52
+ assertNonblank(origin.runId, "Ontology commit origin run id")
53
+ if (origin.batchOrdinal !== null) {
54
+ assertNonnegativeInteger(origin.batchOrdinal, "Ontology telemetry commit batch ordinal")
55
+ }
56
+ const [row] =
57
+ origin.batchOrdinal === null
58
+ ? await sql<PgOntologyCommitRow[]>`
59
+ SELECT * FROM ontology_commits
60
+ WHERE project_id = ${input.projectId}
61
+ AND origin_kind = ${origin.kind}
62
+ AND origin_run_id = ${origin.runId}
63
+ AND origin_batch_ordinal IS NULL
64
+ `
65
+ : await sql<PgOntologyCommitRow[]>`
66
+ SELECT * FROM ontology_commits
67
+ WHERE project_id = ${input.projectId}
68
+ AND origin_kind = ${origin.kind}
69
+ AND origin_run_id = ${origin.runId}
70
+ AND origin_batch_ordinal = ${origin.batchOrdinal}
71
+ `
72
+ return row ? commitRecord(row) : null
73
+ })
74
+ }
75
+
76
+ async list(input: ListOntologyCommitsInput): Promise<ListOntologyCommitsResult> {
77
+ return this.runRootOperation(async (sql) => {
78
+ assertNonblank(input.projectId, "Ontology commit project id")
79
+ if (input.run) assertNonblank(input.run.id, "Ontology commit run id")
80
+ const offset = input.offset ?? 0
81
+ const limit = input.limit
82
+ assertNonnegativeInteger(offset, "Ontology commit list offset")
83
+ if (limit !== undefined) assertNonnegativeInteger(limit, "Ontology commit list limit")
84
+
85
+ const runFilter =
86
+ input.run?.kind === "action"
87
+ ? sql`AND origin_kind = 'action' AND origin_run_id = ${input.run.id}`
88
+ : input.run?.kind === "projection"
89
+ ? sql`AND origin_run_id = ${input.run.id}
90
+ AND origin_kind IN ('projection', 'telemetry')`
91
+ : sql``
92
+ const [{ total }] = await sql<{ readonly total: number | string }[]>`
93
+ SELECT COUNT(*) AS total
94
+ FROM ontology_commits
95
+ WHERE project_id = ${input.projectId} ${runFilter}
96
+ `
97
+ const direction = input.order === "desc" ? sql`DESC` : sql`ASC`
98
+ const order =
99
+ input.run?.kind === "projection"
100
+ ? sql`CASE WHEN origin_kind = 'telemetry' THEN origin_batch_ordinal ELSE -1 END
101
+ ${direction}, committed_at ${direction}, id ${direction}`
102
+ : sql`committed_at ${direction}, id ${direction}`
103
+ const limitFragment = limit === undefined ? sql`` : sql`LIMIT ${limit}`
104
+ const rows = await sql<PgOntologyCommitRow[]>`
105
+ SELECT * FROM ontology_commits
106
+ WHERE project_id = ${input.projectId} ${runFilter}
107
+ ORDER BY ${order}
108
+ ${limitFragment} OFFSET ${offset}
109
+ `
110
+ const count = Number(total)
111
+ const commits = rows.map(commitRecord)
112
+ return { commits, total: count, hasMore: offset + commits.length < count }
113
+ })
114
+ }
115
+ }
@@ -0,0 +1,35 @@
1
+ import type { OntologyStorage } from "@sixb/core/storage"
2
+ import type { SQLClient } from "../pg-client"
3
+ import { PgOntologyCommitStorage } from "./commits"
4
+ import type { PgOntologyTransactionContext } from "./materialization-session"
5
+ import { PgOntologyMaterializationStorage } from "./materializations"
6
+ import { PgOntologyOutboxStorage } from "./outbox"
7
+ import type { PgRootOperation } from "./shared"
8
+ import { PgOntologySourceStorage } from "./sources"
9
+
10
+ export class PgOntologyStorage implements OntologyStorage {
11
+ readonly commits: PgOntologyCommitStorage
12
+ readonly sources: PgOntologySourceStorage
13
+ readonly materializations: PgOntologyMaterializationStorage
14
+ readonly outbox: PgOntologyOutboxStorage
15
+
16
+ constructor(input: {
17
+ readonly sql: SQLClient
18
+ readonly runRootOperation: PgRootOperation
19
+ readonly transactionContext: PgOntologyTransactionContext | null
20
+ }) {
21
+ this.commits = new PgOntologyCommitStorage(input.runRootOperation)
22
+ this.sources = new PgOntologySourceStorage(input.runRootOperation)
23
+ this.materializations = new PgOntologyMaterializationStorage(
24
+ input.sql,
25
+ input.transactionContext
26
+ )
27
+ this.outbox = new PgOntologyOutboxStorage(input.runRootOperation)
28
+ }
29
+
30
+ deactivateSessions(): void {
31
+ this.materializations.deactivateSessions()
32
+ }
33
+ }
34
+
35
+ export type { PgOntologyTransactionContext } from "./materialization-session"