@metaobjectsdev/migrate-ts 0.20.6 → 0.20.7
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/emit/d1-fk-refuse.d.ts +22 -0
- package/dist/emit/d1-fk-refuse.d.ts.map +1 -0
- package/dist/emit/d1-fk-refuse.js +43 -0
- package/dist/emit/d1-fk-refuse.js.map +1 -0
- package/dist/emit/d1.d.ts.map +1 -1
- package/dist/emit/d1.js +11 -0
- package/dist/emit/d1.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/emit/d1-fk-refuse.ts +56 -0
- package/src/emit/d1.ts +15 -0
- package/src/index.ts +2 -0
|
@@ -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"}
|
package/dist/emit/d1.d.ts.map
CHANGED
|
@@ -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;
|
|
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),
|
package/dist/emit/d1.js.map
CHANGED
|
@@ -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;
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,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,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,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;AACvE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG3E,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG1D,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EACV,cAAc,EAAE,YAAY,EAC5B,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAC/E,cAAc,EAAE,QAAQ,EACxB,MAAM,EAAE,UAAU,EAAE,YAAY,EAChC,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EACrE,UAAU,EAAE,UAAU,EAAE,OAAO,GAChC,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACxF,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAM/F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAMxE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;
|
|
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;
|
|
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.
|
|
3
|
+
"version": "0.20.7",
|
|
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.
|
|
48
|
+
"@metaobjectsdev/metadata": "0.20.7"
|
|
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";
|