@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,592 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { randomUUID } from "node:crypto"
|
|
3
|
+
import {
|
|
4
|
+
advanceProjectionTelemetry,
|
|
5
|
+
assertGenericProgressDoesNotAdvanceTelemetry,
|
|
6
|
+
assertProjectionMissingTarget,
|
|
7
|
+
assertProjectionRunExecution,
|
|
8
|
+
assertProjectionRunListWindow,
|
|
9
|
+
assertProjectionRunNonEmpty,
|
|
10
|
+
assertProjectionRunRunning,
|
|
11
|
+
assertProjectionRunStartInput,
|
|
12
|
+
createProjectionRunClaim,
|
|
13
|
+
immutableDatasetVersionConflict,
|
|
14
|
+
mergeProjectionRunProgress,
|
|
15
|
+
type PersistedProjectionRunRecord,
|
|
16
|
+
planProjectionRunFinish,
|
|
17
|
+
planProjectionRunReclaim,
|
|
18
|
+
projectionRunNotFound,
|
|
19
|
+
publicProjectionRunRecord,
|
|
20
|
+
requireTelemetryProjectionRun,
|
|
21
|
+
restoreProjectionRun,
|
|
22
|
+
type StoredProjectionRunRecord,
|
|
23
|
+
staleProjectionRunExecution,
|
|
24
|
+
} from "@sixb/core/internal/projection-run-storage-provider"
|
|
25
|
+
import type {
|
|
26
|
+
AdvanceProjectionTelemetryCheckpointInput,
|
|
27
|
+
FinishProjectionRunInput,
|
|
28
|
+
ListLatestProjectionRunsInput,
|
|
29
|
+
ListLatestProjectionRunsResult,
|
|
30
|
+
ListProjectionRunsInput,
|
|
31
|
+
ListProjectionRunsResult,
|
|
32
|
+
LockProjectionRunForMaterializationInput,
|
|
33
|
+
ProjectionKind,
|
|
34
|
+
ProjectionRunClaim,
|
|
35
|
+
ProjectionRunProgress,
|
|
36
|
+
ProjectionRunRecord,
|
|
37
|
+
ProjectionRunStatus,
|
|
38
|
+
ProjectionRunStorage,
|
|
39
|
+
RecordProjectionMissingTargetInput,
|
|
40
|
+
StartOrReclaimProjectionRunInput,
|
|
41
|
+
TelemetryProjectionRunRecord,
|
|
42
|
+
UpdateProjectionRunInput,
|
|
43
|
+
} from "@sixb/core/storage"
|
|
44
|
+
import { ProjectionRunError } from "@sixb/core/storage"
|
|
45
|
+
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
46
|
+
import { installFreshSqliteSchema } from "./migrations"
|
|
47
|
+
import {
|
|
48
|
+
closeSqliteStoreConnection,
|
|
49
|
+
openSqliteStoreConnection,
|
|
50
|
+
runImmediateTransaction,
|
|
51
|
+
type SqliteStoreConnection,
|
|
52
|
+
} from "./transactions"
|
|
53
|
+
|
|
54
|
+
export interface SqliteProjectionRunStorageOptions {
|
|
55
|
+
/** Path to SQLite database file. Defaults to ':memory:' for in-memory database. */
|
|
56
|
+
path?: string
|
|
57
|
+
/** Internal shared connection used by bundled SqliteStorage. */
|
|
58
|
+
connection?: SqliteStoreConnection
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
62
|
+
private readonly connection: SqliteStoreConnection
|
|
63
|
+
private readonly db: Database
|
|
64
|
+
|
|
65
|
+
constructor(options: SqliteProjectionRunStorageOptions = {}) {
|
|
66
|
+
this.connection = openSqliteStoreConnection(options)
|
|
67
|
+
this.db = this.connection.db
|
|
68
|
+
|
|
69
|
+
if (this.connection.installFreshSchema) {
|
|
70
|
+
installFreshSqliteSchema(this.db)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async startOrReclaim(input: StartOrReclaimProjectionRunInput): Promise<ProjectionRunClaim> {
|
|
75
|
+
return runImmediateTransaction(this.db, () => {
|
|
76
|
+
assertProjectionRunStartInput(input)
|
|
77
|
+
|
|
78
|
+
const metadataConflict = this.db
|
|
79
|
+
.query(
|
|
80
|
+
`
|
|
81
|
+
SELECT 1 FROM projection_runs
|
|
82
|
+
WHERE project_id = ? AND id <> ? AND dataset_id = ? AND dataset_version_id = ?
|
|
83
|
+
AND dataset_version_created_at IS NOT NULL
|
|
84
|
+
AND dataset_version_created_at <> ?
|
|
85
|
+
LIMIT 1
|
|
86
|
+
`
|
|
87
|
+
)
|
|
88
|
+
.get(
|
|
89
|
+
input.projectId,
|
|
90
|
+
input.id,
|
|
91
|
+
input.identity.datasetVersion.datasetId,
|
|
92
|
+
input.identity.datasetVersion.versionId,
|
|
93
|
+
input.identity.datasetVersion.createdAt
|
|
94
|
+
)
|
|
95
|
+
if (metadataConflict) {
|
|
96
|
+
throw immutableDatasetVersionConflict(input.identity)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const existing = this.findRow(input.projectId, input.id)
|
|
100
|
+
let attempt = 1
|
|
101
|
+
if (existing) {
|
|
102
|
+
const existingRecord = restoreProjectionRunRow(existing)
|
|
103
|
+
attempt = planProjectionRunReclaim(existingRecord, input).attempt
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const executionToken = createFreshExecutionToken(existing?.execution_token ?? null)
|
|
107
|
+
|
|
108
|
+
if (existing) {
|
|
109
|
+
const result = this.db
|
|
110
|
+
.query(
|
|
111
|
+
`
|
|
112
|
+
UPDATE projection_runs
|
|
113
|
+
SET attempt = ?, execution_token = ?
|
|
114
|
+
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
115
|
+
`
|
|
116
|
+
)
|
|
117
|
+
.run(attempt, executionToken, input.projectId, input.id, existing.execution_token)
|
|
118
|
+
if (result.changes !== 1) throw staleProjectionRunExecution(input.id)
|
|
119
|
+
} else {
|
|
120
|
+
try {
|
|
121
|
+
this.db
|
|
122
|
+
.query(
|
|
123
|
+
`
|
|
124
|
+
INSERT INTO projection_runs (
|
|
125
|
+
project_id,
|
|
126
|
+
id,
|
|
127
|
+
projection_id,
|
|
128
|
+
projection_kind,
|
|
129
|
+
materialization_protocol,
|
|
130
|
+
dataset_id,
|
|
131
|
+
dataset_version_id,
|
|
132
|
+
dataset_version_created_at,
|
|
133
|
+
ontology_revision,
|
|
134
|
+
projection_revision,
|
|
135
|
+
ownership_hash,
|
|
136
|
+
object_type_id,
|
|
137
|
+
source_object_type_id,
|
|
138
|
+
target_object_type_id,
|
|
139
|
+
status,
|
|
140
|
+
started_at,
|
|
141
|
+
attempt,
|
|
142
|
+
execution_token,
|
|
143
|
+
fixed_batch_size,
|
|
144
|
+
next_batch_ordinal,
|
|
145
|
+
next_row_offset,
|
|
146
|
+
input_exhausted,
|
|
147
|
+
source_rows_read,
|
|
148
|
+
source_rows_skipped
|
|
149
|
+
) VALUES (
|
|
150
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, 1, ?, ?, ?, ?, ?,
|
|
151
|
+
0, 0
|
|
152
|
+
)
|
|
153
|
+
`
|
|
154
|
+
)
|
|
155
|
+
.run(
|
|
156
|
+
input.projectId,
|
|
157
|
+
input.id,
|
|
158
|
+
input.identity.projectionId,
|
|
159
|
+
input.identity.projectionKind,
|
|
160
|
+
input.identity.protocol,
|
|
161
|
+
input.identity.datasetVersion.datasetId,
|
|
162
|
+
input.identity.datasetVersion.versionId,
|
|
163
|
+
input.identity.datasetVersion.createdAt,
|
|
164
|
+
input.identity.ontologyRevision,
|
|
165
|
+
input.identity.projectionRevision,
|
|
166
|
+
input.identity.ownershipHash,
|
|
167
|
+
"objectTypeId" in input.target ? input.target.objectTypeId : null,
|
|
168
|
+
"sourceObjectTypeId" in input.target ? input.target.sourceObjectTypeId : null,
|
|
169
|
+
"sourceObjectTypeId" in input.target ? input.target.targetObjectTypeId : null,
|
|
170
|
+
(input.startedAt ?? new Date()).toISOString(),
|
|
171
|
+
executionToken,
|
|
172
|
+
input.fixedBatchSize ?? null,
|
|
173
|
+
input.fixedBatchSize === undefined ? null : 0,
|
|
174
|
+
input.fixedBatchSize === undefined ? null : 0,
|
|
175
|
+
input.fixedBatchSize === undefined ? null : 0
|
|
176
|
+
)
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (isUniqueConstraintError(error)) {
|
|
179
|
+
throw new ProjectionRunError(
|
|
180
|
+
`[SixbSqlite] Projection run '${input.id}' already exists for project '${input.projectId}'.`
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
throw error
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return projectionRunClaim(this.requireRow(input.projectId, input.id))
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async lockForMaterialization(
|
|
192
|
+
input: LockProjectionRunForMaterializationInput
|
|
193
|
+
): Promise<ProjectionRunRecord> {
|
|
194
|
+
return rowToProjectionRunRecord(this.requireMaterializationExecution(input))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async update(input: UpdateProjectionRunInput): Promise<ProjectionRunRecord> {
|
|
198
|
+
return runImmediateTransaction(this.db, () => {
|
|
199
|
+
const existingRow = this.requireMaterializationExecution(input)
|
|
200
|
+
const existing = restoreProjectionRunRow(existingRow)
|
|
201
|
+
assertGenericProgressDoesNotAdvanceTelemetry(existing, input.progress)
|
|
202
|
+
const progress = mergeProjectionRunProgress(existing.progress, input.progress)
|
|
203
|
+
const result = this.updateProgress(existingRow, progress, input.executionToken)
|
|
204
|
+
if (result !== 1) throw staleProjectionRunExecution(input.id)
|
|
205
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async finish(input: FinishProjectionRunInput): Promise<ProjectionRunRecord> {
|
|
210
|
+
return runImmediateTransaction(this.db, () => {
|
|
211
|
+
const existingRow = this.requireMaterializationExecution(input)
|
|
212
|
+
const plan = planProjectionRunFinish(restoreProjectionRunRow(existingRow), input)
|
|
213
|
+
const result = this.db
|
|
214
|
+
.query(
|
|
215
|
+
`
|
|
216
|
+
UPDATE projection_runs
|
|
217
|
+
SET
|
|
218
|
+
status = ?,
|
|
219
|
+
finished_at = ?,
|
|
220
|
+
execution_token = NULL,
|
|
221
|
+
source_rows_read = ?,
|
|
222
|
+
source_rows_skipped = ?,
|
|
223
|
+
input_exhausted = ?,
|
|
224
|
+
error_message = ?
|
|
225
|
+
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
226
|
+
`
|
|
227
|
+
)
|
|
228
|
+
.run(
|
|
229
|
+
input.status,
|
|
230
|
+
plan.finishedAt.toISOString(),
|
|
231
|
+
plan.progress.sourceRowsRead,
|
|
232
|
+
plan.progress.sourceRowsSkipped,
|
|
233
|
+
plan.inputExhausted ? 1 : existingRow.input_exhausted,
|
|
234
|
+
plan.errorMessage ?? null,
|
|
235
|
+
input.projectId,
|
|
236
|
+
input.id,
|
|
237
|
+
input.executionToken
|
|
238
|
+
)
|
|
239
|
+
if (result.changes !== 1) throw staleProjectionRunExecution(input.id)
|
|
240
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async advanceTelemetryCheckpoint(
|
|
245
|
+
input: AdvanceProjectionTelemetryCheckpointInput
|
|
246
|
+
): Promise<TelemetryProjectionRunRecord> {
|
|
247
|
+
return runImmediateTransaction(this.db, () => {
|
|
248
|
+
const existing = this.requireMaterializationExecution(input)
|
|
249
|
+
const advance = advanceProjectionTelemetry(restoreProjectionRunRow(existing), input)
|
|
250
|
+
const result = this.db
|
|
251
|
+
.query(
|
|
252
|
+
`
|
|
253
|
+
UPDATE projection_runs
|
|
254
|
+
SET
|
|
255
|
+
next_batch_ordinal = ?,
|
|
256
|
+
next_row_offset = ?,
|
|
257
|
+
input_exhausted = ?,
|
|
258
|
+
source_rows_read = ?,
|
|
259
|
+
source_rows_skipped = ?,
|
|
260
|
+
missing_target_object_type_id = NULL,
|
|
261
|
+
missing_target_object_id = NULL,
|
|
262
|
+
missing_target_batch_ordinal = NULL,
|
|
263
|
+
missing_target_first_seen_at = NULL
|
|
264
|
+
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
265
|
+
AND next_batch_ordinal = ? AND input_exhausted = 0
|
|
266
|
+
`
|
|
267
|
+
)
|
|
268
|
+
.run(
|
|
269
|
+
advance.checkpoint.nextBatchOrdinal,
|
|
270
|
+
advance.checkpoint.nextRowOffset,
|
|
271
|
+
advance.checkpoint.inputExhausted ? 1 : 0,
|
|
272
|
+
advance.progress.sourceRowsRead,
|
|
273
|
+
advance.progress.sourceRowsSkipped,
|
|
274
|
+
input.projectId,
|
|
275
|
+
input.id,
|
|
276
|
+
input.executionToken,
|
|
277
|
+
input.batchOrdinal
|
|
278
|
+
)
|
|
279
|
+
if (result.changes !== 1) throw staleProjectionRunExecution(input.id)
|
|
280
|
+
return rowToTelemetryProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async recordMissingTarget(
|
|
285
|
+
input: RecordProjectionMissingTargetInput
|
|
286
|
+
): Promise<TelemetryProjectionRunRecord> {
|
|
287
|
+
return runImmediateTransaction(this.db, () => {
|
|
288
|
+
const existing = this.requireMaterializationExecution(input)
|
|
289
|
+
const missingTarget = assertProjectionMissingTarget(
|
|
290
|
+
requireTelemetryProjectionRun(restoreProjectionRunRow(existing)),
|
|
291
|
+
input.missingTarget
|
|
292
|
+
)
|
|
293
|
+
const result = this.db
|
|
294
|
+
.query(
|
|
295
|
+
`
|
|
296
|
+
UPDATE projection_runs
|
|
297
|
+
SET
|
|
298
|
+
missing_target_object_type_id = ?,
|
|
299
|
+
missing_target_object_id = ?,
|
|
300
|
+
missing_target_batch_ordinal = ?,
|
|
301
|
+
missing_target_first_seen_at = ?
|
|
302
|
+
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
303
|
+
AND next_batch_ordinal = ?
|
|
304
|
+
`
|
|
305
|
+
)
|
|
306
|
+
.run(
|
|
307
|
+
missingTarget.objectTypeId,
|
|
308
|
+
missingTarget.objectId,
|
|
309
|
+
missingTarget.batchOrdinal,
|
|
310
|
+
missingTarget.firstSeenAt.toISOString(),
|
|
311
|
+
input.projectId,
|
|
312
|
+
input.id,
|
|
313
|
+
input.executionToken,
|
|
314
|
+
missingTarget.batchOrdinal
|
|
315
|
+
)
|
|
316
|
+
if (result.changes !== 1) throw staleProjectionRunExecution(input.id)
|
|
317
|
+
return rowToTelemetryProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async getById(params: { projectId: string; id: string }): Promise<ProjectionRunRecord | null> {
|
|
322
|
+
const row = this.findRow(params.projectId, params.id)
|
|
323
|
+
return row ? rowToProjectionRunRecord(row) : null
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async list(input: ListProjectionRunsInput): Promise<ListProjectionRunsResult> {
|
|
327
|
+
assertProjectionRunNonEmpty(input.projectId, "projectId")
|
|
328
|
+
|
|
329
|
+
if (input.statuses && input.statuses.length === 0) {
|
|
330
|
+
return { runs: [], hasMore: false, total: 0 }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const whereClauses = ["project_id = ?"]
|
|
334
|
+
const args: (string | number)[] = [input.projectId]
|
|
335
|
+
|
|
336
|
+
if (input.projectionId) {
|
|
337
|
+
whereClauses.push("projection_id = ?")
|
|
338
|
+
args.push(input.projectionId)
|
|
339
|
+
}
|
|
340
|
+
if (input.projectionKind) {
|
|
341
|
+
whereClauses.push("projection_kind = ?")
|
|
342
|
+
args.push(input.projectionKind)
|
|
343
|
+
}
|
|
344
|
+
if (input.datasetId) {
|
|
345
|
+
whereClauses.push("dataset_id = ?")
|
|
346
|
+
args.push(input.datasetId)
|
|
347
|
+
}
|
|
348
|
+
if (input.datasetVersionId) {
|
|
349
|
+
whereClauses.push("dataset_version_id = ?")
|
|
350
|
+
args.push(input.datasetVersionId)
|
|
351
|
+
}
|
|
352
|
+
if (input.objectTypeIds) {
|
|
353
|
+
if (input.objectTypeIds.length === 0) {
|
|
354
|
+
return { runs: [], hasMore: false, total: 0 }
|
|
355
|
+
}
|
|
356
|
+
const placeholders = input.objectTypeIds.map(() => "?").join(", ")
|
|
357
|
+
whereClauses.push(
|
|
358
|
+
`(object_type_id IN (${placeholders})` +
|
|
359
|
+
` OR (source_object_type_id IN (${placeholders})` +
|
|
360
|
+
` AND target_object_type_id IN (${placeholders})))`
|
|
361
|
+
)
|
|
362
|
+
args.push(...input.objectTypeIds, ...input.objectTypeIds, ...input.objectTypeIds)
|
|
363
|
+
}
|
|
364
|
+
if (input.statuses) {
|
|
365
|
+
whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
|
|
366
|
+
args.push(...input.statuses)
|
|
367
|
+
}
|
|
368
|
+
if (input.startedAfter) {
|
|
369
|
+
whereClauses.push("started_at >= ?")
|
|
370
|
+
args.push(input.startedAfter.toISOString())
|
|
371
|
+
}
|
|
372
|
+
if (input.startedBefore) {
|
|
373
|
+
whereClauses.push("started_at <= ?")
|
|
374
|
+
args.push(input.startedBefore.toISOString())
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const where = `WHERE ${whereClauses.join(" AND ")}`
|
|
378
|
+
const order = input.order === "asc" ? "ASC" : "DESC"
|
|
379
|
+
const offset = input.offset ?? 0
|
|
380
|
+
const limit = input.limit
|
|
381
|
+
|
|
382
|
+
const totalRow = this.db
|
|
383
|
+
.query(`SELECT COUNT(*) AS count FROM projection_runs ${where}`)
|
|
384
|
+
.get(...args) as { count: number }
|
|
385
|
+
|
|
386
|
+
let query = `
|
|
387
|
+
SELECT * FROM projection_runs
|
|
388
|
+
${where}
|
|
389
|
+
ORDER BY started_at ${order}, id ${order}
|
|
390
|
+
`
|
|
391
|
+
const queryArgs = [...args]
|
|
392
|
+
|
|
393
|
+
if (limit !== undefined) {
|
|
394
|
+
assertProjectionRunListWindow(limit, "limit")
|
|
395
|
+
assertProjectionRunListWindow(offset, "offset")
|
|
396
|
+
query += " LIMIT ? OFFSET ?"
|
|
397
|
+
queryArgs.push(limit, offset)
|
|
398
|
+
} else if (offset > 0) {
|
|
399
|
+
assertProjectionRunListWindow(offset, "offset")
|
|
400
|
+
query += " LIMIT -1 OFFSET ?"
|
|
401
|
+
queryArgs.push(offset)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const rows = this.db.query(query).all(...queryArgs) as DatabaseRow[]
|
|
405
|
+
const runs = rows.map(rowToProjectionRunRecord)
|
|
406
|
+
return { runs, hasMore: offset + runs.length < totalRow.count, total: totalRow.count }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async listLatestByProjectionIds(
|
|
410
|
+
input: ListLatestProjectionRunsInput
|
|
411
|
+
): Promise<ListLatestProjectionRunsResult> {
|
|
412
|
+
const rows = queryLatestRunsByOwnerId<DatabaseRow>({
|
|
413
|
+
db: this.db,
|
|
414
|
+
tableName: "projection_runs",
|
|
415
|
+
ownerColumn: "projection_id",
|
|
416
|
+
ownerIds: input.projectionIds,
|
|
417
|
+
projectId: input.projectId,
|
|
418
|
+
ownerIdFor: (row) => row.projection_id,
|
|
419
|
+
})
|
|
420
|
+
return { runs: rows.map(rowToProjectionRunRecord) }
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
close(): void {
|
|
424
|
+
closeSqliteStoreConnection(this.connection)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private updateProgress(
|
|
428
|
+
existing: DatabaseRow,
|
|
429
|
+
progress: ProjectionRunProgress,
|
|
430
|
+
executionToken: string
|
|
431
|
+
): number {
|
|
432
|
+
const args: (string | number)[] = [
|
|
433
|
+
progress.sourceRowsRead,
|
|
434
|
+
progress.sourceRowsSkipped,
|
|
435
|
+
existing.project_id,
|
|
436
|
+
existing.id,
|
|
437
|
+
executionToken,
|
|
438
|
+
]
|
|
439
|
+
const result = this.db
|
|
440
|
+
.query(
|
|
441
|
+
`
|
|
442
|
+
UPDATE projection_runs
|
|
443
|
+
SET
|
|
444
|
+
source_rows_read = ?,
|
|
445
|
+
source_rows_skipped = ?
|
|
446
|
+
WHERE project_id = ? AND id = ? AND status = 'running'
|
|
447
|
+
AND execution_token = ?
|
|
448
|
+
`
|
|
449
|
+
)
|
|
450
|
+
.run(...args)
|
|
451
|
+
return result.changes
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private requireMaterializationExecution(
|
|
455
|
+
input: LockProjectionRunForMaterializationInput
|
|
456
|
+
): DatabaseRow {
|
|
457
|
+
const row = this.requireRunning(input.projectId, input.id)
|
|
458
|
+
assertProjectionRunExecution(restoreProjectionRunRow(row), input)
|
|
459
|
+
return row
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private requireRunning(projectId: string, id: string): DatabaseRow {
|
|
463
|
+
const row = this.requireRow(projectId, id)
|
|
464
|
+
assertProjectionRunRunning(restoreProjectionRunRow(row))
|
|
465
|
+
return row
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private findRow(projectId: string, id: string): DatabaseRow | null {
|
|
469
|
+
return this.db
|
|
470
|
+
.query("SELECT * FROM projection_runs WHERE project_id = ? AND id = ?")
|
|
471
|
+
.get(projectId, id) as DatabaseRow | null
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private requireRow(projectId: string, id: string): DatabaseRow {
|
|
475
|
+
assertProjectionRunNonEmpty(projectId, "projectId")
|
|
476
|
+
assertProjectionRunNonEmpty(id, "id")
|
|
477
|
+
const row = this.findRow(projectId, id)
|
|
478
|
+
if (!row) throw projectionRunNotFound(projectId, id)
|
|
479
|
+
return row
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function createFreshExecutionToken(previous: string | null): string {
|
|
484
|
+
let token = randomUUID()
|
|
485
|
+
while (token === previous) token = randomUUID()
|
|
486
|
+
return token
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function rowToProjectionRunRecord(row: DatabaseRow): ProjectionRunRecord {
|
|
490
|
+
return publicProjectionRunRecord(restoreProjectionRunRow(row))
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function restoreProjectionRunRow(row: DatabaseRow): StoredProjectionRunRecord {
|
|
494
|
+
const persisted: PersistedProjectionRunRecord = {
|
|
495
|
+
id: row.id,
|
|
496
|
+
projectId: row.project_id,
|
|
497
|
+
projectionId: row.projection_id,
|
|
498
|
+
projectionKind: row.projection_kind,
|
|
499
|
+
protocol: row.materialization_protocol ?? undefined,
|
|
500
|
+
datasetId: row.dataset_id,
|
|
501
|
+
datasetVersionId: row.dataset_version_id,
|
|
502
|
+
datasetVersionCreatedAt: row.dataset_version_created_at ?? undefined,
|
|
503
|
+
ontologyRevision: row.ontology_revision ?? undefined,
|
|
504
|
+
projectionRevision: row.projection_revision ?? undefined,
|
|
505
|
+
ownershipHash: row.ownership_hash ?? undefined,
|
|
506
|
+
objectTypeId: row.object_type_id ?? undefined,
|
|
507
|
+
sourceObjectTypeId: row.source_object_type_id ?? undefined,
|
|
508
|
+
targetObjectTypeId: row.target_object_type_id ?? undefined,
|
|
509
|
+
status: row.status,
|
|
510
|
+
startedAt: new Date(row.started_at),
|
|
511
|
+
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
512
|
+
attempt: databaseSafeInteger(row.attempt, "attempt"),
|
|
513
|
+
executionToken: row.execution_token ?? undefined,
|
|
514
|
+
fixedBatchSize: optionalDatabaseSafeInteger(row.fixed_batch_size, "fixedBatchSize"),
|
|
515
|
+
nextBatchOrdinal: optionalDatabaseSafeInteger(row.next_batch_ordinal, "nextBatchOrdinal"),
|
|
516
|
+
nextRowOffset: optionalDatabaseSafeInteger(row.next_row_offset, "nextRowOffset"),
|
|
517
|
+
inputExhausted: row.input_exhausted === null ? undefined : row.input_exhausted === 1,
|
|
518
|
+
missingTargetObjectTypeId: row.missing_target_object_type_id ?? undefined,
|
|
519
|
+
missingTargetObjectId: row.missing_target_object_id ?? undefined,
|
|
520
|
+
missingTargetBatchOrdinal: optionalDatabaseSafeInteger(
|
|
521
|
+
row.missing_target_batch_ordinal,
|
|
522
|
+
"missingTargetBatchOrdinal"
|
|
523
|
+
),
|
|
524
|
+
missingTargetFirstSeenAt: row.missing_target_first_seen_at
|
|
525
|
+
? new Date(row.missing_target_first_seen_at)
|
|
526
|
+
: undefined,
|
|
527
|
+
progress: {
|
|
528
|
+
sourceRowsRead: databaseSafeInteger(row.source_rows_read, "sourceRowsRead"),
|
|
529
|
+
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped"),
|
|
530
|
+
},
|
|
531
|
+
errorMessage: row.error_message ?? undefined,
|
|
532
|
+
}
|
|
533
|
+
return restoreProjectionRun(persisted)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function projectionRunClaim(row: DatabaseRow): ProjectionRunClaim {
|
|
537
|
+
return createProjectionRunClaim(restoreProjectionRunRow(row))
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function rowToTelemetryProjectionRunRecord(row: DatabaseRow): TelemetryProjectionRunRecord {
|
|
541
|
+
return requireTelemetryProjectionRun(rowToProjectionRunRecord(row))
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function optionalDatabaseSafeInteger(value: number | null, fieldName: string): number | undefined {
|
|
545
|
+
return value === null ? undefined : databaseSafeInteger(value, fieldName)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function databaseSafeInteger(value: number, fieldName: string): number {
|
|
549
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
550
|
+
throw new ProjectionRunError(
|
|
551
|
+
`[SixbSqlite] Projection run persisted ${fieldName} is not a non-negative safe integer.`
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
return value
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function isUniqueConstraintError(error: unknown): boolean {
|
|
558
|
+
return error instanceof Error && error.message.includes("UNIQUE constraint failed")
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
interface DatabaseRow {
|
|
562
|
+
project_id: string
|
|
563
|
+
id: string
|
|
564
|
+
projection_id: string
|
|
565
|
+
projection_kind: ProjectionKind
|
|
566
|
+
materialization_protocol: "replacement" | "telemetry" | null
|
|
567
|
+
dataset_id: string
|
|
568
|
+
dataset_version_id: string
|
|
569
|
+
dataset_version_created_at: string | null
|
|
570
|
+
ontology_revision: string | null
|
|
571
|
+
projection_revision: string | null
|
|
572
|
+
ownership_hash: string | null
|
|
573
|
+
object_type_id: string | null
|
|
574
|
+
source_object_type_id: string | null
|
|
575
|
+
target_object_type_id: string | null
|
|
576
|
+
status: ProjectionRunStatus
|
|
577
|
+
started_at: string
|
|
578
|
+
finished_at: string | null
|
|
579
|
+
attempt: number
|
|
580
|
+
execution_token: string | null
|
|
581
|
+
fixed_batch_size: number | null
|
|
582
|
+
next_batch_ordinal: number | null
|
|
583
|
+
next_row_offset: number | null
|
|
584
|
+
input_exhausted: 0 | 1 | null
|
|
585
|
+
missing_target_object_type_id: string | null
|
|
586
|
+
missing_target_object_id: string | null
|
|
587
|
+
missing_target_batch_ordinal: number | null
|
|
588
|
+
missing_target_first_seen_at: string | null
|
|
589
|
+
source_rows_read: number
|
|
590
|
+
source_rows_skipped: number
|
|
591
|
+
error_message: string | null
|
|
592
|
+
}
|