@metaobjectsdev/migrate-ts 0.20.6-rc.1 → 0.20.7-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,22 @@
1
+ import type { SchemaSnapshot } from "../types.js";
2
+ /** A rebuilt table that cannot be rebuilt on D1 because a foreign key targets it. */
3
+ export interface D1RebuildRefusal {
4
+ /** The table being rebuilt. */
5
+ table: string;
6
+ /** Tables whose foreign key targets `table` (includes `table` itself for a self-reference). */
7
+ referencedBy: string[];
8
+ }
9
+ /**
10
+ * Of the tables being rebuilt (recreate-and-copy), which are the target of a foreign
11
+ * key in the expected schema? On remote D1 the rebuild recipe's `PRAGMA foreign_keys
12
+ * = OFF` is a no-op inside D1's implicit transaction, so `DROP TABLE` of a referenced
13
+ * table fails with "FOREIGN KEY constraint failed" (#226). Detection uses the target
14
+ * (expected) schema and errs toward refusing.
15
+ */
16
+ export declare function findReferencedRebuilds(recreatedTables: ReadonlySet<string>, expectedSchema: SchemaSnapshot): D1RebuildRefusal[];
17
+ /** Thrown at generation time when a D1 migration would rebuild an FK-referenced table. */
18
+ export declare class D1ReferencedTableRebuildError extends Error {
19
+ readonly refusals: D1RebuildRefusal[];
20
+ constructor(refusals: D1RebuildRefusal[]);
21
+ }
22
+ //# sourceMappingURL=d1-fk-refuse.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Of the tables being rebuilt (recreate-and-copy), which are the target of a foreign
3
+ * key in the expected schema? On remote D1 the rebuild recipe's `PRAGMA foreign_keys
4
+ * = OFF` is a no-op inside D1's implicit transaction, so `DROP TABLE` of a referenced
5
+ * table fails with "FOREIGN KEY constraint failed" (#226). Detection uses the target
6
+ * (expected) schema and errs toward refusing.
7
+ */
8
+ export function findReferencedRebuilds(recreatedTables, expectedSchema) {
9
+ const refusals = [];
10
+ for (const t of recreatedTables) {
11
+ const referencedBy = expectedSchema.tables
12
+ .filter((tbl) => tbl.foreignKeys.some((fk) => fk.refTable === t))
13
+ .map((tbl) => tbl.name);
14
+ if (referencedBy.length > 0)
15
+ refusals.push({ table: t, referencedBy });
16
+ }
17
+ return refusals;
18
+ }
19
+ /** Thrown at generation time when a D1 migration would rebuild an FK-referenced table. */
20
+ export class D1ReferencedTableRebuildError extends Error {
21
+ refusals;
22
+ constructor(refusals) {
23
+ super(formatMessage(refusals));
24
+ this.refusals = refusals;
25
+ this.name = "D1ReferencedTableRebuildError";
26
+ }
27
+ }
28
+ function formatMessage(refusals) {
29
+ const lines = refusals.map((r) => {
30
+ const refs = r.referencedBy.map((n) => `"${n}"`).join(", ");
31
+ return ` - "${r.table}" is referenced by a foreign key from ${refs}`;
32
+ });
33
+ return (`Cannot rebuild the following table(s) on Cloudflare D1 — each is the target of a ` +
34
+ `foreign key:\n${lines.join("\n")}\n\n` +
35
+ `D1 applies migrations inside an implicit transaction where ` +
36
+ "`PRAGMA foreign_keys = OFF` is a no-op, so dropping a referenced table during the " +
37
+ `rebuild fails with "FOREIGN KEY constraint failed". The rebuild is triggered by a ` +
38
+ `CHECK, column type/nullability/default, foreign-key, or enum-values change on the ` +
39
+ `table. To apply it on D1, hand-write this migration (rebuild the referencing table ` +
40
+ `to temporarily drop its foreign key, rebuild the referenced table, then restore the ` +
41
+ `foreign key), or make the change on an unreferenced table.`);
42
+ }
43
+ //# sourceMappingURL=d1-fk-refuse.js.map
@@ -0,0 +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 +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;AAIpF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY,GACxB,UAAU,CAOZ"}
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"}
package/dist/emit/d1.js CHANGED
@@ -1,7 +1,18 @@
1
1
  import { renderSqlite } from "./sqlite.js";
2
2
  import { applyD1SafetyPass } from "./d1-safety-pass.js";
3
+ import { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./d1-fk-refuse.js";
3
4
  export function renderD1(changes, expectedSchema, actualMeta) {
4
5
  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);
15
+ }
5
16
  return {
6
17
  up: applyD1SafetyPass(sqliteResult.up),
7
18
  down: applyD1SafetyPass(sqliteResult.down),
@@ -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;AAExD,MAAM,UAAU,QAAQ,CACtB,OAA0B,EAC1B,cAA+B,EAC/B,UAAyB;IAEzB,MAAM,YAAY,GAAG,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,UAAU,CAAC,CAAC;IACvE,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":"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"}
package/dist/index.d.ts CHANGED
@@ -24,6 +24,8 @@ export type { WriteMigrationD1Options, WriteMigrationD1Result } from "./write-mi
24
24
  export { normalizeViewSql, viewSqlEquals } from "./view-sql-compare.js";
25
25
  export { renderD1 } from "./emit/d1.js";
26
26
  export { applyD1SafetyPass, D1UnsupportedStatementError } from "./emit/d1-safety-pass.js";
27
+ export { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./emit/d1-fk-refuse.js";
28
+ export type { D1RebuildRefusal } from "./emit/d1-fk-refuse.js";
27
29
  export { introspectD1, type D1Runner, type IntrospectD1Options } from "./introspect/d1.js";
28
30
  export { ensureLedger, recordApplied, deleteApplied, appliedNames, appliedRecords, recordBaseline, baselineRecord, BASELINE_NAME, MIGRATIONS_TABLE, DEFAULT_LEDGER_SCHEMA, type LedgerRow, type LedgerOptions, type LedgerDialect, } from "./apply/ledger.js";
29
31
  export { applyPending, rollbackTo, type ApplyPendingOptions, type ApplyPendingResult, type RollbackToOptions, type RollbackToResult, } from "./apply/apply.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export { normalizeViewSql, viewSqlEquals } from "./view-sql-compare.js";
36
36
  // independently without going through emit().
37
37
  export { renderD1 } from "./emit/d1.js";
38
38
  export { applyD1SafetyPass, D1UnsupportedStatementError } from "./emit/d1-safety-pass.js";
39
+ export { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./emit/d1-fk-refuse.js";
39
40
  // D1 introspection
40
41
  export { introspectD1 } from "./introspect/d1.js";
41
42
  // Migration-history ledger + ordered transactional apply (postgres/sqlite)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,gEAAgE;AAChE,yEAAyE;AACzE,sEAAsE;AACtE,2BAA2B;AAC3B,EAAE;AACF,2DAA2D;AAE3D,qBAAqB;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAA4B,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,0DAA0D;AAC1D,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAGvE,SAAS;AACT,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3E,oEAAoE;AACpE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiB1D,+EAA+E;AAC/E,gFAAgF;AAChF,mFAAmF;AACnF,4EAA4E;AAC5E,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAExE,oCAAoC;AACpC,wEAAwE;AACxE,yEAAyE;AACzE,8CAA8C;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE1F,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAA2C,MAAM,oBAAoB,CAAC;AAE3F,2EAA2E;AAC3E,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,qBAAqB,GAItB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,UAAU,GAKX,MAAM,kBAAkB,CAAC;AAE1B,0EAA0E;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,0BAA0B;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GAGjB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,gEAAgE;AAChE,yEAAyE;AACzE,sEAAsE;AACtE,2BAA2B;AAC3B,EAAE;AACF,2DAA2D;AAE3D,qBAAqB;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAA4B,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,0DAA0D;AAC1D,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAGvE,SAAS;AACT,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3E,oEAAoE;AACpE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiB1D,+EAA+E;AAC/E,gFAAgF;AAChF,mFAAmF;AACnF,4EAA4E;AAC5E,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAExE,oCAAoC;AACpC,wEAAwE;AACxE,yEAAyE;AACzE,8CAA8C;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAG/F,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAA2C,MAAM,oBAAoB,CAAC;AAE3F,2EAA2E;AAC3E,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,qBAAqB,GAItB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,UAAU,GAKX,MAAM,kBAAkB,CAAC;AAE1B,0EAA0E;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,0BAA0B;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GAGjB,MAAM,sBAAsB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/migrate-ts",
3
- "version": "0.20.6-rc.1",
3
+ "version": "0.20.7-rc.1",
4
4
  "description": "Schema migration tooling for MetaObjects: diff metadata vs DB and emit SQL for Postgres and SQLite.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@iarna/toml": "^2.2.5",
48
- "@metaobjectsdev/metadata": "0.20.6-rc.1"
48
+ "@metaobjectsdev/metadata": "0.20.7-rc.1"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "kysely": ">=0.27.0"
@@ -0,0 +1,56 @@
1
+ import type { SchemaSnapshot } from "../types.js";
2
+
3
+ /** A rebuilt table that cannot be rebuilt on D1 because a foreign key targets it. */
4
+ export interface D1RebuildRefusal {
5
+ /** The table being rebuilt. */
6
+ table: string;
7
+ /** Tables whose foreign key targets `table` (includes `table` itself for a self-reference). */
8
+ referencedBy: string[];
9
+ }
10
+
11
+ /**
12
+ * Of the tables being rebuilt (recreate-and-copy), which are the target of a foreign
13
+ * key in the expected schema? On remote D1 the rebuild recipe's `PRAGMA foreign_keys
14
+ * = OFF` is a no-op inside D1's implicit transaction, so `DROP TABLE` of a referenced
15
+ * table fails with "FOREIGN KEY constraint failed" (#226). Detection uses the target
16
+ * (expected) schema and errs toward refusing.
17
+ */
18
+ export function findReferencedRebuilds(
19
+ recreatedTables: ReadonlySet<string>,
20
+ expectedSchema: SchemaSnapshot,
21
+ ): D1RebuildRefusal[] {
22
+ const refusals: D1RebuildRefusal[] = [];
23
+ for (const t of recreatedTables) {
24
+ const referencedBy = expectedSchema.tables
25
+ .filter((tbl) => tbl.foreignKeys.some((fk) => fk.refTable === t))
26
+ .map((tbl) => tbl.name);
27
+ if (referencedBy.length > 0) refusals.push({ table: t, referencedBy });
28
+ }
29
+ return refusals;
30
+ }
31
+
32
+ /** Thrown at generation time when a D1 migration would rebuild an FK-referenced table. */
33
+ export class D1ReferencedTableRebuildError extends Error {
34
+ constructor(public readonly refusals: D1RebuildRefusal[]) {
35
+ super(formatMessage(refusals));
36
+ this.name = "D1ReferencedTableRebuildError";
37
+ }
38
+ }
39
+
40
+ function formatMessage(refusals: D1RebuildRefusal[]): string {
41
+ const lines = refusals.map((r) => {
42
+ const refs = r.referencedBy.map((n) => `"${n}"`).join(", ");
43
+ return ` - "${r.table}" is referenced by a foreign key from ${refs}`;
44
+ });
45
+ return (
46
+ `Cannot rebuild the following table(s) on Cloudflare D1 — each is the target of a ` +
47
+ `foreign key:\n${lines.join("\n")}\n\n` +
48
+ `D1 applies migrations inside an implicit transaction where ` +
49
+ "`PRAGMA foreign_keys = OFF` is a no-op, so dropping a referenced table during the " +
50
+ `rebuild fails with "FOREIGN KEY constraint failed". The rebuild is triggered by a ` +
51
+ `CHECK, column type/nullability/default, foreign-key, or enum-values change on the ` +
52
+ `table. To apply it on D1, hand-write this migration (rebuild the referencing table ` +
53
+ `to temporarily drop its foreign key, rebuild the referenced table, then restore the ` +
54
+ `foreign key), or make the change on an unreferenced table.`
55
+ );
56
+ }
package/src/emit/d1.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Change, EmitResult, SchemaSnapshot, SnapshotMeta } from "../types.js";
2
2
  import { renderSqlite } from "./sqlite.js";
3
3
  import { applyD1SafetyPass } from "./d1-safety-pass.js";
4
+ import { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./d1-fk-refuse.js";
4
5
 
5
6
  export function renderD1(
6
7
  changes: readonly Change[],
@@ -8,6 +9,20 @@ export function renderD1(
8
9
  actualMeta?: SnapshotMeta,
9
10
  ): EmitResult {
10
11
  const sqliteResult = renderSqlite(changes, expectedSchema, actualMeta);
12
+
13
+ // #226: a rebuild (recreate-and-copy) of a table referenced by a foreign key cannot
14
+ // apply on remote D1 — the recipe's `PRAGMA foreign_keys = OFF` is a no-op inside
15
+ // D1's implicit transaction, so `DROP TABLE <referenced>` fails. Refuse at generation
16
+ // time rather than emit SQL that fails silently against production. renderSqlite has
17
+ // already guaranteed expectedSchema is present when recreatedTables is non-empty.
18
+ if (sqliteResult.recreatedTables.size > 0) {
19
+ const refusals = findReferencedRebuilds(
20
+ sqliteResult.recreatedTables,
21
+ expectedSchema ?? { tables: [], views: [] },
22
+ );
23
+ if (refusals.length > 0) throw new D1ReferencedTableRebuildError(refusals);
24
+ }
25
+
11
26
  return {
12
27
  up: applyD1SafetyPass(sqliteResult.up),
13
28
  down: applyD1SafetyPass(sqliteResult.down),
package/src/index.ts CHANGED
@@ -63,6 +63,8 @@ export { normalizeViewSql, viewSqlEquals } from "./view-sql-compare.js";
63
63
  // independently without going through emit().
64
64
  export { renderD1 } from "./emit/d1.js";
65
65
  export { applyD1SafetyPass, D1UnsupportedStatementError } from "./emit/d1-safety-pass.js";
66
+ export { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./emit/d1-fk-refuse.js";
67
+ export type { D1RebuildRefusal } from "./emit/d1-fk-refuse.js";
66
68
 
67
69
  // D1 introspection
68
70
  export { introspectD1, type D1Runner, type IntrospectD1Options } from "./introspect/d1.js";