@metaobjectsdev/migrate-ts 0.20.7 → 0.20.8-rc.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.
@@ -0,0 +1,38 @@
1
+ import type { Change, SchemaSnapshot } from "../types.js";
2
+ /**
3
+ * Result of the D1 FK-cascade emitter: either a validated cascade (with the
4
+ * affected-table set so the dispatcher can partition the remaining changes) or a
5
+ * refusal because the affected tables form a foreign-key cycle.
6
+ */
7
+ export type D1CascadeResult = {
8
+ up: string;
9
+ downWarning: string;
10
+ affected: Set<string>;
11
+ } | {
12
+ refuseCycle: string[];
13
+ };
14
+ /**
15
+ * Emit the D1-legal FK-cascade rebuild for a set of recreated tables and every
16
+ * table transitively referencing them.
17
+ *
18
+ * On remote D1 the plain SQLite recreate-and-copy recipe fails: its `PRAGMA
19
+ * foreign_keys = OFF` is a no-op inside D1's implicit transaction, so dropping a
20
+ * referenced table raises "FOREIGN KEY constraint failed". This emitter instead
21
+ * rebuilds the WHOLE affected set inside one implicit transaction, deferring FK
22
+ * enforcement to commit via `PRAGMA defer_foreign_keys = ON`:
23
+ *
24
+ * 1. `PRAGMA defer_foreign_keys = ON;`
25
+ * 2. CREATE `__f_<t>` for each affected `t` (FKs whose target is also affected
26
+ * are rewritten to the target's temp name).
27
+ * 3. INSERT the carried columns from each old table into its temp.
28
+ * 4. DROP the old tables referrers-first (reverse topological order).
29
+ * 5. RENAME each temp to the real name parents-first (topological order),
30
+ * recreating that table's indexes immediately after its rename.
31
+ *
32
+ * No `BEGIN/COMMIT` and no `foreign_keys = OFF/ON` bracket: D1 runs the file in
33
+ * one implicit transaction and the safety pass would strip the former anyway.
34
+ *
35
+ * `renderD1` only calls this when `actualSchema` is present, so it is required.
36
+ */
37
+ export declare function emitD1Cascade(changes: readonly Change[], expectedSchema: SchemaSnapshot, actualSchema: SchemaSnapshot, recreatedTables: ReadonlySet<string>): D1CascadeResult;
38
+ //# sourceMappingURL=d1-cascade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-cascade.d.ts","sourceRoot":"","sources":["../../src/emit/d1-cascade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAmB,MAAM,aAAa,CAAC;AAiB3E;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAC1D;IAAE,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,cAAc,EAC5B,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,eAAe,CA+EjB"}
@@ -0,0 +1,98 @@
1
+ import { renderCreateTable, renderCreateIndex, computeCarryColumns, changeTable, quote, } from "./sqlite.js";
2
+ import { buildFkEdges, unionEdges, affectedSet, topoOrder } from "./fk-graph.js";
3
+ /**
4
+ * Prefix for the transient rebuild tables the cascade recipe creates
5
+ * (`__f_<table>`). Distinct from SQLite's own `__new_<table>` recreate temp so
6
+ * the two recipes never collide.
7
+ */
8
+ const TEMP_TABLE_PREFIX = "__f_";
9
+ /**
10
+ * Emit the D1-legal FK-cascade rebuild for a set of recreated tables and every
11
+ * table transitively referencing them.
12
+ *
13
+ * On remote D1 the plain SQLite recreate-and-copy recipe fails: its `PRAGMA
14
+ * foreign_keys = OFF` is a no-op inside D1's implicit transaction, so dropping a
15
+ * referenced table raises "FOREIGN KEY constraint failed". This emitter instead
16
+ * rebuilds the WHOLE affected set inside one implicit transaction, deferring FK
17
+ * enforcement to commit via `PRAGMA defer_foreign_keys = ON`:
18
+ *
19
+ * 1. `PRAGMA defer_foreign_keys = ON;`
20
+ * 2. CREATE `__f_<t>` for each affected `t` (FKs whose target is also affected
21
+ * are rewritten to the target's temp name).
22
+ * 3. INSERT the carried columns from each old table into its temp.
23
+ * 4. DROP the old tables referrers-first (reverse topological order).
24
+ * 5. RENAME each temp to the real name parents-first (topological order),
25
+ * recreating that table's indexes immediately after its rename.
26
+ *
27
+ * No `BEGIN/COMMIT` and no `foreign_keys = OFF/ON` bracket: D1 runs the file in
28
+ * one implicit transaction and the safety pass would strip the former anyway.
29
+ *
30
+ * `renderD1` only calls this when `actualSchema` is present, so it is required.
31
+ */
32
+ export function emitD1Cascade(changes, expectedSchema, actualSchema, recreatedTables) {
33
+ const edges = unionEdges(buildFkEdges(expectedSchema), buildFkEdges(actualSchema));
34
+ const affectedAll = affectedSet(recreatedTables, edges);
35
+ // Restrict to tables that EXIST in the actual DB. `affectedSet` walks the
36
+ // expected edges too, so a brand-new table (created this migration) with an FK
37
+ // into a rebuilt table is pulled in as a referrer — but it cannot be
38
+ // INSERT...SELECTed or DROPped. New tables flow through the native "rest" path,
39
+ // emitted after the cascade so the renamed parent already exists.
40
+ const actualNames = new Set(actualSchema.tables.map((t) => t.name));
41
+ const affected = new Set([...affectedAll].filter((t) => actualNames.has(t)));
42
+ const { order, cycle } = topoOrder(affected, edges);
43
+ if (cycle)
44
+ return { refuseCycle: cycle };
45
+ const expectedByName = new Map(expectedSchema.tables.map((t) => [t.name, t]));
46
+ const expectedTable = (name) => {
47
+ const d = expectedByName.get(name);
48
+ if (!d)
49
+ throw new Error(`expectedSchema missing table "${name}" needed for D1 FK-cascade`);
50
+ return d;
51
+ };
52
+ const temp = (name) => TEMP_TABLE_PREFIX + name;
53
+ const stmts = [];
54
+ // Defer FK enforcement to commit — the D1-legal alternative to the (no-op) OFF bracket.
55
+ stmts.push("PRAGMA defer_foreign_keys = ON;");
56
+ // CREATE temps. FK targets inside the affected set are rewritten to their temp
57
+ // name (forward-refs are fine — SQLite resolves FK targets lazily); targets
58
+ // outside the set keep their real name.
59
+ for (const t of order) {
60
+ const src = expectedTable(t);
61
+ const clone = {
62
+ ...src,
63
+ name: temp(t),
64
+ foreignKeys: src.foreignKeys.map((fk) => affected.has(fk.refTable) ? { ...fk, refTable: temp(fk.refTable) } : fk),
65
+ };
66
+ stmts.push(renderCreateTable(clone));
67
+ }
68
+ // INSERT carried columns. A referrer-only table has no changes → carry every
69
+ // expected column (computeCarryColumns([], …)).
70
+ for (const t of order) {
71
+ const tableChanges = changes.filter((c) => changeTable(c) === t);
72
+ const { insertCols, selectCols } = computeCarryColumns(tableChanges, expectedTable(t));
73
+ if (insertCols.length > 0) {
74
+ stmts.push(`INSERT INTO ${quote(temp(t))} (${insertCols.map(quote).join(", ")}) ` +
75
+ `SELECT ${selectCols.map(quote).join(", ")} FROM ${quote(t)};`);
76
+ }
77
+ }
78
+ // DROP referrers-first (reverse topological order).
79
+ for (const t of [...order].reverse()) {
80
+ stmts.push(`DROP TABLE ${quote(t)};`);
81
+ }
82
+ // RENAME parents-first; recreate each table's indexes immediately after rename.
83
+ for (const t of order) {
84
+ stmts.push(`ALTER TABLE ${quote(temp(t))} RENAME TO ${quote(t)};`);
85
+ for (const ix of expectedTable(t).indexes) {
86
+ stmts.push(renderCreateIndex(t, ix));
87
+ }
88
+ }
89
+ const up = stmts.join("\n\n");
90
+ // Best-effort down, mirroring renderRecreate's WARNING block.
91
+ const downWarning = [
92
+ `-- WARNING: SQLite recreate-and-copy down migration is best-effort.`,
93
+ `-- Reverse the column type/nullable/default changes by hand if needed.`,
94
+ `-- Dropped data cannot be restored.`,
95
+ ].join("\n");
96
+ return { up, downWarning, affected };
97
+ }
98
+ //# sourceMappingURL=d1-cascade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-cascade.js","sourceRoot":"","sources":["../../src/emit/d1-cascade.ts"],"names":[],"mappings":"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"}
@@ -19,4 +19,14 @@ export declare class D1ReferencedTableRebuildError extends Error {
19
19
  readonly refusals: D1RebuildRefusal[];
20
20
  constructor(refusals: D1RebuildRefusal[]);
21
21
  }
