Open History to see the last commit
1import * as Effect from "effect/Effect";
2import * as Migrator from "effect/sql/Migrator";
3import * as SqlClient from "effect/sql/SqlClient";
4
5import PullRequestFilesViewed from "./Migrations/053_PullRequestFilesViewed.ts";
6import AutoSettleDisabledAt from "./Migrations/054_ProjectionThreadsAutoSettleDisabledAt.ts";
7
8type MigrationRow = { readonly id: number; readonly name: string };
9
10// Previews used V2 ids 53/54; preserve their schema and import progress while reserving main's ids.
11function v2PreviewId(history: ReadonlyArray<MigrationRow>): number | "invalid" | undefined {
12 const later = history.filter((row) => row.id >= 53);
13 const legacy = later.find(
14 (row) => row.name === "OrchestrationV2" && (row.id === 53 || row.id === 54),
15 );
16 if (!legacy) return undefined;
17 const valid = later.every(
18 (row) =>
19 row === legacy ||
20 (legacy.id === 54 &&
21 ((row.id === 53 && row.name === "PullRequestFilesViewed") ||
22 (row.id === 55 && row.name === "RemoveRedundantProjectionIndexes"))),
23 );
24 return valid ? legacy.id : "invalid";
25}
26
27/** The history as startup's preview reconciliation leaves it, or null when startup rejects it. */
28export function reconciledV2PreviewHistory(
29 history: ReadonlyArray<MigrationRow>,
30): ReadonlyArray<MigrationRow> | null {
31 const previewId = v2PreviewId(history);
32 if (previewId === undefined) return history;
33 if (previewId === "invalid") return null;
34 return [
35 ...history.filter((row) => row.id < 53),
36 { id: 53, name: "PullRequestFilesViewed" },
37 { id: 54, name: "ProjectionThreadsAutoSettleDisabledAt" },
38 { id: 55, name: "OrchestrationV2" },
39 ...(history.some((row) => row.id === 55)
40 ? [{ id: 56, name: "RemoveRedundantProjectionIndexes" }]
41 : []),
42 ];
43}
44
45export const reconcileV2PreviewMigration = Effect.fn("reconcileV2PreviewMigration")(function* () {
46 const sql = yield* SqlClient.SqlClient;
47 return yield* sql.withTransaction(
48 Effect.gen(function* () {
49 const tables = yield* sql`
50 SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'effect_sql_migrations'
51 `;
52 if (tables.length === 0) return [];
53 const history = yield* sql<{ readonly migration_id: number; readonly name: string }>`
54 SELECT migration_id, name FROM effect_sql_migrations WHERE migration_id >= 53
55 `;
56 const previewId = v2PreviewId(
57 history.map((row) => ({ id: row.migration_id, name: row.name })),
58 );
59 if (previewId === undefined) return [];
60 if (previewId === "invalid") {
61 return yield* new Migrator.MigrationError({
62 kind: "BadState",
63 message: "Cannot upgrade V2 preview with unexpected later migrations.",
64 });
65 }
66 const executed: Array<readonly [number, string]> = [];
67 if (previewId === 53) {
68 yield* PullRequestFilesViewed;
69 executed.push([53, "PullRequestFilesViewed"]);
70 }
71 yield* AutoSettleDisabledAt;
72 executed.push([54, "ProjectionThreadsAutoSettleDisabledAt"]);
73 // Move the later entry first to avoid a primary-key collision.
74 yield* sql`UPDATE effect_sql_migrations SET migration_id = 56 WHERE migration_id = 55 AND name = 'RemoveRedundantProjectionIndexes'`;
75 yield* sql`UPDATE effect_sql_migrations SET migration_id = 55 WHERE migration_id = ${previewId} AND name = 'OrchestrationV2'`;
76 if (previewId === 53) {
77 yield* sql`INSERT INTO effect_sql_migrations (migration_id, name) VALUES (53, 'PullRequestFilesViewed')`;
78 }
79 yield* sql`INSERT INTO effect_sql_migrations (migration_id, name) VALUES (54, 'ProjectionThreadsAutoSettleDisabledAt')`;
80 return executed;
81 }),
82 );
83});