@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,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
|
+
}
|