22
+ /**
23
+ * Thrown when a D1 FK-cascade rebuild (#241) cannot be ordered because the
24
+ * affected tables form a multi-table foreign-key cycle. Unlike the acyclic case
25
+ * (which the cascade emitter rebuilds via `PRAGMA defer_foreign_keys = ON`), a
26
+ * cycle has no parents-first order, so the rebuild is refused at generation time.
27
+ */
28
+ export declare class D1CyclicForeignKeyError extends Error {
29
+ readonly cycle: string[];
30
+ constructor(cycle: string[]);
31
+ }
22
32
  //# sourceMappingURL=d1-fk-refuse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"d1-fk-refuse.d.ts","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,cAAc,EAAE,cAAc,GAC7B,gBAAgB,EAAE,CASpB;AAED,0FAA0F;AAC1F,qBAAa,6BAA8B,SAAQ,KAAK;aAC1B,QAAQ,EAAE,gBAAgB,EAAE;gBAA5B,QAAQ,EAAE,gBAAgB,EAAE;CAIzD"}
1
+ {"version":3,"file":"d1-fk-refuse.d.ts","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,cAAc,EAAE,cAAc,GAC7B,gBAAgB,EAAE,CASpB;AAED,0FAA0F;AAC1F,qBAAa,6BAA8B,SAAQ,KAAK;aAC1B,QAAQ,EAAE,gBAAgB,EAAE;gBAA5B,QAAQ,EAAE,gBAAgB,EAAE;CAIzD;AAED;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aACpB,KAAK,EAAE,MAAM,EAAE;gBAAf,KAAK,EAAE,MAAM,EAAE;CAI5C"}
@@ -25,6 +25,31 @@ export class D1ReferencedTableRebuildError extends Error {
25
25
  this.name = "D1ReferencedTableRebuildError";
26
26
  }
