@sixb/pg 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 +73 -0
  3. package/dist/agents/agent-storage.d.ts +20 -0
  4. package/dist/agents/agent-storage.d.ts.map +1 -0
  5. package/dist/agents/index.d.ts +6 -0
  6. package/dist/agents/index.d.ts.map +1 -0
  7. package/dist/agents/messages.d.ts +13 -0
  8. package/dist/agents/messages.d.ts.map +1 -0
  9. package/dist/agents/rows.d.ts +77 -0
  10. package/dist/agents/rows.d.ts.map +1 -0
  11. package/dist/agents/runs.d.ts +25 -0
  12. package/dist/agents/runs.d.ts.map +1 -0
  13. package/dist/agents/threads.d.ts +13 -0
  14. package/dist/agents/threads.d.ts.map +1 -0
  15. package/dist/auth-storage/access-tokens.d.ts +33 -0
  16. package/dist/auth-storage/access-tokens.d.ts.map +1 -0
  17. package/dist/auth-storage/auth-storage.d.ts +41 -0
  18. package/dist/auth-storage/auth-storage.d.ts.map +1 -0
  19. package/dist/auth-storage/group-memberships.d.ts +21 -0
  20. package/dist/auth-storage/group-memberships.d.ts.map +1 -0
  21. package/dist/auth-storage/identities.d.ts +17 -0
  22. package/dist/auth-storage/identities.d.ts.map +1 -0
  23. package/dist/auth-storage/index.d.ts +3 -0
  24. package/dist/auth-storage/index.d.ts.map +1 -0
  25. package/dist/auth-storage/invitations.d.ts +29 -0
  26. package/dist/auth-storage/invitations.d.ts.map +1 -0
  27. package/dist/auth-storage/magic-links.d.ts +28 -0
  28. package/dist/auth-storage/magic-links.d.ts.map +1 -0
  29. package/dist/auth-storage/oidc-attempts.d.ts +18 -0
  30. package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
  31. package/dist/auth-storage/rows.d.ts +138 -0
  32. package/dist/auth-storage/rows.d.ts.map +1 -0
  33. package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
  34. package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
  35. package/dist/auth-storage/service-accounts.d.ts +14 -0
  36. package/dist/auth-storage/service-accounts.d.ts.map +1 -0
  37. package/dist/auth-storage/sessions.d.ts +55 -0
  38. package/dist/auth-storage/sessions.d.ts.map +1 -0
  39. package/dist/auth-storage/shared.d.ts +159 -0
  40. package/dist/auth-storage/shared.d.ts.map +1 -0
  41. package/dist/auth-storage/users.d.ts +19 -0
  42. package/dist/auth-storage/users.d.ts.map +1 -0
  43. package/dist/index.d.ts +127 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +11957 -0
  46. package/dist/index.js.map +55 -0
  47. package/dist/latest-run-query.d.ts +24 -0
  48. package/dist/latest-run-query.d.ts.map +1 -0
  49. package/dist/migrations.d.ts +20 -0
  50. package/dist/migrations.d.ts.map +1 -0
  51. package/dist/ontology-storage/commits.d.ts +11 -0
  52. package/dist/ontology-storage/commits.d.ts.map +1 -0
  53. package/dist/ontology-storage/index.d.ts +22 -0
  54. package/dist/ontology-storage/index.d.ts.map +1 -0
  55. package/dist/ontology-storage/materialization-session.d.ts +63 -0
  56. package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
  57. package/dist/ontology-storage/materialization-state.d.ts +69 -0
  58. package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
  59. package/dist/ontology-storage/materialization-writer.d.ts +15 -0
  60. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
  61. package/dist/ontology-storage/materializations.d.ts +33 -0
  62. package/dist/ontology-storage/materializations.d.ts.map +1 -0
  63. package/dist/ontology-storage/outbox.d.ts +12 -0
  64. package/dist/ontology-storage/outbox.d.ts.map +1 -0
  65. package/dist/ontology-storage/shared.d.ts +95 -0
  66. package/dist/ontology-storage/shared.d.ts.map +1 -0
  67. package/dist/ontology-storage/sources.d.ts +25 -0
  68. package/dist/ontology-storage/sources.d.ts.map +1 -0
  69. package/dist/pg-action-run-storage.d.ts +21 -0
  70. package/dist/pg-action-run-storage.d.ts.map +1 -0
  71. package/dist/pg-client.d.ts +58 -0
  72. package/dist/pg-client.d.ts.map +1 -0
  73. package/dist/pg-object-query-compiler.d.ts +55 -0
  74. package/dist/pg-object-query-compiler.d.ts.map +1 -0
  75. package/dist/pg-object-storage.d.ts +90 -0
  76. package/dist/pg-object-storage.d.ts.map +1 -0
  77. package/dist/pg-pipeline-run-storage.d.ts +18 -0
  78. package/dist/pg-pipeline-run-storage.d.ts.map +1 -0
  79. package/dist/pg-projection-run-storage.d.ts +19 -0
  80. package/dist/pg-projection-run-storage.d.ts.map +1 -0
  81. package/dist/pg-rules-storage.d.ts +25 -0
  82. package/dist/pg-rules-storage.d.ts.map +1 -0
  83. package/dist/pg-sync-run-storage.d.ts +15 -0
  84. package/dist/pg-sync-run-storage.d.ts.map +1 -0
  85. package/dist/pg-timeseries-storage.d.ts +32 -0
  86. package/dist/pg-timeseries-storage.d.ts.map +1 -0
  87. package/dist/pg-webhook-delivery-storage.d.ts +17 -0
  88. package/dist/pg-webhook-delivery-storage.d.ts.map +1 -0
  89. package/dist/pg-webhook-run-storage.d.ts +14 -0
  90. package/dist/pg-webhook-run-storage.d.ts.map +1 -0
  91. package/dist/pg-workflow-intervention-storage.d.ts +16 -0
  92. package/dist/pg-workflow-intervention-storage.d.ts.map +1 -0
  93. package/dist/pg-workflow-run-storage.d.ts +49 -0
  94. package/dist/pg-workflow-run-storage.d.ts.map +1 -0
  95. package/dist/run-list-query.d.ts +30 -0
  96. package/dist/run-list-query.d.ts.map +1 -0
  97. package/dist/storage-errors.d.ts +16 -0
  98. package/dist/storage-errors.d.ts.map +1 -0
  99. package/dist/transactions.d.ts +10 -0
  100. package/dist/transactions.d.ts.map +1 -0
  101. package/dist/tsconfig.json +5 -0
  102. package/package.json +60 -0
  103. package/src/agents/agent-storage.ts +26 -0
  104. package/src/agents/index.ts +5 -0
  105. package/src/agents/messages.ts +144 -0
  106. package/src/agents/rows.ts +227 -0
  107. package/src/agents/runs.ts +330 -0
  108. package/src/agents/threads.ts +116 -0
  109. package/src/auth-storage/access-tokens.ts +254 -0
  110. package/src/auth-storage/auth-storage.ts +798 -0
  111. package/src/auth-storage/group-memberships.ts +47 -0
  112. package/src/auth-storage/identities.ts +85 -0
  113. package/src/auth-storage/index.ts +2 -0
  114. package/src/auth-storage/invitations.ts +280 -0
  115. package/src/auth-storage/magic-links.ts +140 -0
  116. package/src/auth-storage/oidc-attempts.ts +93 -0
  117. package/src/auth-storage/rows.ts +352 -0
  118. package/src/auth-storage/service-account-group-memberships.ts +150 -0
  119. package/src/auth-storage/service-accounts.ts +154 -0
  120. package/src/auth-storage/sessions.ts +193 -0
  121. package/src/auth-storage/shared.ts +799 -0
  122. package/src/auth-storage/users.ts +196 -0
  123. package/src/index.ts +367 -0
  124. package/src/latest-run-query.ts +42 -0
  125. package/src/migrations/001-initial-schema.sql +1195 -0
  126. package/src/migrations.ts +280 -0
  127. package/src/ontology-storage/commits.ts +115 -0
  128. package/src/ontology-storage/index.ts +35 -0
  129. package/src/ontology-storage/materialization-session.ts +426 -0
  130. package/src/ontology-storage/materialization-state.ts +1109 -0
  131. package/src/ontology-storage/materialization-writer.ts +509 -0
  132. package/src/ontology-storage/materializations.ts +939 -0
  133. package/src/ontology-storage/outbox.ts +216 -0
  134. package/src/ontology-storage/shared.ts +293 -0
  135. package/src/ontology-storage/sources.ts +658 -0
  136. package/src/pg-action-run-storage.ts +657 -0
  137. package/src/pg-client.ts +124 -0
  138. package/src/pg-object-query-compiler.ts +1545 -0
  139. package/src/pg-object-storage.ts +638 -0
  140. package/src/pg-pipeline-run-storage.ts +457 -0
  141. package/src/pg-projection-run-storage.ts +526 -0
  142. package/src/pg-rules-storage.ts +308 -0
  143. package/src/pg-sync-run-storage.ts +263 -0
  144. package/src/pg-timeseries-storage.ts +180 -0
  145. package/src/pg-webhook-delivery-storage.ts +180 -0
  146. package/src/pg-webhook-run-storage.ts +185 -0
  147. package/src/pg-workflow-intervention-storage.ts +339 -0
  148. package/src/pg-workflow-run-storage.ts +1065 -0
  149. package/src/run-list-query.ts +108 -0
  150. package/src/sql.d.ts +4 -0
  151. package/src/storage-errors.ts +72 -0
  152. package/src/transactions.ts +58 -0
