@tenetkit/pg 0.31.0 → 0.33.0
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/dist/postgres/claims.js +6 -6
- package/dist/postgres/locks.js +3 -3
- package/dist/postgres/pg-helpers.js +31 -31
- package/dist/postgres/run-schema.js +31 -13
- package/dist/postgres/schema.d.ts +6 -4
- package/dist/postgres/schema.js +83 -57
- package/dist/postgres/session-storage.js +6 -6
- package/dist/postgres/session-store.js +14 -14
- package/dist/postgres/store-admit.js +2 -2
- package/dist/postgres/store-cancel.js +6 -6
- package/dist/postgres/store-fan-out.js +3 -3
- package/dist/postgres/store-model-response.js +9 -9
- package/dist/postgres/store-ops.js +29 -29
- package/dist/postgres/store-program.js +1 -1
- package/dist/postgres/store-suspend.js +5 -5
- package/dist/postgres/store.js +15 -15
- package/package.json +2 -2
package/dist/postgres/claims.js
CHANGED
|
@@ -8,13 +8,13 @@ export const claimReadyRuns = (options) => Effect.gen(function* () {
|
|
|
8
8
|
const claimed = yield* sql `
|
|
9
9
|
WITH candidates AS (
|
|
10
10
|
SELECT r.run_id
|
|
11
|
-
FROM
|
|
11
|
+
FROM tenetkit_runs r
|
|
12
12
|
WHERE r.status IN ('queued', 'running', 'cancelling')
|
|
13
13
|
AND r.cancellation_requested = FALSE
|
|
14
14
|
AND (
|
|
15
|
-
(r.parent_run_id IS NULL AND EXISTS (SELECT 1 FROM
|
|
15
|
+
(r.parent_run_id IS NULL AND EXISTS (SELECT 1 FROM tenetkit_lanes l WHERE l.head_run_id = r.run_id))
|
|
16
16
|
OR EXISTS (
|
|
17
|
-
SELECT 1 FROM
|
|
17
|
+
SELECT 1 FROM tenetkit_run_links link
|
|
18
18
|
WHERE link.child_run_id = r.run_id AND link.readiness = 'ready'
|
|
19
19
|
)
|
|
20
20
|
)
|
|
@@ -27,7 +27,7 @@ export const claimReadyRuns = (options) => Effect.gen(function* () {
|
|
|
27
27
|
FOR UPDATE OF r SKIP LOCKED
|
|
28
28
|
LIMIT ${options.limit}
|
|
29
29
|
)
|
|
30
|
-
UPDATE
|
|
30
|
+
UPDATE tenetkit_runs AS r
|
|
31
31
|
SET
|
|
32
32
|
owner_worker_id = ${options.workerId},
|
|
33
33
|
lease_expires_at = NOW() + (${leaseMs}::double precision * INTERVAL '1 millisecond'),
|
|
@@ -61,7 +61,7 @@ export const refreshLease = (input) => Effect.gen(function* () {
|
|
|
61
61
|
const sql = yield* SqlClient.SqlClient;
|
|
62
62
|
const leaseMs = Duration.toMillis(input.lease);
|
|
63
63
|
const rows = yield* sql `
|
|
64
|
-
UPDATE
|
|
64
|
+
UPDATE tenetkit_runs
|
|
65
65
|
SET
|
|
66
66
|
lease_expires_at = NOW() + (${leaseMs}::double precision * INTERVAL '1 millisecond'),
|
|
67
67
|
updated_at = NOW()
|
|
@@ -86,7 +86,7 @@ export const requireClaim = (input) => Effect.gen(function* () {
|
|
|
86
86
|
export const releaseClaim = (input) => Effect.gen(function* () {
|
|
87
87
|
const sql = yield* SqlClient.SqlClient;
|
|
88
88
|
yield* sql `
|
|
89
|
-
UPDATE
|
|
89
|
+
UPDATE tenetkit_runs
|
|
90
90
|
SET owner_worker_id = NULL, lease_expires_at = NULL, updated_at = NOW()
|
|
91
91
|
WHERE run_id = ${input.runId}
|
|
92
92
|
AND owner_worker_id = ${input.workerId}
|
package/dist/postgres/locks.js
CHANGED
|
@@ -11,7 +11,7 @@ import { loadRun } from "./pg-helpers.js";
|
|
|
11
11
|
export const lockRun = (runId) => Effect.gen(function* () {
|
|
12
12
|
const sql = yield* SqlClient.SqlClient;
|
|
13
13
|
yield* sql `SELECT pg_advisory_xact_lock(hashtext(${`run:${runId}`}))`;
|
|
14
|
-
yield* sql `SELECT run_id FROM
|
|
14
|
+
yield* sql `SELECT run_id FROM tenetkit_runs WHERE run_id = ${runId} FOR UPDATE`;
|
|
15
15
|
});
|
|
16
16
|
export const lockRunHierarchy = (runId) => Effect.gen(function* () {
|
|
17
17
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -19,7 +19,7 @@ export const lockRunHierarchy = (runId) => Effect.gen(function* () {
|
|
|
19
19
|
let current = runId;
|
|
20
20
|
while (true) {
|
|
21
21
|
const row = (yield* sql `
|
|
22
|
-
SELECT parent_run_id FROM
|
|
22
|
+
SELECT parent_run_id FROM tenetkit_runs WHERE run_id = ${current}
|
|
23
23
|
`)[0];
|
|
24
24
|
if (row?.parent_run_id === null || row?.parent_run_id === undefined)
|
|
25
25
|
break;
|
|
@@ -41,7 +41,7 @@ export const lockMailbox = (targetSessionId) => Effect.gen(function* () {
|
|
|
41
41
|
}).pipe(Effect.asVoid);
|
|
42
42
|
export const lockSpawnParent = (runId) => Effect.gen(function* () {
|
|
43
43
|
const sql = yield* SqlClient.SqlClient;
|
|
44
|
-
yield* sql `SELECT run_id FROM
|
|
44
|
+
yield* sql `SELECT run_id FROM tenetkit_runs WHERE run_id = ${runId} FOR UPDATE`;
|
|
45
45
|
const parent = yield* loadRun(runId);
|
|
46
46
|
if (parent === undefined)
|
|
47
47
|
return yield* RunNotFound.make({ runId });
|
|
@@ -16,14 +16,14 @@ import { hasUnsettledChild, loadTerminalEvent, reconcileChildWaitWith, } from "t
|
|
|
16
16
|
import { appendTerminalToolResultsForEvent } from "tenetkit/runtime/driver/sql/session-terminalization";
|
|
17
17
|
export const loadRun = (runId) => Effect.gen(function* () {
|
|
18
18
|
const sql = yield* SqlClient.SqlClient;
|
|
19
|
-
const rows = yield* sql `SELECT * FROM
|
|
19
|
+
const rows = yield* sql `SELECT * FROM tenetkit_runs WHERE run_id = ${runId}`;
|
|
20
20
|
const row = rows[0];
|
|
21
21
|
return row === undefined ? undefined : yield* decodeRunEffect(row);
|
|
22
22
|
});
|
|
23
23
|
export const requireRun = (runId) => loadRun(runId).pipe(Effect.flatMap((loaded) => loaded === undefined ? Effect.fail(RunNotFound.make({ runId })) : Effect.succeed(loaded)));
|
|
24
24
|
export const emitAgentEvent = Function.dual(2, (hub, input) => Effect.gen(function* () {
|
|
25
25
|
const sql = yield* SqlClient.SqlClient;
|
|
26
|
-
yield* sql `SELECT run_id FROM
|
|
26
|
+
yield* sql `SELECT run_id FROM tenetkit_runs WHERE run_id = ${input.runId} FOR UPDATE`;
|
|
27
27
|
const loaded = yield* loadRun(input.runId);
|
|
28
28
|
if (loaded === undefined)
|
|
29
29
|
return yield* RunNotFound.make({ runId: input.runId });
|
|
@@ -38,7 +38,7 @@ export const emitAgentEvent = Function.dual(2, (hub, input) => Effect.gen(functi
|
|
|
38
38
|
return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
|
|
39
39
|
yield* appendEvent(hub, loaded, input.event);
|
|
40
40
|
if (input.event._tag === "TurnCompleted") {
|
|
41
|
-
yield* sql `UPDATE
|
|
41
|
+
yield* sql `UPDATE tenetkit_runs SET continuation_json = NULL WHERE run_id = ${loaded.runId}`;
|
|
42
42
|
}
|
|
43
43
|
}));
|
|
44
44
|
export const loadEventsAfter = Function.dual(2, (runId, cursor) => Effect.gen(function* () {
|
|
@@ -47,7 +47,7 @@ export const loadEventsAfter = Function.dual(2, (runId, cursor) => Effect.gen(fu
|
|
|
47
47
|
if (run === undefined)
|
|
48
48
|
return [];
|
|
49
49
|
const rows = yield* sql `
|
|
50
|
-
SELECT * FROM
|
|
50
|
+
SELECT * FROM tenetkit_run_events
|
|
51
51
|
WHERE run_id = ${runId} AND sequence > ${cursor}
|
|
52
52
|
ORDER BY sequence ASC
|
|
53
53
|
`;
|
|
@@ -56,7 +56,7 @@ export const loadEventsAfter = Function.dual(2, (runId, cursor) => Effect.gen(fu
|
|
|
56
56
|
export const allocateSequence = (runId) => Effect.gen(function* () {
|
|
57
57
|
const sql = yield* SqlClient.SqlClient;
|
|
58
58
|
const rows = yield* sql `
|
|
59
|
-
UPDATE
|
|
59
|
+
UPDATE tenetkit_runs
|
|
60
60
|
SET last_sequence = last_sequence + 1, updated_at = NOW()
|
|
61
61
|
WHERE run_id = ${runId}
|
|
62
62
|
RETURNING last_sequence
|
|
@@ -90,15 +90,15 @@ export const appendEvent = Function.dual((args) => args.length >= 4 || (args.len
|
|
|
90
90
|
...partial,
|
|
91
91
|
};
|
|
92
92
|
yield* sql `
|
|
93
|
-
INSERT INTO
|
|
93
|
+
INSERT INTO tenetkit_run_events (run_id, sequence, event_id, event_json)
|
|
94
94
|
VALUES (${run.runId}, ${sequence}, ${event.eventId}, ${encodeEvent(event)})
|
|
95
95
|
`;
|
|
96
96
|
const treeRoot = (yield* sql `
|
|
97
|
-
UPDATE
|
|
97
|
+
UPDATE tenetkit_tree_roots SET last_position = last_position + 1
|
|
98
98
|
WHERE root_run_id = ${run.rootRunId} RETURNING last_position
|
|
99
99
|
`)[0];
|
|
100
100
|
yield* sql `
|
|
101
|
-
INSERT INTO
|
|
101
|
+
INSERT INTO tenetkit_tree_event_index (root_run_id, position, run_id, run_sequence, event_id)
|
|
102
102
|
VALUES (${run.rootRunId}, ${Number(treeRoot.last_position)}, ${run.runId}, ${sequence}, ${event.eventId})
|
|
103
103
|
`;
|
|
104
104
|
const status = nextStatus ?? run.status;
|
|
@@ -118,7 +118,7 @@ export const appendEvent = Function.dual((args) => args.length >= 4 || (args.len
|
|
|
118
118
|
const terminalPartial = event._tag === "RunCompleted" || event._tag === "RunFailed" || event._tag === "RunCancelled";
|
|
119
119
|
if (terminalPartial) {
|
|
120
120
|
yield* sql `
|
|
121
|
-
UPDATE
|
|
121
|
+
UPDATE tenetkit_runs SET
|
|
122
122
|
status = ${status},
|
|
123
123
|
active_wait_id = ${activeWaitId},
|
|
124
124
|
terminal_event_id = ${terminalEventId},
|
|
@@ -137,7 +137,7 @@ export const appendEvent = Function.dual((args) => args.length >= 4 || (args.len
|
|
|
137
137
|
}
|
|
138
138
|
else {
|
|
139
139
|
yield* sql `
|
|
140
|
-
UPDATE
|
|
140
|
+
UPDATE tenetkit_runs SET
|
|
141
141
|
status = ${status},
|
|
142
142
|
active_wait_id = ${activeWaitId},
|
|
143
143
|
terminal_event_id = ${terminalEventId},
|
|
@@ -154,7 +154,7 @@ export const appendEvent = Function.dual((args) => args.length >= 4 || (args.len
|
|
|
154
154
|
export const promoteHead = Function.dual(3, (hub, address, sessionId) => Effect.gen(function* () {
|
|
155
155
|
const sql = yield* SqlClient.SqlClient;
|
|
156
156
|
const lanes = yield* sql `
|
|
157
|
-
SELECT head_run_id, queue_json FROM
|
|
157
|
+
SELECT head_run_id, queue_json FROM tenetkit_lanes
|
|
158
158
|
WHERE address = ${address} AND session_id = ${sessionId}
|
|
159
159
|
FOR UPDATE
|
|
160
160
|
`;
|
|
@@ -167,7 +167,7 @@ export const promoteHead = Function.dual(3, (hub, address, sessionId) => Effect.
|
|
|
167
167
|
return;
|
|
168
168
|
if (lane.head_run_id !== headId) {
|
|
169
169
|
yield* sql `
|
|
170
|
-
UPDATE
|
|
170
|
+
UPDATE tenetkit_lanes SET head_run_id = ${headId}
|
|
171
171
|
WHERE address = ${address} AND session_id = ${sessionId}
|
|
172
172
|
`;
|
|
173
173
|
}
|
|
@@ -178,7 +178,7 @@ export const promoteHead = Function.dual(3, (hub, address, sessionId) => Effect.
|
|
|
178
178
|
export const removeFromLane = Function.dual(3, (address, sessionId, runId) => Effect.gen(function* () {
|
|
179
179
|
const sql = yield* SqlClient.SqlClient;
|
|
180
180
|
const lanes = yield* sql `
|
|
181
|
-
SELECT queue_json FROM
|
|
181
|
+
SELECT queue_json FROM tenetkit_lanes
|
|
182
182
|
WHERE address = ${address} AND session_id = ${sessionId}
|
|
183
183
|
FOR UPDATE
|
|
184
184
|
`;
|
|
@@ -187,11 +187,11 @@ export const removeFromLane = Function.dual(3, (address, sessionId, runId) => Ef
|
|
|
187
187
|
return;
|
|
188
188
|
const queue = decodeQueue(lane.queue_json).filter((id) => id !== runId);
|
|
189
189
|
if (queue.length === 0) {
|
|
190
|
-
yield* sql `DELETE FROM
|
|
190
|
+
yield* sql `DELETE FROM tenetkit_lanes WHERE address = ${address} AND session_id = ${sessionId}`;
|
|
191
191
|
}
|
|
192
192
|
else {
|
|
193
193
|
yield* sql `
|
|
194
|
-
UPDATE
|
|
194
|
+
UPDATE tenetkit_lanes
|
|
195
195
|
SET queue_json = ${encodeQueue(queue)}, head_run_id = ${queue[0]}
|
|
196
196
|
WHERE address = ${address} AND session_id = ${sessionId}
|
|
197
197
|
`;
|
|
@@ -207,11 +207,11 @@ export const completeRun = Function.dual(3, (hub, run, result) => Effect.gen(fun
|
|
|
207
207
|
return yield* RunTerminal.make({ runId: run.runId, status: run.status });
|
|
208
208
|
if (run.cancellationRequested) {
|
|
209
209
|
const runningFanOut = yield* sql `
|
|
210
|
-
SELECT fan_out_id FROM
|
|
210
|
+
SELECT fan_out_id FROM tenetkit_fan_outs WHERE parent_run_id = ${run.runId} AND status = 'running' LIMIT 1
|
|
211
211
|
`;
|
|
212
212
|
if (runningFanOut.length > 0 || (yield* hasUnsettledChild(run.runId))) {
|
|
213
213
|
yield* sql `
|
|
214
|
-
UPDATE
|
|
214
|
+
UPDATE tenetkit_runs SET owner_worker_id = NULL, lease_expires_at = NULL WHERE run_id = ${run.runId}
|
|
215
215
|
`;
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
@@ -222,11 +222,11 @@ export const completeRun = Function.dual(3, (hub, run, result) => Effect.gen(fun
|
|
|
222
222
|
return;
|
|
223
223
|
}
|
|
224
224
|
const runningFanOut = yield* sql `
|
|
225
|
-
SELECT fan_out_id FROM
|
|
225
|
+
SELECT fan_out_id FROM tenetkit_fan_outs WHERE parent_run_id = ${run.runId} AND status = 'running' LIMIT 1
|
|
226
226
|
`;
|
|
227
227
|
if (runningFanOut.length > 0) {
|
|
228
228
|
yield* sql `
|
|
229
|
-
UPDATE
|
|
229
|
+
UPDATE tenetkit_runs SET status = 'waiting', owner_worker_id = NULL, lease_expires_at = NULL,
|
|
230
230
|
suspension_json = NULL,
|
|
231
231
|
pending_outcome_json = ${encodeJson(PendingRunOutcome, { _tag: "Completed", result })}
|
|
232
232
|
WHERE run_id = ${run.runId}
|
|
@@ -242,18 +242,18 @@ export const settleParent = Function.dual(3, (hub, child, terminalEventId) => Ef
|
|
|
242
242
|
if (child.parentRunId === undefined)
|
|
243
243
|
return;
|
|
244
244
|
const sql = yield* SqlClient.SqlClient;
|
|
245
|
-
yield* sql `SELECT run_id FROM
|
|
245
|
+
yield* sql `SELECT run_id FROM tenetkit_runs WHERE run_id = ${child.parentRunId} FOR UPDATE`;
|
|
246
246
|
const parent = yield* loadRun(child.parentRunId);
|
|
247
247
|
if (parent === undefined)
|
|
248
248
|
return;
|
|
249
249
|
const existing = yield* sql `
|
|
250
|
-
SELECT child_run_id FROM
|
|
250
|
+
SELECT child_run_id FROM tenetkit_run_links
|
|
251
251
|
WHERE parent_run_id = ${parent.runId} AND child_run_id = ${child.runId} AND terminal_event_id IS NOT NULL
|
|
252
252
|
`;
|
|
253
253
|
if (existing.length > 0)
|
|
254
254
|
return;
|
|
255
255
|
yield* sql `
|
|
256
|
-
UPDATE
|
|
256
|
+
UPDATE tenetkit_run_links
|
|
257
257
|
SET readiness = 'settled', terminal_event_id = ${terminalEventId}, settled_at = NOW()
|
|
258
258
|
WHERE parent_run_id = ${parent.runId} AND child_run_id = ${child.runId}
|
|
259
259
|
`;
|
|
@@ -279,15 +279,15 @@ export const settleParent = Function.dual(3, (hub, child, terminalEventId) => Ef
|
|
|
279
279
|
}
|
|
280
280
|
if (currentParent?.status === "queued") {
|
|
281
281
|
const unsettled = yield* sql `
|
|
282
|
-
SELECT l.child_run_id FROM
|
|
283
|
-
JOIN
|
|
282
|
+
SELECT l.child_run_id FROM tenetkit_run_links l
|
|
283
|
+
JOIN tenetkit_runs r ON r.run_id = l.child_run_id
|
|
284
284
|
WHERE l.parent_run_id = ${parent.runId}
|
|
285
285
|
AND r.status NOT IN ('succeeded', 'failed', 'cancelled')
|
|
286
286
|
LIMIT 1
|
|
287
287
|
`;
|
|
288
288
|
if (unsettled.length === 0) {
|
|
289
289
|
const attempt = currentParent.attempt + 1;
|
|
290
|
-
yield* sql `UPDATE
|
|
290
|
+
yield* sql `UPDATE tenetkit_runs SET attempt_fence = ${attempt} WHERE run_id = ${parent.runId}`;
|
|
291
291
|
yield* appendEvent(hub, { ...currentParent, attempt }, { _tag: "RunAttemptStarted", attempt }, "running");
|
|
292
292
|
}
|
|
293
293
|
return;
|
|
@@ -295,7 +295,7 @@ export const settleParent = Function.dual(3, (hub, child, terminalEventId) => Ef
|
|
|
295
295
|
if (currentParent?.status !== "cancelling" || currentParent.ownerWorkerId !== undefined)
|
|
296
296
|
return;
|
|
297
297
|
const running = yield* sql `
|
|
298
|
-
SELECT fan_out_id FROM
|
|
298
|
+
SELECT fan_out_id FROM tenetkit_fan_outs WHERE parent_run_id = ${parent.runId} AND status = 'running' LIMIT 1
|
|
299
299
|
`;
|
|
300
300
|
if (running.length > 0)
|
|
301
301
|
return;
|
|
@@ -312,7 +312,7 @@ export const settleParent = Function.dual(3, (hub, child, terminalEventId) => Ef
|
|
|
312
312
|
export const insertRun = (input) => Effect.gen(function* () {
|
|
313
313
|
const sql = yield* SqlClient.SqlClient;
|
|
314
314
|
yield* sql `
|
|
315
|
-
INSERT INTO
|
|
315
|
+
INSERT INTO tenetkit_runs (
|
|
316
316
|
run_id, status, address, session_id, message_id, message_json, message_digest, idempotency_key,
|
|
317
317
|
executable_ref_json, executable_manifest_json, root_run_id, depth, max_depth, max_subagents, parent_run_id, invocation_id, active_wait_id, attempt, attempt_fence,
|
|
318
318
|
last_sequence, cancellation_requested, cancel_reason, terminal_event_id, accepted_sequence,
|
|
@@ -327,20 +327,20 @@ export const insertRun = (input) => Effect.gen(function* () {
|
|
|
327
327
|
)
|
|
328
328
|
`;
|
|
329
329
|
if (input.runId === input.rootRunId) {
|
|
330
|
-
yield* sql `INSERT INTO
|
|
330
|
+
yield* sql `INSERT INTO tenetkit_tree_roots (root_run_id) VALUES (${input.runId})`;
|
|
331
331
|
}
|
|
332
332
|
});
|
|
333
333
|
export const enqueueLane = Function.dual(3, (address, sessionId, runId) => Effect.gen(function* () {
|
|
334
334
|
const sql = yield* SqlClient.SqlClient;
|
|
335
335
|
const lanes = yield* sql `
|
|
336
|
-
SELECT accepted_sequence, queue_json, head_run_id FROM
|
|
336
|
+
SELECT accepted_sequence, queue_json, head_run_id FROM tenetkit_lanes
|
|
337
337
|
WHERE address = ${address} AND session_id = ${sessionId}
|
|
338
338
|
FOR UPDATE
|
|
339
339
|
`;
|
|
340
340
|
const lane = lanes[0];
|
|
341
341
|
if (lane === undefined) {
|
|
342
342
|
yield* sql `
|
|
343
|
-
INSERT INTO
|
|
343
|
+
INSERT INTO tenetkit_lanes (address, session_id, accepted_sequence, queue_json, head_run_id)
|
|
344
344
|
VALUES (${address}, ${sessionId}, 0, ${encodeQueue([runId])}, ${runId})
|
|
345
345
|
`;
|
|
346
346
|
return { acceptedSequence: 0, isHead: true };
|
|
@@ -349,7 +349,7 @@ export const enqueueLane = Function.dual(3, (address, sessionId, runId) => Effec
|
|
|
349
349
|
const queue = [...decodeQueue(lane.queue_json), runId];
|
|
350
350
|
const head = lane.head_run_id ?? queue[0];
|
|
351
351
|
yield* sql `
|
|
352
|
-
UPDATE
|
|
352
|
+
UPDATE tenetkit_lanes
|
|
353
353
|
SET accepted_sequence = ${acceptedSequence}, queue_json = ${encodeQueue(queue)}, head_run_id = ${head}
|
|
354
354
|
WHERE address = ${address} AND session_id = ${sessionId}
|
|
355
355
|
`;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { DateTime, Effect, Layer, Redacted } from "effect";
|
|
1
|
+
import { Cause, DateTime, Effect, Layer, Redacted } from "effect";
|
|
2
2
|
import { Migrator, SqlClient } from "effect/unstable/sql";
|
|
3
3
|
import { PgClient } from "@effect/sql-pg";
|
|
4
4
|
import { SchemaChecksumMismatch, SchemaDirty, SchemaMigrationFailed, SchemaUpgradeRequired, SchemaVersionUnsupported, } from "tenetkit/runtime/driver/sql/errors";
|
|
5
5
|
import { mapSqlError } from "tenetkit/runtime/driver/sql/sql-effect";
|
|
6
|
-
import { MIGRATIONS_TABLE, SCHEMA_META_TABLE, SCHEMA_STATEMENTS, SCHEMA_VERSION, schemaChecksum } from "./schema.js";
|
|
6
|
+
import { MIGRATION_NAME, MIGRATIONS_TABLE, SCHEMA_META_TABLE, SCHEMA_STATEMENTS, SCHEMA_TABLES, SCHEMA_VERSION, schemaChecksum, } from "./schema.js";
|
|
7
|
+
const migrationFailure = (source, fallback) => (error) => SchemaMigrationFailed.make({
|
|
8
|
+
source,
|
|
9
|
+
message: typeof error === "object" && error !== null && "message" in error ? String(error.message) : fallback,
|
|
10
|
+
});
|
|
7
11
|
const readMeta = (source) => mapSqlError(Effect.gen(function* () {
|
|
8
12
|
const sql = yield* SqlClient.SqlClient;
|
|
9
13
|
const exists = yield* sql `
|
|
@@ -25,7 +29,18 @@ const readMeta = (source) => mapSqlError(Effect.gen(function* () {
|
|
|
25
29
|
source,
|
|
26
30
|
message: "message" in error ? String(error.message) : "schema meta read failed",
|
|
27
31
|
})));
|
|
28
|
-
const
|
|
32
|
+
const verifyMigrationIdentity = (source) => Effect.gen(function* () {
|
|
33
|
+
const sql = yield* SqlClient.SqlClient;
|
|
34
|
+
const migrations = yield* sql `
|
|
35
|
+
SELECT migration_id, name FROM ${sql(MIGRATIONS_TABLE)} ORDER BY migration_id
|
|
36
|
+
`;
|
|
37
|
+
if (migrations.length !== 1 ||
|
|
38
|
+
Number(migrations[0]?.migration_id) !== 1 ||
|
|
39
|
+
migrations[0]?.name !== MIGRATION_NAME) {
|
|
40
|
+
return yield* SchemaMigrationFailed.make({ source, message: "migration identity mismatch" });
|
|
41
|
+
}
|
|
42
|
+
}).pipe(Effect.mapError(migrationFailure(source, "migration identity read failed")));
|
|
43
|
+
const baselineMigration = Effect.gen(function* () {
|
|
29
44
|
const sql = yield* SqlClient.SqlClient;
|
|
30
45
|
for (const statement of SCHEMA_STATEMENTS)
|
|
31
46
|
yield* sql.unsafe(statement);
|
|
@@ -35,12 +50,21 @@ const migrationEffect = Effect.gen(function* () {
|
|
|
35
50
|
VALUES (1, ${SCHEMA_VERSION}, ${schemaChecksum()}, FALSE, ${now})
|
|
36
51
|
`;
|
|
37
52
|
});
|
|
53
|
+
const runMigrations = (source) => {
|
|
54
|
+
const migrate = Migrator.make({});
|
|
55
|
+
return migrate({
|
|
56
|
+
loader: Migrator.fromRecord({ [`0001_${MIGRATION_NAME}`]: baselineMigration }),
|
|
57
|
+
table: MIGRATIONS_TABLE,
|
|
58
|
+
}).pipe(Effect.catchCause((cause) => Cause.hasInterrupts(cause)
|
|
59
|
+
? Effect.interrupt
|
|
60
|
+
: SchemaMigrationFailed.make({ source, message: Cause.pretty(cause) || "migration failed" })));
|
|
61
|
+
};
|
|
38
62
|
export const plan = (source) => Effect.map(readMeta(source), (meta) => ({
|
|
39
63
|
current: meta.version,
|
|
40
64
|
required: SCHEMA_VERSION,
|
|
41
65
|
checksum: schemaChecksum(),
|
|
42
66
|
statements: meta.present ? [] : SCHEMA_STATEMENTS,
|
|
43
|
-
upgradeRequired:
|
|
67
|
+
upgradeRequired: meta.version < SCHEMA_VERSION,
|
|
44
68
|
}));
|
|
45
69
|
export const check = (source) => Effect.gen(function* () {
|
|
46
70
|
const meta = yield* readMeta(source);
|
|
@@ -55,6 +79,7 @@ export const check = (source) => Effect.gen(function* () {
|
|
|
55
79
|
if (meta.version !== SCHEMA_VERSION || meta.checksum !== expected) {
|
|
56
80
|
return yield* SchemaChecksumMismatch.make({ source, expected, actual: meta.checksum });
|
|
57
81
|
}
|
|
82
|
+
yield* verifyMigrationIdentity(source);
|
|
58
83
|
});
|
|
59
84
|
export const apply = (source) => Effect.gen(function* () {
|
|
60
85
|
const meta = yield* readMeta(source);
|
|
@@ -63,7 +88,7 @@ export const apply = (source) => Effect.gen(function* () {
|
|
|
63
88
|
const sql = yield* SqlClient.SqlClient;
|
|
64
89
|
const existing = yield* sql `
|
|
65
90
|
SELECT COUNT(*) AS present FROM information_schema.tables
|
|
66
|
-
WHERE table_schema = current_schema() AND table_name
|
|
91
|
+
WHERE table_schema = current_schema() AND table_name IN ${sql.in(SCHEMA_TABLES)}
|
|
67
92
|
`;
|
|
68
93
|
if (Number(existing[0]?.present ?? 0) > 0) {
|
|
69
94
|
return yield* SchemaMigrationFailed.make({
|
|
@@ -71,14 +96,7 @@ export const apply = (source) => Effect.gen(function* () {
|
|
|
71
96
|
message: "cannot create the baseline over an existing TenetKit schema",
|
|
72
97
|
});
|
|
73
98
|
}
|
|
74
|
-
|
|
75
|
-
yield* runMigrations({
|
|
76
|
-
loader: Migrator.fromRecord({ "0001_baton_runtime": migrationEffect }),
|
|
77
|
-
table: MIGRATIONS_TABLE,
|
|
78
|
-
}).pipe(Effect.mapError((error) => SchemaMigrationFailed.make({
|
|
79
|
-
source,
|
|
80
|
-
message: "message" in error ? String(error.message) : "migration failed",
|
|
81
|
-
})));
|
|
99
|
+
yield* runMigrations(source);
|
|
82
100
|
yield* check(source).pipe(Effect.catchTag("tenetkit/runtime/SchemaUpgradeRequired", (error) => SchemaMigrationFailed.make({ source, message: `schema absent after apply: ${error.current}` })));
|
|
83
101
|
});
|
|
84
102
|
export const markDirty = (source) => mapSqlError(Effect.gen(function* () {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export declare const SCHEMA_VERSION =
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
1
|
+
export declare const SCHEMA_VERSION = 1;
|
|
2
|
+
export declare const MIGRATION_NAME = "tenetkit_runtime";
|
|
3
|
+
export declare const SCHEMA_META_TABLE = "tenetkit_schema_meta";
|
|
4
|
+
export declare const MIGRATIONS_TABLE = "tenetkit_sql_migrations";
|
|
5
|
+
export declare const NOTIFY_CHANNEL = "tenetkit_run_events";
|
|
5
6
|
export declare const SCHEMA_STATEMENTS: ReadonlyArray<string>;
|
|
7
|
+
export declare const SCHEMA_TABLES: ReadonlyArray<string>;
|
|
6
8
|
export declare const schemaChecksum: () => string;
|