@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.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/action-run-storage.d.ts +29 -0
- package/dist/action-run-storage.d.ts.map +1 -0
- package/dist/agents/agent-storage.d.ts +25 -0
- package/dist/agents/agent-storage.d.ts.map +1 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/messages.d.ts +14 -0
- package/dist/agents/messages.d.ts.map +1 -0
- package/dist/agents/rows.d.ts +77 -0
- package/dist/agents/rows.d.ts.map +1 -0
- package/dist/agents/runs.d.ts +26 -0
- package/dist/agents/runs.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +14 -0
- package/dist/agents/threads.d.ts.map +1 -0
- package/dist/auth-storage/access-tokens.d.ts +33 -0
- package/dist/auth-storage/access-tokens.d.ts.map +1 -0
- package/dist/auth-storage/auth-storage.d.ts +46 -0
- package/dist/auth-storage/auth-storage.d.ts.map +1 -0
- package/dist/auth-storage/group-memberships.d.ts +21 -0
- package/dist/auth-storage/group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/identities.d.ts +17 -0
- package/dist/auth-storage/identities.d.ts.map +1 -0
- package/dist/auth-storage/index.d.ts +3 -0
- package/dist/auth-storage/index.d.ts.map +1 -0
- package/dist/auth-storage/invitations.d.ts +28 -0
- package/dist/auth-storage/invitations.d.ts.map +1 -0
- package/dist/auth-storage/magic-links.d.ts +28 -0
- package/dist/auth-storage/magic-links.d.ts.map +1 -0
- package/dist/auth-storage/oidc-attempts.d.ts +18 -0
- package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
- package/dist/auth-storage/rows.d.ts +134 -0
- package/dist/auth-storage/rows.d.ts.map +1 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts +14 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -0
- package/dist/auth-storage/sessions.d.ts +55 -0
- package/dist/auth-storage/sessions.d.ts.map +1 -0
- package/dist/auth-storage/shared.d.ts +131 -0
- package/dist/auth-storage/shared.d.ts.map +1 -0
- package/dist/auth-storage/users.d.ts +19 -0
- package/dist/auth-storage/users.d.ts.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10204 -0
- package/dist/index.js.map +54 -0
- package/dist/latest-run-query.d.ts +22 -0
- package/dist/latest-run-query.d.ts.map +1 -0
- package/dist/migrations.d.ts +18 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/object-query-compiler.d.ts +44 -0
- package/dist/object-query-compiler.d.ts.map +1 -0
- package/dist/object-storage.d.ts +93 -0
- package/dist/object-storage.d.ts.map +1 -0
- package/dist/ontology-storage/commits.d.ts +14 -0
- package/dist/ontology-storage/commits.d.ts.map +1 -0
- package/dist/ontology-storage/index.d.ts +22 -0
- package/dist/ontology-storage/index.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-session.d.ts +49 -0
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-state.d.ts +63 -0
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-writer.d.ts +15 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
- package/dist/ontology-storage/materializations.d.ts +32 -0
- package/dist/ontology-storage/materializations.d.ts.map +1 -0
- package/dist/ontology-storage/outbox.d.ts +14 -0
- package/dist/ontology-storage/outbox.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +91 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts +26 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -0
- package/dist/pipeline-run-storage.d.ts +26 -0
- package/dist/pipeline-run-storage.d.ts.map +1 -0
- package/dist/projection-run-storage.d.ts +32 -0
- package/dist/projection-run-storage.d.ts.map +1 -0
- package/dist/rules-storage.d.ts +33 -0
- package/dist/rules-storage.d.ts.map +1 -0
- package/dist/run-list-query.d.ts +27 -0
- package/dist/run-list-query.d.ts.map +1 -0
- package/dist/storage-errors.d.ts +2 -0
- package/dist/storage-errors.d.ts.map +1 -0
- package/dist/sync-run-storage.d.ts +23 -0
- package/dist/sync-run-storage.d.ts.map +1 -0
- package/dist/timeseries-storage.d.ts +41 -0
- package/dist/timeseries-storage.d.ts.map +1 -0
- package/dist/transactions.d.ts +15 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/tsconfig.json +5 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/webhook-delivery-storage.d.ts +27 -0
- package/dist/webhook-delivery-storage.d.ts.map +1 -0
- package/dist/webhook-run-storage.d.ts +22 -0
- package/dist/webhook-run-storage.d.ts.map +1 -0
- package/dist/workflow-intervention-storage.d.ts +26 -0
- package/dist/workflow-intervention-storage.d.ts.map +1 -0
- package/dist/workflow-run-storage.d.ts +63 -0
- package/dist/workflow-run-storage.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/action-run-storage.ts +761 -0
- package/src/agents/agent-storage.ts +46 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/messages.ts +162 -0
- package/src/agents/rows.ts +205 -0
- package/src/agents/runs.ts +389 -0
- package/src/agents/threads.ts +133 -0
- package/src/auth-storage/access-tokens.ts +289 -0
- package/src/auth-storage/auth-storage.ts +784 -0
- package/src/auth-storage/group-memberships.ts +52 -0
- package/src/auth-storage/identities.ts +98 -0
- package/src/auth-storage/index.ts +2 -0
- package/src/auth-storage/invitations.ts +258 -0
- package/src/auth-storage/magic-links.ts +149 -0
- package/src/auth-storage/oidc-attempts.ts +109 -0
- package/src/auth-storage/rows.ts +338 -0
- package/src/auth-storage/service-account-group-memberships.ts +161 -0
- package/src/auth-storage/service-accounts.ts +168 -0
- package/src/auth-storage/sessions.ts +230 -0
- package/src/auth-storage/shared.ts +707 -0
- package/src/auth-storage/users.ts +226 -0
- package/src/index.ts +292 -0
- package/src/latest-run-query.ts +49 -0
- package/src/migrations/001-initial-schema.sql +1068 -0
- package/src/migrations.ts +245 -0
- package/src/object-query-compiler.ts +1198 -0
- package/src/object-storage.ts +605 -0
- package/src/ontology-storage/commits.ts +127 -0
- package/src/ontology-storage/index.ts +35 -0
- package/src/ontology-storage/materialization-session.ts +393 -0
- package/src/ontology-storage/materialization-state.ts +901 -0
- package/src/ontology-storage/materialization-writer.ts +394 -0
- package/src/ontology-storage/materializations.ts +838 -0
- package/src/ontology-storage/outbox.ts +237 -0
- package/src/ontology-storage/shared.ts +269 -0
- package/src/ontology-storage/sources.ts +653 -0
- package/src/pipeline-run-storage.ts +506 -0
- package/src/projection-run-storage.ts +592 -0
- package/src/rules-storage.ts +375 -0
- package/src/run-list-query.ts +98 -0
- package/src/sql.d.ts +4 -0
- package/src/storage-errors.ts +3 -0
- package/src/sync-run-storage.ts +299 -0
- package/src/timeseries-storage.ts +212 -0
- package/src/transactions.ts +96 -0
- package/src/validation.ts +30 -0
- package/src/webhook-delivery-storage.ts +272 -0
- package/src/webhook-run-storage.ts +240 -0
- package/src/workflow-intervention-storage.ts +398 -0
- 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