@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,375 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type { RuleEventSubject } from "@sixb/core"
3
+ import type {
4
+ ListActiveRuleStatesInput,
5
+ ListActiveRuleStatesResult,
6
+ ListRuleStatesReconciliationPageInput,
7
+ ListRuleStatesReconciliationPageResult,
8
+ RuleStateRecord,
9
+ RuleStateTransitionEvent,
10
+ RulesStorage,
11
+ StoredRuleResolvedEvent,
12
+ StoredRuleTriggeredEvent,
13
+ } from "@sixb/core/storage"
14
+ import { installFreshSqliteSchema } from "./migrations"
15
+ import {
16
+ closeSqliteStoreConnection,
17
+ openSqliteStoreConnection,
18
+ runImmediateTransaction,
19
+ type SqliteStoreConnection,
20
+ } from "./transactions"
21
+
22
+ export interface SqliteRulesStorageOptions {
23
+ /** Path to SQLite database file. Defaults to ':memory:' for in-memory database. */
24
+ path?: string
25
+ /** Internal shared connection used by bundled SqliteStorage. */
26
+ connection?: SqliteStoreConnection
27
+ }
28
+
29
+ export class SqliteRulesStorage implements RulesStorage {
30
+ private readonly connection: SqliteStoreConnection
31
+ private readonly db: Database
32
+
33
+ constructor(options: SqliteRulesStorageOptions = {}) {
34
+ this.connection = openSqliteStoreConnection(options)
35
+ this.db = this.connection.db
36
+
37
+ if (this.connection.installFreshSchema) {
38
+ installFreshSqliteSchema(this.db)
39
+ }
40
+ }
41
+
42
+ async getActive(params: {
43
+ projectId: string
44
+ ruleId: string
45
+ subject: RuleEventSubject
46
+ }): Promise<RuleStateRecord | null> {
47
+ const row = this.db
48
+ .query(
49
+ `
50
+ SELECT
51
+ project_id,
52
+ rule_id,
53
+ subject_kind,
54
+ object_type_id,
55
+ primary_id,
56
+ triggered_at
57
+ FROM rule_states
58
+ WHERE project_id = ?
59
+ AND rule_id = ?
60
+ AND subject_kind = ?
61
+ AND object_type_id = ?
62
+ AND primary_id = ?
63
+ `
64
+ )
65
+ .get(
66
+ params.projectId,
67
+ params.ruleId,
68
+ params.subject.kind,
69
+ params.subject.objectTypeId,
70
+ params.subject.primaryId
71
+ ) as SqliteRuleStateRow | null
72
+
73
+ return row ? rowToRuleStateRecord(row) : null
74
+ }
75
+
76
+ async getActiveBatch(params: {
77
+ projectId: string
78
+ items: readonly {
79
+ readonly ruleId: string
80
+ readonly subject: RuleEventSubject
81
+ }[]
82
+ }): Promise<readonly RuleStateRecord[]> {
83
+ if (params.items.length === 0) return []
84
+ const rows = this.db
85
+ .query(
86
+ `
87
+ WITH requested AS (
88
+ SELECT
89
+ json_extract(value, '$.ruleId') AS rule_id,
90
+ json_extract(value, '$.subject.kind') AS subject_kind,
91
+ json_extract(value, '$.subject.objectTypeId') AS object_type_id,
92
+ json_extract(value, '$.subject.primaryId') AS primary_id
93
+ FROM json_each(?)
94
+ )
95
+ SELECT states.project_id, states.rule_id, states.subject_kind,
96
+ states.object_type_id, states.primary_id, states.triggered_at
97
+ FROM rule_states AS states
98
+ JOIN requested USING (rule_id, subject_kind, object_type_id, primary_id)
99
+ WHERE states.project_id = ?
100
+ `
101
+ )
102
+ .all(JSON.stringify(params.items), params.projectId) as SqliteRuleStateRow[]
103
+ return rows.map(rowToRuleStateRecord)
104
+ }
105
+
106
+ async listActive(input: ListActiveRuleStatesInput): Promise<ListActiveRuleStatesResult> {
107
+ if (input.objectTypeIds?.length === 0) {
108
+ return { states: [], hasMore: false, total: 0 }
109
+ }
110
+
111
+ const whereClauses = ["project_id = ?"]
112
+ const args: (string | number)[] = [input.projectId]
113
+
114
+ if (input.ruleId) {
115
+ whereClauses.push("rule_id = ?")
116
+ args.push(input.ruleId)
117
+ }
118
+
119
+ if (input.objectTypeId) {
120
+ whereClauses.push("object_type_id = ?")
121
+ args.push(input.objectTypeId)
122
+ }
123
+
124
+ if (input.objectTypeIds) {
125
+ whereClauses.push(`object_type_id IN (${input.objectTypeIds.map(() => "?").join(", ")})`)
126
+ args.push(...input.objectTypeIds)
127
+ }
128
+
129
+ if (input.primaryId) {
130
+ whereClauses.push("primary_id = ?")
131
+ args.push(input.primaryId)
132
+ }
133
+
134
+ const where = `WHERE ${whereClauses.join(" AND ")}`
135
+ const order = input.order === "asc" ? "ASC" : "DESC"
136
+ const offset = input.offset ?? 0
137
+ const limit = input.limit
138
+ const totalRow = this.db
139
+ .query(`SELECT COUNT(*) AS count FROM rule_states ${where}`)
140
+ .get(...args) as { count: number }
141
+
142
+ let query = `
143
+ SELECT
144
+ project_id,
145
+ rule_id,
146
+ subject_kind,
147
+ object_type_id,
148
+ primary_id,
149
+ triggered_at
150
+ FROM rule_states
151
+ ${where}
152
+ ORDER BY triggered_at ${order}, rule_id ${order}, object_type_id ${order}, primary_id ${order}
153
+ `
154
+ const queryArgs = [...args]
155
+
156
+ if (limit !== undefined) {
157
+ query += " LIMIT ? OFFSET ?"
158
+ queryArgs.push(limit, offset)
159
+ } else if (offset > 0) {
160
+ query += " LIMIT -1 OFFSET ?"
161
+ queryArgs.push(offset)
162
+ }
163
+
164
+ const rows = this.db.query(query).all(...queryArgs) as SqliteRuleStateRow[]
165
+ const states = rows.map(rowToRuleStateRecord)
166
+
167
+ return {
168
+ states,
169
+ hasMore: offset + states.length < totalRow.count,
170
+ total: totalRow.count,
171
+ }
172
+ }
173
+
174
+ async listReconciliationPage(
175
+ input: ListRuleStatesReconciliationPageInput
176
+ ): Promise<ListRuleStatesReconciliationPageResult> {
177
+ assertPositiveLimit(input.limit)
178
+ const rows = this.db
179
+ .query(
180
+ `
181
+ SELECT project_id, rule_id, subject_kind, object_type_id, primary_id, triggered_at
182
+ FROM rule_states
183
+ WHERE project_id = ? AND subject_kind = 'object'
184
+ AND (
185
+ ? IS NULL OR
186
+ (rule_id, object_type_id, primary_id) > (?, ?, ?)
187
+ )
188
+ ORDER BY rule_id ASC, object_type_id ASC, primary_id ASC
189
+ LIMIT ?
190
+ `
191
+ )
192
+ .all(
193
+ input.projectId,
194
+ input.after?.ruleId ?? null,
195
+ input.after?.ruleId ?? null,
196
+ input.after?.objectTypeId ?? null,
197
+ input.after?.primaryId ?? null,
198
+ input.limit + 1
199
+ ) as SqliteRuleStateRow[]
200
+ return reconciliationPage(rows, input.limit)
201
+ }
202
+
203
+ async applyTriggered(event: StoredRuleTriggeredEvent): Promise<void> {
204
+ this.db
205
+ .query(
206
+ `
207
+ INSERT INTO rule_states (
208
+ project_id,
209
+ rule_id,
210
+ subject_kind,
211
+ object_type_id,
212
+ primary_id,
213
+ triggered_at
214
+ ) VALUES (?, ?, ?, ?, ?, ?)
215
+ ON CONFLICT(project_id, rule_id, subject_kind, object_type_id, primary_id)
216
+ DO UPDATE SET triggered_at = excluded.triggered_at
217
+ `
218
+ )
219
+ .run(
220
+ event.projectId,
221
+ event.payload.ruleId,
222
+ event.payload.subject.kind,
223
+ event.payload.subject.objectTypeId,
224
+ event.payload.subject.primaryId,
225
+ event.payload.triggeredAt
226
+ )
227
+ }
228
+
229
+ async applyResolved(event: StoredRuleResolvedEvent): Promise<void> {
230
+ this.db
231
+ .query(
232
+ `
233
+ DELETE FROM rule_states
234
+ WHERE project_id = ?
235
+ AND rule_id = ?
236
+ AND subject_kind = ?
237
+ AND object_type_id = ?
238
+ AND primary_id = ?
239
+ `
240
+ )
241
+ .run(
242
+ event.projectId,
243
+ event.payload.ruleId,
244
+ event.payload.subject.kind,
245
+ event.payload.subject.objectTypeId,
246
+ event.payload.subject.primaryId
247
+ )
248
+ }
249
+
250
+ async applyTransitions(events: readonly RuleStateTransitionEvent[]): Promise<void> {
251
+ if (events.length === 0) return
252
+ const rows = events.map(transitionRow)
253
+
254
+ runImmediateTransaction(this.db, () => {
255
+ this.db
256
+ .query(
257
+ `
258
+ WITH transitions AS (
259
+ SELECT
260
+ json_extract(value, '$.type') AS type,
261
+ json_extract(value, '$.projectId') AS project_id,
262
+ json_extract(value, '$.ruleId') AS rule_id,
263
+ json_extract(value, '$.subjectKind') AS subject_kind,
264
+ json_extract(value, '$.objectTypeId') AS object_type_id,
265
+ json_extract(value, '$.primaryId') AS primary_id,
266
+ json_extract(value, '$.transitionAt') AS transition_at
267
+ FROM json_each(?)
268
+ )
269
+ INSERT INTO rule_states (
270
+ project_id, rule_id, subject_kind, object_type_id, primary_id, triggered_at
271
+ )
272
+ SELECT project_id, rule_id, subject_kind, object_type_id, primary_id, transition_at
273
+ FROM transitions
274
+ WHERE type = 'rule.triggered'
275
+ ON CONFLICT(project_id, rule_id, subject_kind, object_type_id, primary_id)
276
+ DO UPDATE SET triggered_at = excluded.triggered_at
277
+ `
278
+ )
279
+ .run(JSON.stringify(rows))
280
+
281
+ this.db
282
+ .query(
283
+ `
284
+ WITH transitions AS (
285
+ SELECT
286
+ json_extract(value, '$.type') AS type,
287
+ json_extract(value, '$.projectId') AS project_id,
288
+ json_extract(value, '$.ruleId') AS rule_id,
289
+ json_extract(value, '$.subjectKind') AS subject_kind,
290
+ json_extract(value, '$.objectTypeId') AS object_type_id,
291
+ json_extract(value, '$.primaryId') AS primary_id
292
+ FROM json_each(?)
293
+ )
294
+ DELETE FROM rule_states
295
+ WHERE EXISTS (
296
+ SELECT 1 FROM transitions
297
+ WHERE transitions.type = 'rule.resolved'
298
+ AND transitions.project_id = rule_states.project_id
299
+ AND transitions.rule_id = rule_states.rule_id
300
+ AND transitions.subject_kind = rule_states.subject_kind
301
+ AND transitions.object_type_id = rule_states.object_type_id
302
+ AND transitions.primary_id = rule_states.primary_id
303
+ )
304
+ `
305
+ )
306
+ .run(JSON.stringify(rows))
307
+ })
308
+ }
309
+
310
+ close(): void {
311
+ closeSqliteStoreConnection(this.connection)
312
+ }
313
+ }
314
+
315
+ function transitionRow(event: RuleStateTransitionEvent) {
316
+ return {
317
+ type: event.type,
318
+ projectId: event.projectId,
319
+ ruleId: event.payload.ruleId,
320
+ subjectKind: event.payload.subject.kind,
321
+ objectTypeId: event.payload.subject.objectTypeId,
322
+ primaryId: event.payload.subject.primaryId,
323
+ transitionAt:
324
+ event.type === "rule.triggered" ? event.payload.triggeredAt : event.payload.resolvedAt,
325
+ }
326
+ }
327
+
328
+ function reconciliationPage(
329
+ rows: readonly SqliteRuleStateRow[],
330
+ limit: number
331
+ ): ListRuleStatesReconciliationPageResult {
332
+ const hasMore = rows.length > limit
333
+ const states = rows.slice(0, limit).map(rowToRuleStateRecord)
334
+ const last = states.at(-1)
335
+ return {
336
+ states,
337
+ ...(hasMore && last
338
+ ? {
339
+ next: {
340
+ ruleId: last.ruleId,
341
+ objectTypeId: last.subject.objectTypeId,
342
+ primaryId: last.subject.primaryId,
343
+ },
344
+ }
345
+ : {}),
346
+ }
347
+ }
348
+
349
+ function assertPositiveLimit(limit: number): void {
350
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
351
+ throw new Error("Rules reconciliation page limit must be a positive safe integer.")
352
+ }
353
+ }
354
+
355
+ interface SqliteRuleStateRow {
356
+ readonly project_id: string
357
+ readonly rule_id: string
358
+ readonly subject_kind: RuleEventSubject["kind"]
359
+ readonly object_type_id: string
360
+ readonly primary_id: string
361
+ readonly triggered_at: string
362
+ }
363
+
364
+ function rowToRuleStateRecord(row: SqliteRuleStateRow): RuleStateRecord {
365
+ return {
366
+ projectId: row.project_id,
367
+ ruleId: row.rule_id,
368
+ subject: {
369
+ kind: row.subject_kind,
370
+ objectTypeId: row.object_type_id,
371
+ primaryId: row.primary_id,
372
+ },
373
+ triggeredAt: row.triggered_at,
374
+ }
375
+ }
@@ -0,0 +1,98 @@
1
+ import type { Database } from "bun:sqlite"
2
+
3
+ export type SqliteValue = string | number | null
4
+
5
+ type SqliteRunListTable =
6
+ | "agent_runs"
7
+ | "pipeline_runs"
8
+ | "pipeline_step_runs"
9
+ | "sync_runs"
10
+ | "webhook_runs"
11
+ | "workflow_runs"
12
+ | "workflow_node_runs"
13
+ | "workflow_agent_node_runs"
14
+
15
+ export function hasEmptyStatuses(input: { readonly statuses?: readonly unknown[] }): boolean {
16
+ return input.statuses !== undefined && input.statuses.length === 0
17
+ }
18
+
19
+ type StartedAtExpression = "started_at" | "COALESCE(started_at, created_at)"
20
+
21
+ export function appendRunListFilters(
22
+ whereClauses: string[],
23
+ args: SqliteValue[],
24
+ input: {
25
+ readonly statuses?: readonly string[]
26
+ readonly startedAfter?: Date
27
+ readonly startedBefore?: Date
28
+ },
29
+ startedAtExpression: StartedAtExpression = "started_at"
30
+ ): void {
31
+ if (input.statuses) {
32
+ if (input.statuses.length === 0) {
33
+ whereClauses.push("1 = 0")
34
+ } else {
35
+ whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
36
+ args.push(...input.statuses)
37
+ }
38
+ }
39
+
40
+ if (input.startedAfter) {
41
+ whereClauses.push(`${startedAtExpression} >= ?`)
42
+ args.push(input.startedAfter.toISOString())
43
+ }
44
+
45
+ if (input.startedBefore) {
46
+ whereClauses.push(`${startedAtExpression} <= ?`)
47
+ args.push(input.startedBefore.toISOString())
48
+ }
49
+ }
50
+
51
+ export function queryRunList<TRow>(input: {
52
+ readonly db: Database
53
+ readonly tableName: SqliteRunListTable
54
+ readonly whereClauses: readonly string[]
55
+ readonly args: readonly SqliteValue[]
56
+ readonly order?: "asc" | "desc"
57
+ readonly limit?: number
58
+ readonly offset?: number
59
+ }): { readonly rows: readonly TRow[]; readonly total: number; readonly hasMore: boolean } {
60
+ const where = `WHERE ${input.whereClauses.join(" AND ")}`
61
+ const order = input.order === "asc" ? "ASC" : "DESC"
62
+ const offset = input.offset ?? 0
63
+
64
+ const totalRow = input.db
65
+ .query(`SELECT COUNT(*) AS count FROM ${input.tableName} ${where}`)
66
+ .get(...input.args) as { count: number }
67
+
68
+ const orderColumn =
69
+ input.tableName === "agent_runs"
70
+ ? "COALESCE(started_at, created_at)"
71
+ : input.tableName === "workflow_agent_node_runs"
72
+ ? "created_at"
73
+ : "started_at"
74
+ const idColumn = input.tableName === "workflow_agent_node_runs" ? "node_run_id" : "id"
75
+ let query = `
76
+ SELECT * FROM ${input.tableName}
77
+ ${where}
78
+ ORDER BY ${orderColumn} ${order}, ${idColumn} ${order}
79
+ `
80
+ const queryArgs = [...input.args]
81
+
82
+ if (input.limit !== undefined) {
83
+ query += " LIMIT ? OFFSET ?"
84
+ queryArgs.push(input.limit, offset)
85
+ } else if (offset > 0) {
86
+ query += " LIMIT -1 OFFSET ?"
87
+ queryArgs.push(offset)
88
+ }
89
+
90
+ const rows = input.db.query(query).all(...queryArgs) as TRow[]
91
+ const total = totalRow.count
92
+
93
+ return {
94
+ rows,
95
+ total,
96
+ hasMore: offset + rows.length < total,
97
+ }
98
+ }
package/src/sql.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module "*.sql" {
2
+ const sql: string
3
+ export default sql
4
+ }
@@ -0,0 +1,3 @@
1
+ export function isUniqueConstraintError(error: unknown): boolean {
2
+ return error instanceof Error && error.message.includes("UNIQUE constraint failed")
3
+ }