27
27
  }
28
+ /**
29
+ * Thrown when a D1 FK-cascade rebuild (#241) cannot be ordered because the
30
+ * affected tables form a multi-table foreign-key cycle. Unlike the acyclic case
31
+ * (which the cascade emitter rebuilds via `PRAGMA defer_foreign_keys = ON`), a
32
+ * cycle has no parents-first order, so the rebuild is refused at generation time.
33
+ */
34
+ export class D1CyclicForeignKeyError extends Error {
35
+ cycle;
36
+ constructor(cycle) {
37
+ super(formatCycleMessage(cycle));
38
+ this.cycle = cycle;
39
+ this.name = "D1CyclicForeignKeyError";
40
+ }
41
+ }
42
+ function formatCycleMessage(cycle) {
43
+ const members = cycle.map((n) => `"${n}"`).join(", ");
44
+ return (`Cannot rebuild the following table(s) on Cloudflare D1 — their foreign keys form a ` +
45
+ `cycle: ${members}.\n\n` +
46
+ `The FK-cascade rebuild recipe recreates tables parents-first, which is impossible ` +
47
+ `when tables reference each other in a cycle. Even with ` +
48
+ "`PRAGMA defer_foreign_keys = ON`" +
49
+ ` the DROP/RENAME sequence cannot be ordered to keep every reference valid. To apply ` +
50
+ `this on D1, hand-write the migration (drop the foreign key on one side of the cycle, ` +
51
+ `rebuild the tables, then restore it), or break the cycle in your metadata.`);
52
+ }
28
53
  function formatMessage(refusals) {
29
54
  const lines = refusals.map((r) => {
30
55
  const refs = r.referencedBy.map((n) => `"${n}"`).join(", ");
@@ -1 +1 @@
1
- {"version":3,"file":"d1-fk-refuse.js","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAAoC,EACpC,cAA8B;IAE9B,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM;aACvC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;aAChE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,0FAA0F;AAC1F,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAC1B;IAA5B,YAA4B,QAA4B;QACtD,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QADL,aAAQ,GAAR,QAAQ,CAAoB;QAEtD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAED,SAAS,aAAa,CAAC,QAA4B;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,QAAQ,CAAC,CAAC,KAAK,yCAAyC,IAAI,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,OAAO,CACL,mFAAmF;QACnF,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QACvC,6DAA6D;QAC7D,oFAAoF;QACpF,oFAAoF;QACpF,oFAAoF;QACpF,qFAAqF;QACrF,sFAAsF;QACtF,4DAA4D,CAC7D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"d1-fk-refuse.js","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAAoC,EACpC,cAA8B;IAE9B,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM;aACvC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;aAChE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,0FAA0F;AAC1F,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAC1B;IAA5B,YAA4B,QAA4B;QACtD,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QADL,aAAQ,GAAR,QAAQ,CAAoB;QAEtD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACpB;IAA5B,YAA4B,KAAe;QACzC,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;QADP,UAAK,GAAL,KAAK,CAAU;QAEzC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,SAAS,kBAAkB,CAAC,KAAe;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,CACL,qFAAqF;QACrF,UAAU,OAAO,OAAO;QACxB,oFAAoF;QACpF,yDAAyD;QACzD,kCAAkC;QAClC,sFAAsF;QACtF,uFAAuF;QACvF,4EAA4E,CAC7E,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,QAA4B;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,QAAQ,CAAC,CAAC,KAAK,yCAAyC,IAAI,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,OAAO,CACL,mFAAmF;QACnF,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QACvC,6DAA6D;QAC7D,oFAAoF;QACpF,oFAAoF;QACpF,oFAAoF;QACpF,qFAAqF;QACrF,sFAAsF;QACtF,4DAA4D,CAC7D,CAAC;AACJ,CAAC"}
package/dist/emit/d1.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  import type { Change, EmitResult, SchemaSnapshot, SnapshotMeta } from "../types.js";
2
- export declare function renderD1(changes: readonly Change[], expectedSchema?: SchemaSnapshot, actualMeta?: SnapshotMeta): EmitResult;
2
+ export declare function renderD1(changes: readonly Change[], expectedSchema?: SchemaSnapshot, actualMeta?: SnapshotMeta,
3
+ /** The actual (introspected) DB schema — enables the #241 FK-cascade rebuild. */
4
+ actualSchema?: SchemaSnapshot): EmitResult;
3
5
  //# sourceMappingURL=d1.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKpF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY,GACxB,UAAU,CAqBZ"}
1
+ {"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAapF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY;AACzB,iFAAiF;AACjF,YAAY,CAAC,EAAE,cAAc,GAC5B,UAAU,CA8DZ"}
package/dist/emit/d1.js CHANGED
@@ -1,22 +1,61 @@
1
- import { renderSqlite } from "./sqlite.js";
1
+ import { renderSqlite, changeTable } from "./sqlite.js";
2
2
  import { applyD1SafetyPass } from "./d1-safety-pass.js";
3
- import { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./d1-fk-refuse.js";
4
- export function renderD1(changes, expectedSchema, actualMeta) {
3
+ import { findReferencedRebuilds, D1ReferencedTableRebuildError, D1CyclicForeignKeyError, } from "./d1-fk-refuse.js";
4
+ import { buildFkEdges, unionEdges } from "./fk-graph.js";
5
+ import { emitD1Cascade } from "./d1-cascade.js";
6
+ const EMPTY_SCHEMA = { tables: [], views: [] };
7
+ export function renderD1(changes, expectedSchema, actualMeta,
8
+ /** The actual (introspected) DB schema — enables the #241 FK-cascade rebuild. */
9
+ actualSchema) {
5
10
  const sqliteResult = renderSqlite(changes, expectedSchema, actualMeta);
6
- // #226: a rebuild (recreate-and-copy) of a table referenced by a foreign key cannot
7
- // apply on remote D1 the recipe's `PRAGMA foreign_keys = OFF` is a no-op inside
8
- // D1's implicit transaction, so `DROP TABLE <referenced>` fails. Refuse at generation
9
- // time rather than emit SQL that fails silently against production. renderSqlite has
10
- // already guaranteed expectedSchema is present when recreatedTables is non-empty.
11
- if (sqliteResult.recreatedTables.size > 0) {
12
- const refusals = findReferencedRebuilds(sqliteResult.recreatedTables, expectedSchema ?? { tables: [], views: [] });
13
- if (refusals.length > 0)
14
- throw new D1ReferencedTableRebuildError(refusals);
11
+ // Trigger detection: is any recreated table the target of a foreign key in the
12
+ // expected OR actual schema (a self-reference counts)? Only such rebuilds are
13
+ // un-appliable on D1 the recipe's `PRAGMA foreign_keys = OFF` is a no-op
14
+ // inside D1's implicit transaction, so `DROP TABLE <referenced>` fails (#226).
15
+ const edges = unionEdges(buildFkEdges(expectedSchema ?? EMPTY_SCHEMA), actualSchema ? buildFkEdges(actualSchema) : new Map());
16
+ const isReferenced = (t) => {
17
+ for (const parents of edges.values()) {
18
+ if (parents.has(t))
19
+ return true;
20
+ }
21
+ return false;
22
+ };
23
+ const referenced = [...sqliteResult.recreatedTables].filter(isReferenced);
24
+ // No referenced rebuild → byte-identical to the pre-#241 path.
25
+ if (referenced.length === 0) {
26
+ return {
27
+ up: applyD1SafetyPass(sqliteResult.up),
28
+ down: applyD1SafetyPass(sqliteResult.down),
29
+ recreatedTables: sqliteResult.recreatedTables,
30
+ };
31
+ }
32
+ // A referenced rebuild exists. Without the actual schema we cannot prove a
33
+ // cascade is safe, so refuse exactly as #226 does — never emit an unproven one.
34
+ if (actualSchema === undefined) {
35
+ throw new D1ReferencedTableRebuildError(findReferencedRebuilds(sqliteResult.recreatedTables, expectedSchema ?? EMPTY_SCHEMA));
36
+ }
37
+ // recreatedTables non-empty ⇒ renderSqlite already guaranteed expectedSchema.
38
+ const cascade = emitD1Cascade(changes, expectedSchema, actualSchema, sqliteResult.recreatedTables);
39
+ if ("refuseCycle" in cascade) {
40
+ throw new D1CyclicForeignKeyError(cascade.refuseCycle);
15
41
  }
42
+ // Splice: the affected set is rebuilt by the cascade; every other change flows
43
+ // through the native path, emitted AFTER the cascade so renamed parents exist
44
+ // before any native CREATE TABLE referencing them. `defer_foreign_keys = ON`
45
+ // persists across the whole implicit transaction, so FK checks defer to commit
46
+ // where the final state is consistent.
47
+ const { up: cascadeUp, downWarning, affected } = cascade;
48
+ const nonAffected = changes.filter((c) => {
49
+ const t = changeTable(c);
50
+ return !(t !== undefined && affected.has(t));
51
+ });
52
+ const rest = renderSqlite(nonAffected, expectedSchema, actualMeta);
53
+ const up = [cascadeUp, rest.up].filter((s) => s.length > 0).join("\n\n");
54
+ const down = [rest.down, downWarning].filter((s) => s.length > 0).join("\n\n");
16
55
  return {
17
- up: applyD1SafetyPass(sqliteResult.up),
18
- down: applyD1SafetyPass(sqliteResult.down),
19
- recreatedTables: sqliteResult.recreatedTables,
56
+ up: applyD1SafetyPass(up),
57
+ down: applyD1SafetyPass(down),
58
+ recreatedTables: new Set([...affected, ...rest.recreatedTables]),
20
59
  };
21
60
  }
22
61
  //# sourceMappingURL=d1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"d1.js","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAE1F,MAAM,UAAU,QAAQ,CACtB,OAA0B,EAC1B,cAA+B,EAC/B,UAAyB;IAEzB,MAAM,YAAY,GAAG,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,UAAU,CAAC,CAAC;IAEvE,oFAAoF;IACpF,kFAAkF;IAClF,sFAAsF;IACtF,qFAAqF;IACrF,kFAAkF;IAClF,IAAI,YAAY,CAAC,eAAe,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,sBAAsB,CACrC,YAAY,CAAC,eAAe,EAC5B,cAAc,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAC5C,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,6BAA6B,CAAC,QAAQ,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,EAAE,EAAE,iBAAiB,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,IAAI,EAAE,iBAAiB,CAAC,YAAY,CAAC,IAAI,CAAC;QAC1C,eAAe,EAAE,YAAY,CAAC,eAAe;KAC9C,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"d1.js","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { SchemaSnapshot } from "../types.js";
2
+ /**
3
+ * Pure, I/O-free graph core the D1 FK-cascade emitter orders rebuilds by. Edges
4
+ * point child→parent (a table → the table(s) its foreign keys reference),
5
+ * self-loops included for a self-referential table.
6
+ */
7
+ /** Builds child→parent FK edges from a schema. Self-loops included. */
8
+ export declare function buildFkEdges(schema: SchemaSnapshot): Map<string, Set<string>>;
9
+ /** Merges two edge maps into a new one; neither input is mutated. */
10
+ export declare function unionEdges(a: Map<string, Set<string>>, b: Map<string, Set<string>>): Map<string, Set<string>>;
11
+ /**
12
+ * `recreated` plus every transitive *referrer* — walk edges backwards: any
13
+ * table with an edge into the current set (i.e. it references a member of the
14
+ * set) joins the set, repeated to a fixpoint.
15
+ */
16
+ export declare function affectedSet(recreated: ReadonlySet<string>, edges: Map<string, Set<string>>): Set<string>;
17
+ /**
18
+ * Kahn's algorithm over `nodes`, treating an edge `child→parent` as "parent
19
+ * must come before child". Self-loops (`x→x`) and edges whose target is not
20
+ * in `nodes` are dropped before ordering. `order` is parents-first. If nodes
21
+ * remain when the queue empties, the sort cannot complete — those remaining
22
+ * nodes (a multi-node cycle) are returned as `cycle` instead.
23
+ */
24
+ export declare function topoOrder(nodes: Set<string>, edges: Map<string, Set<string>>): {
25
+ order: string[];
26
+ cycle: string[] | null;
27
+ };
28
+ //# sourceMappingURL=fk-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fk-graph.d.ts","sourceRoot":"","sources":["../../src/emit/fk-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;GAIG;AAEH,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAU7E;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CACxB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAC3B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC1B,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAc1B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAC9B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC9B,GAAG,CAAC,MAAM,CAAC,CAiBb;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAClB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC9B;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAAE,CA8C7C"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pure, I/O-free graph core the D1 FK-cascade emitter orders rebuilds by. Edges
3
+ * point child→parent (a table → the table(s) its foreign keys reference),
4
+ * self-loops included for a self-referential table.
5
+ */
6
+ /** Builds child→parent FK edges from a schema. Self-loops included. */
7
+ export function buildFkEdges(schema) {
8
+ const edges = new Map();
9
+ for (const t of schema.tables) {
10
+ const parents = new Set();
11
+ for (const fk of t.foreignKeys) {
12
+ parents.add(fk.refTable);
13
+ }
14
+ edges.set(t.name, parents);
15
+ }
16
+ return edges;
17
+ }
18
+ /** Merges two edge maps into a new one; neither input is mutated. */
19
+ export function unionEdges(a, b) {
20
+ const merged = new Map();
21
+ for (const [node, parents] of a) {
22
+ merged.set(node, new Set(parents));
23
+ }
24
+ for (const [node, parents] of b) {
25
+ const existing = merged.get(node);
26
+ if (existing) {
27
+ for (const p of parents)
28
+ existing.add(p);
29
+ }
30
+ else {
31
+ merged.set(node, new Set(parents));
32
+ }
33
+ }
34
+ return merged;
35
+ }
36
+ /**
37
+ * `recreated` plus every transitive *referrer* — walk edges backwards: any
38
+ * table with an edge into the current set (i.e. it references a member of the
39
+ * set) joins the set, repeated to a fixpoint.
40
+ */
41
+ export function affectedSet(recreated, edges) {
42
+ const affected = new Set(recreated);
43
+ let changed = true;
44
+ while (changed) {
45
+ changed = false;
46
+ for (const [node, parents] of edges) {
47
+ if (affected.has(node))
48
+ continue;
49
+ for (const p of parents) {
50
+ if (affected.has(p)) {
51
+ affected.add(node);
52
+ changed = true;
53
+ break;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ return affected;
59
+ }
60
+ /**
61
+ * Kahn's algorithm over `nodes`, treating an edge `child→parent` as "parent
62
+ * must come before child". Self-loops (`x→x`) and edges whose target is not
63
+ * in `nodes` are dropped before ordering. `order` is parents-first. If nodes
64
+ * remain when the queue empties, the sort cannot complete — those remaining
65
+ * nodes (a multi-node cycle) are returned as `cycle` instead.
66
+ */
67
+ export function topoOrder(nodes, edges) {
68
+ // childOf(parent) = children within `nodes` that must be emitted after `parent`.
69
+ const childrenOf = new Map();
70
+ const inDegree = new Map();
71
+ for (const n of nodes) {
72
+ childrenOf.set(n, new Set());
73
+ inDegree.set(n, 0);
74
+ }
75
+ for (const n of nodes) {
76
+ const parents = edges.get(n) ?? new Set();
77
+ for (const p of parents) {
78
+ if (p === n)
79
+ continue; // drop self-loop
80
+ if (!nodes.has(p))
81
+ continue; // drop edge leaving `nodes`
82
+ const kids = childrenOf.get(p);
83
+ if (kids === undefined)
84
+ continue;
85
+ if (kids.has(n))
86
+ continue; // already recorded (dedupe parallel edges)
87
+ kids.add(n);
88
+ inDegree.set(n, (inDegree.get(n) ?? 0) + 1);
89
+ }
90
+ }
91
+ // Stable order: iterate `nodes` in insertion order for the initial queue.
92
+ const queue = [];
93
+ for (const n of nodes) {
94
+ if (inDegree.get(n) === 0)
95
+ queue.push(n);
96
+ }
97
+ const order = [];
98
+ let head = 0;
99
+ while (head < queue.length) {
100
+ const n = queue[head++];
101
+ if (n === undefined)
102
+ continue;
103
+ order.push(n);
104
+ for (const child of childrenOf.get(n) ?? []) {
105
+ const remaining = (inDegree.get(child) ?? 0) - 1;
106
+ inDegree.set(child, remaining);
107
+ if (remaining === 0)
108
+ queue.push(child);
109
+ }
110
+ }
111
+ if (order.length < nodes.size) {
112
+ const remaining = [...nodes].filter((n) => !order.includes(n));
113
+ return { order: [], cycle: remaining };
114
+ }
115
+ return { order, cycle: null };
116
+ }
117
+ //# sourceMappingURL=fk-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fk-graph.js","sourceRoot":"","sources":["../../src/emit/fk-graph.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,8 @@ export interface EmitOptions {
12
12
  * fall back to recreate-and-copy. Unknown/absent version → assume modern.
13
13
  */
14
14
  actualMeta?: SnapshotMeta;
15
+ /** Used by the d1 cascade emitter to build the actual∪expected FK graph. */
16
+ actualSchema?: SchemaSnapshot;
15
17
  }
16
18
  export declare function emit(changes: Change[], opts: EmitOptions): EmitResult;
17
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS7F,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CASrE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS7F,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,cAAc,CAAC;CAC/B;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CASrE"}
@@ -9,7 +9,7 @@ export function emit(changes, opts) {
9
9
  switch (opts.dialect) {
10
10
  case "postgres": return renderPostgres(changes);
11
11
  case "sqlite": return renderSqlite(changes, opts.expectedSchema, opts.actualMeta);
12
- case "d1": return renderD1(changes, opts.expectedSchema, opts.actualMeta);
12
+ case "d1": return renderD1(changes, opts.expectedSchema, opts.actualMeta, opts.actualSchema);
13
13
  }
14
14
  }
15
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAoBnC,MAAM,UAAU,IAAI,CAAC,OAAiB,EAAE,IAAiB;IACvD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE/D,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,UAAU,CAAC,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;QAChD,KAAK,QAAQ,CAAC,CAAG,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACpF,KAAK,IAAI,CAAC,CAAO,OAAO,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAClF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAsBnC,MAAM,UAAU,IAAI,CAAC,OAAiB,EAAE,IAAiB;IACvD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE/D,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,UAAU,CAAC,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;QAChD,KAAK,QAAQ,CAAC,CAAG,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACpF,KAAK,IAAI,CAAC,CAAO,OAAO,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
@@ -1,3 +1,15 @@
1
- import type { Change, EmitResult, SchemaSnapshot, SnapshotMeta } from "../types.js";
1
+ import type { Change, EmitResult, IndexDescriptor, TableDescriptor, SchemaSnapshot, SnapshotMeta } from "../types.js";
2
+ export interface CarryColumns {
3
+ insertCols: string[];
4
+ selectCols: string[];
5
+ }
2
6
  export declare function renderSqlite(changes: readonly Change[], expectedSchema?: SchemaSnapshot, actualMeta?: SnapshotMeta): EmitResult;
7
+ /** The table a change targets, or undefined for view-scoped changes. Exported for the D1 FK-cascade emitter (read-only). */
8
+ export declare function changeTable(c: Change): string | undefined;
9
+ /** newTable columns not newly-added, mapped to their old-name SELECT source (for renames). */
10
+ export declare function computeCarryColumns(tableChanges: Change[], newTable: TableDescriptor): CarryColumns;
11
+ export declare function renderCreateTable(t: TableDescriptor): string;
12
+ export declare function renderCreateIndex(table: string, ix: IndexDescriptor): string;
13
+ /** SQLite identifier quoter (`"id"`). Exported for the D1 FK-cascade emitter (read-only). */
14
+ export declare function quote(ident: string): string;
3
15
  //# sourceMappingURL=sqlite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/emit/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAAE,UAAU,EACD,cAAc,EAAE,YAAY,EAC9C,MAAM,aAAa,CAAC;AAgCrB,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY,GACxB,UAAU,CAmEZ"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/emit/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAAE,UAAU,EAAoB,eAAe,EACrD,eAAe,EAAE,cAAc,EAAE,YAAY,EAC9C,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,YAAY;IAAG,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;CAAE;AA+B7E,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY,GACxB,UAAU,CAmEZ;AAED,4HAA4H;AAC5H,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqBzD;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,eAAe,GAAG,YAAY,CASnG;AA4GD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAyB5D;AAqFD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,GAAG,MAAM,CAoB5E;AAED,6FAA6F;AAC7F,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3C"}
@@ -92,7 +92,8 @@ export function renderSqlite(changes, expectedSchema, actualMeta) {
92
92
  recreatedTables: recreateTables,
93
93
  };
94
94
  }
95
- function changeTable(c) {
95
+ /** The table a change targets, or undefined for view-scoped changes. Exported for the D1 FK-cascade emitter (read-only). */
96
+ export function changeTable(c) {
96
97
  switch (c.kind) {
97
98
  case "create-table": return c.table.name;
98
99
  case "drop-table": return c.table;
@@ -114,27 +115,24 @@ function changeTable(c) {
114
115
  return undefined;
115
116
  }
116
117
  }
117
- function renderRecreate(table, tableChanges, newTable) {
118
- // Build the column-name remapping: old_name → new_name (for renames).
118
+ /** newTable columns not newly-added, mapped to their old-name SELECT source (for renames). */
119
+ export function computeCarryColumns(tableChanges, newTable) {
119
120
  const renames = new Map();
120
- for (const c of tableChanges) {
121
+ for (const c of tableChanges)
121
122
  if (c.kind === "rename-column")
122
123
  renames.set(c.from, c.to);
123
- }
124
- // Columns added in this migration don't exist in old table — exclude from SELECT.
125
124
  const addedNames = new Set();
126
- for (const c of tableChanges) {
125
+ for (const c of tableChanges)
127
126
  if (c.kind === "add-column")
128
127
  addedNames.add(c.column.name);
129
- }
130
- // Reverse map: new_name → old_name (for SELECT source column lookup).
131
128
  const renamesReverse = new Map();
132
129
  for (const [from, to] of renames)
133
130
  renamesReverse.set(to, from);
134
- // carryColumns = newTable columns NOT being newly added (they exist in the old table).
135
- const carryColumns = newTable.columns.filter((c) => !addedNames.has(c.name));
136
- const insertCols = carryColumns.map((c) => c.name);
137
- const selectCols = carryColumns.map((c) => renamesReverse.get(c.name) ?? c.name);
131
+ const carry = newTable.columns.filter((c) => !addedNames.has(c.name));
132
+ return { insertCols: carry.map((c) => c.name), selectCols: carry.map((c) => renamesReverse.get(c.name) ?? c.name) };
133
+ }
134
+ function renderRecreate(table, tableChanges, newTable) {
135
+ const { insertCols, selectCols } = computeCarryColumns(tableChanges, newTable);
138
136
  // Build the new-table CREATE using temp name.
139
137
  const tmp = `__new_${table}`;
140
138
  const tmpDescriptor = { ...newTable, name: tmp };
@@ -226,7 +224,7 @@ function renderDownNative(c) {
226
224
  case "replace-view": return `-- WARNING: down migration cannot restore the original view definition`;
227
225
  }
228
226
  }
229
- function renderCreateTable(t) {
227
+ export function renderCreateTable(t) {
230
228
  const compositePk = t.primaryKey.length > 1;
231
229
  const colDefs = t.columns.map((c) => {
232
230
  const isSinglePk = !compositePk && t.primaryKey[0] === c.name;
@@ -341,7 +339,7 @@ function renderDefault(d, t) {
341
339
  return quoted;
342
340
  }
343
341
  }
344
- function renderCreateIndex(table, ix) {
342
+ export function renderCreateIndex(table, ix) {
345
343
  const u = ix.unique ? "UNIQUE " : "";
346
344
  // SQLite natively supports expression indexes, per-column DESC, and partial
347
345
  // (WHERE) indexes — render all three. Dropping them is not an option:
@@ -362,7 +360,8 @@ function renderCreateIndex(table, ix) {
362
360
  const where = ix.where ? ` WHERE (${ix.where})` : "";
363
361
  return `CREATE ${u}INDEX ${quote(ix.name)} ON ${quote(table)} (${keys})${where};`;
364
362
  }
365
- function quote(ident) {
363
+ /** SQLite identifier quoter (`"id"`). Exported for the D1 FK-cascade emitter (read-only). */
364
+ export function quote(ident) {
366
365
  if (ident.includes('"'))
367
366
  throw new Error(`unsafe identifier: ${ident}`);
368
367
  return `"${ident}"`;