@sixb/sqlite 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/action-run-storage.d.ts +29 -0
  4. package/dist/action-run-storage.d.ts.map +1 -0
  5. package/dist/agents/agent-storage.d.ts +25 -0
  6. package/dist/agents/agent-storage.d.ts.map +1 -0
  7. package/dist/agents/index.d.ts +6 -0
  8. package/dist/agents/index.d.ts.map +1 -0
  9. package/dist/agents/messages.d.ts +14 -0
  10. package/dist/agents/messages.d.ts.map +1 -0
  11. package/dist/agents/rows.d.ts +77 -0
  12. package/dist/agents/rows.d.ts.map +1 -0
  13. package/dist/agents/runs.d.ts +26 -0
  14. package/dist/agents/runs.d.ts.map +1 -0
  15. package/dist/agents/threads.d.ts +14 -0
  16. package/dist/agents/threads.d.ts.map +1 -0
  17. package/dist/auth-storage/access-tokens.d.ts +33 -0
  18. package/dist/auth-storage/access-tokens.d.ts.map +1 -0
  19. package/dist/auth-storage/auth-storage.d.ts +46 -0
  20. package/dist/auth-storage/auth-storage.d.ts.map +1 -0
  21. package/dist/auth-storage/group-memberships.d.ts +21 -0
  22. package/dist/auth-storage/group-memberships.d.ts.map +1 -0
  23. package/dist/auth-storage/identities.d.ts +17 -0
  24. package/dist/auth-storage/identities.d.ts.map +1 -0
  25. package/dist/auth-storage/index.d.ts +3 -0
  26. package/dist/auth-storage/index.d.ts.map +1 -0
  27. package/dist/auth-storage/invitations.d.ts +28 -0
  28. package/dist/auth-storage/invitations.d.ts.map +1 -0
  29. package/dist/auth-storage/magic-links.d.ts +28 -0
  30. package/dist/auth-storage/magic-links.d.ts.map +1 -0
  31. package/dist/auth-storage/oidc-attempts.d.ts +18 -0
  32. package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
  33. package/dist/auth-storage/rows.d.ts +134 -0
  34. package/dist/auth-storage/rows.d.ts.map +1 -0
  35. package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
  36. package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
  37. package/dist/auth-storage/service-accounts.d.ts +14 -0
  38. package/dist/auth-storage/service-accounts.d.ts.map +1 -0
  39. package/dist/auth-storage/sessions.d.ts +55 -0
  40. package/dist/auth-storage/sessions.d.ts.map +1 -0
  41. package/dist/auth-storage/shared.d.ts +131 -0
  42. package/dist/auth-storage/shared.d.ts.map +1 -0
  43. package/dist/auth-storage/users.d.ts +19 -0
  44. package/dist/auth-storage/users.d.ts.map +1 -0
  45. package/dist/index.d.ts +71 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +10204 -0
  48. package/dist/index.js.map +54 -0
  49. package/dist/latest-run-query.d.ts +22 -0
  50. package/dist/latest-run-query.d.ts.map +1 -0
  51. package/dist/migrations.d.ts +18 -0
  52. package/dist/migrations.d.ts.map +1 -0
  53. package/dist/object-query-compiler.d.ts +44 -0
  54. package/dist/object-query-compiler.d.ts.map +1 -0
  55. package/dist/object-storage.d.ts +93 -0
  56. package/dist/object-storage.d.ts.map +1 -0
  57. package/dist/ontology-storage/commits.d.ts +14 -0
  58. package/dist/ontology-storage/commits.d.ts.map +1 -0
  59. package/dist/ontology-storage/index.d.ts +22 -0
  60. package/dist/ontology-storage/index.d.ts.map +1 -0
  61. package/dist/ontology-storage/materialization-session.d.ts +49 -0
  62. package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
  63. package/dist/ontology-storage/materialization-state.d.ts +63 -0
  64. package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
  65. package/dist/ontology-storage/materialization-writer.d.ts +15 -0
  66. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
  67. package/dist/ontology-storage/materializations.d.ts +32 -0
  68. package/dist/ontology-storage/materializations.d.ts.map +1 -0
  69. package/dist/ontology-storage/outbox.d.ts +14 -0
  70. package/dist/ontology-storage/outbox.d.ts.map +1 -0
  71. package/dist/ontology-storage/shared.d.ts +91 -0
  72. package/dist/ontology-storage/shared.d.ts.map +1 -0
  73. package/dist/ontology-storage/sources.d.ts +26 -0
  74. package/dist/ontology-storage/sources.d.ts.map +1 -0
  75. package/dist/pipeline-run-storage.d.ts +26 -0
  76. package/dist/pipeline-run-storage.d.ts.map +1 -0
  77. package/dist/projection-run-storage.d.ts +32 -0
  78. package/dist/projection-run-storage.d.ts.map +1 -0
  79. package/dist/rules-storage.d.ts +33 -0
  80. package/dist/rules-storage.d.ts.map +1 -0
  81. package/dist/run-list-query.d.ts +27 -0
  82. package/dist/run-list-query.d.ts.map +1 -0
  83. package/dist/storage-errors.d.ts +2 -0
  84. package/dist/storage-errors.d.ts.map +1 -0
  85. package/dist/sync-run-storage.d.ts +23 -0
  86. package/dist/sync-run-storage.d.ts.map +1 -0
  87. package/dist/timeseries-storage.d.ts +41 -0
  88. package/dist/timeseries-storage.d.ts.map +1 -0
  89. package/dist/transactions.d.ts +15 -0
  90. package/dist/transactions.d.ts.map +1 -0
  91. package/dist/tsconfig.json +5 -0
  92. package/dist/validation.d.ts +5 -0
  93. package/dist/validation.d.ts.map +1 -0
  94. package/dist/webhook-delivery-storage.d.ts +27 -0
  95. package/dist/webhook-delivery-storage.d.ts.map +1 -0
  96. package/dist/webhook-run-storage.d.ts +22 -0
  97. package/dist/webhook-run-storage.d.ts.map +1 -0
  98. package/dist/workflow-intervention-storage.d.ts +26 -0
  99. package/dist/workflow-intervention-storage.d.ts.map +1 -0
  100. package/dist/workflow-run-storage.d.ts +63 -0
  101. package/dist/workflow-run-storage.d.ts.map +1 -0
  102. package/package.json +58 -0
  103. package/src/action-run-storage.ts +761 -0
  104. package/src/agents/agent-storage.ts +46 -0
  105. package/src/agents/index.ts +5 -0
  106. package/src/agents/messages.ts +162 -0
  107. package/src/agents/rows.ts +205 -0
  108. package/src/agents/runs.ts +389 -0
  109. package/src/agents/threads.ts +133 -0
  110. package/src/auth-storage/access-tokens.ts +289 -0
  111. package/src/auth-storage/auth-storage.ts +784 -0
  112. package/src/auth-storage/group-memberships.ts +52 -0
  113. package/src/auth-storage/identities.ts +98 -0
  114. package/src/auth-storage/index.ts +2 -0
  115. package/src/auth-storage/invitations.ts +258 -0
  116. package/src/auth-storage/magic-links.ts +149 -0
  117. package/src/auth-storage/oidc-attempts.ts +109 -0
  118. package/src/auth-storage/rows.ts +338 -0
  119. package/src/auth-storage/service-account-group-memberships.ts +161 -0
  120. package/src/auth-storage/service-accounts.ts +168 -0
  121. package/src/auth-storage/sessions.ts +230 -0
  122. package/src/auth-storage/shared.ts +707 -0
  123. package/src/auth-storage/users.ts +226 -0
  124. package/src/index.ts +292 -0
  125. package/src/latest-run-query.ts +49 -0
  126. package/src/migrations/001-initial-schema.sql +1068 -0
  127. package/src/migrations.ts +245 -0
  128. package/src/object-query-compiler.ts +1198 -0
  129. package/src/object-storage.ts +605 -0
  130. package/src/ontology-storage/commits.ts +127 -0
  131. package/src/ontology-storage/index.ts +35 -0
  132. package/src/ontology-storage/materialization-session.ts +393 -0
  133. package/src/ontology-storage/materialization-state.ts +901 -0
  134. package/src/ontology-storage/materialization-writer.ts +394 -0
  135. package/src/ontology-storage/materializations.ts +838 -0
  136. package/src/ontology-storage/outbox.ts +237 -0
  137. package/src/ontology-storage/shared.ts +269 -0
  138. package/src/ontology-storage/sources.ts +653 -0
  139. package/src/pipeline-run-storage.ts +506 -0
  140. package/src/projection-run-storage.ts +592 -0
  141. package/src/rules-storage.ts +375 -0
  142. package/src/run-list-query.ts +98 -0
  143. package/src/sql.d.ts +4 -0
  144. package/src/storage-errors.ts +3 -0
  145. package/src/sync-run-storage.ts +299 -0
  146. package/src/timeseries-storage.ts +212 -0
  147. package/src/transactions.ts +96 -0
  148. package/src/validation.ts +30 -0
  149. package/src/webhook-delivery-storage.ts +272 -0
  150. package/src/webhook-run-storage.ts +240 -0
  151. package/src/workflow-intervention-storage.ts +398 -0
  152. package/src/workflow-run-storage.ts +1148 -0
