@supabase/pg-delta 1.0.0-alpha.20 → 1.0.0-alpha.21

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.
Files changed (28) hide show
  1. package/dist/core/catalog.diff.js +0 -1
  2. package/dist/core/objects/publication/changes/publication.alter.d.ts +1 -1
  3. package/dist/core/objects/sequence/sequence.diff.js +13 -5
  4. package/dist/core/objects/table/changes/table.alter.d.ts +4 -0
  5. package/dist/core/objects/table/changes/table.alter.js +19 -4
  6. package/dist/core/objects/table/table.diff.js +21 -2
  7. package/dist/core/objects/table/table.model.js +10 -7
  8. package/dist/core/post-diff-cycle-breaking.d.ts +21 -21
  9. package/dist/core/post-diff-cycle-breaking.js +24 -133
  10. package/dist/core/sort/cycle-breakers.d.ts +15 -0
  11. package/dist/core/sort/cycle-breakers.js +269 -0
  12. package/dist/core/sort/sort-changes.js +97 -43
  13. package/package.json +1 -1
  14. package/src/core/catalog.diff.ts +0 -1
  15. package/src/core/expand-replace-dependencies.test.ts +8 -5
  16. package/src/core/objects/publication/changes/publication.alter.ts +1 -1
  17. package/src/core/objects/sequence/sequence.diff.test.ts +6 -1
  18. package/src/core/objects/sequence/sequence.diff.ts +12 -4
  19. package/src/core/objects/table/changes/table.alter.test.ts +13 -2
  20. package/src/core/objects/table/changes/table.alter.ts +36 -7
  21. package/src/core/objects/table/table.diff.test.ts +43 -0
  22. package/src/core/objects/table/table.diff.ts +28 -4
  23. package/src/core/objects/table/table.model.ts +10 -7
  24. package/src/core/post-diff-cycle-breaking.test.ts +0 -156
  25. package/src/core/post-diff-cycle-breaking.ts +23 -202
  26. package/src/core/sort/cycle-breakers.test.ts +476 -0
  27. package/src/core/sort/cycle-breakers.ts +311 -0
  28. package/src/core/sort/sort-changes.ts +135 -50
@@ -161,7 +161,6 @@ export function diffCatalogs(main, branch, options) {
161
161
  });
162
162
  filteredChanges = normalizePostDiffCycles({
163
163
  changes: expandedDependencies.changes,
164
- mainCatalog: main,
165
164
  replacedTableIds: expandedDependencies.replacedTableIds,
166
165
  });
167
166
  debugCatalog("changes catalog diff: %O", stringifyWithBigInt(filteredChanges, 2));
