@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,226 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type {
3
+ AuthUserStore,
4
+ CreateAuthUserInput,
5
+ ListAuthUsersInput,
6
+ ListAuthUsersResult,
7
+ UpdateAuthUserProfileInput,
8
+ UpdateAuthUserStatusInput,
9
+ UserRecord,
10
+ } from "@sixb/core/storage"
11
+ import { AuthStorageError } from "@sixb/core/storage"
12
+ import type { SqliteAuthUserRow } from "./rows"
13
+ import { rowToUserRecord } from "./rows"
14
+ import {
15
+ appendPagination,
16
+ assertNonEmpty,
17
+ dateOrNow,
18
+ getUserRowByEmail,
19
+ getUserRowById,
20
+ hasEmptyFilter,
21
+ mapUniqueConstraintError,
22
+ normalizeEmail,
23
+ type SqliteValue,
24
+ toIso,
25
+ } from "./shared"
26
+
27
+ export class SqliteAuthUserStore implements AuthUserStore {
28
+ constructor(private readonly db: Database) {}
29
+
30
+ async create(input: CreateAuthUserInput): Promise<UserRecord> {
31
+ const id = assertNonEmpty(input.id, "User id")
32
+ const projectId = assertNonEmpty(input.projectId, "Project id")
33
+ const email = normalizeEmail(input.email)
34
+
35
+ if (getUserRowById(this.db, { projectId, id })) {
36
+ throw new AuthStorageError(
37
+ "duplicate_user",
38
+ `[Sixb] User '${id}' already exists for project '${projectId}'.`
39
+ )
40
+ }
41
+
42
+ if (getUserRowByEmail(this.db, { projectId, email })) {
43
+ throw new AuthStorageError(
44
+ "duplicate_user",
45
+ `[Sixb] User email '${email}' already exists for project '${projectId}'.`
46
+ )
47
+ }
48
+
49
+ const createdAt = dateOrNow(input.createdAt)
50
+ const updatedAt = input.updatedAt ? new Date(input.updatedAt) : createdAt
51
+
52
+ try {
53
+ this.db
54
+ .query(
55
+ `
56
+ INSERT INTO auth_users (
57
+ project_id,
58
+ id,
59
+ email,
60
+ display_name,
61
+ avatar_url,
62
+ status,
63
+ created_at,
64
+ updated_at
65
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
66
+ `
67
+ )
68
+ .run(
69
+ projectId,
70
+ id,
71
+ email,
72
+ input.displayName ?? null,
73
+ input.avatarUrl ?? null,
74
+ input.status ?? "active",
75
+ toIso(createdAt),
76
+ toIso(updatedAt)
77
+ )
78
+ } catch (error) {
79
+ mapUniqueConstraintError(
80
+ error,
81
+ "duplicate_user",
82
+ `[Sixb] User '${id}' already exists for project '${projectId}'.`
83
+ )
84
+ }
85
+
86
+ return {
87
+ id,
88
+ projectId,
89
+ email,
90
+ displayName: input.displayName,
91
+ avatarUrl: input.avatarUrl,
92
+ status: input.status ?? "active",
93
+ createdAt,
94
+ updatedAt,
95
+ }
96
+ }
97
+
98
+ async getById(params: {
99
+ readonly projectId: string
100
+ readonly id: string
101
+ }): Promise<UserRecord | null> {
102
+ const row = getUserRowById(this.db, params)
103
+ return row ? rowToUserRecord(row) : null
104
+ }
105
+
106
+ async getByEmail(params: {
107
+ readonly projectId: string
108
+ readonly email: string
109
+ }): Promise<UserRecord | null> {
110
+ const row = getUserRowByEmail(this.db, params)
111
+ return row ? rowToUserRecord(row) : null
112
+ }
113
+
114
+ async updateProfile(input: UpdateAuthUserProfileInput): Promise<UserRecord> {
115
+ const existing = getUserRowById(this.db, {
116
+ projectId: input.projectId,
117
+ id: input.id,
118
+ })
119
+
120
+ if (!existing) {
121
+ throw new AuthStorageError(
122
+ "missing_user",
123
+ `[Sixb] User '${input.id}' not found for project '${input.projectId}'.`
124
+ )
125
+ }
126
+
127
+ const updatedAt = dateOrNow(input.updatedAt)
128
+ this.db
129
+ .query(
130
+ `
131
+ UPDATE auth_users
132
+ SET display_name = ?,
133
+ avatar_url = ?,
134
+ updated_at = ?
135
+ WHERE project_id = ?
136
+ AND id = ?
137
+ `
138
+ )
139
+ .run(
140
+ input.displayName ?? null,
141
+ input.avatarUrl ?? null,
142
+ toIso(updatedAt),
143
+ input.projectId,
144
+ input.id
145
+ )
146
+
147
+ return rowToUserRecord({
148
+ ...existing,
149
+ display_name: input.displayName ?? null,
150
+ avatar_url: input.avatarUrl ?? null,
151
+ updated_at: toIso(updatedAt),
152
+ })
153
+ }
154
+
155
+ async updateStatus(input: UpdateAuthUserStatusInput): Promise<UserRecord> {
156
+ const existing = getUserRowById(this.db, {
157
+ projectId: input.projectId,
158
+ id: input.id,
159
+ })
160
+
161
+ if (!existing) {
162
+ throw new AuthStorageError(
163
+ "missing_user",
164
+ `[Sixb] User '${input.id}' not found for project '${input.projectId}'.`
165
+ )
166
+ }
167
+
168
+ const updatedAt = dateOrNow(input.updatedAt)
169
+ this.db
170
+ .query(
171
+ `
172
+ UPDATE auth_users
173
+ SET status = ?,
174
+ updated_at = ?
175
+ WHERE project_id = ?
176
+ AND id = ?
177
+ `
178
+ )
179
+ .run(input.status, toIso(updatedAt), input.projectId, input.id)
180
+
181
+ return rowToUserRecord({
182
+ ...existing,
183
+ status: input.status,
184
+ updated_at: toIso(updatedAt),
185
+ })
186
+ }
187
+
188
+ async list(input: ListAuthUsersInput): Promise<ListAuthUsersResult> {
189
+ if (hasEmptyFilter(input.statuses)) {
190
+ return { users: [], hasMore: false, total: 0 }
191
+ }
192
+
193
+ const whereClauses = ["project_id = ?"]
194
+ const args: SqliteValue[] = [input.projectId]
195
+
196
+ if (input.statuses) {
197
+ whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
198
+ args.push(...input.statuses)
199
+ }
200
+
201
+ const where = `WHERE ${whereClauses.join(" AND ")}`
202
+ const order = input.order === "asc" ? "ASC" : "DESC"
203
+ const totalRow = this.db
204
+ .query(`SELECT COUNT(*) AS count FROM auth_users ${where}`)
205
+ .get(...args) as { readonly count: number }
206
+
207
+ const queryArgs = [...args]
208
+ const query = appendPagination(
209
+ `
210
+ SELECT *
211
+ FROM auth_users
212
+ ${where}
213
+ ORDER BY created_at ${order}, id ${order}
214
+ `,
215
+ queryArgs,
216
+ input
217
+ )
218
+ const rows = this.db.query(query).all(...queryArgs) as SqliteAuthUserRow[]
219
+
220
+ return {
221
+ users: rows.map(rowToUserRecord),
222
+ hasMore: (input.offset ?? 0) + rows.length < totalRow.count,
223
+ total: totalRow.count,
224
+ }
225
+ }
226
+ }
package/src/index.ts ADDED
@@ -0,0 +1,292 @@
1
+ // Keep SQL module declarations visible to downstream workspaces that typecheck this package from
2
+ // source.
3
+ /// <reference path="./sql.d.ts" />
4
+
5
+ import { AsyncLocalStorage } from "node:async_hooks"
6
+ import type {
7
+ MigrationCapableStorage,
8
+ Storage,
9
+ StorageMigrator,
10
+ StorageTransactionOptions,
11
+ } from "@sixb/core"
12
+ import { ProviderMaterializationTransactionLifecycle } from "@sixb/core/internal/ontology-storage-provider"
13
+ import {
14
+ createAgentOperationScope,
15
+ createAuthOperationScope,
16
+ createOntologyOperationScope,
17
+ createOperationScopedFacade,
18
+ createStorageOperationScope,
19
+ createWorkflowRunOperationScope,
20
+ } from "@sixb/core/internal/storage-operation-scope"
21
+ import {
22
+ createTransactionStorageProxy,
23
+ StorageTransactionError,
24
+ throwNestedStorageTransaction,
25
+ } from "@sixb/core/storage"
26
+ import { SqliteActionRunStorage } from "./action-run-storage"
27
+ import { SqliteAgentStorage } from "./agents"
28
+ import { SqliteAuthStorage } from "./auth-storage"
29
+ import {
30
+ createSqliteStorageMigrators,
31
+ installFreshSqliteSchema,
32
+ sqliteStoragePath,
33
+ } from "./migrations"
34
+ import { SqliteObjectStorage } from "./object-storage"
35
+ import { SqliteOntologyStorage, type SqliteOntologyTransactionContext } from "./ontology-storage"
36
+ import { SqlitePipelineRunStorage } from "./pipeline-run-storage"
37
+ import { SqliteProjectionRunStorage } from "./projection-run-storage"
38
+ import { SqliteRulesStorage } from "./rules-storage"
39
+ import { SqliteSyncRunStorage } from "./sync-run-storage"
40
+ import { SqliteTimeseriesStorage } from "./timeseries-storage"
41
+ import {
42
+ closeSqliteStoreConnection,
43
+ openSqliteStoreConnection,
44
+ runImmediateTransactionAsync,
45
+ type SqliteStoreConnection,
46
+ } from "./transactions"
47
+ import { SqliteWebhookDeliveryStorage } from "./webhook-delivery-storage"
48
+ import { SqliteWebhookRunStorage } from "./webhook-run-storage"
49
+ import { SqliteWorkflowInterventionStorage } from "./workflow-intervention-storage"
50
+ import { SqliteWorkflowRunStorage } from "./workflow-run-storage"
51
+
52
+ export interface SqliteStorageOptions {
53
+ /** Directory path for the SQLite storage database. */
54
+ path?: string
55
+ }
56
+
57
+ /**
58
+ * SQLite storage provider for Sixb.
59
+ *
60
+ * Bundles object, timeseries, auth, sync run, pipeline run, projection run, workflow run, webhook
61
+ * run, and webhook delivery storage backed by SQLite.
62
+ *
63
+ * Usage:
64
+ * ```ts
65
+ * const storage = new SqliteStorage({ path: ".sixb" })
66
+ *
67
+ * export const sixb = createSixb({
68
+ * broker: new InMemoryBroker(),
69
+ * storage,
70
+ * })
71
+ * ```
72
+ */
73
+ export class SqliteStorage implements MigrationCapableStorage {
74
+ readonly objects: Storage["objects"]
75
+ readonly ontology: SqliteOntologyStorage
76
+ readonly auth: SqliteAuthStorage
77
+ readonly agents: SqliteAgentStorage
78
+ readonly actionRuns: SqliteActionRunStorage
79
+ readonly pipelineRuns: SqlitePipelineRunStorage
80
+ readonly syncRuns: SqliteSyncRunStorage
81
+ readonly projectionRuns: SqliteProjectionRunStorage
82
+ readonly workflowRuns: SqliteWorkflowRunStorage
83
+ readonly workflowInterventions: SqliteWorkflowInterventionStorage
84
+ readonly timeseries: Storage["timeseries"]
85
+ readonly webhookDeliveries: SqliteWebhookDeliveryStorage
86
+ readonly webhookRuns: SqliteWebhookRunStorage
87
+ readonly rules: SqliteRulesStorage
88
+ readonly migrators: readonly StorageMigrator[]
89
+
90
+ private readonly connection: SqliteStoreConnection
91
+ private readonly transactionScope = new AsyncLocalStorage<{ active: boolean }>()
92
+ private transactionTail: Promise<void> = Promise.resolve()
93
+
94
+ constructor(options: SqliteStorageOptions = {}) {
95
+ const path = options.path ? sqliteStoragePath(options.path) : undefined
96
+ this.connection = openSqliteStoreConnection({ path })
97
+
98
+ if (this.connection.installFreshSchema) {
99
+ installFreshSqliteSchema(this.connection.db)
100
+ }
101
+
102
+ const stores = createSqliteStores(this.connection, {
103
+ runOntologyOperation: async (run) => run(),
104
+ transactionContext: null,
105
+ })
106
+ const assertRootOperationAvailable = () => this.assertRootOperationAvailable()
107
+ const scope = createStorageOperationScope(
108
+ (run) => this.runRootStorageOperation(run),
109
+ assertRootOperationAvailable
110
+ )
111
+ const ontologyScope = createStorageOperationScope(
112
+ (run) => this.runRootOntologyOperation(run),
113
+ assertRootOperationAvailable
114
+ )
115
+ this.objects = createOperationScopedFacade(stores.objects, scope)
116
+ this.ontology = createOntologyOperationScope(stores.ontology, ontologyScope)
117
+ this.auth = createAuthOperationScope(stores.auth, scope)
118
+ this.agents = createAgentOperationScope(stores.agents, scope)
119
+ this.actionRuns = createOperationScopedFacade(stores.actionRuns, scope)
120
+ this.pipelineRuns = createOperationScopedFacade(stores.pipelineRuns, scope)
121
+ this.timeseries = createOperationScopedFacade(stores.timeseries, scope)
122
+ this.syncRuns = createOperationScopedFacade(stores.syncRuns, scope)
123
+ this.projectionRuns = createOperationScopedFacade(stores.projectionRuns, scope)
124
+ this.workflowRuns = createWorkflowRunOperationScope(stores.workflowRuns, scope)
125
+ this.workflowInterventions = createOperationScopedFacade(stores.workflowInterventions, scope)
126
+ this.webhookDeliveries = createOperationScopedFacade(stores.webhookDeliveries, scope)
127
+ this.webhookRuns = createOperationScopedFacade(stores.webhookRuns, scope)
128
+ this.rules = createOperationScopedFacade(stores.rules, scope)
129
+ this.migrators = options.path ? createSqliteStorageMigrators(options.path) : []
130
+ }
131
+
132
+ async ping(): Promise<void> {
133
+ await this.runRootStorageOperation(() => {
134
+ this.connection.db.query("SELECT 1").get()
135
+ })
136
+ }
137
+
138
+ /**
139
+ * The `isolation` option is intentionally ignored. SQLite runs every transaction through one
140
+ * shared connection serialized by {@link withTransactionLock} and a `BEGIN IMMEDIATE`, so there is
141
+ * no concurrent transaction to isolate against — the lock already provides serializable semantics
142
+ * for transaction-vs-transaction races. Postgres, which has true concurrent connections, is where
143
+ * `isolation: "serializable"` translates to a real isolation level.
144
+ */
145
+ async transaction<T>(
146
+ run: (tx: Storage) => Promise<T> | T,
147
+ _options: StorageTransactionOptions = {}
148
+ ): Promise<T> {
149
+ if (this.transactionScope.getStore()) {
150
+ throwNestedStorageTransaction()
151
+ }
152
+
153
+ return this.withTransactionLock(async () => {
154
+ let active = true
155
+ const scope = { active: true }
156
+ const transactionContext: SqliteOntologyTransactionContext = {
157
+ id: {},
158
+ materializations: new ProviderMaterializationTransactionLifecycle(),
159
+ active: true,
160
+ }
161
+ const txStorage = this.createTransactionStorage(transactionContext)
162
+ const tx = createTransactionStorageProxy(txStorage, () => active)
163
+
164
+ try {
165
+ return await runImmediateTransactionAsync(this.connection.db, () =>
166
+ this.transactionScope.run(scope, async () => {
167
+ try {
168
+ const result = await run(tx)
169
+ transactionContext.materializations.assertCommittable()
170
+ return result
171
+ } finally {
172
+ scope.active = false
173
+ transactionContext.active = false
174
+ txStorage.ontology.deactivateSessions()
175
+ transactionContext.materializations.deactivate()
176
+ }
177
+ })
178
+ )
179
+ } finally {
180
+ active = false
181
+ }
182
+ })
183
+ }
184
+
185
+ close(): void {
186
+ closeSqliteStoreConnection(this.connection)
187
+ }
188
+
189
+ private createTransactionStorage(
190
+ transactionContext: SqliteOntologyTransactionContext
191
+ ): Storage & { readonly ontology: SqliteOntologyStorage } {
192
+ return {
193
+ ...createSqliteStores(
194
+ {
195
+ db: this.connection.db,
196
+ ownsConnection: false,
197
+ installFreshSchema: false,
198
+ },
199
+ {
200
+ runOntologyOperation: async (run) => run(),
201
+ transactionContext,
202
+ }
203
+ ),
204
+ ping: async () => {
205
+ this.connection.db.query("SELECT 1").get()
206
+ },
207
+ transaction: async <T>(): Promise<T> => {
208
+ throwNestedStorageTransaction()
209
+ },
210
+ }
211
+ }
212
+
213
+ private runRootStorageOperation<T>(run: () => Promise<T> | T): Promise<T> {
214
+ this.assertRootOperationAvailable()
215
+ return this.withTransactionLock(async () => run())
216
+ }
217
+
218
+ private runRootOntologyOperation<T>(run: () => Promise<T> | T): Promise<T> {
219
+ this.assertRootOperationAvailable()
220
+ return this.withTransactionLock(() => runImmediateTransactionAsync(this.connection.db, run))
221
+ }
222
+
223
+ private assertRootOperationAvailable(): void {
224
+ if (!this.transactionScope.getStore()?.active) return
225
+ throw new StorageTransactionError(
226
+ "[SixbSqlite] Root storage cannot be used inside a transaction callback; use the provided tx storage."
227
+ )
228
+ }
229
+
230
+ private async withTransactionLock<T>(run: () => Promise<T>): Promise<T> {
231
+ const previous = this.transactionTail
232
+ let release!: () => void
233
+ this.transactionTail = new Promise<void>((resolve) => {
234
+ release = resolve
235
+ })
236
+
237
+ await previous
238
+ try {
239
+ return await run()
240
+ } finally {
241
+ release()
242
+ }
243
+ }
244
+ }
245
+
246
+ function createSqliteStores(
247
+ connection: SqliteStoreConnection,
248
+ options: {
249
+ readonly runOntologyOperation: <T>(run: () => Promise<T> | T) => Promise<T>
250
+ readonly transactionContext: SqliteOntologyTransactionContext | null
251
+ }
252
+ ): SqliteStoreSet {
253
+ return {
254
+ objects: new SqliteObjectStorage({ connection }),
255
+ ontology: new SqliteOntologyStorage({
256
+ db: connection.db,
257
+ runRootOperation: options.runOntologyOperation,
258
+ transactionContext: options.transactionContext,
259
+ }),
260
+ auth: new SqliteAuthStorage({ connection }),
261
+ agents: new SqliteAgentStorage({ connection }),
262
+ actionRuns: new SqliteActionRunStorage({ connection }),
263
+ pipelineRuns: new SqlitePipelineRunStorage({ connection }),
264
+ timeseries: new SqliteTimeseriesStorage({ connection }),
265
+ syncRuns: new SqliteSyncRunStorage({ connection }),
266
+ projectionRuns: new SqliteProjectionRunStorage({ connection }),
267
+ workflowRuns: new SqliteWorkflowRunStorage({ connection }),
268
+ workflowInterventions: new SqliteWorkflowInterventionStorage({ connection }),
269
+ webhookDeliveries: new SqliteWebhookDeliveryStorage({ connection }),
270
+ webhookRuns: new SqliteWebhookRunStorage({ connection }),
271
+ rules: new SqliteRulesStorage({ connection }),
272
+ }
273
+ }
274
+
275
+ interface SqliteStoreSet {
276
+ readonly objects: SqliteObjectStorage
277
+ readonly ontology: SqliteOntologyStorage
278
+ readonly auth: SqliteAuthStorage
279
+ readonly agents: SqliteAgentStorage
280
+ readonly actionRuns: SqliteActionRunStorage
281
+ readonly pipelineRuns: SqlitePipelineRunStorage
282
+ readonly syncRuns: SqliteSyncRunStorage
283
+ readonly projectionRuns: SqliteProjectionRunStorage
284
+ readonly workflowRuns: SqliteWorkflowRunStorage
285
+ readonly workflowInterventions: SqliteWorkflowInterventionStorage
286
+ readonly timeseries: SqliteTimeseriesStorage
287
+ readonly webhookDeliveries: SqliteWebhookDeliveryStorage
288
+ readonly webhookRuns: SqliteWebhookRunStorage
289
+ readonly rules: SqliteRulesStorage
290
+ }
291
+
292
+ export { migrateSqliteStorage } from "./migrations"
@@ -0,0 +1,49 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type { SqliteValue } from "./run-list-query"
3
+
4
+ type SqliteLatestRunTarget =
5
+ | { readonly tableName: "pipeline_runs"; readonly ownerColumn: "pipeline_id" }
6
+ | { readonly tableName: "projection_runs"; readonly ownerColumn: "projection_id" }
7
+ | { readonly tableName: "sync_runs"; readonly ownerColumn: "sync_id" }
8
+ | { readonly tableName: "workflow_runs"; readonly ownerColumn: "workflow_id" }
9
+
10
+ export function queryLatestRunsByOwnerId<TRow>(
11
+ input: {
12
+ readonly db: Database
13
+ readonly ownerIds: readonly string[]
14
+ readonly projectId: string
15
+ readonly ownerIdFor: (row: TRow) => string
16
+ } & SqliteLatestRunTarget
17
+ ): readonly TRow[] {
18
+ const ownerIds = [...new Set(input.ownerIds)]
19
+ if (ownerIds.length === 0) {
20
+ return []
21
+ }
22
+
23
+ const placeholders = ownerIds.map(() => "?").join(", ")
24
+ const rows = input.db
25
+ .query(
26
+ `
27
+ SELECT *
28
+ FROM (
29
+ SELECT
30
+ *,
31
+ ROW_NUMBER() OVER (
32
+ PARTITION BY ${input.ownerColumn}
33
+ ORDER BY started_at DESC, id DESC
34
+ ) AS sixb_latest_rank
35
+ FROM ${input.tableName}
36
+ WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})
37
+ )
38
+ WHERE sixb_latest_rank = 1
39
+ `
40
+ )
41
+ .all(input.projectId, ...ownerIds) as (TRow & { sixb_latest_rank: SqliteValue })[]
42
+
43
+ const latestByOwnerId = new Map(rows.map((row) => [input.ownerIdFor(row), row]))
44
+
45
+ return ownerIds.flatMap((ownerId) => {
46
+ const row = latestByOwnerId.get(ownerId)
47
+ return row ? [row] : []
48
+ })
49
+ }