@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,237 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import {
|
|
3
|
+
MaterializationConflictError,
|
|
4
|
+
MaterializationValidationError,
|
|
5
|
+
} from "@sixb/core/internal/materialization"
|
|
6
|
+
import type {
|
|
7
|
+
ClaimedOntologyOutboxRow,
|
|
8
|
+
ClaimOntologyOutboxInput,
|
|
9
|
+
CompleteOntologyOutboxLeaseInput,
|
|
10
|
+
OntologyOutboxStorage,
|
|
11
|
+
OntologyOutboxSummary,
|
|
12
|
+
PurgePublishedOntologyOutboxInput,
|
|
13
|
+
RescheduleOntologyOutboxLeaseInput,
|
|
14
|
+
SummarizeOntologyOutboxInput,
|
|
15
|
+
} from "@sixb/core/storage"
|
|
16
|
+
import {
|
|
17
|
+
assertNonblank,
|
|
18
|
+
assertPositiveInteger,
|
|
19
|
+
assertTimestamp,
|
|
20
|
+
outboxRecord,
|
|
21
|
+
type SqliteOntologyOutboxRow,
|
|
22
|
+
type SqliteRootOperation,
|
|
23
|
+
} from "./shared"
|
|
24
|
+
|
|
25
|
+
export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly db: Database,
|
|
28
|
+
private readonly runRootOperation: SqliteRootOperation
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async claim(input: ClaimOntologyOutboxInput): Promise<readonly ClaimedOntologyOutboxRow[]> {
|
|
32
|
+
return this.runRootOperation(() => {
|
|
33
|
+
assertNonblank(input.projectId, "Ontology outbox project id")
|
|
34
|
+
assertPositiveInteger(input.limit, "Ontology outbox limit")
|
|
35
|
+
const now = assertTimestamp(input.now, "Ontology outbox claim time")
|
|
36
|
+
const leaseExpiry = assertTimestamp(input.leaseExpiresAt, "Ontology outbox lease expiry")
|
|
37
|
+
assertNonblank(input.leaseId, "Ontology outbox lease id")
|
|
38
|
+
if (leaseExpiry <= now) {
|
|
39
|
+
throw new MaterializationValidationError(
|
|
40
|
+
"Ontology outbox lease expiry must be later than the claim time."
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rows = this.db
|
|
45
|
+
.query(
|
|
46
|
+
`
|
|
47
|
+
UPDATE ontology_outbox
|
|
48
|
+
SET attempts = attempts + 1, lease_id = ?, lease_expires_at = ?
|
|
49
|
+
WHERE rowid IN (
|
|
50
|
+
SELECT rowid
|
|
51
|
+
FROM ontology_outbox
|
|
52
|
+
WHERE project_id = ? AND published_at IS NULL AND available_at <= ?
|
|
53
|
+
AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
|
|
54
|
+
ORDER BY created_at, commit_id, commit_ordinal
|
|
55
|
+
LIMIT ?
|
|
56
|
+
)
|
|
57
|
+
RETURNING envelope, available_at, attempts, lease_id, lease_expires_at,
|
|
58
|
+
published_at, last_error, created_at
|
|
59
|
+
`
|
|
60
|
+
)
|
|
61
|
+
.all(
|
|
62
|
+
input.leaseId,
|
|
63
|
+
input.leaseExpiresAt,
|
|
64
|
+
input.projectId,
|
|
65
|
+
input.now,
|
|
66
|
+
input.now,
|
|
67
|
+
input.limit
|
|
68
|
+
) as SqliteOntologyOutboxRow[]
|
|
69
|
+
return rows
|
|
70
|
+
.map((row) => outboxRecord(row))
|
|
71
|
+
.sort(
|
|
72
|
+
(left, right) =>
|
|
73
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
74
|
+
left.envelope.id.localeCompare(right.envelope.id)
|
|
75
|
+
)
|
|
76
|
+
.map((record) => {
|
|
77
|
+
if (record.leaseId === null || record.leaseExpiresAt === null) {
|
|
78
|
+
throw new MaterializationConflictError(
|
|
79
|
+
"outbox-lease",
|
|
80
|
+
"Ontology outbox claim returned an unpaired lease."
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
return { ...record, leaseId: record.leaseId, leaseExpiresAt: record.leaseExpiresAt }
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async markPublished(input: CompleteOntologyOutboxLeaseInput): Promise<void> {
|
|
89
|
+
await this.runRootOperation(() => {
|
|
90
|
+
assertLeaseInput(input)
|
|
91
|
+
assertTimestamp(input.publishedAt, "Ontology outbox publishedAt")
|
|
92
|
+
const ids = validateLeaseIds(input.ids)
|
|
93
|
+
const changed = this.db
|
|
94
|
+
.query(
|
|
95
|
+
`
|
|
96
|
+
WITH requested(id) AS MATERIALIZED (
|
|
97
|
+
SELECT CAST(value AS TEXT) FROM json_each(?)
|
|
98
|
+
), leased(id) AS MATERIALIZED (
|
|
99
|
+
SELECT outbox.id FROM ontology_outbox AS outbox
|
|
100
|
+
JOIN requested USING (id)
|
|
101
|
+
WHERE outbox.project_id = ? AND outbox.lease_id = ?
|
|
102
|
+
AND outbox.lease_expires_at IS NOT NULL
|
|
103
|
+
), eligible(id) AS (
|
|
104
|
+
SELECT id FROM leased WHERE (SELECT COUNT(*) FROM leased) = ?
|
|
105
|
+
)
|
|
106
|
+
UPDATE ontology_outbox
|
|
107
|
+
SET published_at = ?, lease_id = NULL, lease_expires_at = NULL
|
|
108
|
+
WHERE project_id = ? AND id IN (SELECT id FROM eligible)
|
|
109
|
+
AND lease_id = ? AND lease_expires_at IS NOT NULL
|
|
110
|
+
`
|
|
111
|
+
)
|
|
112
|
+
.run(
|
|
113
|
+
JSON.stringify(ids),
|
|
114
|
+
input.projectId,
|
|
115
|
+
input.leaseId,
|
|
116
|
+
ids.length,
|
|
117
|
+
input.publishedAt,
|
|
118
|
+
input.projectId,
|
|
119
|
+
input.leaseId
|
|
120
|
+
).changes
|
|
121
|
+
if (changed !== ids.length) throw leaseConflict()
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async reschedule(input: RescheduleOntologyOutboxLeaseInput): Promise<void> {
|
|
126
|
+
await this.runRootOperation(() => {
|
|
127
|
+
assertLeaseInput(input)
|
|
128
|
+
assertTimestamp(input.availableAt, "Ontology outbox availableAt")
|
|
129
|
+
const ids = validateLeaseIds(input.ids)
|
|
130
|
+
const changed = this.db
|
|
131
|
+
.query(
|
|
132
|
+
`
|
|
133
|
+
WITH requested(id) AS MATERIALIZED (
|
|
134
|
+
SELECT CAST(value AS TEXT) FROM json_each(?)
|
|
135
|
+
), leased(id) AS MATERIALIZED (
|
|
136
|
+
SELECT outbox.id FROM ontology_outbox AS outbox
|
|
137
|
+
JOIN requested USING (id)
|
|
138
|
+
WHERE outbox.project_id = ? AND outbox.lease_id = ?
|
|
139
|
+
AND outbox.lease_expires_at IS NOT NULL
|
|
140
|
+
), eligible(id) AS (
|
|
141
|
+
SELECT id FROM leased WHERE (SELECT COUNT(*) FROM leased) = ?
|
|
142
|
+
)
|
|
143
|
+
UPDATE ontology_outbox
|
|
144
|
+
SET available_at = ?, last_error = ?, lease_id = NULL, lease_expires_at = NULL
|
|
145
|
+
WHERE project_id = ? AND id IN (SELECT id FROM eligible)
|
|
146
|
+
AND lease_id = ? AND lease_expires_at IS NOT NULL
|
|
147
|
+
`
|
|
148
|
+
)
|
|
149
|
+
.run(
|
|
150
|
+
JSON.stringify(ids),
|
|
151
|
+
input.projectId,
|
|
152
|
+
input.leaseId,
|
|
153
|
+
ids.length,
|
|
154
|
+
input.availableAt,
|
|
155
|
+
input.error,
|
|
156
|
+
input.projectId,
|
|
157
|
+
input.leaseId
|
|
158
|
+
).changes
|
|
159
|
+
if (changed !== ids.length) throw leaseConflict()
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async purgePublished(input: PurgePublishedOntologyOutboxInput): Promise<number> {
|
|
164
|
+
return this.runRootOperation(() => {
|
|
165
|
+
assertNonblank(input.projectId, "Ontology outbox project id")
|
|
166
|
+
assertPositiveInteger(input.limit, "Ontology outbox limit")
|
|
167
|
+
assertTimestamp(input.publishedBefore, "Ontology outbox purge cutoff")
|
|
168
|
+
return this.db
|
|
169
|
+
.query(
|
|
170
|
+
`
|
|
171
|
+
DELETE FROM ontology_outbox
|
|
172
|
+
WHERE rowid IN (
|
|
173
|
+
SELECT rowid FROM ontology_outbox
|
|
174
|
+
WHERE project_id = ? AND published_at IS NOT NULL AND published_at < ?
|
|
175
|
+
ORDER BY published_at, id
|
|
176
|
+
LIMIT ?
|
|
177
|
+
)
|
|
178
|
+
`
|
|
179
|
+
)
|
|
180
|
+
.run(input.projectId, input.publishedBefore, input.limit).changes
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async summarize(input: SummarizeOntologyOutboxInput): Promise<OntologyOutboxSummary> {
|
|
185
|
+
return this.runRootOperation(() => {
|
|
186
|
+
assertNonblank(input.projectId, "Ontology outbox project id")
|
|
187
|
+
const summary = this.db
|
|
188
|
+
.query(
|
|
189
|
+
`
|
|
190
|
+
SELECT COUNT(*) AS pending_count,
|
|
191
|
+
MIN(created_at) AS oldest_pending_at,
|
|
192
|
+
SUM(CASE WHEN attempts > 0 THEN 1 ELSE 0 END) AS retrying_count,
|
|
193
|
+
COALESCE(MAX(attempts), 0) AS max_attempts
|
|
194
|
+
FROM ontology_outbox
|
|
195
|
+
WHERE project_id = ? AND published_at IS NULL
|
|
196
|
+
`
|
|
197
|
+
)
|
|
198
|
+
.get(input.projectId) as {
|
|
199
|
+
readonly pending_count: number
|
|
200
|
+
readonly oldest_pending_at: string | null
|
|
201
|
+
readonly retrying_count: number | null
|
|
202
|
+
readonly max_attempts: number
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
pendingCount: summary.pending_count,
|
|
206
|
+
oldestPendingAt: summary.oldest_pending_at,
|
|
207
|
+
retryingCount: summary.retrying_count ?? 0,
|
|
208
|
+
maxAttempts: summary.max_attempts,
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function validateLeaseIds(ids: readonly string[]): string[] {
|
|
215
|
+
const seen = new Set<string>()
|
|
216
|
+
for (const id of ids) {
|
|
217
|
+
assertNonblank(id, "Ontology outbox event id")
|
|
218
|
+
if (seen.has(id)) {
|
|
219
|
+
throw new MaterializationValidationError(`Ontology outbox lease batch repeats event '${id}'.`)
|
|
220
|
+
}
|
|
221
|
+
seen.add(id)
|
|
222
|
+
}
|
|
223
|
+
return [...seen]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function assertLeaseInput(input: {
|
|
227
|
+
readonly projectId: string
|
|
228
|
+
readonly ids: readonly string[]
|
|
229
|
+
readonly leaseId: string
|
|
230
|
+
}): void {
|
|
231
|
+
assertNonblank(input.projectId, "Ontology outbox project id")
|
|
232
|
+
assertNonblank(input.leaseId, "Ontology outbox lease id")
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function leaseConflict(): MaterializationConflictError {
|
|
236
|
+
return new MaterializationConflictError("outbox-lease", "Ontology outbox lease does not match.")
|
|
237
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type { ProjectionEntityRef } from "@sixb/core/internal/materialization"
|
|
3
|
+
import { MaterializationConflictError } from "@sixb/core/internal/materialization"
|
|
4
|
+
import type {
|
|
5
|
+
AssertSourceMaterializationExecutionInput,
|
|
6
|
+
OntologyCommitRecord,
|
|
7
|
+
OntologyCommitWrite,
|
|
8
|
+
OntologyOutboxRecord,
|
|
9
|
+
OntologySourceRecord,
|
|
10
|
+
StageSourceAssertion,
|
|
11
|
+
} from "@sixb/core/storage"
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
assertNonblank,
|
|
15
|
+
assertNonnegativeInteger,
|
|
16
|
+
assertPositiveInteger,
|
|
17
|
+
assertTimestamp,
|
|
18
|
+
canonicalJson,
|
|
19
|
+
linkRefFromColumns,
|
|
20
|
+
objectRefFromColumns,
|
|
21
|
+
originColumns,
|
|
22
|
+
originWhere,
|
|
23
|
+
sourceEntityKey,
|
|
24
|
+
} from "@sixb/core/internal/ontology-storage-provider"
|
|
25
|
+
|
|
26
|
+
export type SqliteRootOperation = <T>(run: () => Promise<T> | T) => Promise<T>
|
|
27
|
+
|
|
28
|
+
export interface SqliteOntologyCommitRow {
|
|
29
|
+
readonly project_id: string
|
|
30
|
+
readonly id: string
|
|
31
|
+
readonly idempotency_key: string
|
|
32
|
+
readonly request_hash: string
|
|
33
|
+
readonly origin_kind: string
|
|
34
|
+
readonly origin_run_id: string | null
|
|
35
|
+
readonly origin_batch_ordinal: number | null
|
|
36
|
+
readonly origin: string
|
|
37
|
+
readonly actor: string | null
|
|
38
|
+
readonly ontology_revision: string
|
|
39
|
+
readonly projection_revision: string | null
|
|
40
|
+
readonly ownership_hash: string | null
|
|
41
|
+
readonly intent: string
|
|
42
|
+
readonly result: string
|
|
43
|
+
readonly committed_at: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SqliteOntologySourceRow {
|
|
47
|
+
readonly project_id: string
|
|
48
|
+
readonly source_id: string
|
|
49
|
+
readonly materialization_id: string
|
|
50
|
+
readonly projection_run_id: string
|
|
51
|
+
readonly projection_kind: "object" | "link"
|
|
52
|
+
readonly protocol: "replacement"
|
|
53
|
+
readonly status: OntologySourceRecord["status"]
|
|
54
|
+
readonly execution_token: string | null
|
|
55
|
+
readonly dataset_id: string
|
|
56
|
+
readonly dataset_version_id: string
|
|
57
|
+
readonly dataset_version_created_at: string
|
|
58
|
+
readonly projection_revision: string
|
|
59
|
+
readonly ownership_hash: string
|
|
60
|
+
readonly ontology_revision: string
|
|
61
|
+
readonly root_count: number | null
|
|
62
|
+
readonly assertion_count: number | null
|
|
63
|
+
readonly created_at: string
|
|
64
|
+
readonly ready_at: string | null
|
|
65
|
+
readonly activated_at: string | null
|
|
66
|
+
readonly terminal_at: string | null
|
|
67
|
+
readonly last_commit_id: string | null
|
|
68
|
+
readonly updated_at: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SqliteOntologySourceAssertionRow {
|
|
72
|
+
readonly project_id: string
|
|
73
|
+
readonly source_id: string
|
|
74
|
+
readonly materialization_id: string
|
|
75
|
+
readonly entity_kind: "object" | "link"
|
|
76
|
+
readonly entity_key: string
|
|
77
|
+
readonly entity_sort_key: string
|
|
78
|
+
readonly root_kind: "object" | "link"
|
|
79
|
+
readonly root_key: string
|
|
80
|
+
readonly root_sort_key: string
|
|
81
|
+
readonly staging_ordinal: number
|
|
82
|
+
readonly root: string
|
|
83
|
+
readonly assertion: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface SqliteOntologyOverrideRow {
|
|
87
|
+
readonly entity_kind: "object" | "link"
|
|
88
|
+
readonly value: string
|
|
89
|
+
readonly last_commit_id: string
|
|
90
|
+
readonly updated_at: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface SqliteOntologyOutboxRow {
|
|
94
|
+
readonly envelope: string
|
|
95
|
+
readonly available_at: string
|
|
96
|
+
readonly attempts: number
|
|
97
|
+
readonly lease_id: string | null
|
|
98
|
+
readonly lease_expires_at: string | null
|
|
99
|
+
readonly published_at: string | null
|
|
100
|
+
readonly last_error: string | null
|
|
101
|
+
readonly created_at: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function parseJson<T>(value: string): T {
|
|
105
|
+
return JSON.parse(value) as T
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function sourceRecord(row: SqliteOntologySourceRow): OntologySourceRecord {
|
|
109
|
+
return {
|
|
110
|
+
projectId: row.project_id,
|
|
111
|
+
source: { projectionId: row.source_id },
|
|
112
|
+
materializationId: row.materialization_id,
|
|
113
|
+
projectionRunId: row.projection_run_id,
|
|
114
|
+
projectionKind: row.projection_kind,
|
|
115
|
+
protocol: row.protocol,
|
|
116
|
+
status: row.status,
|
|
117
|
+
executionToken: row.execution_token,
|
|
118
|
+
datasetVersion: {
|
|
119
|
+
datasetId: row.dataset_id,
|
|
120
|
+
versionId: row.dataset_version_id,
|
|
121
|
+
createdAt: row.dataset_version_created_at,
|
|
122
|
+
},
|
|
123
|
+
projectionRevision: row.projection_revision,
|
|
124
|
+
ownershipHash: row.ownership_hash,
|
|
125
|
+
ontologyRevision: row.ontology_revision,
|
|
126
|
+
rootCount: row.root_count,
|
|
127
|
+
assertionCount: row.assertion_count,
|
|
128
|
+
createdAt: row.created_at,
|
|
129
|
+
readyAt: row.ready_at,
|
|
130
|
+
activatedAt: row.activated_at,
|
|
131
|
+
terminalAt: row.terminal_at,
|
|
132
|
+
lastCommitId: row.last_commit_id,
|
|
133
|
+
updatedAt: row.updated_at,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function sourceAssertion(row: SqliteOntologySourceAssertionRow): StageSourceAssertion {
|
|
138
|
+
return {
|
|
139
|
+
root: parseJson<ProjectionEntityRef>(row.root),
|
|
140
|
+
assertion: parseJson<StageSourceAssertion["assertion"]>(row.assertion),
|
|
141
|
+
stagingOrdinal: row.staging_ordinal,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function commitRecord(row: SqliteOntologyCommitRow): OntologyCommitRecord {
|
|
146
|
+
const base = {
|
|
147
|
+
projectId: row.project_id,
|
|
148
|
+
id: row.id,
|
|
149
|
+
idempotencyKey: row.idempotency_key,
|
|
150
|
+
requestHash: row.request_hash,
|
|
151
|
+
origin: parseJson<OntologyCommitWrite["origin"]>(row.origin),
|
|
152
|
+
...(row.actor === null
|
|
153
|
+
? {}
|
|
154
|
+
: { actor: parseJson<NonNullable<OntologyCommitWrite["actor"]>>(row.actor) }),
|
|
155
|
+
ontologyRevision: row.ontology_revision,
|
|
156
|
+
...(row.projection_revision === null ? {} : { projectionRevision: row.projection_revision }),
|
|
157
|
+
...(row.ownership_hash === null ? {} : { ownershipHash: row.ownership_hash }),
|
|
158
|
+
committedAt: row.committed_at,
|
|
159
|
+
}
|
|
160
|
+
const intent = parseJson<OntologyCommitWrite["intent"]>(row.intent)
|
|
161
|
+
const result = parseJson<OntologyCommitRecord["result"]>(row.result)
|
|
162
|
+
return { ...base, intent, result } as OntologyCommitRecord
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function outboxRecord(row: SqliteOntologyOutboxRow): OntologyOutboxRecord {
|
|
166
|
+
return {
|
|
167
|
+
envelope: parseJson<OntologyOutboxRecord["envelope"]>(row.envelope),
|
|
168
|
+
availableAt: row.available_at,
|
|
169
|
+
attempts: row.attempts,
|
|
170
|
+
leaseId: row.lease_id,
|
|
171
|
+
leaseExpiresAt: row.lease_expires_at,
|
|
172
|
+
publishedAt: row.published_at,
|
|
173
|
+
lastError: row.last_error,
|
|
174
|
+
createdAt: row.created_at,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function assertProjectionExecution(
|
|
179
|
+
db: Database,
|
|
180
|
+
input: {
|
|
181
|
+
readonly projectId: string
|
|
182
|
+
readonly sourceId: string
|
|
183
|
+
readonly projectionRunId: string
|
|
184
|
+
readonly executionToken: string
|
|
185
|
+
readonly identity?: AssertSourceMaterializationExecutionInput["identity"]
|
|
186
|
+
}
|
|
187
|
+
): void {
|
|
188
|
+
const run = db
|
|
189
|
+
.query(
|
|
190
|
+
`
|
|
191
|
+
SELECT projection_id, projection_kind, status, materialization_protocol, execution_token,
|
|
192
|
+
dataset_id, dataset_version_id, dataset_version_created_at,
|
|
193
|
+
ontology_revision, projection_revision, ownership_hash
|
|
194
|
+
FROM projection_runs
|
|
195
|
+
WHERE project_id = ? AND id = ?
|
|
196
|
+
`
|
|
197
|
+
)
|
|
198
|
+
.get(input.projectId, input.projectionRunId) as {
|
|
199
|
+
projection_id: string
|
|
200
|
+
projection_kind: string
|
|
201
|
+
status: string
|
|
202
|
+
materialization_protocol: string | null
|
|
203
|
+
execution_token: string | null
|
|
204
|
+
dataset_id: string
|
|
205
|
+
dataset_version_id: string
|
|
206
|
+
dataset_version_created_at: string | null
|
|
207
|
+
ontology_revision: string | null
|
|
208
|
+
projection_revision: string | null
|
|
209
|
+
ownership_hash: string | null
|
|
210
|
+
} | null
|
|
211
|
+
if (!run || run.status !== "running") {
|
|
212
|
+
throw new MaterializationConflictError(
|
|
213
|
+
"run-correlation",
|
|
214
|
+
`Projection run '${input.projectionRunId}' is missing or is not running.`
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
if (run.projection_id !== input.sourceId || run.materialization_protocol !== "replacement") {
|
|
218
|
+
throw new MaterializationConflictError(
|
|
219
|
+
"run-correlation",
|
|
220
|
+
`Projection run '${input.projectionRunId}' does not own replacement source '${input.sourceId}'.`
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
if (run.execution_token !== input.executionToken) {
|
|
224
|
+
throw new MaterializationConflictError(
|
|
225
|
+
"execution-lost",
|
|
226
|
+
`Projection run '${input.projectionRunId}' execution token is stale.`
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
const identity = input.identity
|
|
230
|
+
if (
|
|
231
|
+
identity &&
|
|
232
|
+
(run.projection_kind !== identity.projectionKind ||
|
|
233
|
+
run.materialization_protocol !== identity.protocol ||
|
|
234
|
+
run.dataset_id !== identity.datasetVersion.datasetId ||
|
|
235
|
+
run.dataset_version_id !== identity.datasetVersion.versionId ||
|
|
236
|
+
run.dataset_version_created_at !== identity.datasetVersion.createdAt ||
|
|
237
|
+
run.ontology_revision !== identity.ontologyRevision ||
|
|
238
|
+
run.projection_revision !== identity.projectionRevision ||
|
|
239
|
+
run.ownership_hash !== identity.ownershipHash)
|
|
240
|
+
) {
|
|
241
|
+
throw new MaterializationConflictError(
|
|
242
|
+
"run-correlation",
|
|
243
|
+
`Projection run '${input.projectionRunId}' immutable source identity does not match.`
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function requireChanges(
|
|
249
|
+
changes: number,
|
|
250
|
+
kind: ConstructorParameters<typeof MaterializationConflictError>[0],
|
|
251
|
+
message: string
|
|
252
|
+
): void {
|
|
253
|
+
if (changes !== 1) throw new MaterializationConflictError(kind, message)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function isSqliteConstraintError(error: unknown): boolean {
|
|
257
|
+
if (!(error instanceof Error)) return false
|
|
258
|
+
const details = error as Error & { readonly code?: unknown; readonly errno?: unknown }
|
|
259
|
+
if (typeof details.code === "string") {
|
|
260
|
+
return [
|
|
261
|
+
"SQLITE_CONSTRAINT_CHECK",
|
|
262
|
+
"SQLITE_CONSTRAINT_FOREIGNKEY",
|
|
263
|
+
"SQLITE_CONSTRAINT_NOTNULL",
|
|
264
|
+
"SQLITE_CONSTRAINT_PRIMARYKEY",
|
|
265
|
+
"SQLITE_CONSTRAINT_UNIQUE",
|
|
266
|
+
].includes(details.code)
|
|
267
|
+
}
|
|
268
|
+
return details.errno === 19
|
|
269
|
+
}
|