@@ -0,0 +1,426 @@
1
+ import { MaterializationConflictError } from "@sixb/core/internal/materialization"
2
+ import {
3
+ correlateMaterializationChunk,
4
+ duplicateMaterializationWork as duplicateWork,
5
+ invalidCorrelation,
6
+ materializationPlanItems,
7
+ ProviderMaterializationSessionState,
8
+ type ProviderMaterializationTransactionLifecycle,
9
+ prepareMaterializationWork,
10
+ } from "@sixb/core/internal/ontology-storage-provider"
11
+ import type {
12
+ MaterializationEventWorkRecord,
13
+ MaterializationPlanChunk,
14
+ MaterializationPlanHeader,
15
+ MaterializationSession,
16
+ MaterializationWorkPage,
17
+ MaterializationWorkRecord,
18
+ StageMaterializationWorkInput,
19
+ StreamMaterializationWorkInput,
20
+ } from "@sixb/core/storage"
21
+ import type { SQLClient } from "../pg-client"
22
+ import { PG_MATERIALIZATION_WORK_TABLE, PG_REPLACEMENT_WORK_TABLE } from "./materialization-state"
23
+ import { jsonParameter } from "./shared"
24
+
25
+ export interface PgOntologyTransactionContext {
26
+ readonly id: object
27
+ readonly materializations: ProviderMaterializationTransactionLifecycle
28
+ active: boolean
29
+ }
30
+
31
+ interface WorkCursor {
32
+ readonly majorOrder: number
33
+ readonly minorOrder: number
34
+ readonly sortOne: string
35
+ readonly sortTwo: string
36
+ readonly recordKey: string
37
+ }
38
+
39
+ export interface PgChunkSequenceProgress {
40
+ readonly appliedPlanCount: number
41
+ readonly appliedOutboxCount: number
42
+ readonly appliedPlanCursor: WorkCursor | null
43
+ readonly appliedEventCursor: WorkCursor | null
44
+ }
45
+
46
+ export class PgMaterializationSessionState extends ProviderMaterializationSessionState {
47
+ appliedPlanCursor: WorkCursor | null = null
48
+ appliedEventCursor: WorkCursor | null = null
49
+ }
50
+
51
+ interface WorkDatabaseRow {
52
+ readonly record_key: string
53
+ readonly major_order: number
54
+ readonly minor_order: number
55
+ readonly sort_one: string
56
+ readonly sort_two: string
57
+ readonly payload: unknown
58
+ }
59
+
60
+ export class PgMaterializationSessions {
61
+ private readonly sessions = new WeakMap<object, PgMaterializationSessionState>()
62
+ private readonly live = new Set<PgMaterializationSessionState>()
63
+ private tablesReady = false
64
+
65
+ constructor(
66
+ private readonly sql: SQLClient,
67
+ private readonly context: PgOntologyTransactionContext | null
68
+ ) {}
69
+
70
+ async create(header: MaterializationPlanHeader): Promise<PgMaterializationSessionState> {
71
+ if (!this.context?.active) {
72
+ throw new MaterializationConflictError(
73
+ "effective-state",
74
+ "Materialization sessions require an active storage transaction."
75
+ )
76
+ }
77
+ await this.ensureTables()
78
+ const session = new PgMaterializationSessionState(structuredClone(header), this.context.id)
79
+ this.sessions.set(session.providerToken, session)
80
+ this.live.add(session)
81
+ this.context.materializations.register(session.providerToken)
82
+ return session
83
+ }
84
+
85
+ require(session: MaterializationSession): PgMaterializationSessionState {
86
+ const value = this.sessions.get(session.providerToken)
87
+ if (
88
+ !value ||
89
+ !value.active ||
90
+ !this.context?.active ||
91
+ value.transactionId !== this.context.id
92
+ ) {
93
+ throw new MaterializationConflictError(
94
+ "effective-state",
95
+ "Materialization session is inactive."
96
+ )
97
+ }
98
+ return value
99
+ }
100
+
101
+ async release(session: PgMaterializationSessionState): Promise<void> {
102
+ if (!session.active) return
103
+ await this.deleteWork(session.id)
104
+ session.active = false
105
+ session.replacement = null
106
+ this.live.delete(session)
107
+ this.context?.materializations.complete(session.providerToken)
108
+ }
109
+
110
+ deactivateAll(): void {
111
+ for (const session of this.live) {
112
+ session.active = false
113
+ session.replacement = null
114
+ }
115
+ this.live.clear()
116
+ }
117
+
118
+ async stage(input: StageMaterializationWorkInput): Promise<void> {
119
+ const session = this.require(input.session)
120
+ const records = prepareMaterializationWork(session, input)
121
+ if (records.length === 0) return
122
+ const keys = records.map(({ record }) => record.recordKey)
123
+ const uniqueKeys = records.map(({ uniqueKey }) => uniqueKey)
124
+
125
+ const [duplicate] = await this.sql<{ readonly record_key: string }[]>`
126
+ SELECT record_key
127
+ FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
128
+ WHERE session_id = ${session.id}
129
+ AND (
130
+ record_key = ANY(${this.sql.array(keys)}::text[])
131
+ OR unique_key = ANY(${this.sql.array(uniqueKeys)}::text[])
132
+ )
133
+ LIMIT 1
134
+ `
135
+ if (duplicate) throw duplicateWork(duplicate.record_key)
136
+
137
+ const payload = records.map(({ record, uniqueKey, columns }) => {
138
+ return {
139
+ recordKey: record.recordKey,
140
+ uniqueKey,
141
+ kind: record.kind,
142
+ lane: columns.lane,
143
+ majorOrder: columns.majorOrder,
144
+ minorOrder: columns.minorOrder,
145
+ sortOne: columns.sortOne,
146
+ sortTwo: columns.sortTwo,
147
+ record,
148
+ }
149
+ })
150
+ await this.sql`
151
+ WITH staged AS (
152
+ SELECT value FROM jsonb_array_elements(${jsonParameter(this.sql, payload)}::jsonb)
153
+ )
154
+ INSERT INTO ${this.sql(PG_MATERIALIZATION_WORK_TABLE)} (
155
+ session_id, record_key, unique_key, kind, lane,
156
+ major_order, minor_order, sort_one, sort_two, payload
157
+ )
158
+ SELECT ${session.id}, value->>'recordKey', value->>'uniqueKey', value->>'kind',
159
+ value->>'lane', (value->>'majorOrder')::integer, (value->>'minorOrder')::integer,
160
+ value->>'sortOne', value->>'sortTwo', value->'record'
161
+ FROM staged
162
+ `
163
+ }
164
+
165
+ async *stream(input: StreamMaterializationWorkInput): AsyncIterable<MaterializationWorkPage> {
166
+ const session = this.require(input.session)
167
+ const stream = session.workStreams[input.order]
168
+ if (stream.started) {
169
+ throw new MaterializationConflictError(
170
+ "effective-state",
171
+ `Materialization ${input.order} work may only be streamed once per session.`
172
+ )
173
+ }
174
+ stream.started = true
175
+ session.workSealed = true
176
+ let cursor: WorkCursor | null = null
177
+ while (true) {
178
+ this.require(input.session)
179
+ const rows = await this.readLane(input.order, session.id, cursor, input.pageRows)
180
+ if (rows.length === 0) break
181
+ const records = rows.map((row) =>
182
+ structuredClone(row.payload)
183
+ ) as MaterializationWorkPage["records"]
184
+ cursor = workCursor(rows[rows.length - 1]!)
185
+ stream.emittedCount += records.length
186
+ yield { records }
187
+ }
188
+ this.require(input.session)
189
+ stream.completed = true
190
+ }
191
+
192
+ async readObjectExistence(
193
+ session: PgMaterializationSessionState,
194
+ refs: readonly { readonly objectTypeId: string; readonly primaryId: string }[]
195
+ ): Promise<
196
+ readonly {
197
+ readonly ref: { readonly objectTypeId: string; readonly primaryId: string }
198
+ readonly exists: boolean
199
+ }[]
200
+ > {
201
+ if (refs.length === 0) return []
202
+ const keys = refs.map(
203
+ (ref) => `object-existence:${JSON.stringify([ref.objectTypeId, ref.primaryId])}`
204
+ )
205
+ const rows = await this.sql<{ readonly unique_key: string; readonly payload: unknown }[]>`
206
+ SELECT unique_key, payload FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
207
+ WHERE session_id = ${session.id}
208
+ AND unique_key = ANY(${this.sql.array(keys)}::text[])
209
+ AND kind = 'object-existence'
210
+ `
211
+ const found = new Map(rows.map((row) => [row.unique_key, row.payload] as const))
212
+ return keys.flatMap((key) => {
213
+ const payload = found.get(key)
214
+ if (!payload) return []
215
+ const record = structuredClone(payload) as Extract<
216
+ MaterializationWorkRecord,
217
+ { readonly kind: "object-existence" }
218
+ >
219
+ return [{ ref: record.ref, exists: record.exists }]
220
+ })
221
+ }
222
+
223
+ async prepareChunkSequence(
224
+ session: PgMaterializationSessionState,
225
+ chunk: MaterializationPlanChunk
226
+ ): Promise<PgChunkSequenceProgress> {
227
+ const items = materializationPlanItems(chunk)
228
+ const planRows = await this.readLane(
229
+ "apply",
230
+ session.id,
231
+ session.appliedPlanCursor,
232
+ items.length
233
+ )
234
+ const expectedItems = planRows.map(
235
+ (row) =>
236
+ (
237
+ structuredClone(row.payload) as Extract<
238
+ MaterializationWorkRecord,
239
+ { readonly kind: "plan" }
240
+ >
241
+ ).item
242
+ )
243
+ const eventRows = await this.readLane(
244
+ "event",
245
+ session.id,
246
+ session.appliedEventCursor,
247
+ chunk.outbox.length
248
+ )
249
+ const expectedEvents = eventRows.map(
250
+ (row) => structuredClone(row.payload) as MaterializationEventWorkRecord
251
+ )
252
+ const progress = correlateMaterializationChunk(
253
+ session,
254
+ chunk,
255
+ items,
256
+ expectedItems,
257
+ expectedEvents
258
+ )
259
+
260
+ return {
261
+ ...progress,
262
+ appliedPlanCursor:
263
+ planRows.length === 0
264
+ ? session.appliedPlanCursor
265
+ : workCursor(planRows[planRows.length - 1]!),
266
+ appliedEventCursor:
267
+ eventRows.length === 0
268
+ ? session.appliedEventCursor
269
+ : workCursor(eventRows[eventRows.length - 1]!),
270
+ }
271
+ }
272
+
273
+ commitChunkSequence(
274
+ session: PgMaterializationSessionState,
275
+ progress: PgChunkSequenceProgress
276
+ ): void {
277
+ session.appliedPlanCount = progress.appliedPlanCount
278
+ session.appliedOutboxCount = progress.appliedOutboxCount
279
+ session.appliedPlanCursor = progress.appliedPlanCursor
280
+ session.appliedEventCursor = progress.appliedEventCursor
281
+ }
282
+
283
+ async count(session: PgMaterializationSessionState, kind: string): Promise<number> {
284
+ const [row] = await this.sql<{ readonly count: number | string }[]>`
285
+ SELECT COUNT(*) AS count FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
286
+ WHERE session_id = ${session.id} AND kind = ${kind}
287
+ `
288
+ return Number(row?.count ?? 0)
289
+ }
290
+
291
+ async laneCounts(session: PgMaterializationSessionState): Promise<{
292
+ readonly apply: number
293
+ readonly cardinality: number
294
+ readonly event: number
295
+ }> {
296
+ const rows = await this.sql<
297
+ { readonly lane: "apply" | "cardinality" | "event"; readonly count: number | string }[]
298
+ >`
299
+ SELECT lane, COUNT(*) AS count FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
300
+ WHERE session_id = ${session.id} AND lane <> 'none'
301
+ GROUP BY lane
302
+ `
303
+ const counts = { apply: 0, cardinality: 0, event: 0 }
304
+ for (const row of rows) counts[row.lane] = Number(row.count)
305
+ return counts
306
+ }
307
+
308
+ async assertClassificationCoverage(session: PgMaterializationSessionState): Promise<void> {
309
+ if (!session.replacement) return
310
+ const includeObjects = session.replacement.projectionKind === "object"
311
+ const [mismatch] = await this.sql<{ readonly marker: number }[]>`
312
+ WITH expected AS (
313
+ SELECT entity_kind, identity_key
314
+ FROM ${this.sql(PG_REPLACEMENT_WORK_TABLE)}
315
+ WHERE session_id = ${session.id} AND diff_required
316
+ AND (entity_kind = 'link' OR ${includeObjects})
317
+ ), actual AS (
318
+ SELECT payload->>'entityKind' AS entity_kind,
319
+ payload->>'identityKey' AS identity_key
320
+ FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
321
+ WHERE session_id = ${session.id} AND kind = 'classification'
322
+ AND payload->>'entityKind' IN ('object', 'link')
323
+ ), differences AS (
324
+ (SELECT * FROM expected EXCEPT SELECT * FROM actual)
325
+ UNION ALL
326
+ (SELECT * FROM actual EXCEPT SELECT * FROM expected)
327
+ ), invalid AS (
328
+ SELECT 1 AS marker FROM differences
329
+ UNION ALL
330
+ SELECT 1 AS marker FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
331
+ WHERE session_id = ${session.id} AND kind = 'classification'
332
+ AND payload->>'entityKind' = 'point'
333
+ )
334
+ SELECT marker FROM invalid LIMIT 1
335
+ `
336
+ if (mismatch) {
337
+ invalidCorrelation(
338
+ "Projection replacement classification coverage does not match its streamed state."
339
+ )
340
+ }
341
+ }
342
+
343
+ private async readLane(
344
+ lane: StreamMaterializationWorkInput["order"],
345
+ sessionId: string,
346
+ cursor: WorkCursor | null,
347
+ limit: number
348
+ ): Promise<WorkDatabaseRow[]> {
349
+ if (limit === 0) return []
350
+ const after = cursor
351
+ ? this.sql`AND (major_order, minor_order, sort_one, sort_two, record_key) >
352
+ (${cursor.majorOrder}, ${cursor.minorOrder}, ${cursor.sortOne}, ${cursor.sortTwo},
353
+ ${cursor.recordKey})`
354
+ : this.sql``
355
+ return this.sql<WorkDatabaseRow[]>`
356
+ SELECT record_key, major_order, minor_order, sort_one, sort_two, payload
357
+ FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
358
+ WHERE session_id = ${sessionId} AND lane = ${lane} ${after}
359
+ ORDER BY major_order, minor_order, sort_one, sort_two, record_key
360
+ LIMIT ${limit}
361
+ `
362
+ }
363
+
364
+ private async ensureTables(): Promise<void> {
365
+ if (this.tablesReady) return
366
+ await this.sql`
367
+ CREATE TEMP TABLE ${this.sql(PG_MATERIALIZATION_WORK_TABLE)} (
368
+ session_id TEXT NOT NULL,
369
+ record_key TEXT COLLATE "C" NOT NULL,
370
+ unique_key TEXT NOT NULL,
371
+ kind TEXT NOT NULL,
372
+ lane TEXT NOT NULL,
373
+ major_order INTEGER NOT NULL,
374
+ minor_order INTEGER NOT NULL,
375
+ sort_one TEXT COLLATE "C" NOT NULL,
376
+ sort_two TEXT COLLATE "C" NOT NULL,
377
+ payload JSONB NOT NULL,
378
+ PRIMARY KEY (session_id, record_key),
379
+ UNIQUE (session_id, unique_key)
380
+ ) ON COMMIT DROP
381
+ `
382
+ await this.sql`
383
+ CREATE INDEX ontology_materialization_work_lane
384
+ ON ${this.sql(PG_MATERIALIZATION_WORK_TABLE)} (
385
+ session_id, lane, major_order, minor_order, sort_one, sort_two, record_key
386
+ )
387
+ `
388
+ await this.sql`
389
+ CREATE TEMP TABLE ${this.sql(PG_REPLACEMENT_WORK_TABLE)} (
390
+ session_id TEXT NOT NULL,
391
+ entity_kind TEXT NOT NULL,
392
+ identity_key TEXT COLLATE "C" NOT NULL,
393
+ sort_key TEXT COLLATE "C" NOT NULL,
394
+ diff_required BOOLEAN NOT NULL,
395
+ PRIMARY KEY (session_id, entity_kind, identity_key)
396
+ ) ON COMMIT DROP
397
+ `
398
+ await this.sql`
399
+ CREATE INDEX ontology_replacement_work_order
400
+ ON ${this.sql(PG_REPLACEMENT_WORK_TABLE)} (
401
+ session_id, entity_kind, sort_key, identity_key
402
+ )
403
+ `
404
+ this.tablesReady = true
405
+ }
406
+
407
+ private async deleteWork(sessionId: string): Promise<void> {
408
+ if (!this.tablesReady) return
409
+ await this.sql`
410
+ DELETE FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)} WHERE session_id = ${sessionId}
411
+ `
412
+ await this.sql`
413
+ DELETE FROM ${this.sql(PG_REPLACEMENT_WORK_TABLE)} WHERE session_id = ${sessionId}
414
+ `
415
+ }
416
+ }
417
+
418
+ function workCursor(row: WorkDatabaseRow): WorkCursor {
419
+ return {
420
+ majorOrder: row.major_order,
421
+ minorOrder: row.minor_order,
422
+ sortOne: row.sort_one,
423
+ sortTwo: row.sort_two,
424
+ recordKey: row.record_key,
425
+ }
426
+ }