@@ -37,7 +37,7 @@ export declare class AlterPublicationAddTables extends AlterPublicationChange {
37
37
  export declare class AlterPublicationDropTables extends AlterPublicationChange {
38
38
  readonly publication: Publication;
39
39
  readonly scope: "object";
40
- private readonly tables;
40
+ readonly tables: PublicationTableProps[];
41
41
  constructor(props: {
42
42
  publication: Publication;
43
43
  tables: PublicationTableProps[];
@@ -87,10 +87,7 @@ export function diffSequences(ctx, main, branch, branchTables = {}) {
87
87
  const branchSequence = branch[sequenceId];
88
88
  // Check if non-alterable properties have changed
89
89
  // These require dropping and recreating the sequence
90
- const NON_ALTERABLE_FIELDS = [
91
- "data_type",
92
- "persistence",
93
- ];
90
+ const NON_ALTERABLE_FIELDS = ["persistence"];
94
91
  const nonAlterablePropsChanged = hasNonAlterableChanges(mainSequence, branchSequence, NON_ALTERABLE_FIELDS);
95
92
  if (nonAlterablePropsChanged) {
96
93
  // Replace the entire sequence (drop + create)
@@ -131,7 +128,8 @@ export function diffSequences(ctx, main, branch, branchTables = {}) {
131
128
  }
132
129
  else {
133
130
  // Only alterable properties changed - emit ALTER for options/owner
134
- const optionsChanged = mainSequence.increment !== branchSequence.increment ||
131
+ const optionsChanged = mainSequence.data_type !== branchSequence.data_type ||
132
+ mainSequence.increment !== branchSequence.increment ||
135
133
  mainSequence.minimum_value !== branchSequence.minimum_value ||
136
134
  mainSequence.maximum_value !== branchSequence.maximum_value ||
137
135
  mainSequence.start_value !== branchSequence.start_value ||
@@ -139,6 +137,16 @@ export function diffSequences(ctx, main, branch, branchTables = {}) {
139
137
  mainSequence.cycle_option !== branchSequence.cycle_option;
140
138
  if (optionsChanged) {
141
139
  const options = [];
140
+ // `AS <type>` must come before any MIN/MAX/RESTART clauses per the
141
+ // PG ALTER SEQUENCE grammar. Valid types are smallint, integer,
142
+ // bigint — the same set CREATE SEQUENCE accepts — so the universe
143
+ // of legal transitions is closed. PG enforces last_value range at
144
+ // apply time when shrinking; that's the desired behavior because
145
+ // the previous Drop+Create path silently reset last_value to 1
146
+ // (data-loss bug, see Sentry SUPABASE-API-7RS).
147
+ if (mainSequence.data_type !== branchSequence.data_type) {
148
+ options.push("AS", branchSequence.data_type);
149
+ }
142
150
  if (mainSequence.increment !== branchSequence.increment) {
143
151
  options.push("INCREMENT BY", String(branchSequence.increment));
144
152
  }
@@ -250,9 +250,11 @@ export declare class AlterTableDropColumn extends AlterTableChange {
250
250
  readonly table: Table;
251
251
  readonly column: ColumnProps;
252
252
  readonly scope: "object";
253
+ readonly omitTableRequirement: boolean;
253
254
  constructor(props: {
254
255
  table: Table;
255
256
  column: ColumnProps;
257
+ omitTableRequirement?: boolean;
256
258
  });
257
259
  get drops(): `column:${string}.${string}.${string}`[];
258
260
  get requires(): (`column:${string}.${string}.${string}` | `table:${string}`)[];
@@ -264,10 +266,12 @@ export declare class AlterTableDropColumn extends AlterTableChange {
264
266
  export declare class AlterTableAlterColumnType extends AlterTableChange {
265
267
  readonly table: Table;
266
268
  readonly column: ColumnProps;
269
+ readonly previousColumn?: ColumnProps;
267
270
  readonly scope: "object";
268
271
  constructor(props: {
269
272
  table: Table;
270
273
  column: ColumnProps;
274
+ previousColumn?: ColumnProps;
271
275
  });
272
276
  get requires(): `column:${string}.${string}.${string}`[];
273
277
  serialize(_options?: SerializeOptions): string;
@@ -386,10 +386,16 @@ export class AlterTableDropColumn extends AlterTableChange {
386
386
  table;
387
387
  column;
388
388
  scope = "object";
389
+ // Drop the implicit `requires(table)` edge. Only set by the lazy
390
+ // cycle-breaker for the publication↔column case, where the table survives
391
+ // the migration and the edge is therefore artificial. See
392
+ // `sort/cycle-breakers.ts` for the full justification.
393
+ omitTableRequirement;
389
394
  constructor(props) {
390
395
  super();
391
396
  this.table = props.table;
392
397
  this.column = props.column;
398
+ this.omitTableRequirement = props.omitTableRequirement ?? false;
393
399
  }
394
400
  get drops() {
395
401
  return [
@@ -397,10 +403,8 @@ export class AlterTableDropColumn extends AlterTableChange {
397
403
  ];
398
404
  }
399
405
  get requires() {
400
- return [
401
- this.table.stableId,
402
- stableId.column(this.table.schema, this.table.name, this.column.name),
403
- ];
406
+ const colId = stableId.column(this.table.schema, this.table.name, this.column.name);
407
+ return this.omitTableRequirement ? [colId] : [this.table.stableId, colId];
404
408
  }
405
409
  serialize(_options) {
406
410
  return [
@@ -417,11 +421,13 @@ export class AlterTableDropColumn extends AlterTableChange {
417
421
  export class AlterTableAlterColumnType extends AlterTableChange {
418
422
  table;
419
423
  column;
424
+ previousColumn;
420
425
  scope = "object";
421
426
  constructor(props) {
422
427
  super();
423
428
  this.table = props.table;
424
429
  this.column = props.column;
430
+ this.previousColumn = props.previousColumn;
425
431
  }
426
432
  get requires() {
427
433
  return [
@@ -429,6 +435,12 @@ export class AlterTableAlterColumnType extends AlterTableChange {
429
435
  ];
430
436
  }
431
437
  serialize(_options) {
438
+ // previousColumn is optional so direct serializer tests/fixtures can keep
439
+ // emitting canonical ALTER TYPE SQL without forcing a USING expression.
440
+ // When provided, we can detect true type changes and add USING for casts
441
+ // PostgreSQL cannot perform automatically.
442
+ const hasTypeChangedWithPreviousDefinition = this.previousColumn?.data_type_str !== undefined &&
443
+ this.previousColumn.data_type_str !== this.column.data_type_str;
432
444
  const parts = [
433
445
  "ALTER TABLE",
434
446
  `${this.table.schema}.${this.table.name}`,
@@ -440,6 +452,9 @@ export class AlterTableAlterColumnType extends AlterTableChange {
440
452
  if (this.column.collation) {
441
453
  parts.push("COLLATE", this.column.collation);
442
454
  }
455
+ if (hasTypeChangedWithPreviousDefinition) {
456
+ parts.push("USING", `${this.column.name}::${this.column.data_type_str}`);
457
+ }
443
458
  return parts.join(" ");
444
459
  }
445
460
  }
@@ -451,15 +451,30 @@ export function diffTables(ctx, main, branch) {
451
451
  const branchCol = branchCols.get(name);
452
452
  if (!branchCol)
453
453
  continue;
454
+ const columnTypeChanged = mainCol.data_type_str !== branchCol.data_type_str;
455
+ const columnCollationChanged = mainCol.collation !== branchCol.collation;
456
+ const needsDefaultSafeFlow = columnTypeChanged && mainCol.default !== null;
454
457
  // TYPE or COLLATION change
455
- if (mainCol.data_type_str !== branchCol.data_type_str ||
456
- mainCol.collation !== branchCol.collation) {
458
+ if (columnTypeChanged || columnCollationChanged) {
457
459
  // Skip if parent has the same type/collation change
458
460
  if (!parentHasSameColumnPropertyChange(name, "type")) {
461
+ if (needsDefaultSafeFlow) {
462
+ changes.push(new AlterTableAlterColumnDropDefault({
463
+ table: branchTable,
464
+ column: branchCol,
465
+ }));
466
+ }
459
467
  changes.push(new AlterTableAlterColumnType({
460
468
  table: branchTable,
461
469
  column: branchCol,
470
+ previousColumn: mainCol,
462
471
  }));
472
+ if (needsDefaultSafeFlow && branchCol.default !== null) {
473
+ changes.push(new AlterTableAlterColumnSetDefault({
474
+ table: branchTable,
475
+ column: branchCol,
476
+ }));
477
+ }
463
478
  }
464
479
  }
465
480
  // PostgreSQL rejects SET DEFAULT while the column still has identity metadata,
@@ -476,6 +491,10 @@ export function diffTables(ctx, main, branch) {
476
491
  if (mainCol.default !== branchCol.default) {
477
492
  // Skip if parent has the same default change
478
493
  if (!parentHasSameColumnPropertyChange(name, "default")) {
494
+ if (needsDefaultSafeFlow) {
495
+ // Defaults were already dropped/re-set in the type-change flow above.
496
+ continue;
497
+ }
479
498
  if (branchCol.default === null) {
480
499
  // Drop default value
481
500
  changes.push(new AlterTableAlterColumnDropDefault({
@@ -265,13 +265,16 @@ select
265
265
 
266
266
  'key_columns',
267
267
  case
268
- when c.conkey is not null then (
269
- select json_agg(quote_ident(att.attname) order by pk.ordinality)
270
- from unnest(c.conkey) with ordinality as pk(attnum, ordinality)
271
- join pg_attribute att
272
- on att.attrelid = c.conrelid
273
- and att.attnum = pk.attnum
274
- and att.attisdropped = false
268
+ when c.conkey is not null then coalesce(
269
+ (
270
+ select json_agg(quote_ident(att.attname) order by pk.ordinality)
271
+ from unnest(c.conkey) with ordinality as pk(attnum, ordinality)
272
+ join pg_attribute att
273
+ on att.attrelid = c.conrelid
274
+ and att.attnum = pk.attnum
275
+ and att.attisdropped = false
276
+ ),
277
+ '[]'::json
275
278
  )
276
279
  else '[]'::json
277
280
  end,
@@ -1,29 +1,29 @@
1
- import type { Catalog } from "./catalog.model.ts";
2
1
  import type { Change } from "./change.types.ts";
3
2
  /**
4
- * Normalize change-list cycles that only become apparent after all object
5
- * diffs have been collected.
3
+ * Apply structural rewrites to the change list that are only obvious once
4
+ * every object diff has been collected. This pass does NOT prevent dependency
5
+ * cycles — that responsibility now lives in the sort phase, where
6
+ * `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
7
+ * that edge filtering can't break (FK SCC of dropped tables,
8
+ * AlterPublicationDropTables ↔ AlterTableDropColumn, …).
6
9
  *
7
- * This pass intentionally handles whole-plan interactions only:
8
- * - If replace expansion added `DropTable(T)+CreateTable(T)`, targeted
9
- * `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)` changes are
10
- * redundant and create an unbreakable drop-phase cycle, so we elide them.
11
- * - When the same `DropTable+CreateTable` pair is present, the expansion
12
- * also emits one `AlterTableAddConstraint` / `AlterTableValidateConstraint`
13
- * / `CreateCommentOnConstraint` per branch constraint, which may collide
14
- * with the same change already emitted by `diffTables()` (for example on a
15
- * shape flip or a new constraint). We dedupe these keeping only the last
16
- * occurrence so the expansion's emission survives and the diffTables
17
- * duplicate is removed.
18
- * - If two dropped tables reference each other via FK, we insert dedicated
19
- * `AlterTableDropConstraint` changes and teach the paired `DropTable`
20
- * changes not to claim those FK stable IDs.
10
+ * Concretely, this pass:
21
11
  *
22
- * Object-local PostgreSQL semantics (for example owned-sequence cascades) stay
23
- * in the corresponding `diff*` function instead of this pass.
12
+ * - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
13
+ * changes that are made redundant by an expansion-emitted
14
+ * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
15
+ * would try to drop a column that no longer exists in the freshly
16
+ * recreated table.
17
+ * - Dedupes duplicate `AlterTableAddConstraint` /
18
+ * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
19
+ * produced when `diffTables()` and `expandReplaceDependencies()` both
20
+ * emit the same constraint operation for a replaced table. Last write
21
+ * wins so the expansion's emission survives.
22
+ *
23
+ * Object-local PostgreSQL semantics (for example owned-sequence cascades)
24
+ * stay in the corresponding `diff*` function instead of this pass.
24
25
  */
25
- export declare function normalizePostDiffCycles({ changes, mainCatalog, replacedTableIds, }: {
26
+ export declare function normalizePostDiffCycles({ changes, replacedTableIds, }: {
26
27
  changes: Change[];
27
- mainCatalog: Catalog;
28
28
  replacedTableIds?: ReadonlySet<string>;
29
29
  }): Change[];
@@ -1,32 +1,9 @@
1
1
  import { AlterTableAddConstraint, AlterTableDropColumn, AlterTableDropConstraint, AlterTableValidateConstraint, } from "./objects/table/changes/table.alter.js";
2
2
  import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.js";
3
- import { DropTable } from "./objects/table/changes/table.drop.js";
4
3
  import { stableId } from "./objects/utils.js";
5
4
  function constraintStableId(table, constraintName) {
6
5
  return stableId.constraint(table.schema, table.name, constraintName);
7
6
  }
8
- /**
9
- * Yield FK constraints on `table` whose referenced table is also dropped in the
10
- * final plan. Self-references are left alone because the sort phase already
11
- * handles the resulting self-loop correctly.
12
- */
13
- function* iterCrossDropFkConstraints(table, droppedSet) {
14
- for (const constraint of table.constraints) {
15
- if (constraint.constraint_type !== "f")
16
- continue;
17
- if (constraint.is_partition_clone)
18
- continue;
19
- if (!constraint.foreign_key_schema || !constraint.foreign_key_table) {
20
- continue;
21
- }
22
- const referencedId = stableId.table(constraint.foreign_key_schema, constraint.foreign_key_table);
23
- if (referencedId === table.stableId)
24
- continue;
25
- if (!droppedSet.has(referencedId))
26
- continue;
27
- yield { constraint, referencedId };
28
- }
29
- }
30
7
  function isSupersededByTableReplacement(change, replacedTableIds) {
31
8
  if (!(change instanceof AlterTableDropColumn) &&
32
9
  !(change instanceof AlterTableDropConstraint)) {
@@ -91,119 +68,33 @@ function dropReplacedTableDuplicateConstraintChanges(changes, replacedTableIds)
91
68
  }
92
69
  return mutated ? reversedKept.reverse() : changes;
93
70
  }
94
- function collectExplicitConstraintDropIds(changes) {
95
- const explicitConstraintDropIds = new Set();
96
- for (const change of changes) {
97
- if (!(change instanceof AlterTableDropConstraint))
98
- continue;
99
- explicitConstraintDropIds.add(constraintStableId(change.table, change.constraint.name));
100
- }
101
- return explicitConstraintDropIds;
102
- }
103
- function hasSameEntries(left, right) {
104
- if (left.size !== right.size)
105
- return false;
106
- for (const value of left) {
107
- if (!right.has(value))
108
- return false;
109
- }
110
- return true;
111
- }
112
71
  /**
113
- * Normalize change-list cycles that only become apparent after all object
114
- * diffs have been collected.
72
+ * Apply structural rewrites to the change list that are only obvious once
73
+ * every object diff has been collected. This pass does NOT prevent dependency
74
+ * cycles — that responsibility now lives in the sort phase, where
75
+ * `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
76
+ * that edge filtering can't break (FK SCC of dropped tables,
77
+ * AlterPublicationDropTables ↔ AlterTableDropColumn, …).
115
78
  *
116
- * This pass intentionally handles whole-plan interactions only:
117
- * - If replace expansion added `DropTable(T)+CreateTable(T)`, targeted
118
- * `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)` changes are
119
- * redundant and create an unbreakable drop-phase cycle, so we elide them.
120
- * - When the same `DropTable+CreateTable` pair is present, the expansion
121
- * also emits one `AlterTableAddConstraint` / `AlterTableValidateConstraint`
122
- * / `CreateCommentOnConstraint` per branch constraint, which may collide
123
- * with the same change already emitted by `diffTables()` (for example on a
124
- * shape flip or a new constraint). We dedupe these keeping only the last
125
- * occurrence so the expansion's emission survives and the diffTables
126
- * duplicate is removed.
127
- * - If two dropped tables reference each other via FK, we insert dedicated
128
- * `AlterTableDropConstraint` changes and teach the paired `DropTable`
129
- * changes not to claim those FK stable IDs.
79
+ * Concretely, this pass:
130
80
  *
131
- * Object-local PostgreSQL semantics (for example owned-sequence cascades) stay
132
- * in the corresponding `diff*` function instead of this pass.
81
+ * - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
82
+ * changes that are made redundant by an expansion-emitted
83
+ * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
84
+ * would try to drop a column that no longer exists in the freshly
85
+ * recreated table.
86
+ * - Dedupes duplicate `AlterTableAddConstraint` /
87
+ * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
88
+ * produced when `diffTables()` and `expandReplaceDependencies()` both
89
+ * emit the same constraint operation for a replaced table. Last write
90
+ * wins so the expansion's emission survives.
91
+ *
92
+ * Object-local PostgreSQL semantics (for example owned-sequence cascades)
93
+ * stay in the corresponding `diff*` function instead of this pass.
133
94
  */
134
- export function normalizePostDiffCycles({ changes, mainCatalog, replacedTableIds = new Set(), }) {
95
+ export function normalizePostDiffCycles({ changes, replacedTableIds = new Set(), }) {
135
96
  const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(changes, replacedTableIds);
136
- const structurallyNormalizedChanges = replacedTableIds.size === 0
137
- ? dedupedChanges
138
- : dedupedChanges.filter((change) => !isSupersededByTableReplacement(change, replacedTableIds));
139
- const dropTableChanges = structurallyNormalizedChanges.filter((change) => change instanceof DropTable);
140
- if (dropTableChanges.length < 2) {
141
- return structurallyNormalizedChanges;
142
- }
143
- const droppedSet = new Set(dropTableChanges.map((change) => change.table.stableId));
144
- const droppedFkTargets = new Map();
145
- for (const dropTableChange of dropTableChanges) {
146
- const mainTable = mainCatalog.tables[dropTableChange.table.stableId] ??
147
- dropTableChange.table;
148
- const targets = new Set();
149
- for (const { referencedId } of iterCrossDropFkConstraints(mainTable, droppedSet)) {
150
- targets.add(referencedId);
151
- }
152
- droppedFkTargets.set(mainTable.stableId, targets);
153
- }
154
- const explicitConstraintDropIds = collectExplicitConstraintDropIds(structurallyNormalizedChanges);
155
- const injectedConstraintDropsByTableId = new Map();
156
- const externallyDroppedConstraintsByTableId = new Map();
157
- let didMutate = structurallyNormalizedChanges !== changes;
158
- for (const dropTableChange of dropTableChanges) {
159
- const mainTable = mainCatalog.tables[dropTableChange.table.stableId] ??
160
- dropTableChange.table;
161
- const externallyDroppedConstraints = new Set(dropTableChange.externallyDroppedConstraints);
162
- for (const { constraint, referencedId } of iterCrossDropFkConstraints(mainTable, droppedSet)) {
163
- const isMutual = droppedFkTargets.get(referencedId)?.has(mainTable.stableId) === true;
164
- if (!isMutual)
165
- continue;
166
- const droppedConstraintStableId = constraintStableId(mainTable, constraint.name);
167
- externallyDroppedConstraints.add(constraint.name);
168
- if (!explicitConstraintDropIds.has(droppedConstraintStableId)) {
169
- const injectedDrop = new AlterTableDropConstraint({
170
- table: mainTable,
171
- constraint,
172
- });
173
- const existingDrops = injectedConstraintDropsByTableId.get(mainTable.stableId) ?? [];
174
- existingDrops.push(injectedDrop);
175
- injectedConstraintDropsByTableId.set(mainTable.stableId, existingDrops);
176
- explicitConstraintDropIds.add(droppedConstraintStableId);
177
- didMutate = true;
178
- }
179
- }
180
- if (!hasSameEntries(dropTableChange.externallyDroppedConstraints, externallyDroppedConstraints)) {
181
- externallyDroppedConstraintsByTableId.set(mainTable.stableId, externallyDroppedConstraints);
182
- didMutate = true;
183
- }
184
- }
185
- if (!didMutate) {
186
- return changes;
187
- }
188
- const normalizedChanges = [];
189
- for (const change of structurallyNormalizedChanges) {
190
- if (!(change instanceof DropTable)) {
191
- normalizedChanges.push(change);
192
- continue;
193
- }
194
- const injectedConstraintDrops = injectedConstraintDropsByTableId.get(change.table.stableId) ?? [];
195
- if (injectedConstraintDrops.length > 0) {
196
- normalizedChanges.push(...injectedConstraintDrops);
197
- }
198
- const externallyDroppedConstraints = externallyDroppedConstraintsByTableId.get(change.table.stableId);
199
- if (!externallyDroppedConstraints) {
200
- normalizedChanges.push(change);
201
- continue;
202
- }
203
- normalizedChanges.push(new DropTable({
204
- table: change.table,
205
- externallyDroppedConstraints,
206
- }));
207
- }
208
- return normalizedChanges;
97
+ if (replacedTableIds.size === 0)
98
+ return dedupedChanges;
99
+ return dedupedChanges.filter((change) => !isSupersededByTableReplacement(change, replacedTableIds));
209
100
  }
@@ -0,0 +1,15 @@
1
+ import type { Change } from "../change.types.ts";
2
+ /**
3
+ * Try to break an unbreakable cycle by INJECTING NEW CHANGES or REWRITING
4
+ * existing ones (rather than removing graph edges).
5
+ *
6
+ * Called by `sortPhaseChanges` when its edge-removal cycle handler has seen
7
+ * the same cycle twice — i.e. weak-edge filtering exhausted itself but the
8
+ * cycle is still there. At that point we know the cycle is composed of
9
+ * "hard" edges (explicit `requires` or pg_depend rows) that can only be
10
+ * broken by changing the change list itself.
11
+ *
12
+ * Returns a rewritten `phaseChanges` array, or `null` if no breaker matches
13
+ * (in which case the caller throws the existing CycleError).
14
+ */
15
+ export declare function tryBreakCycleByChangeInjection(cycleNodeIndexes: readonly number[], phaseChanges: readonly Change[]): Change[] | null;