@@ -0,0 +1,901 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import type {
3
+ EffectiveLinkSnapshot,
4
+ EffectiveObjectSnapshot,
5
+ OntologyLinkRef,
6
+ OntologyObjectRef,
7
+ ProjectionEntityRef,
8
+ TelemetrySeriesRef,
9
+ } from "@sixb/core/internal/materialization"
10
+ import {
11
+ linkRefKey,
12
+ linkScopeSortKey,
13
+ MaterializationConflictError,
14
+ objectRefKey,
15
+ projectionEntityKey,
16
+ telemetryPointSortKey,
17
+ } from "@sixb/core/internal/materialization"
18
+ import {
19
+ appendScopeSnapshot,
20
+ finishScopeAccumulator,
21
+ startScopeAccumulator,
22
+ } from "@sixb/core/internal/ontology-storage-provider"
23
+ import type {
24
+ MaterializationLinkScopeState,
25
+ MaterializationLinkState,
26
+ MaterializationObjectState,
27
+ SourceReplacementLinkState,
28
+ SourceReplacementObjectState,
29
+ StoredLinkOverride,
30
+ StoredObjectOverride,
31
+ StoredSourceAssertion,
32
+ StoredSourceLinkAssertion,
33
+ StoredSourceObjectAssertion,
34
+ StoredTelemetryPoint,
35
+ } from "@sixb/core/storage"
36
+ import {
37
+ canonicalJson,
38
+ linkRefFromColumns,
39
+ objectRefFromColumns,
40
+ parseJson,
41
+ type SqliteOntologyOverrideRow,
42
+ type SqliteOntologySourceAssertionRow,
43
+ } from "./shared"
44
+
45
+ export const SQLITE_MATERIALIZATION_WORK_TABLE = "ontology_materialization_work"
46
+ export const SQLITE_REPLACEMENT_WORK_TABLE = "ontology_replacement_work"
47
+
48
+ interface EffectiveObjectRow {
49
+ readonly object_type_id: string
50
+ readonly primary_id: string
51
+ readonly properties: string
52
+ readonly created_at: string
53
+ readonly updated_at: string
54
+ readonly version: number
55
+ readonly last_commit_id: string | null
56
+ }
57
+
58
+ interface EffectiveLinkRow {
59
+ readonly source_type_id: string
60
+ readonly source_id: string
61
+ readonly link_id: string
62
+ readonly target_type_id: string
63
+ readonly target_id: string
64
+ readonly properties: string | null
65
+ readonly created_at: string
66
+ readonly updated_at: string
67
+ readonly last_commit_id: string | null
68
+ }
69
+
70
+ interface TelemetryRow {
71
+ readonly object_type_id: string
72
+ readonly object_id: string
73
+ readonly property_id: string
74
+ readonly value: string
75
+ readonly unit: string | null
76
+ readonly at: string
77
+ readonly last_commit_id: string | null
78
+ }
79
+
80
+ interface LinkScopeRow extends EffectiveLinkRow {
81
+ readonly scope_sort_key: string
82
+ }
83
+
84
+ interface ObjectOverrideRow extends SqliteOntologyOverrideRow {
85
+ readonly object_type_id: string
86
+ readonly primary_id: string
87
+ }
88
+
89
+ interface LinkOverrideRow extends SqliteOntologyOverrideRow {
90
+ readonly source_type_id: string
91
+ readonly source_primary_id: string
92
+ readonly link_id: string
93
+ readonly target_type_id: string
94
+ readonly target_primary_id: string
95
+ }
96
+
97
+ interface ReplacementSources {
98
+ readonly owned: ReadonlySet<string>
99
+ readonly byMaterialization: ReadonlyMap<string, ReadonlyMap<string, StoredSourceAssertion>>
100
+ }
101
+
102
+ export type ReplacementIdentity =
103
+ | {
104
+ readonly kind: "object"
105
+ readonly ref: OntologyObjectRef
106
+ readonly sortKey: string
107
+ readonly diffRequired: true
108
+ }
109
+ | {
110
+ readonly kind: "link"
111
+ readonly ref: OntologyLinkRef
112
+ readonly sortKey: string
113
+ readonly diffRequired: boolean
114
+ }
115
+
116
+ interface ReplacementIdentityInput {
117
+ readonly sessionId: string
118
+ readonly sourceId: string
119
+ readonly candidateMaterializationId: string
120
+ readonly previousMaterializationId: string | null
121
+ readonly kind: "object" | "link"
122
+ readonly pageRows: number
123
+ }
124
+
125
+ export class SqliteMaterializationStateReader {
126
+ constructor(
127
+ private readonly db: Database,
128
+ readonly projectId: string
129
+ ) {}
130
+
131
+ objectState(ref: OntologyObjectRef): MaterializationObjectState {
132
+ return this.objectStates([ref])[0]!
133
+ }
134
+
135
+ objectStates(refs: readonly OntologyObjectRef[]): readonly MaterializationObjectState[] {
136
+ if (refs.length === 0) return []
137
+ const requested = canonicalJson(
138
+ refs.map((ref) => ({ objectTypeId: ref.objectTypeId, primaryId: ref.primaryId }))
139
+ )
140
+ const requestedObjects = `
141
+ SELECT DISTINCT json_extract(value, '$.objectTypeId') AS object_type_id,
142
+ json_extract(value, '$.primaryId') AS primary_id
143
+ FROM json_each(?)
144
+ `
145
+ const effectiveRows = this.db
146
+ .query(
147
+ `WITH requested AS (${requestedObjects})
148
+ SELECT objects.* FROM objects
149
+ JOIN requested USING (object_type_id, primary_id)
150
+ WHERE objects.project_id = ?`
151
+ )
152
+ .all(requested, this.projectId) as EffectiveObjectRow[]
153
+ const sourceRows = this.db
154
+ .query(
155
+ `WITH requested AS (${requestedObjects})
156
+ SELECT rows.* FROM ontology_source_rows AS rows
157
+ JOIN requested USING (object_type_id, primary_id)
158
+ JOIN ontology_sources AS sources
159
+ ON sources.project_id = rows.project_id
160
+ AND sources.source_id = rows.source_id
161
+ AND sources.materialization_id = rows.materialization_id
162
+ WHERE rows.project_id = ? AND rows.entity_kind = 'object'
163
+ AND sources.status = 'active'`
164
+ )
165
+ .all(requested, this.projectId) as SqliteOntologySourceAssertionRow[]
166
+ const overrideRows = this.db
167
+ .query(
168
+ `WITH requested AS (${requestedObjects})
169
+ SELECT overrides.* FROM ontology_overrides AS overrides
170
+ JOIN requested USING (object_type_id, primary_id)
171
+ WHERE overrides.project_id = ? AND overrides.entity_kind = 'object'`
172
+ )
173
+ .all(requested, this.projectId) as ObjectOverrideRow[]
174
+ const telemetryRows = this.db
175
+ .query(
176
+ `WITH requested AS (${requestedObjects}), ranked AS (
177
+ SELECT timeseries.*,
178
+ ROW_NUMBER() OVER (
179
+ PARTITION BY timeseries.object_type_id, timeseries.object_id,
180
+ timeseries.property_id
181
+ ORDER BY timeseries.at DESC
182
+ ) AS rank
183
+ FROM timeseries
184
+ JOIN requested
185
+ ON requested.object_type_id = timeseries.object_type_id
186
+ AND requested.primary_id = timeseries.object_id
187
+ WHERE timeseries.project_id = ?
188
+ )
189
+ SELECT * FROM ranked WHERE rank = 1`
190
+ )
191
+ .all(requested, this.projectId) as TelemetryRow[]
192
+
193
+ const effective = new Map(
194
+ effectiveRows.map((row) => [objectRefKey(objectRefFromColumns(row)), row] as const)
195
+ )
196
+ const sources = activeSourceMap(sourceRows)
197
+ const overrides = new Map(
198
+ overrideRows.map(
199
+ (row) => [objectRefKey(objectRefFromColumns(row)), storedObjectOverride(row)] as const
200
+ )
201
+ )
202
+ const telemetry = new Map<string, StoredTelemetryPoint[]>()
203
+ for (const row of telemetryRows) {
204
+ const key = objectRefKey({ objectTypeId: row.object_type_id, primaryId: row.object_id })
205
+ const points = telemetry.get(key) ?? []
206
+ points.push(storedPoint(row))
207
+ telemetry.set(key, points)
208
+ }
209
+ for (const points of telemetry.values()) {
210
+ points.sort((left, right) =>
211
+ telemetryPointSortKey(left.series, left.at).localeCompare(
212
+ telemetryPointSortKey(right.series, right.at)
213
+ )
214
+ )
215
+ }
216
+
217
+ return refs.map((ref) => {
218
+ const key = objectRefKey(ref)
219
+ const effectiveRow = effective.get(key)
220
+ return {
221
+ ref: structuredClone(ref),
222
+ source: sourceObject(sources.get(projectionEntityKey({ kind: "object", ref }))),
223
+ override: overrides.get(key) ?? null,
224
+ effective: effectiveRow ? effectiveObjectSnapshot(effectiveRow) : null,
225
+ latestTelemetry: telemetry.get(key) ?? [],
226
+ }
227
+ })
228
+ }
229
+
230
+ linkState(ref: OntologyLinkRef): MaterializationLinkState {
231
+ return this.linkStates([ref])[0]!
232
+ }
233
+
234
+ linkStates(refs: readonly OntologyLinkRef[]): readonly MaterializationLinkState[] {
235
+ if (refs.length === 0) return []
236
+ const requested = canonicalJson(
237
+ refs.map((ref) => ({
238
+ sourceTypeId: ref.source.objectTypeId,
239
+ sourceId: ref.source.primaryId,
240
+ linkId: ref.linkId,
241
+ targetTypeId: ref.target.objectTypeId,
242
+ targetId: ref.target.primaryId,
243
+ }))
244
+ )
245
+ const effectiveRequest = `
246
+ SELECT DISTINCT json_extract(value, '$.sourceTypeId') AS source_type_id,
247
+ json_extract(value, '$.sourceId') AS source_id,
248
+ json_extract(value, '$.linkId') AS link_id,
249
+ json_extract(value, '$.targetTypeId') AS target_type_id,
250
+ json_extract(value, '$.targetId') AS target_id
251
+ FROM json_each(?)
252
+ `
253
+ const sourceRequest = `
254
+ SELECT DISTINCT json_extract(value, '$.sourceTypeId') AS source_type_id,
255
+ json_extract(value, '$.sourceId') AS source_primary_id,
256
+ json_extract(value, '$.linkId') AS link_id,
257
+ json_extract(value, '$.targetTypeId') AS target_type_id,
258
+ json_extract(value, '$.targetId') AS target_primary_id
259
+ FROM json_each(?)
260
+ `
261
+ const effectiveRows = this.db
262
+ .query(
263
+ `WITH requested AS (${effectiveRequest})
264
+ SELECT links.* FROM links
265
+ JOIN requested USING (source_type_id, source_id, link_id, target_type_id, target_id)
266
+ WHERE links.project_id = ?`
267
+ )
268
+ .all(requested, this.projectId) as EffectiveLinkRow[]
269
+ const sourceRows = this.db
270
+ .query(
271
+ `WITH requested AS (${sourceRequest})
272
+ SELECT rows.* FROM ontology_source_rows AS rows
273
+ JOIN requested USING (
274
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
275
+ )
276
+ JOIN ontology_sources AS sources
277
+ ON sources.project_id = rows.project_id
278
+ AND sources.source_id = rows.source_id
279
+ AND sources.materialization_id = rows.materialization_id
280
+ WHERE rows.project_id = ? AND rows.entity_kind = 'link'
281
+ AND sources.status = 'active'`
282
+ )
283
+ .all(requested, this.projectId) as SqliteOntologySourceAssertionRow[]
284
+ const overrideRows = this.db
285
+ .query(
286
+ `WITH requested AS (${sourceRequest})
287
+ SELECT overrides.* FROM ontology_overrides AS overrides
288
+ JOIN requested USING (
289
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
290
+ )
291
+ WHERE overrides.project_id = ? AND overrides.entity_kind = 'link'`
292
+ )
293
+ .all(requested, this.projectId) as LinkOverrideRow[]
294
+ const effective = new Map(
295
+ effectiveRows.map((row) => [linkRefKey(linkRefFromColumns(row)), row] as const)
296
+ )
297
+ const sources = activeSourceMap(sourceRows)
298
+ const overrides = new Map(
299
+ overrideRows.map(
300
+ (row) => [linkRefKey(linkRefFromOverrideColumns(row)), storedLinkOverride(row)] as const
301
+ )
302
+ )
303
+ return refs.map((ref) => {
304
+ const key = linkRefKey(ref)
305
+ const effectiveRow = effective.get(key)
306
+ return {
307
+ ref: structuredClone(ref),
308
+ source: sourceLink(sources.get(projectionEntityKey({ kind: "link", ref }))),
309
+ override: overrides.get(key) ?? null,
310
+ effective: effectiveRow ? effectiveLinkSnapshot(effectiveRow) : null,
311
+ }
312
+ })
313
+ }
314
+
315
+ exactPoint(series: TelemetrySeriesRef, at: string): StoredTelemetryPoint | null {
316
+ return this.exactPoints([{ series, at }])[0] ?? null
317
+ }
318
+
319
+ exactPoints(
320
+ points: readonly { readonly series: TelemetrySeriesRef; readonly at: string }[]
321
+ ): readonly StoredTelemetryPoint[] {
322
+ if (points.length === 0) return []
323
+ const requested = canonicalJson(
324
+ points.map(({ series, at }) => ({
325
+ objectTypeId: series.object.objectTypeId,
326
+ objectId: series.object.primaryId,
327
+ propertyId: series.propertyId,
328
+ at,
329
+ }))
330
+ )
331
+ const rows = this.db
332
+ .query(
333
+ `WITH requested AS (
334
+ SELECT DISTINCT json_extract(value, '$.objectTypeId') AS object_type_id,
335
+ json_extract(value, '$.objectId') AS object_id,
336
+ json_extract(value, '$.propertyId') AS property_id,
337
+ json_extract(value, '$.at') AS at
338
+ FROM json_each(?)
339
+ )
340
+ SELECT timeseries.* FROM timeseries
341
+ JOIN requested USING (object_type_id, object_id, property_id, at)
342
+ WHERE timeseries.project_id = ?`
343
+ )
344
+ .all(requested, this.projectId) as TelemetryRow[]
345
+ const found = new Map(
346
+ rows.map((row) => {
347
+ const point = storedPoint(row)
348
+ return [telemetryPointSortKey(point.series, point.at), point] as const
349
+ })
350
+ )
351
+ return points.flatMap((point) => {
352
+ const stored = found.get(telemetryPointSortKey(point.series, point.at))
353
+ return stored ? [stored] : []
354
+ })
355
+ }
356
+
357
+ linkScopes(
358
+ scopes: readonly { readonly source: OntologyObjectRef; readonly linkId: string }[]
359
+ ): readonly MaterializationLinkScopeState[] {
360
+ if (scopes.length === 0) return []
361
+ const requested = scopes.map(({ source, linkId }) => ({
362
+ scopeSortKey: linkScopeSortKey(source, linkId),
363
+ sourceTypeId: source.objectTypeId,
364
+ sourceId: source.primaryId,
365
+ linkId,
366
+ }))
367
+ const accumulators = new Map(
368
+ scopes.map(({ source, linkId }) => {
369
+ const key = linkScopeSortKey(source, linkId)
370
+ return [key, startScopeAccumulator(source, linkId, key)] as const
371
+ })
372
+ )
373
+ const rows = this.db
374
+ .query(
375
+ `
376
+ WITH requested AS (
377
+ SELECT DISTINCT
378
+ json_extract(value, '$.scopeSortKey') AS scope_sort_key,
379
+ json_extract(value, '$.sourceTypeId') AS source_type_id,
380
+ json_extract(value, '$.sourceId') AS source_id,
381
+ json_extract(value, '$.linkId') AS link_id
382
+ FROM json_each(?)
383
+ )
384
+ SELECT requested.scope_sort_key, links.*
385
+ FROM links
386
+ JOIN requested USING (source_type_id, source_id, link_id)
387
+ WHERE links.project_id = ?
388
+ ORDER BY requested.scope_sort_key, ${linkSortExpression("links")}
389
+ `
390
+ )
391
+ .iterate(canonicalJson(requested), this.projectId) as Iterable<LinkScopeRow>
392
+ for (const row of rows) {
393
+ const accumulator = accumulators.get(row.scope_sort_key)
394
+ if (!accumulator) {
395
+ throw new MaterializationConflictError(
396
+ "effective-state",
397
+ `Unexpected link scope '${row.scope_sort_key}' returned by storage.`
398
+ )
399
+ }
400
+ appendScopeSnapshot(accumulator, effectiveLinkSnapshot(row))
401
+ }
402
+ return scopes.map(({ source, linkId }) =>
403
+ finishScopeAccumulator(accumulators.get(linkScopeSortKey(source, linkId))!)
404
+ )
405
+ }
406
+
407
+ linkScope(source: OntologyObjectRef, linkId: string): MaterializationLinkScopeState {
408
+ const [scope] = this.linkScopes([{ source, linkId }])
409
+ if (!scope) {
410
+ throw new MaterializationConflictError(
411
+ "effective-state",
412
+ "Link scope lookup returned no row."
413
+ )
414
+ }
415
+ return scope
416
+ }
417
+
418
+ *incidentLinks(
419
+ objects: readonly OntologyObjectRef[],
420
+ pageRows: number
421
+ ): Iterable<OntologyLinkRef[]> {
422
+ if (objects.length === 0) return
423
+ const values = objects.map(() => "(?, ?)").join(", ")
424
+ const args = objects.flatMap((ref) => [ref.objectTypeId, ref.primaryId])
425
+ let cursor: string | null = null
426
+ while (true) {
427
+ const rows = this.db
428
+ .query(
429
+ `
430
+ WITH requested(object_type_id, primary_id) AS (VALUES ${values}),
431
+ authority_links AS (
432
+ SELECT source_type_id, source_id, link_id, target_type_id, target_id
433
+ FROM links WHERE project_id = ?
434
+ UNION
435
+ SELECT source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
436
+ FROM ontology_overrides
437
+ WHERE project_id = ? AND entity_kind = 'link'
438
+ UNION
439
+ SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
440
+ rows.target_type_id, rows.target_primary_id
441
+ FROM ontology_source_rows AS rows
442
+ JOIN ontology_sources AS sources
443
+ ON sources.project_id = rows.project_id
444
+ AND sources.source_id = rows.source_id
445
+ AND sources.materialization_id = rows.materialization_id
446
+ WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.status = 'active'
447
+ ), selected AS (
448
+ SELECT DISTINCT links.*,
449
+ ${linkSortExpression("links")} AS sort_key
450
+ FROM authority_links AS links
451
+ WHERE EXISTS (
452
+ SELECT 1 FROM requested
453
+ WHERE (requested.object_type_id = links.source_type_id
454
+ AND requested.primary_id = links.source_id)
455
+ OR (requested.object_type_id = links.target_type_id
456
+ AND requested.primary_id = links.target_id)
457
+ )
458
+ )
459
+ SELECT * FROM selected
460
+ WHERE (? IS NULL OR sort_key > ?)
461
+ ORDER BY sort_key
462
+ LIMIT ?
463
+ `
464
+ )
465
+ .all(
466
+ ...args,
467
+ this.projectId,
468
+ this.projectId,
469
+ this.projectId,
470
+ cursor,
471
+ cursor,
472
+ pageRows
473
+ ) as (EffectiveLinkRow & { readonly sort_key: string })[]
474
+ if (rows.length === 0) break
475
+ yield rows.map(linkRefFromColumns)
476
+ cursor = rows[rows.length - 1]?.sort_key ?? null
477
+ }
478
+ }
479
+
480
+ *replacementIdentities(input: ReplacementIdentityInput): Iterable<ReplacementIdentity[]> {
481
+ let cursor: string | null = null
482
+ while (true) {
483
+ if (input.kind === "object") {
484
+ const rows = this.replacementObjectRows(input, cursor)
485
+ if (rows.length === 0) break
486
+ yield rows.map((row) => ({
487
+ kind: "object",
488
+ ref: { objectTypeId: row.object_type_id, primaryId: row.primary_id },
489
+ sortKey: row.sort_key,
490
+ diffRequired: true,
491
+ }))
492
+ cursor = rows[rows.length - 1]?.sort_key ?? null
493
+ continue
494
+ }
495
+ const rows = this.replacementLinkRows(input, cursor)
496
+ if (rows.length === 0) break
497
+ yield rows.map((row) => ({
498
+ kind: "link",
499
+ ref: linkRefFromColumns(row),
500
+ sortKey: row.sort_key,
501
+ diffRequired: row.diff_required === 1,
502
+ }))
503
+ cursor = rows[rows.length - 1]?.sort_key ?? null
504
+ }
505
+ }
506
+
507
+ replacementObjectStates(
508
+ sourceId: string,
509
+ candidateMaterializationId: string,
510
+ refs: readonly OntologyObjectRef[]
511
+ ): readonly SourceReplacementObjectState[] {
512
+ const base = this.objectStates(refs)
513
+ const candidates = this.replacementSources(
514
+ sourceId,
515
+ [candidateMaterializationId],
516
+ refs.map((ref) => ({ kind: "object" as const, ref }))
517
+ )
518
+ return base.map((state) => ({
519
+ ref: state.ref,
520
+ candidateSource: sourceObject(
521
+ candidates.byMaterialization
522
+ .get(candidateMaterializationId)
523
+ ?.get(projectionEntityKey({ kind: "object", ref: state.ref }))
524
+ ),
525
+ override: state.override,
526
+ effective: state.effective,
527
+ latestTelemetry: state.latestTelemetry,
528
+ }))
529
+ }
530
+
531
+ replacementLinkStates(
532
+ sourceId: string,
533
+ candidateMaterializationId: string,
534
+ materializationIds: readonly string[],
535
+ identities: readonly Extract<ReplacementIdentity, { readonly kind: "link" }>[]
536
+ ): readonly SourceReplacementLinkState[] {
537
+ const refs = identities.map((identity) => identity.ref)
538
+ const base = this.linkStates(refs)
539
+ const replacements = this.replacementSources(
540
+ sourceId,
541
+ materializationIds,
542
+ refs.map((ref) => ({ kind: "link" as const, ref }))
543
+ )
544
+ const candidate = replacements.byMaterialization.get(candidateMaterializationId)
545
+ return base.map((state, index) => {
546
+ const key = projectionEntityKey({ kind: "link", ref: state.ref })
547
+ return {
548
+ ref: state.ref,
549
+ candidateSource: replacements.owned.has(key)
550
+ ? sourceLink(candidate?.get(key))
551
+ : state.source,
552
+ override: state.override,
553
+ effective: state.effective,
554
+ diffRequired: identities[index]!.diffRequired,
555
+ }
556
+ })
557
+ }
558
+
559
+ effectiveObjectRevision(ref: OntologyObjectRef): {
560
+ readonly version: number
561
+ readonly lastCommitId: string | null
562
+ } | null {
563
+ const row = this.db
564
+ .query(
565
+ `SELECT version, last_commit_id FROM objects
566
+ WHERE project_id = ? AND object_type_id = ? AND primary_id = ?`
567
+ )
568
+ .get(this.projectId, ref.objectTypeId, ref.primaryId) as {
569
+ readonly version: number
570
+ readonly last_commit_id: string | null
571
+ } | null
572
+ return row ? { version: row.version, lastCommitId: row.last_commit_id } : null
573
+ }
574
+
575
+ effectiveLinkLastCommit(ref: OntologyLinkRef): string | null | undefined {
576
+ const row = this.db
577
+ .query(
578
+ `
579
+ SELECT last_commit_id FROM links
580
+ WHERE project_id = ? AND source_type_id = ? AND source_id = ? AND link_id = ?
581
+ AND target_type_id = ? AND target_id = ?
582
+ `
583
+ )
584
+ .get(
585
+ this.projectId,
586
+ ref.source.objectTypeId,
587
+ ref.source.primaryId,
588
+ ref.linkId,
589
+ ref.target.objectTypeId,
590
+ ref.target.primaryId
591
+ ) as { readonly last_commit_id: string | null } | null
592
+ return row ? row.last_commit_id : undefined
593
+ }
594
+
595
+ overrideLastCommit(kind: "object" | "link", entityKey: string): string | null {
596
+ const row = this.db
597
+ .query(
598
+ `SELECT last_commit_id FROM ontology_overrides
599
+ WHERE project_id = ? AND entity_kind = ? AND entity_key = ?`
600
+ )
601
+ .get(this.projectId, kind, entityKey) as { readonly last_commit_id: string } | null
602
+ return row?.last_commit_id ?? null
603
+ }
604
+
605
+ private replacementSources(
606
+ sourceId: string,
607
+ materializationIds: readonly string[],
608
+ refs: readonly ProjectionEntityRef[]
609
+ ): ReplacementSources {
610
+ if (materializationIds.length === 0 || refs.length === 0) {
611
+ return { owned: new Set(), byMaterialization: new Map() }
612
+ }
613
+ const requestedKeys = canonicalJson(refs.map((ref) => projectionEntityKey(ref)))
614
+ const requestedMaterializations = canonicalJson([...new Set(materializationIds)])
615
+ const rows = this.db
616
+ .query(
617
+ `WITH requested_entities AS (
618
+ SELECT value AS entity_key FROM json_each(?)
619
+ ), requested_materializations AS (
620
+ SELECT value AS materialization_id FROM json_each(?)
621
+ )
622
+ SELECT rows.* FROM ontology_source_rows AS rows
623
+ JOIN requested_entities USING (entity_key)
624
+ JOIN requested_materializations USING (materialization_id)
625
+ WHERE rows.project_id = ? AND rows.source_id = ?`
626
+ )
627
+ .all(
628
+ requestedKeys,
629
+ requestedMaterializations,
630
+ this.projectId,
631
+ sourceId
632
+ ) as SqliteOntologySourceAssertionRow[]
633
+ const owned = new Set<string>()
634
+ const byMaterialization = new Map<string, Map<string, StoredSourceAssertion>>()
635
+ for (const row of rows) {
636
+ const source = storedSource(row)
637
+ const key = projectionEntityKey(source.assertion)
638
+ owned.add(key)
639
+ const materialization = byMaterialization.get(row.materialization_id) ?? new Map()
640
+ materialization.set(key, source)
641
+ byMaterialization.set(row.materialization_id, materialization)
642
+ }
643
+ return { owned, byMaterialization }
644
+ }
645
+
646
+ private replacementObjectRows(
647
+ input: ReplacementIdentityInput,
648
+ cursor: string | null
649
+ ): (EffectiveObjectRow & { readonly sort_key: string })[] {
650
+ return this.db
651
+ .query(
652
+ `
653
+ SELECT DISTINCT object_type_id, primary_id, entity_sort_key AS sort_key
654
+ FROM ontology_source_rows
655
+ WHERE project_id = ? AND source_id = ? AND entity_kind = 'object'
656
+ AND materialization_id IN (?, COALESCE(?, ''))
657
+ AND (? IS NULL OR entity_sort_key > ?)
658
+ ORDER BY sort_key
659
+ LIMIT ?
660
+ `
661
+ )
662
+ .all(
663
+ this.projectId,
664
+ input.sourceId,
665
+ input.candidateMaterializationId,
666
+ input.previousMaterializationId,
667
+ cursor,
668
+ cursor,
669
+ input.pageRows
670
+ ) as (EffectiveObjectRow & { readonly sort_key: string })[]
671
+ }
672
+
673
+ private replacementLinkRows(
674
+ input: ReplacementIdentityInput,
675
+ cursor: string | null
676
+ ): (EffectiveLinkRow & { readonly sort_key: string; readonly diff_required: number })[] {
677
+ return this.db
678
+ .query(
679
+ `
680
+ WITH incident_objects AS (
681
+ SELECT
682
+ json_extract(payload, '$.ref.objectTypeId') AS object_type_id,
683
+ json_extract(payload, '$.ref.primaryId') AS primary_id
684
+ FROM ${SQLITE_MATERIALIZATION_WORK_TABLE}
685
+ WHERE session_id = ? AND kind = 'incident-object'
686
+ ), authority_links AS (
687
+ SELECT source_type_id, source_id, link_id, target_type_id, target_id
688
+ FROM links WHERE project_id = ?
689
+ UNION
690
+ SELECT source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
691
+ FROM ontology_overrides
692
+ WHERE project_id = ? AND entity_kind = 'link'
693
+ UNION
694
+ SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
695
+ rows.target_type_id, rows.target_primary_id
696
+ FROM ontology_source_rows AS rows
697
+ JOIN ontology_sources AS sources
698
+ ON sources.project_id = rows.project_id
699
+ AND sources.source_id = rows.source_id
700
+ AND sources.materialization_id = rows.materialization_id
701
+ WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.status = 'active'
702
+ ), replacement_links AS (
703
+ SELECT source_type_id, source_primary_id AS source_id, link_id,
704
+ target_type_id, target_primary_id AS target_id
705
+ FROM ontology_source_rows
706
+ WHERE project_id = ? AND source_id = ? AND entity_kind = 'link'
707
+ AND materialization_id IN (?, COALESCE(?, ''))
708
+ ), incident_links AS (
709
+ SELECT links.* FROM authority_links AS links
710
+ WHERE EXISTS (
711
+ SELECT 1 FROM incident_objects
712
+ WHERE (incident_objects.object_type_id = links.source_type_id
713
+ AND incident_objects.primary_id = links.source_id)
714
+ OR (incident_objects.object_type_id = links.target_type_id
715
+ AND incident_objects.primary_id = links.target_id)
716
+ )
717
+ ), diff_links AS (
718
+ SELECT * FROM replacement_links
719
+ UNION SELECT * FROM incident_links
720
+ ), affected_scopes AS (
721
+ SELECT DISTINCT source_type_id, source_id, link_id FROM diff_links
722
+ ), all_links AS (
723
+ SELECT *, 1 AS diff_required FROM diff_links
724
+ UNION ALL
725
+ SELECT links.source_type_id, links.source_id, links.link_id,
726
+ links.target_type_id, links.target_id, 0 AS diff_required
727
+ FROM links
728
+ JOIN affected_scopes USING (source_type_id, source_id, link_id)
729
+ WHERE links.project_id = ?
730
+ ), selected AS (
731
+ SELECT source_type_id, source_id, link_id, target_type_id, target_id,
732
+ ${linkSortExpression("all_links")} AS sort_key,
733
+ MAX(diff_required) AS diff_required
734
+ FROM all_links
735
+ GROUP BY source_type_id, source_id, link_id, target_type_id, target_id
736
+ )
737
+ SELECT * FROM selected
738
+ WHERE (? IS NULL OR sort_key > ?)
739
+ ORDER BY sort_key
740
+ LIMIT ?
741
+ `
742
+ )
743
+ .all(
744
+ input.sessionId,
745
+ this.projectId,
746
+ this.projectId,
747
+ this.projectId,
748
+ this.projectId,
749
+ input.sourceId,
750
+ input.candidateMaterializationId,
751
+ input.previousMaterializationId,
752
+ this.projectId,
753
+ cursor,
754
+ cursor,
755
+ input.pageRows
756
+ ) as (EffectiveLinkRow & { readonly sort_key: string; readonly diff_required: number })[]
757
+ }
758
+ }
759
+
760
+ function activeSourceMap(
761
+ rows: readonly SqliteOntologySourceAssertionRow[]
762
+ ): ReadonlyMap<string, StoredSourceAssertion> {
763
+ const result = new Map<string, StoredSourceAssertion>()
764
+ for (const row of rows) {
765
+ const source = storedSource(row)
766
+ const key = projectionEntityKey(source.assertion)
767
+ if (result.has(key)) {
768
+ throw new MaterializationConflictError(
769
+ "source-materialization",
770
+ `Multiple active sources assert ${key}.`
771
+ )
772
+ }
773
+ result.set(key, source)
774
+ }
775
+ return result
776
+ }
777
+
778
+ function sourceObject(
779
+ source: StoredSourceAssertion | undefined
780
+ ): StoredSourceObjectAssertion | null {
781
+ return source?.assertion.kind === "object" ? (source as StoredSourceObjectAssertion) : null
782
+ }
783
+
784
+ function sourceLink(source: StoredSourceAssertion | undefined): StoredSourceLinkAssertion | null {
785
+ return source?.assertion.kind === "link" ? (source as StoredSourceLinkAssertion) : null
786
+ }
787
+
788
+ function storedObjectOverride(row: ObjectOverrideRow): StoredObjectOverride {
789
+ const ref = objectRefFromColumns(row)
790
+ return {
791
+ ref,
792
+ value: parseJson<StoredObjectOverride["value"]>(row.value),
793
+ lastCommitId: row.last_commit_id,
794
+ updatedAt: row.updated_at,
795
+ }
796
+ }
797
+
798
+ function storedLinkOverride(row: LinkOverrideRow): StoredLinkOverride {
799
+ const ref = linkRefFromOverrideColumns(row)
800
+ return {
801
+ ref,
802
+ value: parseJson<StoredLinkOverride["value"]>(row.value),
803
+ lastCommitId: row.last_commit_id,
804
+ updatedAt: row.updated_at,
805
+ }
806
+ }
807
+
808
+ function linkRefFromOverrideColumns(row: LinkOverrideRow): OntologyLinkRef {
809
+ return {
810
+ source: { objectTypeId: row.source_type_id, primaryId: row.source_primary_id },
811
+ linkId: row.link_id,
812
+ target: { objectTypeId: row.target_type_id, primaryId: row.target_primary_id },
813
+ }
814
+ }
815
+
816
+ function storedSource(row: SqliteOntologySourceAssertionRow): StoredSourceAssertion {
817
+ return {
818
+ source: { projectionId: row.source_id },
819
+ materializationId: row.materialization_id,
820
+ root: parseJson<StoredSourceAssertion["root"]>(row.root),
821
+ assertion: parseJson<StoredSourceAssertion["assertion"]>(row.assertion),
822
+ stagingOrdinal: row.staging_ordinal,
823
+ } as StoredSourceAssertion
824
+ }
825
+
826
+ function effectiveObjectSnapshot(row: EffectiveObjectRow): EffectiveObjectSnapshot {
827
+ if (!row.last_commit_id) {
828
+ throw new MaterializationConflictError(
829
+ "effective-state",
830
+ `Effective object ${row.object_type_id}:${row.primary_id} lacks materializer provenance.`
831
+ )
832
+ }
833
+ return {
834
+ ref: objectRefFromColumns(row),
835
+ properties: parseJson<EffectiveObjectSnapshot["properties"]>(row.properties),
836
+ version: row.version,
837
+ createdAt: row.created_at,
838
+ updatedAt: row.updated_at,
839
+ lastCommitId: row.last_commit_id,
840
+ }
841
+ }
842
+
843
+ function effectiveLinkSnapshot(row: EffectiveLinkRow): EffectiveLinkSnapshot {
844
+ if (!row.last_commit_id) {
845
+ throw new MaterializationConflictError(
846
+ "effective-state",
847
+ `Effective link ${linkRefKey(linkRefFromColumns(row))} lacks materializer provenance.`
848
+ )
849
+ }
850
+ return {
851
+ ref: linkRefFromColumns(row),
852
+ ...(row.properties === null
853
+ ? {}
854
+ : {
855
+ properties: parseJson<NonNullable<EffectiveLinkSnapshot["properties"]>>(row.properties),
856
+ }),
857
+ createdAt: row.created_at,
858
+ updatedAt: row.updated_at,
859
+ lastCommitId: row.last_commit_id,
860
+ }
861
+ }
862
+
863
+ function storedPoint(row: TelemetryRow): StoredTelemetryPoint {
864
+ if (!row.last_commit_id) {
865
+ throw new MaterializationConflictError(
866
+ "timeseries-point",
867
+ `Telemetry point ${telemetryPointSortKey(
868
+ {
869
+ object: { objectTypeId: row.object_type_id, primaryId: row.object_id },
870
+ propertyId: row.property_id,
871
+ },
872
+ row.at
873
+ )} lacks materializer provenance.`
874
+ )
875
+ }
876
+ return {
877
+ series: {
878
+ object: { objectTypeId: row.object_type_id, primaryId: row.object_id },
879
+ propertyId: row.property_id,
880
+ },
881
+ value: parseJson<StoredTelemetryPoint["value"]>(row.value),
882
+ ...(row.unit === null ? {} : { unit: row.unit }),
883
+ at: row.at,
884
+ lastCommitId: row.last_commit_id,
885
+ }
886
+ }
887
+
888
+ export function objectSortExpression(alias?: string): string {
889
+ const prefix = alias ? `${alias}.` : ""
890
+ return `LOWER(HEX(CAST(json_array(${prefix}object_type_id, ${prefix}primary_id) AS BLOB)))`
891
+ }
892
+
893
+ export function linkSortExpression(alias?: string): string {
894
+ const prefix = alias ? `${alias}.` : ""
895
+ return `LOWER(HEX(CAST(json_array(${prefix}source_type_id, ${prefix}source_id, ${prefix}link_id, ${prefix}target_type_id, ${prefix}target_id) AS BLOB)))`
896
+ }
897
+
898
+ export function pointSortExpression(alias?: string): string {
899
+ const prefix = alias ? `${alias}.` : ""
900
+ return `LOWER(HEX(CAST(json_array(${prefix}object_type_id, ${prefix}object_id, ${prefix}property_id, ${prefix}at) AS BLOB)))`
901
+ }