@vibeorm/migrate 2.6.0 → 3.0.0

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/dist/desired-state.d.ts +9 -0
  3. package/dist/differ.d.ts +7 -0
  4. package/dist/fs.js +449 -269
  5. package/dist/fs.js.map +15 -14
  6. package/dist/index.js +820 -467
  7. package/dist/index.js.map +17 -15
  8. package/dist/inspect-objects.d.ts +8 -0
  9. package/dist/introspect/group.d.ts +6 -0
  10. package/dist/introspect/mysql.d.ts +4 -3
  11. package/dist/normalize.d.ts +19 -1
  12. package/dist/object-ownership.d.ts +8 -0
  13. package/dist/postgres-types.d.ts +24 -0
  14. package/dist/sqlite-fts-upgrade.d.ts +64 -0
  15. package/package.json +6 -4
  16. package/dist/adoption.d.ts.map +0 -1
  17. package/dist/artifact-probe.d.ts.map +0 -1
  18. package/dist/baseline-record.d.ts.map +0 -1
  19. package/dist/baseline.d.ts.map +0 -1
  20. package/dist/compare-reference-rls.d.ts.map +0 -1
  21. package/dist/compare-sqlite-primary-key.d.ts.map +0 -1
  22. package/dist/compile-desired-state.d.ts.map +0 -1
  23. package/dist/ddl/drop-order.d.ts.map +0 -1
  24. package/dist/ddl/mysql.d.ts.map +0 -1
  25. package/dist/ddl/postgres.d.ts.map +0 -1
  26. package/dist/ddl/render.d.ts.map +0 -1
  27. package/dist/ddl/rls.d.ts.map +0 -1
  28. package/dist/ddl/sqlite.d.ts.map +0 -1
  29. package/dist/desired-state.d.ts.map +0 -1
  30. package/dist/differ.d.ts.map +0 -1
  31. package/dist/down.d.ts.map +0 -1
  32. package/dist/enum-values.d.ts.map +0 -1
  33. package/dist/exclusions.d.ts.map +0 -1
  34. package/dist/execute.d.ts.map +0 -1
  35. package/dist/execution.d.ts.map +0 -1
  36. package/dist/extensions.d.ts.map +0 -1
  37. package/dist/external-dependencies.d.ts.map +0 -1
  38. package/dist/external-objects.d.ts.map +0 -1
  39. package/dist/fs.d.ts.map +0 -1
  40. package/dist/history.d.ts.map +0 -1
  41. package/dist/index-predicates.d.ts.map +0 -1
  42. package/dist/index.d.ts.map +0 -1
  43. package/dist/inspect-objects.d.ts.map +0 -1
  44. package/dist/introspect/mysql.d.ts.map +0 -1
  45. package/dist/introspect/postgres-rls.d.ts.map +0 -1
  46. package/dist/introspect/postgres.d.ts.map +0 -1
  47. package/dist/introspect/shared.d.ts.map +0 -1
  48. package/dist/introspect/sqlite.d.ts.map +0 -1
  49. package/dist/inventory.d.ts.map +0 -1
  50. package/dist/journal-read.d.ts.map +0 -1
  51. package/dist/journal-table.d.ts.map +0 -1
  52. package/dist/migration-checksum.d.ts.map +0 -1
  53. package/dist/module-adoption-record.d.ts.map +0 -1
  54. package/dist/module-adoption-reference.d.ts.map +0 -1
  55. package/dist/module-adoption.d.ts.map +0 -1
  56. package/dist/module-bundle.d.ts.map +0 -1
  57. package/dist/module-export.d.ts.map +0 -1
  58. package/dist/module-history.d.ts.map +0 -1
  59. package/dist/module-journal.d.ts.map +0 -1
  60. package/dist/module-origin.d.ts.map +0 -1
  61. package/dist/module-ownership.d.ts.map +0 -1
  62. package/dist/module-pending.d.ts.map +0 -1
  63. package/dist/module-readiness.d.ts.map +0 -1
  64. package/dist/mysql-catalog-visibility.d.ts.map +0 -1
  65. package/dist/mysql-types.d.ts.map +0 -1
  66. package/dist/normalize.d.ts.map +0 -1
  67. package/dist/object-ownership.d.ts.map +0 -1
  68. package/dist/operator-classes.d.ts.map +0 -1
  69. package/dist/planner.d.ts.map +0 -1
  70. package/dist/postgres-default-equivalence.d.ts.map +0 -1
  71. package/dist/postgres-type-identity.d.ts.map +0 -1
  72. package/dist/postgres-types.d.ts.map +0 -1
  73. package/dist/push.d.ts.map +0 -1
  74. package/dist/recovery.d.ts.map +0 -1
  75. package/dist/reference-state.d.ts.map +0 -1
  76. package/dist/relations.d.ts.map +0 -1
  77. package/dist/renames.d.ts.map +0 -1
  78. package/dist/replay-objects.d.ts.map +0 -1
  79. package/dist/replay-plan.d.ts.map +0 -1
  80. package/dist/replay-reference.d.ts.map +0 -1
  81. package/dist/runner.d.ts.map +0 -1
  82. package/dist/shadow-dedicated.d.ts.map +0 -1
  83. package/dist/shadow-guard.d.ts.map +0 -1
  84. package/dist/shadow-types.d.ts.map +0 -1
  85. package/dist/shadow.d.ts.map +0 -1
  86. package/dist/sql-statements.d.ts.map +0 -1
  87. package/dist/sqlite-types.d.ts.map +0 -1
  88. package/dist/types.d.ts.map +0 -1
  89. package/dist/verify-history.d.ts.map +0 -1
package/dist/fs.js CHANGED
@@ -15,16 +15,12 @@ function hasMysqlGlobalCatalogVisibility(params) {
15
15
  }
16
16
 
17
17
  // packages/migrate/src/module-ownership.ts
18
- import { VibeError as VibeError18, sqlObjectKey as sqlObjectKey7, sqlObjectNamespace as sqlObjectNamespace3 } from "@vibeorm/schema";
18
+ import { VibeError as VibeError19, sqlObjectKey as sqlObjectKey7, sqlObjectNamespace as sqlObjectNamespace3 } from "@vibeorm/schema";
19
19
  import { getSqlDialect as getSqlDialect4, renderModuleDependencyQuery } from "@vibeorm/sql";
20
20
 
21
21
  // packages/migrate/src/desired-state.ts
22
22
  import { VibeError, assertValidSchemaIR, isSqlObjectDefinition, isSqlObjectIdentity, providerToDialect, sqlObjectKey } from "@vibeorm/schema";
23
23
  var UNFINALIZED_DESIRED_STATE = Object.freeze({ version: 1, status: "unfinalized" });
24
- function isUnfinalizedDesiredState(params) {
25
- const value = JSON.parse(params.text);
26
- return record(value) && value.version === 1 && value.status === "unfinalized" && Object.keys(value).length === 2;
27
- }
28
24
  var DESIRED_STATE_METADATA = Object.freeze({ filename: "desired-state.json", version: 1 });
29
25
  function createDesiredState(params) {
30
26
  assertValidSchemaIR({ schema: params.schema });
@@ -107,6 +103,15 @@ function parseDesiredStateSidecar(params) {
107
103
  return checked.value;
108
104
  }, failure: (cause) => invalid({ migration: params.migration, detail: "malformed metadata", cause }) });
109
105
  }
106
+ function loadDesiredStateSidecar(params) {
107
+ return parseMigrationMetadata({ text: params.text, parse: (value) => {
108
+ if (record(value) && value.version === 1 && value.status === "unfinalized" && Object.keys(value).length === 2)
109
+ return { desiredStatePending: true };
110
+ const checked = { value, migration: params.migration };
111
+ assertDesiredState(checked);
112
+ return { desiredState: checked.value };
113
+ }, failure: (cause) => invalid({ migration: params.migration, detail: "malformed metadata", cause }) });
114
+ }
110
115
  function parseMigrationMetadata(params) {
111
116
  try {
112
117
  return params.parse(JSON.parse(params.text));
@@ -172,6 +177,9 @@ var SCALAR_TO_PG = {
172
177
  var CANONICAL_EQUIVALENTS = {
173
178
  DateTime: ["timestamptz(3)", "timestamptz(6)"]
174
179
  };
180
+ var EXACT_CANONICAL_EQUIVALENTS = {
181
+ DateTime: ["timestamptz(6)"]
182
+ };
175
183
  var CANONICAL_UDT = {
176
184
  text: "String",
177
185
  bool: "Boolean",
@@ -240,13 +248,15 @@ var NATIVE_ALIASES = {
240
248
  var IMPLICIT_MODIFIERS = {
241
249
  bit: "(1)"
242
250
  };
251
+ var DEFAULT_PRECISION_SIX = new Set(["timestamp", "time", "timetz"]);
243
252
  function normalizeNativeType(params) {
244
253
  const compact = params.raw.toLowerCase().replace(/\s+/g, "");
245
254
  const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(\[\])?$/);
246
255
  if (match === null)
247
256
  return compact;
248
257
  const base = NATIVE_ALIASES[match[1]] ?? match[1];
249
- return `${base}${match[2] ?? IMPLICIT_MODIFIERS[base] ?? ""}${match[3] ?? ""}`;
258
+ const modifier = DEFAULT_PRECISION_SIX.has(base) && match[2] === "(6)" ? "" : match[2] ?? IMPLICIT_MODIFIERS[base] ?? "";
259
+ return `${base}${modifier}${match[3] ?? ""}`;
250
260
  }
251
261
  function parseUnsupportedPostgresType(params) {
252
262
  const temporal = /^\s*(time(?:stamp)?)\s*(\(\s*\d+\s*\))?\s+(with|without)\s+time\s+zone\s*((?:\[\s*\]\s*)*)$/i.exec(params.raw);
@@ -259,7 +269,7 @@ function parseUnsupportedPostgresType(params) {
259
269
  return NATIVE_ALIASES[base] === undefined ? undefined : parsePostgresTypeName({ raw: normalizeNativeType(params) });
260
270
  }
261
271
  function columnTypeKey(params) {
262
- const { field } = params;
272
+ const { field, exact } = params;
263
273
  const listSuffix = field.isList ? "[]" : "";
264
274
  if (field.isUnsupported === true) {
265
275
  const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
@@ -270,7 +280,7 @@ function columnTypeKey(params) {
270
280
  const scalar = namespace === undefined || namespace === "pg_catalog" ? scalarForBuiltinType({ name: parsed.name }) : undefined;
271
281
  if (scalar !== undefined) {
272
282
  const { isUnsupported: _unsupported, nativeTypeIdentity: _identity, ...rest } = field;
273
- return columnTypeKey({ field: { ...rest, type: scalar, nativeType: `${parsed.name}${parsed.modifier ?? ""}`, isList: parsed.array || field.isList } });
283
+ return columnTypeKey({ field: { ...rest, type: scalar, nativeType: `${parsed.name}${parsed.modifier ?? ""}`, isList: parsed.array || field.isList }, ...exact === undefined ? {} : { exact } });
274
284
  }
275
285
  return `u:${JSON.stringify([namespace ?? null, parsed.name, parsed.modifier, parsed.array || field.isList])}`;
276
286
  }
@@ -281,12 +291,37 @@ function columnTypeKey(params) {
281
291
  if (field.nativeType !== undefined) {
282
292
  const normalized = normalizeNativeType({ raw: field.nativeType });
283
293
  const canonical = normalizeNativeType({ raw: SCALAR_TO_PG[field.type] });
284
- const isCanonical = normalized === canonical || (CANONICAL_EQUIVALENTS[field.type]?.includes(normalized) ?? false);
294
+ const equivalents = exact === true ? EXACT_CANONICAL_EQUIVALENTS : CANONICAL_EQUIVALENTS;
295
+ const isCanonical = normalized === canonical || (equivalents[field.type]?.includes(normalized) ?? false);
285
296
  if (!isCanonical)
286
297
  return `n:${normalized}${listSuffix}`;
287
298
  }
288
299
  return `s:${field.type}${listSuffix}`;
289
300
  }
301
+ function declaresExactColumnType(params) {
302
+ const { field } = params;
303
+ if (field.isUnsupported === true || field.nativeType === undefined || isEnumType(field.type))
304
+ return false;
305
+ return CANONICAL_EQUIVALENTS[field.type]?.includes(normalizeNativeType({ raw: field.nativeType })) ?? false;
306
+ }
307
+ function timestamptzPrecision(params) {
308
+ const { field } = params;
309
+ if (field.type !== "DateTime" || field.isUnsupported === true)
310
+ return;
311
+ const normalized = field.nativeType === undefined ? "timestamptz" : normalizeNativeType({ raw: field.nativeType });
312
+ if (normalized === "timestamptz")
313
+ return 6;
314
+ const match = /^timestamptz\((\d)\)$/.exec(normalized);
315
+ return match === null ? undefined : Number(match[1]);
316
+ }
317
+ function isTimestamptzPrecisionWidening(params) {
318
+ const { from, to } = params;
319
+ if (from.isList !== to.isList)
320
+ return false;
321
+ const before = timestamptzPrecision({ field: from });
322
+ const after = timestamptzPrecision({ field: to });
323
+ return before !== undefined && after !== undefined && after >= before;
324
+ }
290
325
  function isScalarToArrayChange(params) {
291
326
  const { from, to } = params;
292
327
  if (from.isList || !to.isList)
@@ -751,6 +786,32 @@ function parseMysqlEnumColumnType(params) {
751
786
  }
752
787
 
753
788
  // packages/migrate/src/normalize.ts
789
+ function firstByName(params) {
790
+ const result = new Map;
791
+ for (const entry of params.entries)
792
+ if (!result.has(entry.name))
793
+ result.set(entry.name, entry);
794
+ return result;
795
+ }
796
+ function normalizationLookup(params) {
797
+ const enums = new Map;
798
+ const scopedEnums = new Map;
799
+ for (const entry of params.schema.enums) {
800
+ const namespace = qualifiedSqlName3({ name: entry.name, namespace: entry.namespace }).namespace;
801
+ for (const name of new Set([entry.name, entry.dbName ?? entry.name])) {
802
+ if (!enums.has(name))
803
+ enums.set(name, entry);
804
+ const key = JSON.stringify([namespace, name]);
805
+ if (!scopedEnums.has(key))
806
+ scopedEnums.set(key, entry);
807
+ }
808
+ }
809
+ return { models: firstByName({ entries: params.schema.models }), fields: new Map(params.schema.models.map((model) => [model, firstByName({ entries: model.fields })])), enums, scopedEnums };
810
+ }
811
+ function lookupFieldName(params) {
812
+ const field = params.lookup.fields.get(params.model)?.get(params.name);
813
+ return field?.dbName ?? field?.name ?? params.name;
814
+ }
754
815
  function normalizeSchema(params) {
755
816
  const { schema } = params;
756
817
  const dialect = params.dialect ?? "postgres";
@@ -763,6 +824,7 @@ function normalizeSchema(params) {
763
824
  continue;
764
825
  enums.set(physicalNameKey({ name: enumDef.dbName ?? enumDef.name, namespace: enumDef.namespace, dialect }), enumDef);
765
826
  }
827
+ const lookup = normalizationLookup({ schema });
766
828
  const tables = new Map;
767
829
  const views = new Map;
768
830
  for (const model of schema.models) {
@@ -772,25 +834,25 @@ function normalizeSchema(params) {
772
834
  views.set(physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect }), model);
773
835
  continue;
774
836
  }
775
- const table = normalizeModel({ model, schema, dialect, enums });
837
+ const table = normalizeModel({ model, lookup, dialect, enums });
776
838
  tables.set(physicalNameKey({ name: table.table, namespace: table.namespace, dialect }), table);
777
839
  }
778
840
  const joinTables = new Map;
779
841
  for (const pair of detectImplicitM2MPairs({ schema })) {
780
- const join = normalizeJoinTable({ pair, schema, dialect });
842
+ const join = normalizeJoinTable({ pair, lookup, dialect });
781
843
  joinTables.set(physicalNameKey({ name: join.table, namespace: join.namespace, dialect }), join);
782
844
  }
783
845
  return { tables, joinTables, enums, views };
784
846
  }
785
847
  function physicalEnumField(params) {
786
- const { field, schema, dialect, table } = params;
848
+ const { field, lookup, dialect, table } = params;
787
849
  if (!isEnumType5(field.type))
788
850
  return field;
789
851
  const type = field.type;
790
852
  if (type.namespace !== undefined)
791
853
  assertSqlNamespace({ namespace: type.namespace, dialect });
792
854
  const name = type.enum;
793
- const enumDef = schema.enums.find((candidate) => (candidate.name === name || candidate.dbName === name) && (type.namespace === undefined || qualifiedSqlName3({ name, namespace: candidate.namespace }).namespace === type.namespace));
855
+ const enumDef = type.namespace === undefined ? lookup.enums.get(name) : lookup.scopedEnums.get(JSON.stringify([type.namespace, name]));
794
856
  if (enumDef === undefined)
795
857
  return field;
796
858
  if (enumStorageOf({ enumDef }) !== "string") {
@@ -829,16 +891,20 @@ function assertStringEnumLabelsFit(params) {
829
891
  }
830
892
  }
831
893
  function columnTypeKeyFor(params) {
832
- const { field, dialect, enums } = params;
894
+ const { field, dialect, enums, exact } = params;
833
895
  switch (dialect) {
834
896
  case "postgres":
835
- return columnTypeKey({ field });
897
+ return columnTypeKey({ field, ...exact === undefined ? {} : { exact } });
836
898
  case "sqlite":
837
899
  return sqliteColumnTypeKey({ field, enums });
838
900
  case "mysql":
839
901
  return mysqlColumnTypeKey({ field, enums });
840
902
  }
841
903
  }
904
+ function columnTypeChanged(params) {
905
+ const { declared, actual } = params;
906
+ return declared.pinsExactType ? declared.exactTypeKey !== actual.exactTypeKey : declared.typeKey !== actual.typeKey;
907
+ }
842
908
  function defaultFingerprintFor(params) {
843
909
  const { field, dialect } = params;
844
910
  switch (dialect) {
@@ -851,46 +917,49 @@ function defaultFingerprintFor(params) {
851
917
  }
852
918
  }
853
919
  function normalizeModel(params) {
854
- const { model, schema, dialect, enums } = params;
920
+ const { model, lookup, dialect, enums } = params;
855
921
  const table = model.dbName ?? model.name;
856
922
  const columns = new Map;
857
923
  for (const logicalField of model.fields) {
858
- const field = physicalEnumField({ field: logicalField, schema, dialect, table });
924
+ const field = physicalEnumField({ field: logicalField, lookup, dialect, table });
859
925
  const name = field.dbName ?? field.name;
926
+ const typeKey = columnTypeKeyFor({ field, dialect, enums });
860
927
  columns.set(name, {
861
928
  name,
862
929
  field,
863
- typeKey: columnTypeKeyFor({ field, dialect, enums }),
930
+ typeKey,
931
+ exactTypeKey: dialect === "postgres" ? columnTypeKeyFor({ field, dialect, enums, exact: true }) : typeKey,
932
+ pinsExactType: dialect === "postgres" && declaresExactColumnType({ field }),
864
933
  nullable: field.isOptional,
865
934
  defaultKey: defaultFingerprintFor({ field, dialect })
866
935
  });
867
936
  }
868
- const pk = normalizePk({ model });
869
- const indexes = normalizeIndexes({ model, table, dialect });
870
- const fks = normalizeFks({ model, table, schema, dialect });
937
+ const pk = normalizePk({ model, lookup });
938
+ const indexes = normalizeIndexes({ model, table, dialect, lookup });
939
+ const fks = normalizeFks({ model, table, lookup, dialect });
871
940
  const checks = new Map;
872
941
  for (const check of model.checks ?? [])
873
942
  checks.set(check.name, check.expression);
874
943
  return { model, table, ...model.namespace === undefined ? {} : { namespace: model.namespace }, columns, pk, indexes, fks, checks };
875
944
  }
876
945
  function normalizePk(params) {
877
- const { model } = params;
946
+ const { model, lookup } = params;
878
947
  if (model.primaryKey !== undefined && model.primaryKey.fields.length > 0) {
879
- return model.primaryKey.fields.map((name) => fieldDbName({ model, name }));
948
+ return model.primaryKey.fields.map((name) => lookupFieldName({ lookup, model, name }));
880
949
  }
881
950
  return model.fields.filter((field) => field.isId).map((field) => field.dbName ?? field.name);
882
951
  }
883
952
  function normalizeIndexes(params) {
884
- const { model, table, dialect } = params;
953
+ const { model, table, dialect, lookup } = params;
885
954
  const result = new Map;
886
955
  const add = (index) => {
887
956
  const unique = index.kind === "unique";
888
957
  const kind = unique ? "btree" : index.kind;
889
958
  const columns = index.fields.map((field) => {
890
- const fieldIR = model.fields.find((candidate) => candidate.name === field.name);
959
+ const fieldIR = lookup.fields.get(model)?.get(field.name);
891
960
  const decimal = fieldIR !== undefined && fieldIR.type === "Decimal" && fieldIR.isList !== true;
892
961
  return {
893
- name: fieldDbName({ model, name: field.name }),
962
+ name: fieldIR?.dbName ?? fieldIR?.name ?? field.name,
894
963
  desc: field.sort === "desc",
895
964
  ...decimal ? { decimal: true } : {},
896
965
  ...field.operatorClass === undefined ? {} : { operatorClass: field.operatorClass }
@@ -924,8 +993,7 @@ function normalizeIndexes(params) {
924
993
  }
925
994
  function indexComparisonKey(params) {
926
995
  const { unique, kind, columns, where, nullsNotDistinct } = params;
927
- const cols = columns.map((column) => `${column.name}${column.desc ? " desc" : ""}${column.operatorClass === undefined ? "" : ` ops:${JSON.stringify([column.operatorClass.namespace, column.operatorClass.name])}`}`).join(",");
928
- return `${unique ? "u" : "i"}${nullsNotDistinct === true ? "n" : ""}:${kind}:${cols}:${normalizeWhere({ where })}`;
996
+ return JSON.stringify([unique, nullsNotDistinct === true, kind, columns.map((column) => [column.name, column.desc === true, column.operatorClass === undefined ? null : [column.operatorClass.namespace, column.operatorClass.name]]), normalizeWhere({ where })]);
929
997
  }
930
998
  function normalizeWhere(params) {
931
999
  const { where } = params;
@@ -1087,22 +1155,22 @@ function parsePrimaryExpression(params) {
1087
1155
  return { text: parts.join(" "), at, kind: "atom" };
1088
1156
  }
1089
1157
  function normalizeFks(params) {
1090
- const { model, table, schema } = params;
1158
+ const { model, table, lookup } = params;
1091
1159
  const result = new Map;
1092
1160
  for (const relation of model.relations) {
1093
1161
  if (relation.fields === undefined || relation.fields.length === 0)
1094
1162
  continue;
1095
- const target = schema.models.find((candidate) => candidate.name === relation.targetModel);
1163
+ const target = lookup.models.get(relation.targetModel);
1096
1164
  if (target !== undefined && target.isView)
1097
1165
  continue;
1098
1166
  const refTable = target === undefined ? relation.targetModel : target.dbName ?? target.name;
1099
- const columns = relation.fields.map((name) => fieldDbName({ model, name }));
1100
- const refColumns = (relation.references ?? []).map((name) => target === undefined ? name : fieldDbName({ model: target, name }));
1167
+ const columns = relation.fields.map((name) => lookupFieldName({ lookup, model, name }));
1168
+ const refColumns = (relation.references ?? []).map((name) => target === undefined ? name : lookupFieldName({ lookup, model: target, name }));
1101
1169
  const required = relation.fields.every((name) => {
1102
- const field = model.fields.find((candidate) => candidate.name === name);
1170
+ const field = lookup.fields.get(model)?.get(name);
1103
1171
  return field === undefined ? true : !field.isOptional;
1104
1172
  });
1105
- const key = `${columns.join(",")}\u2192${physicalNameKey({ name: refTable, namespace: target?.namespace, dialect: params.dialect })}.${refColumns.join(",")}`;
1173
+ const key = JSON.stringify([columns, physicalNameKey({ name: refTable, namespace: target?.namespace, dialect: params.dialect }), refColumns]);
1106
1174
  if (result.has(key))
1107
1175
  continue;
1108
1176
  result.set(key, {
@@ -1119,17 +1187,17 @@ function normalizeFks(params) {
1119
1187
  return result;
1120
1188
  }
1121
1189
  function normalizeJoinTable(params) {
1122
- const { pair, schema, dialect } = params;
1190
+ const { pair, lookup, dialect } = params;
1123
1191
  return {
1124
1192
  table: pair.tableName,
1125
1193
  ...pair.namespace === undefined ? {} : { namespace: pair.namespace },
1126
- a: joinTableEnd({ modelName: pair.modelA, schema, dialect, joinTable: pair.tableName }),
1127
- b: joinTableEnd({ modelName: pair.modelB, schema, dialect, joinTable: pair.tableName })
1194
+ a: joinTableEnd({ modelName: pair.modelA, lookup, dialect, joinTable: pair.tableName }),
1195
+ b: joinTableEnd({ modelName: pair.modelB, lookup, dialect, joinTable: pair.tableName })
1128
1196
  };
1129
1197
  }
1130
1198
  function joinTableEnd(params) {
1131
- const { modelName, schema, joinTable } = params;
1132
- const model = schema.models.find((candidate) => candidate.name === modelName);
1199
+ const { modelName, lookup, joinTable } = params;
1200
+ const model = lookup.models.get(modelName);
1133
1201
  if (model === undefined) {
1134
1202
  throw new VibeError5({
1135
1203
  code: "VIBE_MIGRATION",
@@ -1137,7 +1205,7 @@ function joinTableEnd(params) {
1137
1205
  meta: { joinTable, model: modelName }
1138
1206
  });
1139
1207
  }
1140
- const pkFields = model.primaryKey !== undefined && model.primaryKey.fields.length > 0 ? model.primaryKey.fields.map((name) => model.fields.find((field) => field.name === name)) : model.fields.filter((field) => field.isId);
1208
+ const pkFields = model.primaryKey !== undefined && model.primaryKey.fields.length > 0 ? model.primaryKey.fields.map((name) => lookup.fields.get(model)?.get(name)) : model.fields.filter((field) => field.isId);
1141
1209
  const pkField = pkFields[0];
1142
1210
  if (pkFields.length !== 1 || pkField === undefined) {
1143
1211
  throw new VibeError5({
@@ -1150,7 +1218,7 @@ function joinTableEnd(params) {
1150
1218
  table: model.dbName ?? model.name,
1151
1219
  ...model.namespace === undefined ? {} : { namespace: model.namespace },
1152
1220
  column: pkField.dbName ?? pkField.name,
1153
- field: physicalEnumField({ field: pkField, schema, dialect: params.dialect, table: model.dbName ?? model.name })
1221
+ field: physicalEnumField({ field: pkField, lookup, dialect: params.dialect, table: model.dbName ?? model.name })
1154
1222
  };
1155
1223
  }
1156
1224
  function physicalNameKey(params) {
@@ -1166,11 +1234,6 @@ function schemaNamespaces(params) {
1166
1234
  ...(params.schema.migrationObjects ?? []).flatMap((object) => object.identity.kind === "namespace" ? [object.identity.name] : [])
1167
1235
  ])];
1168
1236
  }
1169
- function fieldDbName(params) {
1170
- const { model, name } = params;
1171
- const field = model.fields.find((candidate) => candidate.name === name);
1172
- return field === undefined ? name : field.dbName ?? field.name;
1173
- }
1174
1237
 
1175
1238
  // packages/migrate/src/object-ownership.ts
1176
1239
  import { VibeError as VibeError6, defaultPrimaryKeyName, enumStorageOf as enumStorageOf2, implicitJoinTableObjectNames, postgresGeneratedObjectName, qualifiedSqlName as qualifiedSqlName4, sqlObjectKey as sqlObjectKey2 } from "@vibeorm/schema";
@@ -1275,7 +1338,10 @@ function removalIdentity(params) {
1275
1338
  }
1276
1339
  }
1277
1340
  function schemaObjectIdentities(params) {
1278
- const normalized = normalizeSchema({ schema: params.schema, dialect: params.dialect });
1341
+ return schemaObjectIdentitiesFromNormalized({ ...params, normalized: normalizeSchema({ schema: params.schema, dialect: params.dialect }) });
1342
+ }
1343
+ function schemaObjectIdentitiesFromNormalized(params) {
1344
+ const { normalized } = params;
1279
1345
  const result = [];
1280
1346
  if (params.dialect === "postgres") {
1281
1347
  for (const namespace of schemaNamespaces({ schema: params.schema }))
@@ -1390,7 +1456,7 @@ function assertRemovalAuthorization(params) {
1390
1456
  }
1391
1457
 
1392
1458
  // packages/migrate/src/inventory.ts
1393
- import { VibeError as VibeError17, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
1459
+ import { VibeError as VibeError18, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
1394
1460
  import { getSqlDialect as getSqlDialect3, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
1395
1461
 
1396
1462
  // packages/migrate/src/external-objects.ts
@@ -1760,7 +1826,7 @@ async function verifyExclusions(params) {
1760
1826
 
1761
1827
  // packages/migrate/src/inspect-objects.ts
1762
1828
  import { randomUUID as randomUUID2 } from "crypto";
1763
- import { VibeError as VibeError16, sqlObjectKey as sqlObjectKey5, sqlObjectNamespace as sqlObjectNamespace2, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2, qualifiedSqlName as qualifiedSqlName8 } from "@vibeorm/schema";
1829
+ import { VibeError as VibeError17, sqlObjectKey as sqlObjectKey5, sqlObjectNamespace as sqlObjectNamespace2, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2, qualifiedSqlName as qualifiedSqlName8 } from "@vibeorm/schema";
1764
1830
  import { getSqlDialect as getSqlDialect2, renderMigrationObjectBatch, renderMigrationObjectQuery as renderMigrationObjectQuery2, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
1765
1831
 
1766
1832
  // packages/migrate/src/introspect/postgres.ts
@@ -1958,6 +2024,20 @@ function lowerFirst(value) {
1958
2024
  return value.charAt(0).toLowerCase() + value.slice(1);
1959
2025
  }
1960
2026
 
2027
+ // packages/migrate/src/introspect/group.ts
2028
+ function groupCatalogRows(params) {
2029
+ const groups = new Map;
2030
+ for (const row of params.rows) {
2031
+ const key = params.key(row);
2032
+ const group = groups.get(key);
2033
+ if (group === undefined)
2034
+ groups.set(key, [row]);
2035
+ else
2036
+ group.push(row);
2037
+ }
2038
+ return groups;
2039
+ }
2040
+
1961
2041
  // packages/migrate/src/postgres-type-identity.ts
1962
2042
  import { VibeError as VibeError10, qualifiedSqlName as qualifiedSqlName6 } from "@vibeorm/schema";
1963
2043
  import { postgresDialect as postgresDialect3, renderPostgresTypeIdentity } from "@vibeorm/sql";
@@ -2359,10 +2439,15 @@ async function introspectPhysicalPostgres(params) {
2359
2439
  read("checks"),
2360
2440
  Promise.all(namespaces.map(async (namespace) => (await executor({ text: renderExclusionCatalog2({ dialect: postgresDialect6, namespace }) })).map((row) => ({ ...row, table_name: physicalNameKey({ namespace, name: String(row.table_name) }) })))).then((rows) => rows.flat())
2361
2441
  ]);
2362
- const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6 });
2442
+ const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6, namespaces });
2363
2443
  const indexKeys = await executor({ text: keysQuery.text, values: [...keysQuery.values] });
2364
2444
  if (indexKeys.some((row) => typeof row.table_namespace !== "string" || typeof row.table_name !== "string" || typeof row.index_name !== "string" || typeof row.method !== "string" || typeof row.unique !== "boolean" || typeof row.descending !== "boolean" || typeof row.exact_default !== "boolean" && row.column_name !== null || typeof row.operator_namespace !== "string" || typeof row.operator_name !== "string" || !Number.isSafeInteger(Number(row.ordinal)) || Number(row.ordinal) < 1))
2365
2445
  throw new VibeError13({ code: "VIBE_MIGRATION", message: "PostgreSQL index-key identity is incomplete", meta: { reason: "index-inspection-unavailable" } });
2446
+ const keysByIndex = groupCatalogRows({ rows: indexKeys, key: (row) => JSON.stringify([physicalNameKey({ namespace: String(row.table_namespace), name: String(row.table_name) }), row.index_name]) });
2447
+ const catalogColumns = new Map(columnRows.map((row) => [JSON.stringify([row.table_name, row.column_name]), row]));
2448
+ const foreignKeysByTable = groupCatalogRows({ rows: fkRows, key: (row) => String(row.table_name) });
2449
+ const catalogIndexesByTable = groupCatalogRows({ rows: indexRows, key: (row) => String(row.table_name) });
2450
+ const exclusionsByTable = groupCatalogRows({ rows: exclusionRows, key: (row) => String(row.table_name) });
2366
2451
  const tables = tableRows.map((row) => row.table_name);
2367
2452
  const views = viewRows.map((row) => row.table_name);
2368
2453
  const columns = columnRows.map((row) => ({
@@ -2380,6 +2465,8 @@ async function introspectPhysicalPostgres(params) {
2380
2465
  numericScale: toNumberOrNull(row.numeric_scale),
2381
2466
  fullType: row.full_type ?? null
2382
2467
  }));
2468
+ const columnsByTable = groupCatalogRows({ rows: columns, key: (column) => column.table });
2469
+ const columnsByIdentity = new Map(columns.map((column) => [JSON.stringify([column.table, column.name]), column]));
2383
2470
  const enums = [];
2384
2471
  {
2385
2472
  const valuesByName = new Map;
@@ -2420,6 +2507,7 @@ async function introspectPhysicalPostgres(params) {
2420
2507
  entry.columns.push(columnName);
2421
2508
  }
2422
2509
  }
2510
+ const uniquesByTable = groupCatalogRows({ rows: uniquesByConstraint.values(), key: (unique) => unique.table });
2423
2511
  const checksByTable = new Map;
2424
2512
  for (const row of checkRows) {
2425
2513
  const table = row.table_name;
@@ -2454,7 +2542,7 @@ async function introspectPhysicalPostgres(params) {
2454
2542
  const fkGroups = [...fkGroupsByName.values()].map((group) => ({
2455
2543
  ...group,
2456
2544
  required: group.columns.every((columnName) => {
2457
- const column = columns.find((c) => c.table === group.table && c.name === columnName);
2545
+ const column = columnsByIdentity.get(JSON.stringify([group.table, columnName]));
2458
2546
  return column === undefined ? true : !column.nullable;
2459
2547
  })
2460
2548
  }));
@@ -2462,7 +2550,7 @@ async function introspectPhysicalPostgres(params) {
2462
2550
  for (const row of indexRows) {
2463
2551
  if (row.backing_constraint_type === "x")
2464
2552
  continue;
2465
- const operands = indexKeys.filter((key) => physicalNameKey({ namespace: String(key.table_namespace), name: String(key.table_name) }) === row.table_name && key.index_name === row.index_name);
2553
+ const operands = keysByIndex.get(JSON.stringify([row.table_name, row.index_name])) ?? [];
2466
2554
  if (row.plain_columns === true && operands.length !== Number(row.key_count))
2467
2555
  throw new VibeError13({ code: "VIBE_MIGRATION", message: "Plain index keys could not be read completely; refusing name-only operator-class introspection", meta: { reason: "index-inspection-unavailable", table: row.table_name, index: row.index_name } });
2468
2556
  if (operands.length > 0) {
@@ -2487,22 +2575,24 @@ async function introspectPhysicalPostgres(params) {
2487
2575
  indexes.push(parsed);
2488
2576
  }
2489
2577
  }
2578
+ const indexesByTable = groupCatalogRows({ rows: indexes, key: (index) => index.table });
2579
+ const fkGroupsByTable = groupCatalogRows({ rows: fkGroups, key: (group) => group.table });
2490
2580
  const joinTables = new Map;
2491
2581
  for (const table of tables) {
2492
2582
  const physicalTable = physicalNames.get(table);
2493
2583
  if (!physicalTable.name.startsWith("_"))
2494
2584
  continue;
2495
- const tableFks = fkRows.filter((row) => row.table_name === table);
2496
- const tableIndexes = indexRows.filter((row) => row.table_name === table);
2497
- if (pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0 || exclusionRows.some((row) => row.table_name === table))
2585
+ const tableFks = foreignKeysByTable.get(table) ?? [];
2586
+ const tableIndexes = catalogIndexesByTable.get(table) ?? [];
2587
+ if (pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0 || exclusionsByTable.has(table))
2498
2588
  continue;
2499
2589
  if (!tableFks.every((row) => row.validated === true && row.deferrable === false && row.triggers_enabled === true))
2500
2590
  continue;
2501
2591
  if (tableIndexes.length !== 2 || !tableIndexes.every((row) => row.valid === true && row.ready === true && row.immediate === true && row.plain_columns === true && typeof row.key_count === "number" && row.key_count === row.attribute_count))
2502
2592
  continue;
2503
2593
  if (tableFks.some((row) => {
2504
- const source = columnRows.find((column) => column.table_name === table && column.column_name === row.column_name);
2505
- const target = columnRows.find((column) => column.table_name === row.foreign_table_name && column.column_name === row.foreign_column_name);
2594
+ const source = catalogColumns.get(JSON.stringify([table, row.column_name]));
2595
+ const target = catalogColumns.get(JSON.stringify([row.foreign_table_name, row.foreign_column_name]));
2506
2596
  return source === undefined || target === undefined || typeof source.full_type !== "string" || source.full_type.length === 0 || source.full_type !== target.full_type || source.column_default !== null || source.is_identity !== "NO" || source.is_generated !== "NEVER" || source.is_nullable !== "NO" || source.collation_name === undefined || source.collation_name !== target.collation_name;
2507
2597
  }))
2508
2598
  continue;
@@ -2510,9 +2600,9 @@ async function introspectPhysicalPostgres(params) {
2510
2600
  continue;
2511
2601
  const detected = detectJoinTable({
2512
2602
  table: physicalTable.name,
2513
- columnNames: columns.filter((column) => column.table === table).map((column) => column.name),
2514
- fkGroups: fkGroups.filter((group) => group.table === table).map((group) => ({ ...group, table: physicalTable.name, refTable: physicalNames.get(group.refTable)?.name ?? group.refTable })),
2515
- indexes: indexes.filter((index) => index.table === table).map((index) => ({ ...index, table: physicalTable.name }))
2603
+ columnNames: (columnsByTable.get(table) ?? []).map((column) => column.name),
2604
+ fkGroups: (fkGroupsByTable.get(table) ?? []).map((group) => ({ ...group, table: physicalTable.name, refTable: physicalNames.get(group.refTable)?.name ?? group.refTable })),
2605
+ indexes: (indexesByTable.get(table) ?? []).map((index) => ({ ...index, table: physicalTable.name }))
2516
2606
  });
2517
2607
  if (detected !== null)
2518
2608
  joinTables.set(table, { ...detected, ...physicalTable.namespace === "public" && namespaces.length === 1 ? {} : { relationName: detected.relationName ?? physicalTable.name.slice(1) }, tableA: physicalNameKey({ namespace: physicalTable.namespace, name: detected.tableA }), tableB: physicalNameKey({ namespace: physicalTable.namespace, name: detected.tableB }) });
@@ -2525,14 +2615,14 @@ async function introspectPhysicalPostgres(params) {
2525
2615
  table,
2526
2616
  physicalTable: physicalNames.get(table).name,
2527
2617
  isView: false,
2528
- columns,
2618
+ columns: columnsByTable.get(table) ?? [],
2529
2619
  pkByTable,
2530
2620
  pkNames,
2531
- uniquesByConstraint,
2621
+ uniques: uniquesByTable.get(table) ?? [],
2532
2622
  constraintNames,
2533
- indexes,
2623
+ indexes: indexesByTable.get(table) ?? [],
2534
2624
  enumNames,
2535
- exclusions: exclusionRows.filter((row) => row.table_name === table).map((row) => ({ name: String(row.constraint_name), definition: String(row.definition), requiredExtensions: [], catalogDefinition: true })),
2625
+ exclusions: (exclusionsByTable.get(table) ?? []).map((row) => ({ name: String(row.constraint_name), definition: String(row.definition), requiredExtensions: [], catalogDefinition: true })),
2536
2626
  checks: checksByTable.get(table) ?? []
2537
2627
  }));
2538
2628
  }
@@ -2541,10 +2631,10 @@ async function introspectPhysicalPostgres(params) {
2541
2631
  table: view,
2542
2632
  physicalTable: physicalNames.get(view).name,
2543
2633
  isView: true,
2544
- columns,
2634
+ columns: columnsByTable.get(view) ?? [],
2545
2635
  pkByTable,
2546
2636
  pkNames,
2547
- uniquesByConstraint,
2637
+ uniques: uniquesByTable.get(view) ?? [],
2548
2638
  constraintNames,
2549
2639
  indexes: [],
2550
2640
  enumNames,
@@ -2564,14 +2654,16 @@ async function introspectPhysicalPostgres(params) {
2564
2654
  logicalNames.add(name);
2565
2655
  return name;
2566
2656
  };
2657
+ const modelBasenames = groupCatalogRows({ rows: physicalNames.values(), key: (entry) => entry.name });
2658
+ const enumBasenames = groupCatalogRows({ rows: enumIdentities.values(), key: (entry) => entry.name });
2567
2659
  const modelNames = new Map(models.map((model) => {
2568
2660
  const identity = physicalNames.get(model.name);
2569
- const duplicates = [...physicalNames.values()].filter((entry) => entry.name === identity.name).length > 1;
2661
+ const duplicates = (modelBasenames.get(identity.name)?.length ?? 0) > 1;
2570
2662
  return [model.name, logicalName({ key: model.name, name: identity.name, duplicates })];
2571
2663
  }));
2572
2664
  const enumLogicalNames = new Map(enums.map((entry) => {
2573
2665
  const identity = enumIdentities.get(entry.name);
2574
- const duplicates = [...enumIdentities.values()].filter((candidate) => candidate.name === identity.name).length > 1;
2666
+ const duplicates = (enumBasenames.get(identity.name)?.length ?? 0) > 1;
2575
2667
  return [entry.name, logicalName({ key: entry.name, name: identity.name, duplicates })];
2576
2668
  }));
2577
2669
  const qualifiedModels = models.map((model) => {
@@ -2609,20 +2701,18 @@ async function introspectPhysicalPostgres(params) {
2609
2701
  return resolved.schema;
2610
2702
  }
2611
2703
  function buildModel(params) {
2612
- const { table, isView, columns, pkByTable, uniquesByConstraint, constraintNames, indexes, enumNames } = params;
2613
- const tableColumns = columns.filter((column) => column.table === table);
2704
+ const { table, isView, columns, pkByTable, uniques, constraintNames, indexes, enumNames } = params;
2705
+ const tableColumns = columns;
2614
2706
  const pk = pkByTable.get(table) ?? [];
2615
2707
  const uniqueSingles = new Set;
2616
2708
  const multiUniques = [];
2617
- for (const unique of uniquesByConstraint.values()) {
2709
+ for (const unique of uniques) {
2618
2710
  const name = unique.name;
2619
- if (unique.table !== table)
2620
- continue;
2621
2711
  if (unique.columns.length === 1)
2622
2712
  uniqueSingles.add(unique.columns[0]);
2623
2713
  multiUniques.push({ name, columns: unique.columns });
2624
2714
  }
2625
- const tableIndexes = indexes.filter((index) => index.table === table && !constraintNames.has(JSON.stringify([table, index.name])));
2715
+ const tableIndexes = indexes.filter((index) => !constraintNames.has(JSON.stringify([table, index.name])));
2626
2716
  for (const index of tableIndexes) {
2627
2717
  if (!index.unique)
2628
2718
  continue;
@@ -2720,8 +2810,9 @@ function reverseMapType(params) {
2720
2810
  function nonCanonicalModifier(params) {
2721
2811
  const { column, udt, scalar } = params;
2722
2812
  if (column.dataType === "ARRAY" && column.fullType !== null) {
2723
- const element = normalizeNativeType({ raw: column.fullType }).replace(/\[\]$/, "");
2724
- if (/^(numeric|timestamptz)\([\d,]+\)$/.test(element))
2813
+ const temporal = parseUnsupportedPostgresType({ raw: column.fullType });
2814
+ const element = temporal?.name === "timestamptz" ? `timestamptz${temporal.modifier}` : normalizeNativeType({ raw: column.fullType }).replace(/\[\]$/, "");
2815
+ if (element !== "timestamptz(6)" && /^(numeric|timestamptz)\([\d,]+\)$/.test(element))
2725
2816
  return element;
2726
2817
  }
2727
2818
  if (scalar === "DateTime" && udt === "timestamptz") {
@@ -2738,9 +2829,9 @@ function nonCanonicalModifier(params) {
2738
2829
  }
2739
2830
  function withModifier(params) {
2740
2831
  const { column, udt } = params;
2741
- if (udt === "varchar" && column.dataType === "ARRAY" && column.fullType !== null) {
2832
+ if ((udt === "varchar" || udt === "bpchar" || udt === "bit" || udt === "varbit") && column.dataType === "ARRAY" && column.fullType !== null) {
2742
2833
  const elementType = normalizeNativeType({ raw: column.fullType }).replace(/\[\]$/, "");
2743
- if (/^varchar\(\d+\)$/.test(elementType))
2834
+ if (/^(varchar|char|bit|varbit)\(\d+\)$/.test(elementType))
2744
2835
  return elementType;
2745
2836
  }
2746
2837
  if ((udt === "varchar" || udt === "bpchar") && column.charMaxLength !== null) {
@@ -2750,6 +2841,10 @@ function withModifier(params) {
2750
2841
  return "char";
2751
2842
  if ((udt === "bit" || udt === "varbit") && column.charMaxLength !== null)
2752
2843
  return `${udt}(${column.charMaxLength})`;
2844
+ if ((udt === "timestamp" || udt === "time" || udt === "timetz") && column.dataType === "ARRAY" && column.fullType !== null) {
2845
+ const element = parseUnsupportedPostgresType({ raw: column.fullType });
2846
+ return element === undefined || element.modifier === "" || element.modifier === "(6)" ? udt : `${udt}${element.modifier}`;
2847
+ }
2753
2848
  if ((udt === "timestamp" || udt === "time" || udt === "timetz") && column.datetimePrecision !== null && column.datetimePrecision !== 6) {
2754
2849
  return `${udt}(${column.datetimePrecision})`;
2755
2850
  }
@@ -2902,7 +2997,7 @@ function toNumberOrNull(value) {
2902
2997
  }
2903
2998
 
2904
2999
  // packages/migrate/src/introspect/mysql.ts
2905
- import { exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
3000
+ import { VibeError as VibeError14, exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
2906
3001
  import { mysqlDialect as mysqlDialect2 } from "@vibeorm/sql";
2907
3002
 
2908
3003
  // packages/migrate/src/journal-table.ts
@@ -3031,6 +3126,12 @@ async function introspectMysql(params) {
3031
3126
  collation: catalogField({ row, name: "collation_name" }) ?? null,
3032
3127
  extra: (catalogField({ row, name: "extra" }) ?? "").toLowerCase()
3033
3128
  }));
3129
+ const columnsByTable = groupCatalogRows({ rows: columns, key: (column) => column.table });
3130
+ const columnsByIdentity = new Map(columns.map((column) => [JSON.stringify([column.table, column.name]), column]));
3131
+ const catalogColumns = new Map(columnRows.map((row) => [JSON.stringify([catalogField({ row, name: "table_name" }), catalogField({ row, name: "column_name" })]), row]));
3132
+ const statisticsByTable = groupCatalogRows({ rows: statisticRows, key: (row) => String(catalogField({ row, name: "table_name" })) });
3133
+ const foreignKeysByTable = groupCatalogRows({ rows: fkRows, key: (row) => String(catalogField({ row, name: "table_name" })) });
3134
+ const enginesByTable = new Map(tableRows.map((row) => [catalogField({ row, name: "table_name" }), catalogField({ row, name: "engine" })]));
3034
3135
  const rulesByConstraint = new Map;
3035
3136
  for (const row of refRows) {
3036
3137
  rulesByConstraint.set(catalogField({ row, name: "constraint_name" }), {
@@ -3042,6 +3143,15 @@ async function introspectMysql(params) {
3042
3143
  for (const row of fkRows) {
3043
3144
  const constraintName = catalogField({ row, name: "constraint_name" });
3044
3145
  const table = catalogField({ row, name: "table_name" });
3146
+ const tableSchema = catalogField({ row, name: "table_schema" });
3147
+ const referencedSchema = catalogField({ row, name: "referenced_table_schema" });
3148
+ if (typeof tableSchema === "string" && typeof referencedSchema === "string" && tableSchema !== referencedSchema) {
3149
+ throw new VibeError14({
3150
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
3151
+ message: `MySQL cross-database foreign key "${constraintName}" from "${tableSchema}.${table}" to "${referencedSchema}" cannot be introspected without losing its database identity`,
3152
+ meta: { dialect: "mysql", feature: "cross-database foreign keys", constraint: constraintName, table, tableSchema, referencedSchema }
3153
+ });
3154
+ }
3045
3155
  const key = `${table}\x00${constraintName}`;
3046
3156
  const existing = groupsByKey.get(key);
3047
3157
  if (existing === undefined) {
@@ -3064,17 +3174,11 @@ async function introspectMysql(params) {
3064
3174
  onDelete: rules?.onDelete ?? "NoAction",
3065
3175
  onUpdate: rules?.onUpdate ?? "NoAction",
3066
3176
  required: group.columns.every((columnName) => {
3067
- const column = columns.find((candidate) => candidate.table === group.table && candidate.name === columnName);
3177
+ const column = columnsByIdentity.get(JSON.stringify([group.table, columnName]));
3068
3178
  return column === undefined ? true : !column.nullable;
3069
3179
  })
3070
3180
  };
3071
3181
  });
3072
- const fkNamesByTable = new Map;
3073
- for (const group of fkGroups) {
3074
- const set = fkNamesByTable.get(group.table) ?? new Set;
3075
- set.add(group.constraintName);
3076
- fkNamesByTable.set(group.table, set);
3077
- }
3078
3182
  const pkByTable = new Map;
3079
3183
  const indexes = [];
3080
3184
  {
@@ -3083,6 +3187,12 @@ async function introspectMysql(params) {
3083
3187
  const table = catalogField({ row, name: "table_name" });
3084
3188
  const name = catalogField({ row, name: "index_name" });
3085
3189
  const columnName = catalogField({ row, name: "column_name" });
3190
+ const type = catalogField({ row, name: "index_type" });
3191
+ const unique = Number(catalogField({ row, name: "non_unique" })) === 0;
3192
+ const kind = typeof type === "string" ? type.toLowerCase() : "btree";
3193
+ if (catalogField({ row, name: "sub_part" }) != null || catalogField({ row, name: "expression" }) != null || catalogField({ row, name: "is_visible" }) === "NO" || typeof columnName !== "string" || kind !== "btree" && (kind !== "fulltext" || unique)) {
3194
+ throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `MySQL index "${table}.${name}" cannot be introspected without losing its physical shape`, meta: { dialect: "mysql", feature: "index introspection", table, index: name } });
3195
+ }
3086
3196
  if (name === "PRIMARY") {
3087
3197
  const list = pkByTable.get(table);
3088
3198
  if (list === undefined)
@@ -3091,8 +3201,6 @@ async function introspectMysql(params) {
3091
3201
  list.push(columnName);
3092
3202
  continue;
3093
3203
  }
3094
- if (fkNamesByTable.get(table)?.has(name) === true)
3095
- continue;
3096
3204
  const key = `${table}\x00${name}`;
3097
3205
  const entry = byKey.get(key);
3098
3206
  const column = {
@@ -3103,7 +3211,8 @@ async function introspectMysql(params) {
3103
3211
  byKey.set(key, {
3104
3212
  table,
3105
3213
  name,
3106
- unique: Number(catalogField({ row, name: "non_unique" })) === 0,
3214
+ unique,
3215
+ kind,
3107
3216
  columns: [column]
3108
3217
  });
3109
3218
  } else {
@@ -3111,34 +3220,39 @@ async function introspectMysql(params) {
3111
3220
  }
3112
3221
  }
3113
3222
  for (const entry of byKey.values()) {
3114
- indexes.push({ ...entry, kind: "btree" });
3223
+ const support = groupsByKey.get(`${entry.table}\x00${entry.name}`);
3224
+ if (!entry.unique && entry.kind === "btree" && support !== undefined && entry.columns.length === support.columns.length && entry.columns.every((column, index) => column.name === support.columns[index] && !column.desc))
3225
+ continue;
3226
+ indexes.push(entry);
3115
3227
  }
3116
3228
  }
3229
+ const indexesByTable = groupCatalogRows({ rows: indexes, key: (index) => index.table });
3230
+ const fkGroupsByTable = groupCatalogRows({ rows: fkGroups, key: (group) => group.table });
3117
3231
  const enums = [];
3118
3232
  const joinTables = new Map;
3119
3233
  let foreignKeysEnabled;
3120
3234
  for (const table of tables) {
3121
3235
  if (!table.startsWith("_") || pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0)
3122
3236
  continue;
3123
- const tableIndexes = statisticRows.filter((row) => catalogField({ row, name: "table_name" }) === table);
3237
+ const tableIndexes = statisticsByTable.get(table) ?? [];
3124
3238
  if (new Set(tableIndexes.map((row) => catalogField({ row, name: "index_name" }))).size !== 2 || !tableIndexes.every((row) => catalogField({ row, name: "index_type" }) === "BTREE" && catalogField({ row, name: "is_visible" }) === "YES" && catalogField({ row, name: "sub_part" }) === null && catalogField({ row, name: "expression" }) === null && catalogField({ row, name: "collation" }) === "A" && Number.isInteger(Number(catalogField({ row, name: "seq_in_index" }))) && Number(catalogField({ row, name: "seq_in_index" })) > 0))
3125
3239
  continue;
3126
- const tableFks = fkRows.filter((row) => catalogField({ row, name: "table_name" }) === table);
3240
+ const tableFks = foreignKeysByTable.get(table) ?? [];
3127
3241
  if (tableFks.some((row) => {
3128
3242
  const targetTable = catalogField({ row, name: "referenced_table_name" });
3129
- const source = columnRows.find((column) => catalogField({ row: column, name: "table_name" }) === table && catalogField({ row: column, name: "column_name" }) === catalogField({ row, name: "column_name" }));
3130
- const target = columnRows.find((column) => catalogField({ row: column, name: "table_name" }) === targetTable && catalogField({ row: column, name: "column_name" }) === catalogField({ row, name: "referenced_column_name" }));
3243
+ const source = catalogColumns.get(JSON.stringify([table, catalogField({ row, name: "column_name" })]));
3244
+ const target = catalogColumns.get(JSON.stringify([targetTable, catalogField({ row, name: "referenced_column_name" })]));
3131
3245
  if (source === undefined || target === undefined)
3132
3246
  return true;
3133
3247
  const type = catalogField({ row: source, name: "column_type" });
3134
- return typeof type !== "string" || type.length === 0 || type !== catalogField({ row: target, name: "column_type" }) || catalogField({ row: source, name: "column_default" }) !== null || catalogField({ row: source, name: "extra" }) !== "" || catalogField({ row: source, name: "generation_expression" }) !== "" || catalogField({ row: source, name: "is_nullable" }) !== "NO" || catalogField({ row: source, name: "collation_name" }) === undefined || catalogField({ row: source, name: "collation_name" }) !== catalogField({ row: target, name: "collation_name" }) || catalogField({ row, name: "table_schema" }) === undefined || catalogField({ row, name: "table_schema" }) !== catalogField({ row, name: "referenced_table_schema" }) || ![table, targetTable].every((name) => tableRows.some((entry) => catalogField({ row: entry, name: "table_name" }) === name && catalogField({ row: entry, name: "engine" }) === "InnoDB"));
3248
+ return typeof type !== "string" || type.length === 0 || type !== catalogField({ row: target, name: "column_type" }) || catalogField({ row: source, name: "column_default" }) !== null || catalogField({ row: source, name: "extra" }) !== "" || catalogField({ row: source, name: "generation_expression" }) !== "" || catalogField({ row: source, name: "is_nullable" }) !== "NO" || catalogField({ row: source, name: "collation_name" }) === undefined || catalogField({ row: source, name: "collation_name" }) !== catalogField({ row: target, name: "collation_name" }) || catalogField({ row, name: "table_schema" }) === undefined || catalogField({ row, name: "table_schema" }) !== catalogField({ row, name: "referenced_table_schema" }) || ![table, targetTable].every((name) => enginesByTable.get(name) === "InnoDB");
3135
3249
  }))
3136
3250
  continue;
3137
3251
  const detected = detectJoinTable({
3138
3252
  table,
3139
- columnNames: columns.filter((column) => column.table === table).map((column) => column.name),
3140
- fkGroups,
3141
- indexes
3253
+ columnNames: (columnsByTable.get(table) ?? []).map((column) => column.name),
3254
+ fkGroups: fkGroupsByTable.get(table) ?? [],
3255
+ indexes: indexesByTable.get(table) ?? []
3142
3256
  });
3143
3257
  if (detected === null)
3144
3258
  continue;
@@ -3150,7 +3264,7 @@ async function introspectMysql(params) {
3150
3264
  continue;
3151
3265
  const q = (name) => mysqlDialect2.quoteIdent({ name });
3152
3266
  let valid = true;
3153
- for (const fk of fkGroups.filter((group) => group.table === table)) {
3267
+ for (const fk of fkGroupsByTable.get(table) ?? []) {
3154
3268
  const rows = await executor({ text: `SELECT 1 AS invalid FROM ${q(table)} child LEFT JOIN ${q(fk.refTable)} parent ON child.${q(fk.columns[0])} = parent.${q(fk.refColumns[0])} WHERE parent.${q(fk.refColumns[0])} IS NULL LIMIT 1`, values: [] });
3155
3269
  if (rows.length > 0) {
3156
3270
  valid = false;
@@ -3167,9 +3281,9 @@ async function introspectMysql(params) {
3167
3281
  models.push(buildModel2({
3168
3282
  table,
3169
3283
  isView: false,
3170
- columns,
3284
+ columns: columnsByTable.get(table) ?? [],
3171
3285
  pkByTable,
3172
- indexes,
3286
+ indexes: indexesByTable.get(table) ?? [],
3173
3287
  enums,
3174
3288
  checks: checksByTable.get(table) ?? []
3175
3289
  }));
@@ -3178,7 +3292,7 @@ async function introspectMysql(params) {
3178
3292
  models.push(buildModel2({
3179
3293
  table: view,
3180
3294
  isView: true,
3181
- columns,
3295
+ columns: columnsByTable.get(view) ?? [],
3182
3296
  pkByTable: new Map,
3183
3297
  indexes: [],
3184
3298
  enums,
@@ -3196,9 +3310,9 @@ async function introspectMysql(params) {
3196
3310
  }
3197
3311
  function buildModel2(params) {
3198
3312
  const { table, isView, columns, pkByTable, indexes, enums } = params;
3199
- const tableColumns = columns.filter((column) => column.table === table);
3313
+ const tableColumns = columns;
3200
3314
  const pk = pkByTable.get(table) ?? [];
3201
- const tableIndexes = indexes.filter((index) => index.table === table);
3315
+ const tableIndexes = indexes;
3202
3316
  const uniqueSingles = new Set;
3203
3317
  for (const index of tableIndexes) {
3204
3318
  if (index.unique && index.columns.length === 1) {
@@ -3229,7 +3343,7 @@ function buildModel2(params) {
3229
3343
  continue;
3230
3344
  modelIndexes.push({
3231
3345
  name: index.name,
3232
- kind: index.unique ? "unique" : "btree",
3346
+ kind: index.unique ? "unique" : index.kind,
3233
3347
  fields: index.columns.map((column) => ({
3234
3348
  name: column.name,
3235
3349
  ...column.desc ? { sort: "desc" } : {}
@@ -3761,7 +3875,7 @@ function q(name) {
3761
3875
  }
3762
3876
 
3763
3877
  // packages/migrate/src/execute.ts
3764
- import { VibeError as VibeError14 } from "@vibeorm/schema";
3878
+ import { VibeError as VibeError15 } from "@vibeorm/schema";
3765
3879
  var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
3766
3880
  var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
3767
3881
  function usesSelfManagedTransactions(params) {
@@ -3802,7 +3916,7 @@ function assertSelfManagedResumeSafe(params) {
3802
3916
  const completedIndex = params.direction === "up" ? params.start + 1 : -(params.start + 2);
3803
3917
  const rewind = selfManagedResumeStart({ sql: params.sql, start: params.start });
3804
3918
  const retryIndex = params.direction === "up" ? rewind : -(rewind + 1);
3805
- throw new VibeError14({
3919
+ throw new VibeError15({
3806
3920
  code: "VIBE_MIGRATION",
3807
3921
  message: `migration "${params.migration}" has a legacy sqlite checkpoint at ${params.direction} COMMIT (statement ${params.start + 1}). The block may already have committed; replay is refused. Inspect the block's effects. If it committed, set its journal statement_index to ${completedIndex} and error to NULL to resume the remaining statements. Only if the entire block did not commit, set statement_index to ${retryIndex} and error to NULL to retry the block. Do not mark the whole migration applied unless all remaining statements also ran.`,
3808
3922
  meta: { reason: "uncertain-statement", migration: params.migration, statementIndex: params.start, direction: params.direction, dialect: "sqlite" }
@@ -3831,7 +3945,7 @@ function unsupportedScript(params) {
3831
3945
  const { migration, direction, index, problem, found } = params;
3832
3946
  const subject = direction === "down" ? `the down script of migration "${migration}"` : `migration "${migration}"`;
3833
3947
  const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${params.statements}`;
3834
- return new VibeError14({
3948
+ return new VibeError15({
3835
3949
  code: "VIBE_MIGRATION",
3836
3950
  message: `${subject} manages its own sqlite transactions, and ${position} breaks the shape recovery depends on: ${found}. ` + `A table-rebuild block must be written exactly as \`${SUPPORTED_BLOCK_SHAPE}\`. ` + "Inside that block a crash rolls everything back, so a resume can replay it safely. " + "A statement that sits in a foreign-keys-off region with no transaction around it commits on its own, " + "and the resume would run it a second time with no error raised either time. " + "Nothing has been executed. Move the statement after `PRAGMA foreign_keys=ON`, where it commits together " + "with its own journal checkpoint, or give it its own `BEGIN;` and `COMMIT;`. " + 'See "Self-managed sqlite scripts" in docs/migrations.md.',
3837
3951
  meta: { reason: "unsupported-self-managed-script", problem, migration, statementIndex: index, direction }
@@ -3887,7 +4001,7 @@ async function executeStatement(params) {
3887
4001
  const rows = await executor({ text: statement, values: [] });
3888
4002
  if (rows.length > 0) {
3889
4003
  const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
3890
- throw new VibeError14({
4004
+ throw new VibeError15({
3891
4005
  code: "VIBE_MIGRATION",
3892
4006
  message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
3893
4007
  meta: { violations: rows }
@@ -3942,7 +4056,7 @@ async function prepareOperatorClassDiff(params) {
3942
4056
  }
3943
4057
 
3944
4058
  // packages/migrate/src/postgres-default-equivalence.ts
3945
- import { VibeError as VibeError15 } from "@vibeorm/schema";
4059
+ import { VibeError as VibeError16 } from "@vibeorm/schema";
3946
4060
  import { postgresDialect as postgresDialect8, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
3947
4061
  var IDENTIFIER = '(?:"(?:[^"\\u0000]|"")+"|[A-Za-z_][A-Za-z_0-9$]*)';
3948
4062
  var CALL = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*\\(\\s*\\)\\s*$`);
@@ -3961,7 +4075,7 @@ async function equivalentPostgresDefaultFunction(params) {
3961
4075
  return false;
3962
4076
  const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect8, table: params.table, column: params.column });
3963
4077
  const rows = await params.executor({ text: query.text, values: [...query.values] }).catch((cause) => {
3964
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot inspect the stored default function identity", meta: { reason: "default-inspection-unavailable", table: params.table, column: params.column }, cause });
4078
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot inspect the stored default function identity", meta: { reason: "default-inspection-unavailable", table: params.table, column: params.column }, cause });
3965
4079
  });
3966
4080
  if (rows.length !== 1)
3967
4081
  return false;
@@ -4056,7 +4170,7 @@ async function inspectSqlObject(params) {
4056
4170
  }
4057
4171
  }
4058
4172
  return { state: "present", definition: { identity: params.identity, facts, dependencies: [], format: "catalog-v2" } };
4059
- }).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError16 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
4173
+ }).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError17 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
4060
4174
  }
4061
4175
  function postgresInspection(params) {
4062
4176
  if (!params.addresses.every((address) => typeof address === "string"))
@@ -4071,7 +4185,7 @@ function postgresInspection(params) {
4071
4185
  return { state: "present", definition: { identity: params.identity, facts, dependencies, format: "catalog-v2" } };
4072
4186
  }
4073
4187
  function unavailableReason(params) {
4074
- return params.error instanceof VibeError16 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
4188
+ return params.error instanceof VibeError17 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
4075
4189
  }
4076
4190
  async function readBatch(params) {
4077
4191
  const positions = new Set(params.batch.positions);
@@ -4079,7 +4193,7 @@ async function readBatch(params) {
4079
4193
  const rows = new Map;
4080
4194
  for (const row of await params.executor({ text: params.batch.text, values: [...params.batch.values] })) {
4081
4195
  if (typeof row.position !== "number" || !Number.isInteger(row.position) || !positions.has(row.position))
4082
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
4196
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
4083
4197
  const matches = rows.get(row.position) ?? [];
4084
4198
  matches.push(row);
4085
4199
  rows.set(row.position, matches);
@@ -4124,7 +4238,7 @@ async function assertCatalogVisibility(params) {
4124
4238
  const grants = new Set(rows.map((row) => row.privilege));
4125
4239
  const missing = ["SELECT", "TRIGGER", "SHOW VIEW", "EXECUTE"].filter((privilege) => !grants.has(privilege));
4126
4240
  if (missing.length > 0)
4127
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL catalog visibility requires direct database-wide grants; role-only or partial grants cannot prove object absence", meta: { reason: "inventory-unavailable", missing } });
4241
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "MySQL catalog visibility requires direct database-wide grants; role-only or partial grants cannot prove object absence", meta: { reason: "inventory-unavailable", missing } });
4128
4242
  }
4129
4243
  var CATALOG_V1_OMITTED_FACTS = Object.freeze({
4130
4244
  function: ["definerOwner"],
@@ -4148,9 +4262,20 @@ function compare(params) {
4148
4262
  differences.push({ attribute: "dependencies", expected, actual });
4149
4263
  return differences;
4150
4264
  }
4265
+ function compareInspectedSqlObject(params) {
4266
+ const { identity, ownership, expected, live } = params;
4267
+ if (live.state !== "present")
4268
+ return { identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} };
4269
+ if (expected === undefined)
4270
+ return identity.kind === "namespace" ? { identity, ownership, state: "matching", differences: [] } : { identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] };
4271
+ if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined))
4272
+ return { identity, ownership, state: "unavailable", reason: "table-physical-reference-required", differences: compare({ expected, live: live.definition }) };
4273
+ const differences = compare({ expected, live: live.definition });
4274
+ return { identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences };
4275
+ }
4151
4276
  function assertProbeMode(params) {
4152
4277
  if (params.probe !== undefined && params.probe !== "none" && DIALECT_CAPABILITIES2[params.dialect].checkNormalization !== params.probe)
4153
- throw new VibeError16({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
4278
+ throw new VibeError17({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
4154
4279
  }
4155
4280
  async function verifyMigrationState(params) {
4156
4281
  return verifyState(params);
@@ -4173,18 +4298,7 @@ async function verifyState(params) {
4173
4298
  const expected = definitions.get(key);
4174
4299
  const live = inspections === undefined ? await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity }) : inspections[position];
4175
4300
  position++;
4176
- if (live.state !== "present") {
4177
- objects.push({ identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
4178
- } else if (expected === undefined && identity.kind === "namespace") {
4179
- objects.push({ identity, ownership, state: "matching", differences: [] });
4180
- } else if (expected === undefined) {
4181
- objects.push({ identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] });
4182
- } else if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined)) {
4183
- objects.push({ identity, ownership, state: "unavailable", reason: "table-physical-reference-required", differences: compare({ expected, live: live.definition }) });
4184
- } else {
4185
- const differences = compare({ expected, live: live.definition });
4186
- objects.push({ identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences });
4187
- }
4301
+ objects.push(compareInspectedSqlObject({ identity, ownership, expected, live, dialect: params.dialect }));
4188
4302
  }
4189
4303
  if (params.dialect === "postgres" && schemaNamespaces({ schema: params.expected.schema }).length > 0 || params.expected.schema.models.length > 0 || params.expected.schema.enums.length > 0)
4190
4304
  objects.push(...await verifySchemaObjects(params));
@@ -4248,7 +4362,8 @@ async function verifySchemaObjects(params) {
4248
4362
  pin("primaryKey", desired.pk, live.pk);
4249
4363
  for (const [column, field] of desired.columns) {
4250
4364
  const stored = live.columns.get(column);
4251
- pin(`columns.${column}.type`, field.typeKey, stored?.typeKey);
4365
+ const exactType = field.pinsExactType;
4366
+ pin(`columns.${column}.type`, exactType ? field.exactTypeKey : field.typeKey, exactType ? stored?.exactTypeKey : stored?.typeKey);
4252
4367
  pin(`columns.${column}.nullable`, field.nullable, stored?.nullable);
4253
4368
  if (params.dialect === "mysql" && field.field.collation !== undefined) {
4254
4369
  pin(`columns.${column}.collation`, field.field.collation.toLowerCase(), stored?.field.collation?.toLowerCase());
@@ -4352,7 +4467,7 @@ async function verifyMigrationTransition(params) {
4352
4467
  }
4353
4468
  async function verifySqliteTransitionBeforeRestore(params) {
4354
4469
  if (params.dialect !== "sqlite")
4355
- throw new VibeError16({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
4470
+ throw new VibeError17({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
4356
4471
  await executeStatement({ executor: params.executor, dialect: "sqlite", statement: "PRAGMA foreign_key_check;" });
4357
4472
  return verifyTransition({ ...params, sqliteRestorePending: true });
4358
4473
  }
@@ -4362,7 +4477,7 @@ async function verifyTransition(params) {
4362
4477
  for (const identity of params.removals ?? []) {
4363
4478
  const owner = externalObjectOwner({ identity, external });
4364
4479
  if (owner !== undefined)
4365
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Transition removals cannot target externally owned objects", meta: { reason: "external-owned-removal", owner, identity } });
4480
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Transition removals cannot target externally owned objects", meta: { reason: "external-owned-removal", owner, identity } });
4366
4481
  }
4367
4482
  params = { ...params, from: projectExternalState({ state: params.from, external }), to: projectExternalState({ state: params.to, external }) };
4368
4483
  }
@@ -4387,12 +4502,12 @@ async function migrationNamespace(params) {
4387
4502
  const rows = await params.executor({ text: query.text, values: [] });
4388
4503
  const namespace = rows[0]?.namespace;
4389
4504
  if (typeof namespace !== "string" || namespace.length === 0)
4390
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
4505
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
4391
4506
  return namespace;
4392
4507
  }
4393
4508
  function assertMigrationState(params) {
4394
4509
  if (!params.report.inSync)
4395
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Migration state could not be verified", meta: { reason: "migration-state-unverified", shadowReusable: true, objects: params.report.objects.filter((object) => object.state !== "matching") } });
4510
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Migration state could not be verified", meta: { reason: "migration-state-unverified", shadowReusable: true, objects: params.report.objects.filter((object) => object.state !== "matching") } });
4396
4511
  }
4397
4512
  async function deparsePostgresCheck(params) {
4398
4513
  const definition = await deparsePostgresCheckDefinition({ ...params, constraint: "vibeorm_check_probe" });
@@ -4406,7 +4521,7 @@ async function deparsePostgresCheckDefinition(params) {
4406
4521
  await params.executor({ text: query.text });
4407
4522
  const result = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: { kind: "constraint", constraintType: "check", table: params.table, name } });
4408
4523
  if (result.state !== "present" || typeof result.definition.facts.definition !== "string")
4409
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
4524
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
4410
4525
  return { ...result.definition, identity: { kind: "constraint", constraintType: "check", table: params.table, name: params.constraint }, facts: { ...result.definition.facts, definition: result.definition.facts.definition.replace(/ NOT VALID$/, ""), validated: true } };
4411
4526
  } finally {
4412
4527
  await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_check_probe" });
@@ -4424,7 +4539,7 @@ async function deparseMysqlCheck(params) {
4424
4539
  const source = await params.executor({ text: read.text, values: [] });
4425
4540
  const definition = source[0]?.["Create Table"];
4426
4541
  if (typeof definition !== "string")
4427
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
4542
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
4428
4543
  const drop = renderMysqlCheckProbe({ dialect, probe, operation: "drop" });
4429
4544
  await params.executor({ text: drop.text });
4430
4545
  created = false;
@@ -4435,7 +4550,7 @@ async function deparseMysqlCheck(params) {
4435
4550
  const normalizedDefinition = rows[0]?.["Create Table"];
4436
4551
  const check = typeof normalizedDefinition === "string" ? parseSqliteNamedChecks({ tableSql: normalizedDefinition }).find((entry) => entry.name === probe) : undefined;
4437
4552
  if (check === undefined)
4438
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
4553
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
4439
4554
  return check.expression;
4440
4555
  } finally {
4441
4556
  if (created) {
@@ -4462,7 +4577,7 @@ async function prepareCheckDiff(params) {
4462
4577
  const desired = await deparsePostgresCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "rollback" });
4463
4578
  const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
4464
4579
  if (live.state !== "present")
4465
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4580
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4466
4581
  if (live.definition.facts.definition === desired) {
4467
4582
  checks.push(next);
4468
4583
  continue;
@@ -4474,7 +4589,7 @@ async function prepareCheckDiff(params) {
4474
4589
  const desired = await deparseMysqlCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "temporary" });
4475
4590
  const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
4476
4591
  if (live.state !== "present")
4477
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4592
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4478
4593
  checks.push(desired === live.definition.facts.definition ? next : { name: check.name, expression: check.expression });
4479
4594
  continue;
4480
4595
  }
@@ -4487,7 +4602,7 @@ async function prepareCheckDiff(params) {
4487
4602
  checks.push({ name: check.name, expression: check.expression });
4488
4603
  continue;
4489
4604
  }
4490
- throw new VibeError16({ code: "VIBE_MIGRATION", message: `Cannot establish CHECK equivalence for ${table}.${check.name}; request a rollback probe or an explicit replacement`, meta: { reason: "explicit-replacement-required", identity } });
4605
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: `Cannot establish CHECK equivalence for ${table}.${check.name}; request a rollback probe or an explicit replacement`, meta: { reason: "explicit-replacement-required", identity } });
4491
4606
  }
4492
4607
  models.push({ ...model, checks });
4493
4608
  }
@@ -4514,7 +4629,7 @@ async function inspectMigrationInventory(params) {
4514
4629
  for (const row of rows) {
4515
4630
  const name = row.name;
4516
4631
  if (typeof name !== "string" || typeof row.kind !== "string")
4517
- throw new VibeError17({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
4632
+ throw new VibeError18({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
4518
4633
  const qualified = { namespace: params.namespace, name };
4519
4634
  const table = { namespace: params.namespace, name: String(row.parent ?? "") };
4520
4635
  let identity;
@@ -4540,7 +4655,7 @@ async function inspectMigrationInventory(params) {
4540
4655
  case "constraint": {
4541
4656
  const constraintType = kinds[String(row.subtype)];
4542
4657
  if (constraintType === undefined)
4543
- throw new VibeError17({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
4658
+ throw new VibeError18({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
4544
4659
  identity = { kind: "constraint", table, name, constraintType };
4545
4660
  break;
4546
4661
  }
@@ -4550,17 +4665,17 @@ async function inspectMigrationInventory(params) {
4550
4665
  const query = renderMysqlObjectMembers2({ dialect: getSqlDialect3({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
4551
4666
  const members = await params.executor({ text: query.text, values: [...query.values] });
4552
4667
  if (members.length === 0)
4553
- throw new VibeError17({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
4668
+ throw new VibeError18({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
4554
4669
  argumentTypes = members.filter((member) => member.mode !== null).map((member) => ({ namespace: "mysql", name: String(member.type) }));
4555
4670
  }
4556
4671
  const candidate = { kind: "function", name: qualified, argumentTypes };
4557
4672
  if (!isSqlObjectIdentity2(candidate))
4558
- throw new VibeError17({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
4673
+ throw new VibeError18({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
4559
4674
  identity = candidate;
4560
4675
  break;
4561
4676
  }
4562
4677
  default:
4563
- throw new VibeError17({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
4678
+ throw new VibeError18({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
4564
4679
  }
4565
4680
  objects.push({ identity, ...typeof row.backing === "string" ? { backingConstraint: row.backing } : {} });
4566
4681
  if (params.dialect === "sqlite" && row.kind === "table" && typeof row.definition === "string" && !/^\s*CREATE\s+VIRTUAL\s+TABLE\b/i.test(row.definition)) {
@@ -4604,7 +4719,7 @@ async function inspectMigrationInventory(params) {
4604
4719
 
4605
4720
  // packages/migrate/src/module-ownership.ts
4606
4721
  function refusal(params) {
4607
- return new VibeError18({ code: "VIBE_MIGRATION", message: "Module ownership or dependencies cannot be reconciled with the declared and live schema; no package SQL was authorized", meta: params });
4722
+ return new VibeError19({ code: "VIBE_MIGRATION", message: "Module ownership or dependencies cannot be reconciled with the declared and live schema; no package SQL was authorized", meta: params });
4608
4723
  }
4609
4724
  function identities(params) {
4610
4725
  return [...schemaObjectIdentities({ schema: params.state.schema, dialect: params.dialect, namespace: params.namespace }), ...(params.state.schema.migrationObjects ?? []).map((object) => object.identity), ...params.state.definitions.map((definition) => definition.identity)];
@@ -4665,7 +4780,7 @@ function assertModuleOwnership(params) {
4665
4780
  throw refusal({ reason: "module-mutates-application-state", moduleId: origin.moduleId });
4666
4781
  }
4667
4782
  function dependencyUnavailable(params) {
4668
- return new VibeError18({ code: "VIBE_MIGRATION", message: "Cannot establish module foreign-key dependencies across namespaces. MySQL requires direct global SHOW VIEW (or SELECT) without partial revokes; SHOW VIEW exposes metadata without granting external row reads. Restore catalog visibility before retrying", meta: { reason: "module-dependency-unavailable", moduleId: params.origin.moduleId }, ...params.cause === undefined ? {} : { cause: params.cause } });
4783
+ return new VibeError19({ code: "VIBE_MIGRATION", message: "Cannot establish module foreign-key dependencies across namespaces. MySQL requires direct global SHOW VIEW (or SELECT) without partial revokes; SHOW VIEW exposes metadata without granting external row reads. Restore catalog visibility before retrying", meta: { reason: "module-dependency-unavailable", moduleId: params.origin.moduleId }, ...params.cause === undefined ? {} : { cause: params.cause } });
4669
4784
  }
4670
4785
  async function assertCrossNamespaceModuleDependencies(params) {
4671
4786
  const { executor, origin } = params;
@@ -4727,15 +4842,15 @@ async function assertApplicationModuleState(params) {
4727
4842
  const options = { executor: params.executor, dialect: origin.dialect, from: state.from, to: state.to, removals: state.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } };
4728
4843
  const report = params.sqliteRestorePending === true ? await verifySqliteTransitionBeforeRestore(options) : await verifyMigrationTransition(options);
4729
4844
  if (!report.inSync)
4730
- throw new VibeError18({ code: "VIBE_MIGRATION", message: `Application SQL cannot change objects owned by module ${origin.moduleId}; use its reviewed module upgrade`, meta: { reason: "module-application-mutation", moduleId: origin.moduleId, report } });
4845
+ throw new VibeError19({ code: "VIBE_MIGRATION", message: `Application SQL cannot change objects owned by module ${origin.moduleId}; use its reviewed module upgrade`, meta: { reason: "module-application-mutation", moduleId: origin.moduleId, report } });
4731
4846
  }
4732
4847
  }
4733
4848
 
4734
4849
  // packages/migrate/src/module-adoption.ts
4735
- import { VibeError as VibeError19 } from "@vibeorm/schema";
4850
+ import { VibeError as VibeError20 } from "@vibeorm/schema";
4736
4851
  var MODULE_ADOPTION_METADATA = Object.freeze({ filename: "module-adoption.json", version: 1 });
4737
4852
  function invalid3() {
4738
- return new VibeError19({ code: "VIBE_MIGRATION", message: "Local module adoption proof is malformed or lacks immutable source provenance; restore the reviewed local history", meta: { reason: "module-adoption-invalid" } });
4853
+ return new VibeError20({ code: "VIBE_MIGRATION", message: "Local module adoption proof is malformed or lacks immutable source provenance; restore the reviewed local history", meta: { reason: "module-adoption-invalid" } });
4739
4854
  }
4740
4855
  function assertMarker(params) {
4741
4856
  const value = params.value;
@@ -4771,7 +4886,7 @@ function serializeModuleAdoption(params) {
4771
4886
  }
4772
4887
 
4773
4888
  // packages/migrate/src/sql-statements.ts
4774
- import { VibeError as VibeError20 } from "@vibeorm/schema";
4889
+ import { VibeError as VibeError21 } from "@vibeorm/schema";
4775
4890
  var COMPOUND_BODY_HEADER = /^(?:\s|--[^\n]*\n|\/\*[\s\S]*?\*\/)*CREATE\s+(?:OR\s+REPLACE\s+)?(?:(?:TEMP|TEMPORARY)\s+)?(?:DEFINER\s*=\s*\S+\s+)?(?:TRIGGER|PROCEDURE|FUNCTION|EVENT)\b/i;
4776
4891
  var BLOCK_OPENERS = new Set(["BEGIN", "CASE", "IF", "LOOP", "WHILE", "REPEAT"]);
4777
4892
  function splitSqlStatements(params) {
@@ -4847,7 +4962,7 @@ function splitSqlStatements(params) {
4847
4962
  continue;
4848
4963
  }
4849
4964
  if (dialect === "mysql" || dialect === undefined) {
4850
- throw new VibeError20({ code: "VIBE_MIGRATION", message: "SQL string backslash escaping depends on the session SQL mode; use mode-independent quoting and pass the migration dialect", meta: { reason: "sql-mode-ambiguous", dialect } });
4965
+ throw new VibeError21({ code: "VIBE_MIGRATION", message: "SQL string backslash escaping depends on the session SQL mode; use mode-independent quoting and pass the migration dialect", meta: { reason: "sql-mode-ambiguous", dialect } });
4851
4966
  }
4852
4967
  }
4853
4968
  if (value === endQuote) {
@@ -4921,10 +5036,10 @@ function joinSqlStatements(params) {
4921
5036
 
4922
5037
  // packages/migrate/src/module-origin.ts
4923
5038
  import { createHash } from "crypto";
4924
- import { VibeError as VibeError21, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
5039
+ import { VibeError as VibeError22, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
4925
5040
  var MODULE_ORIGIN_METADATA = Object.freeze({ filename: "module-origin.json", version: 1 });
4926
5041
  function invalid4() {
4927
- return new VibeError21({ code: "VIBE_MIGRATION", message: "Module migration origin is malformed or differs from its immutable package artifact; restore the reviewed artifact", meta: { reason: "module-origin-invalid" } });
5042
+ return new VibeError22({ code: "VIBE_MIGRATION", message: "Module migration origin is malformed or differs from its immutable package artifact; restore the reviewed artifact", meta: { reason: "module-origin-invalid" } });
4928
5043
  }
4929
5044
  function record3(value) {
4930
5045
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -4988,7 +5103,7 @@ function assertMaterializedModuleMigration(params) {
4988
5103
  return;
4989
5104
  const restored = restoreModuleMigration({ migration, origin: migration.moduleOrigin });
4990
5105
  if (migration.desiredState === undefined || canonicalMigrationJson({ value: restored }) !== canonicalMigrationJson({ value: migration }))
4991
- throw new VibeError21({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
5106
+ throw new VibeError22({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
4992
5107
  }
4993
5108
  function restoreModuleMigration(params) {
4994
5109
  const { migration, origin } = params;
@@ -5013,13 +5128,13 @@ function restoreModuleMigration(params) {
5013
5128
  // packages/migrate/src/fs.ts
5014
5129
  import { readFile, readdir } from "fs/promises";
5015
5130
  import { join } from "path";
5016
- import { VibeError as VibeError36, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
5131
+ import { VibeError as VibeError37, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
5017
5132
 
5018
5133
  // packages/migrate/src/migration-checksum.ts
5019
5134
  import { createHash as createHash2 } from "crypto";
5020
5135
 
5021
5136
  // packages/migrate/src/execution.ts
5022
- import { VibeError as VibeError22 } from "@vibeorm/schema";
5137
+ import { VibeError as VibeError23 } from "@vibeorm/schema";
5023
5138
  var EXECUTION_SIDECAR_FILENAME = "execution.json";
5024
5139
  var EXECUTION_SIDECAR_VERSION = 1;
5025
5140
  function isDefaultExecution(params) {
@@ -5058,7 +5173,7 @@ ${lines.join(`
5058
5173
  `)}`;
5059
5174
  }
5060
5175
  function invalid5(params) {
5061
- return new VibeError22({
5176
+ return new VibeError23({
5062
5177
  code: "VIBE_MIGRATION",
5063
5178
  message: `invalid execution metadata for migration "${params.migration}": ${params.detail}`,
5064
5179
  meta: { reason: "execution-metadata-invalid", migration: params.migration }
@@ -5109,7 +5224,7 @@ function parseExecutionSidecar(params) {
5109
5224
  try {
5110
5225
  parsed = JSON.parse(params.text);
5111
5226
  } catch (error) {
5112
- throw new VibeError22({
5227
+ throw new VibeError23({
5113
5228
  code: "VIBE_MIGRATION",
5114
5229
  message: `invalid execution metadata for migration "${migration}": not valid JSON`,
5115
5230
  meta: { reason: "execution-metadata-invalid", migration },
@@ -5208,7 +5323,7 @@ function migrationChecksum(params) {
5208
5323
  }
5209
5324
 
5210
5325
  // packages/migrate/src/shadow-guard.ts
5211
- import { VibeError as VibeError23 } from "@vibeorm/schema";
5326
+ import { VibeError as VibeError24 } from "@vibeorm/schema";
5212
5327
  import { getSqlDialect as getSqlDialect5, renderShadowOperation } from "@vibeorm/sql";
5213
5328
  var replayExecutors = new WeakSet;
5214
5329
  function isShadowReplayExecutor(params) {
@@ -5220,7 +5335,7 @@ async function assertDatabaseNotShadow(params) {
5220
5335
  const [query] = renderShadowOperation({ dialect: getSqlDialect5({ dialect: params.dialect }), operation: { kind: "markerExists" } });
5221
5336
  const rows = await params.executor({ text: query.text, values: [...query.values] });
5222
5337
  if (rows.length !== 0)
5223
- throw new VibeError23({ code: "VIBE_MIGRATION", message: "This database is claimed as a disposable migration shadow. Application migrations must use the deployment target.", meta: { reason: "shadow-database-is-not-target" } });
5338
+ throw new VibeError24({ code: "VIBE_MIGRATION", message: "This database is claimed as a disposable migration shadow. Application migrations must use the deployment target.", meta: { reason: "shadow-database-is-not-target" } });
5224
5339
  }
5225
5340
  function runShadowReplay(params) {
5226
5341
  replayExecutors.add(params.executor);
@@ -5230,7 +5345,7 @@ function runShadowReplay(params) {
5230
5345
  }
5231
5346
 
5232
5347
  // packages/migrate/src/external-dependencies.ts
5233
- import { VibeError as VibeError24, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
5348
+ import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
5234
5349
  import { getSqlDialect as getSqlDialect6, renderExternalDependents } from "@vibeorm/sql";
5235
5350
  function externalDependencyTargets(params) {
5236
5351
  return params.steps.flatMap((step) => {
@@ -5253,7 +5368,7 @@ function externalDependencyTargets(params) {
5253
5368
  });
5254
5369
  }
5255
5370
  function unavailable() {
5256
- return new VibeError24({ code: "VIBE_MIGRATION", message: "Malformed catalog dependency row", meta: { reason: "external-inventory-unavailable" } });
5371
+ return new VibeError25({ code: "VIBE_MIGRATION", message: "Malformed catalog dependency row", meta: { reason: "external-inventory-unavailable" } });
5257
5372
  }
5258
5373
  function text(params) {
5259
5374
  const value = params.row[params.key];
@@ -5294,7 +5409,7 @@ function assertNoExternalDependents(params) {
5294
5409
  const dependent = dependentOwner({ row, external: params.external });
5295
5410
  if (dependent.owner === undefined)
5296
5411
  continue;
5297
- throw new VibeError24({
5412
+ throw new VibeError25({
5298
5413
  code: "VIBE_MIGRATION",
5299
5414
  message: "An externally owned object depends on an application object this change drops or retypes; automatic DDL is refused",
5300
5415
  meta: { reason: "external-owned-dependency", owner: dependent.owner, ...dependent.identity === undefined ? {} : { identity: dependent.identity }, dependent: { type, identity }, dependency: target.dependency, referenced }
@@ -5315,9 +5430,9 @@ async function refuseExternalDependents(params) {
5315
5430
  }
5316
5431
 
5317
5432
  // packages/migrate/src/module-history.ts
5318
- import { VibeError as VibeError25, sqlObjectKey as sqlObjectKey9, providerToDialect as providerToDialect3 } from "@vibeorm/schema";
5433
+ import { VibeError as VibeError26, sqlObjectKey as sqlObjectKey9, providerToDialect as providerToDialect3 } from "@vibeorm/schema";
5319
5434
  function refusal2(params) {
5320
- return new VibeError25({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} history, ownership or predecessor state is inconsistent; restore or complete the reviewed upgrade first`, meta: params });
5435
+ return new VibeError26({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} history, ownership or predecessor state is inconsistent; restore or complete the reviewed upgrade first`, meta: params });
5321
5436
  }
5322
5437
  function moduleHistoryHeads(params) {
5323
5438
  const heads = new Map;
@@ -5424,11 +5539,11 @@ function assertModuleUpgradeHistory(params) {
5424
5539
  }
5425
5540
 
5426
5541
  // packages/migrate/src/module-journal.ts
5427
- import { VibeError as VibeError27 } from "@vibeorm/schema";
5542
+ import { VibeError as VibeError28 } from "@vibeorm/schema";
5428
5543
  import { getSqlDialect as getSqlDialect7, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
5429
5544
 
5430
5545
  // packages/migrate/src/journal-read.ts
5431
- import { VibeError as VibeError26 } from "@vibeorm/schema";
5546
+ import { VibeError as VibeError27 } from "@vibeorm/schema";
5432
5547
  var MISSING_TABLE_IDENTIFIERS = new Set(["42P01", "42S02", "ER_NO_SUCH_TABLE"]);
5433
5548
  function isMissingTable(params) {
5434
5549
  let current = params.cause;
@@ -5452,7 +5567,7 @@ async function readJournalRows(params) {
5452
5567
  if (presence.state === "missing")
5453
5568
  return;
5454
5569
  }
5455
- throw new VibeError26({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
5570
+ throw new VibeError27({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
5456
5571
  });
5457
5572
  if (rows === undefined)
5458
5573
  return;
@@ -5460,7 +5575,7 @@ async function readJournalRows(params) {
5460
5575
  for (const row of rows) {
5461
5576
  const index = row.statement_index;
5462
5577
  if (typeof row.name !== "string" || typeof row.checksum !== "string" || typeof index !== "number" && typeof index !== "string" || typeof index === "string" && !/^-?\d+$/.test(index) || !Number.isSafeInteger(Number(index)) || row.module_origin !== null && row.module_origin !== undefined && typeof row.module_origin !== "string" || row.error !== null && row.error !== undefined && typeof row.error !== "string" || result.has(row.name)) {
5463
- throw new VibeError26({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
5578
+ throw new VibeError27({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
5464
5579
  }
5465
5580
  result.set(row.name, { checksum: row.checksum, statementIndex: Number(index), ...typeof row.error === "string" ? { error: row.error } : {}, ...typeof row.module_origin === "string" ? { moduleEntry: row.module_origin } : {} });
5466
5581
  }
@@ -5469,7 +5584,7 @@ async function readJournalRows(params) {
5469
5584
 
5470
5585
  // packages/migrate/src/module-journal.ts
5471
5586
  function invalid6() {
5472
- return new VibeError27({ code: "VIBE_MIGRATION", message: "Module migration history is missing, altered or inconsistent; restore the reviewed history before using this module", meta: { reason: "module-history-invalid" } });
5587
+ return new VibeError28({ code: "VIBE_MIGRATION", message: "Module migration history is missing, altered or inconsistent; restore the reviewed history before using this module", meta: { reason: "module-history-invalid" } });
5473
5588
  }
5474
5589
  function parseModuleJournalEntry(params) {
5475
5590
  return parseMigrationMetadata({ text: params.text, failure: invalid6, parse: (value) => {
@@ -5528,7 +5643,7 @@ function moduleRecordComplete(params) {
5528
5643
  }
5529
5644
 
5530
5645
  // packages/migrate/src/runner.ts
5531
- import { VibeError as VibeError35 } from "@vibeorm/schema";
5646
+ import { VibeError as VibeError36 } from "@vibeorm/schema";
5532
5647
  import {
5533
5648
  DIALECT_CAPABILITIES as DIALECT_CAPABILITIES3,
5534
5649
  getSqlDialect as getSqlDialect9,
@@ -5538,7 +5653,7 @@ import {
5538
5653
  } from "@vibeorm/sql";
5539
5654
 
5540
5655
  // packages/migrate/src/recovery.ts
5541
- import { VibeError as VibeError28 } from "@vibeorm/schema";
5656
+ import { VibeError as VibeError29 } from "@vibeorm/schema";
5542
5657
  import {
5543
5658
  getSqlDialect as getSqlDialect8,
5544
5659
  renderColumnStateQuery,
@@ -5554,7 +5669,7 @@ function constraintUnverifiable(params) {
5554
5669
  });
5555
5670
  }
5556
5671
  function conflict2(params) {
5557
- return new VibeError28({
5672
+ return new VibeError29({
5558
5673
  code: "VIBE_MIGRATION",
5559
5674
  message: params.message,
5560
5675
  meta: { reason: params.reason, ...params.meta }
@@ -5659,10 +5774,10 @@ async function establishOperationState(params) {
5659
5774
 
5660
5775
  // packages/migrate/src/differ.ts
5661
5776
  import { assertIndexOperatorClasses as assertIndexOperatorClasses2, isEnumType as isEnumType6, qualifiedSqlName as qualifiedSqlName9, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
5662
- import { VibeError as VibeError30 } from "@vibeorm/schema";
5777
+ import { VibeError as VibeError31 } from "@vibeorm/schema";
5663
5778
 
5664
5779
  // packages/migrate/src/renames.ts
5665
- import { VibeError as VibeError29, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
5780
+ import { VibeError as VibeError30, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
5666
5781
  function tableOf(model) {
5667
5782
  return model.dbName ?? model.name;
5668
5783
  }
@@ -5673,7 +5788,7 @@ function hasColumn(params) {
5673
5788
  return params.model.fields.some((field) => (field.dbName ?? field.name) === params.column);
5674
5789
  }
5675
5790
  function ambiguous(params) {
5676
- return new VibeError29({
5791
+ return new VibeError30({
5677
5792
  code: "VIBE_MIGRATION",
5678
5793
  message: `rename refused: ${params.detail}`,
5679
5794
  meta: { reason: "rename-ambiguous", ...params.meta }
@@ -5775,17 +5890,34 @@ function planRenames(params) {
5775
5890
 
5776
5891
  // packages/migrate/src/differ.ts
5777
5892
  function diffSchemas(params) {
5893
+ return diffSchemasWithNormalization(params).steps;
5894
+ }
5895
+ function diffSchemasWithNormalization(params) {
5778
5896
  const dialect = params.dialect ?? "postgres";
5779
5897
  assertIndexOperatorClasses2({ schema: params.to, dialect });
5780
- for (const kind of ["models", "enums"])
5898
+ for (const kind of ["models", "enums"]) {
5899
+ const sourceByName = new Map;
5900
+ const sourcePhysical = new Set;
5901
+ const targetPhysical = new Set(params.to[kind].map((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect })));
5902
+ const removedByBasename = new Map;
5903
+ for (const entry of params.from[kind]) {
5904
+ if (!sourceByName.has(entry.name))
5905
+ sourceByName.set(entry.name, entry);
5906
+ const basename = entry.dbName ?? entry.name;
5907
+ const key = physicalNameKey({ name: basename, namespace: entry.namespace, dialect });
5908
+ sourcePhysical.add(key);
5909
+ if (!targetPhysical.has(key) && !removedByBasename.has(basename))
5910
+ removedByBasename.set(basename, entry);
5911
+ }
5781
5912
  for (const entry of params.to[kind]) {
5782
5913
  const key = physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect });
5783
- const before = params.from[kind].find((candidate) => candidate.name === entry.name) ?? params.from[kind].find((candidate) => (candidate.dbName ?? candidate.name) === (entry.dbName ?? entry.name) && !params.to[kind].some((target) => physicalNameKey({ name: target.dbName ?? target.name, namespace: target.namespace, dialect }) === physicalNameKey({ name: candidate.dbName ?? candidate.name, namespace: candidate.namespace, dialect })));
5784
- if (before !== undefined && qualifiedSqlName9({ name: entry.name, namespace: before.namespace }).namespace !== qualifiedSqlName9({ name: entry.name, namespace: entry.namespace }).namespace && !params.from[kind].some((candidate) => physicalNameKey({ name: candidate.dbName ?? candidate.name, namespace: candidate.namespace, dialect }) === key))
5914
+ const before = sourceByName.get(entry.name) ?? removedByBasename.get(entry.dbName ?? entry.name);
5915
+ if (before !== undefined && qualifiedSqlName9({ name: entry.name, namespace: before.namespace }).namespace !== qualifiedSqlName9({ name: entry.name, namespace: entry.namespace }).namespace && !sourcePhysical.has(key))
5785
5916
  throw unsupportedCapability10({ dialect, feature: "automaticNamespaceMove", hint: "Moving populated objects to a fixed namespace requires an explicit reviewed migration" });
5786
5917
  }
5787
- for (const schema of [params.from, params.to])
5788
- normalizeSchema({ schema, dialect });
5918
+ }
5919
+ let from = normalizeSchema({ schema: params.from, dialect });
5920
+ const to = normalizeSchema({ schema: params.to, dialect });
5789
5921
  for (const schema of [params.from, params.to])
5790
5922
  if (dialect !== "postgres" && schema.models.some((model) => (model.exclusions?.length ?? 0) > 0))
5791
5923
  throw unsupportedCapability10({ dialect, feature: "exclusionConstraints" });
@@ -5794,11 +5926,12 @@ function diffSchemas(params) {
5794
5926
  if (dialect !== "postgres")
5795
5927
  refuseScalarListColumns({ schema: params.to, dialect });
5796
5928
  const renamed = planRenames({ from: params.from, to: params.to, renames: params.renames ?? [], dialect });
5929
+ if (renamed.from !== params.from)
5930
+ from = normalizeSchema({ schema: renamed.from, dialect });
5797
5931
  params = { ...params, from: renamed.from };
5798
- const from = normalizeSchema({ schema: params.from, dialect });
5799
- const to = normalizeSchema({ schema: params.to, dialect });
5932
+ const sourceNamespaces = new Set(dialect === "postgres" ? schemaNamespaces({ schema: params.from }) : []);
5800
5933
  const buckets = {
5801
- createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !schemaNamespaces({ schema: params.from }).includes(namespace)).map((namespace) => ({ kind: "createNamespace", namespace, destructive: false })) : [],
5934
+ createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !sourceNamespaces.has(namespace)).map((namespace) => ({ kind: "createNamespace", namespace, destructive: false })) : [],
5802
5935
  dropExclusion: [],
5803
5936
  addExclusion: [],
5804
5937
  dropView: [],
@@ -5826,16 +5959,22 @@ function diffSchemas(params) {
5826
5959
  diffViews({ from, to, buckets });
5827
5960
  diffJoinTables({ from, to, buckets });
5828
5961
  diffTables({ from, to, buckets, dialect });
5962
+ const sourceModels = new Map;
5963
+ for (const model of params.from.models) {
5964
+ const key = physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect });
5965
+ if (!sourceModels.has(key))
5966
+ sourceModels.set(key, model);
5967
+ }
5829
5968
  for (const model of params.to.models) {
5830
5969
  const table = model.dbName ?? model.name;
5831
5970
  const placement = model.namespace === undefined ? {} : { namespace: model.namespace };
5832
- const previous = params.from.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect }) === physicalNameKey({ name: table, namespace: model.namespace, dialect }));
5971
+ const previous = sourceModels.get(physicalNameKey({ name: table, namespace: model.namespace, dialect }));
5833
5972
  for (const exclusion of model.exclusions ?? []) {
5834
5973
  const before = previous?.exclusions?.find((entry) => entry.name === exclusion.name);
5835
5974
  if (before?.definition === exclusion.definition)
5836
5975
  continue;
5837
5976
  if (before?.catalogDefinition)
5838
- throw new VibeError30({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
5977
+ throw new VibeError31({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
5839
5978
  if (before !== undefined)
5840
5979
  buckets.dropExclusion.push({ kind: "dropExclusion", table, ...placement, exclusion: before, destructive: false });
5841
5980
  buckets.addExclusion.push({ kind: "addExclusion", table, ...placement, exclusion, destructive: false });
@@ -5845,7 +5984,8 @@ function diffSchemas(params) {
5845
5984
  buckets.dropExclusion.push({ kind: "dropExclusion", table, exclusion, destructive: false });
5846
5985
  }
5847
5986
  refuseDependentEnumStorageChanges({ from, to, buckets, dialect });
5848
- return withRlsSteps({ from: params.from, to: params.to, steps: [
5987
+ refuseMysqlForeignKeyColumnRewrites({ from, to, buckets, dialect });
5988
+ return { from, to, steps: withRlsSteps({ from: params.from, to: params.to, steps: [
5849
5989
  ...buckets.createNamespace,
5850
5990
  ...renamed.steps,
5851
5991
  ...buckets.dropView,
@@ -5869,7 +6009,7 @@ function diffSchemas(params) {
5869
6009
  ...buckets.addForeignKey,
5870
6010
  ...buckets.createJoinTable,
5871
6011
  ...buckets.dropEnum
5872
- ] });
6012
+ ] }) };
5873
6013
  }
5874
6014
  function refuseScalarListColumns(params) {
5875
6015
  const { schema, dialect } = params;
@@ -5936,6 +6076,40 @@ function refuseDependentEnumStorageChanges(params) {
5936
6076
  }
5937
6077
  }
5938
6078
  }
6079
+ function refuseMysqlForeignKeyColumnRewrites(params) {
6080
+ const { from, to, buckets, dialect } = params;
6081
+ if (dialect !== "mysql")
6082
+ return;
6083
+ const droppedFks = new Set(buckets.dropForeignKey.map((step) => step.kind === "dropForeignKey" ? `${step.table}\x00${step.constraintName}` : ""));
6084
+ for (const step of buckets.alterColumn) {
6085
+ if (step.kind !== "alterColumn")
6086
+ continue;
6087
+ const collation = step.changes.includes("collation");
6088
+ const type = step.changes.includes("type") && !isMysqlVarcharWidening({ from: step.from, to: step.to });
6089
+ if (!collation && !type)
6090
+ continue;
6091
+ const joined = [...from.joinTables].some(([key, join]) => to.joinTables.has(key) && [join.a, join.b].some((end) => end.table === step.table && end.column === step.column));
6092
+ const foreign = [...from.tables].some(([tableKey, table]) => [...table.fks].some(([fkKey, fk]) => to.tables.get(tableKey)?.fks.has(fkKey) === true && !droppedFks.has(`${table.table}\x00${fk.constraintName}`) && (table.table === step.table && fk.columns.includes(step.column) || fk.refTable === step.table && fk.refColumns.includes(step.column))));
6093
+ if (!joined && !foreign)
6094
+ continue;
6095
+ throw unsupportedCapability10({
6096
+ dialect,
6097
+ feature: `${collation ? "collation" : "type"} change of "${step.table}"."${step.column}" ${joined ? "referenced by an implicit many-to-many join table" : "in a foreign key"}`,
6098
+ hint: `drop the foreign key, change both columns' ${collation ? "collation" : "type"} and re-add it in a reviewed manual migration`
6099
+ });
6100
+ }
6101
+ }
6102
+ function isMysqlVarcharWidening(params) {
6103
+ const length = (field) => {
6104
+ if (isEnumType6(field.type) || field.isList || field.isUnsupported === true)
6105
+ return null;
6106
+ const match = /^varchar\((\d+)\)$/.exec(normalizeMysqlType({ raw: mysqlColumnTypeSql({ field, enums: new Map }) }));
6107
+ return match === null ? null : Number(match[1]);
6108
+ };
6109
+ const before = length(params.from);
6110
+ const after = length(params.to);
6111
+ return before !== null && after !== null && after >= before;
6112
+ }
5939
6113
  function diffViews(params) {
5940
6114
  const { from, to, buckets } = params;
5941
6115
  for (const [name, view] of from.views) {
@@ -6044,7 +6218,7 @@ function diffOneTable(params) {
6044
6218
  continue;
6045
6219
  }
6046
6220
  const changes = [];
6047
- if (before.typeKey !== column.typeKey)
6221
+ if (columnTypeChanged({ declared: column, actual: before }))
6048
6222
  changes.push("type");
6049
6223
  if (before.nullable !== column.nullable)
6050
6224
  changes.push("nullability");
@@ -6067,7 +6241,7 @@ function diffOneTable(params) {
6067
6241
  assertStringEnumLabelsFit({ field: column.field, values, dialect, table });
6068
6242
  }
6069
6243
  }
6070
- let narrowingType = changes.includes("type") && !isScalarToArrayChange({ from: before.field, to: column.field }) && !(storageChange && column.field.type === "String");
6244
+ let narrowingType = changes.includes("type") && !isScalarToArrayChange({ from: before.field, to: column.field }) && !(dialect === "postgres" && isTimestamptzPrecisionWidening({ from: before.field, to: column.field })) && !(storageChange && column.field.type === "String");
6071
6245
  if (dialect !== "postgres" && changes.includes("type") && isEnumType6(before.field.type) && isEnumType6(column.field.type)) {
6072
6246
  const fromValues = resolveEnumValues({ name: before.field.type.enum, enums: fromEnums });
6073
6247
  const toValues = resolveEnumValues({ name: column.field.type.enum, enums: toEnums });
@@ -6091,7 +6265,7 @@ function diffOneTable(params) {
6091
6265
  from: before.field,
6092
6266
  to: column.field,
6093
6267
  changes,
6094
- destructive: narrowingType || collationChanged
6268
+ destructive: narrowingType || collationChanged || dialect === "mysql" && before.nullable && !column.nullable
6095
6269
  });
6096
6270
  }
6097
6271
  for (const [name] of previous.columns) {
@@ -6099,9 +6273,7 @@ function diffOneTable(params) {
6099
6273
  buckets.dropColumn.push({ kind: "dropColumn", table, ...placement, column: name, destructive: true });
6100
6274
  }
6101
6275
  }
6102
- const pkBefore = previous.pk.join(",");
6103
- const pkAfter = current.pk.join(",");
6104
- if (pkBefore !== pkAfter) {
6276
+ if (previous.pk.length !== current.pk.length || previous.pk.some((column, index) => column !== current.pk[index])) {
6105
6277
  if (previous.pk.length > 0) {
6106
6278
  buckets.dropPrimaryKey.push({ kind: "dropPrimaryKey", table, ...placement, destructive: false, ...previous.model.primaryKeyConstraintName === undefined ? {} : { constraintName: previous.model.primaryKeyConstraintName } });
6107
6279
  }
@@ -6121,7 +6293,7 @@ function diffOneTable(params) {
6121
6293
  if (before === expression)
6122
6294
  continue;
6123
6295
  if (before !== undefined && previous.model.checks?.some((check) => check.name === name && check.catalogExpression === true))
6124
- throw new VibeError30({ code: "VIBE_MIGRATION", message: "Unequal catalog CHECK text requires normalized evidence or explicit replacement through the live planner", meta: { reason: "check-inspection-unavailable", table, name } });
6296
+ throw new VibeError31({ code: "VIBE_MIGRATION", message: "Unequal catalog CHECK text requires normalized evidence or explicit replacement through the live planner", meta: { reason: "check-inspection-unavailable", table, name } });
6125
6297
  if (before !== undefined)
6126
6298
  buckets.dropCheck.push({ kind: "dropCheck", table, ...placement, name, destructive: false });
6127
6299
  buckets.addCheck.push({ kind: "addCheck", table, ...placement, name, expression, destructive: false });
@@ -6207,11 +6379,11 @@ function diffOneTable(params) {
6207
6379
  }
6208
6380
 
6209
6381
  // packages/migrate/src/ddl/render.ts
6210
- import { VibeError as VibeError34, sqlObjectKey as sqlObjectKey10 } from "@vibeorm/schema";
6382
+ import { VibeError as VibeError35, sqlObjectKey as sqlObjectKey10 } from "@vibeorm/schema";
6211
6383
 
6212
6384
  // packages/migrate/src/ddl/postgres.ts
6213
6385
  import { renderExclusionSetup } from "@vibeorm/sql";
6214
- import { VibeError as VibeError31, assertSqlNamespace as assertSqlNamespace3, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
6386
+ import { VibeError as VibeError32, assertSqlNamespace as assertSqlNamespace3, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
6215
6387
  import { postgresDialect as postgresDialect9, renderRlsPolicyPath, renderIndexColumns } from "@vibeorm/sql";
6216
6388
  function renderPostgresSteps(params) {
6217
6389
  refuseSameTransactionEnumUse({ steps: params.steps });
@@ -6248,7 +6420,7 @@ function refuseSameTransactionEnumUse(params) {
6248
6420
  const found = use(field);
6249
6421
  if (found === undefined)
6250
6422
  continue;
6251
- throw new VibeError31({
6423
+ throw new VibeError32({
6252
6424
  code: "VIBE_MIGRATION",
6253
6425
  message: `enum value "${found.value}" is added to "${found.enumName}" and used as a default on "${step.table}"."${field.dbName ?? field.name}" in the same migration \u2014 PostgreSQL cannot use an enum member before the transaction that added it commits. Split this into two migrations: the first containing only the ALTER TYPE ... ADD VALUE, the second the column change`,
6254
6426
  meta: { enum: found.enumName, value: found.value, table: step.table, column: field.dbName ?? field.name, rule: "enum-value-used-in-adding-transaction" }
@@ -6322,7 +6494,7 @@ ${lines.join(`,
6322
6494
  case "dropIndex":
6323
6495
  if (step.constraintName !== undefined) {
6324
6496
  if (step.table === undefined)
6325
- throw new VibeError31({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
6497
+ throw new VibeError32({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
6326
6498
  return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.constraintName)};`];
6327
6499
  }
6328
6500
  return [`DROP INDEX ${qualifiedName(step.name, step.namespace)};`];
@@ -6387,7 +6559,7 @@ function columnDefSql(params) {
6387
6559
  const { field } = params;
6388
6560
  const name = q2(field.dbName ?? field.name);
6389
6561
  if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
6390
- throw new VibeError31({
6562
+ throw new VibeError32({
6391
6563
  code: "VIBE_MIGRATION",
6392
6564
  message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
6393
6565
  meta: { field: field.name }
@@ -6423,7 +6595,7 @@ function renderAlterColumn(params) {
6423
6595
  }
6424
6596
  if (step.changes.includes("default") || droppedDefault) {
6425
6597
  if (step.to.default?.kind === "autoincrement") {
6426
- throw new VibeError31({
6598
+ throw new VibeError32({
6427
6599
  code: "VIBE_MIGRATION",
6428
6600
  message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
6429
6601
  meta: { table: step.table, column: step.column }
@@ -6443,7 +6615,7 @@ function q2(name) {
6443
6615
  }
6444
6616
 
6445
6617
  // packages/migrate/src/ddl/sqlite.ts
6446
- import { VibeError as VibeError32, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
6618
+ import { VibeError as VibeError33, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
6447
6619
  import { sqliteDialect as sqliteDialect3 } from "@vibeorm/sql";
6448
6620
 
6449
6621
  // packages/migrate/src/ddl/drop-order.ts
@@ -6591,7 +6763,7 @@ function renderSqliteSteps(params) {
6591
6763
  continue;
6592
6764
  const awaitsNewParent = [...toNorm?.tables.get(table)?.fks.values() ?? []].some((fk) => createdTables.has(fk.refTable));
6593
6765
  if (awaitsNewParent) {
6594
- throw new VibeError32({
6766
+ throw new VibeError33({
6595
6767
  code: "VIBE_MIGRATION",
6596
6768
  message: `cannot order this sqlite migration safely: rebuilding "${table}" must happen BEFORE the dropped table(s) it references (${[
6597
6769
  ...droppedTables
@@ -6620,7 +6792,7 @@ function renderSqliteSteps(params) {
6620
6792
  switch (step.kind) {
6621
6793
  case "addExclusion":
6622
6794
  case "dropExclusion":
6623
- throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6795
+ throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6624
6796
  case "createEnum":
6625
6797
  case "dropEnum":
6626
6798
  case "addEnumValue":
@@ -6706,7 +6878,7 @@ function renderSqliteSteps(params) {
6706
6878
  case "renameColumn": {
6707
6879
  const renamed = step.kind === "renameTable" ? step.from : step.table;
6708
6880
  if (rebuildSet.has(renamed) || step.kind === "renameTable" && rebuildSet.has(step.to)) {
6709
- throw new VibeError32({
6881
+ throw new VibeError33({
6710
6882
  code: "VIBE_MIGRATION",
6711
6883
  message: `sqlite cannot rename "${renamed}" in the same migration that rebuilds it \u2014 the table-rebuild recipe is written against a single table name. Split the rename and the column/constraint change into two migrations`,
6712
6884
  meta: { reason: "rename-ambiguous", table: renamed, dialect: "sqlite" }
@@ -6760,7 +6932,7 @@ function classifyRebuilds(params) {
6760
6932
  switch (step.kind) {
6761
6933
  case "addExclusion":
6762
6934
  case "dropExclusion":
6763
- throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6935
+ throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6764
6936
  case "alterColumn":
6765
6937
  case "dropForeignKey":
6766
6938
  case "addPrimaryKey":
@@ -6821,7 +6993,7 @@ function renderTableRebuild(params) {
6821
6993
  const source = from.tables.get(table);
6822
6994
  const target = to.tables.get(table);
6823
6995
  if (source === undefined || target === undefined) {
6824
- throw new VibeError32({
6996
+ throw new VibeError33({
6825
6997
  code: "VIBE_MIGRATION",
6826
6998
  message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
6827
6999
  meta: { table }
@@ -6956,7 +7128,7 @@ function columnDefSql2(params) {
6956
7128
  const { field, constraintTable, enums, inlinePkAutoincrement } = params;
6957
7129
  const name = field.dbName ?? field.name;
6958
7130
  if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
6959
- throw new VibeError32({
7131
+ throw new VibeError33({
6960
7132
  code: "VIBE_MIGRATION",
6961
7133
  message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
6962
7134
  meta: { field: field.name }
@@ -6975,7 +7147,7 @@ function columnDefSql2(params) {
6975
7147
  const enumName = field.type.enum;
6976
7148
  const values = resolveEnumValues({ name: enumName, enums });
6977
7149
  if (values === undefined) {
6978
- throw new VibeError32({
7150
+ throw new VibeError33({
6979
7151
  code: "VIBE_MIGRATION",
6980
7152
  message: `enum "${enumName}" on column "${field.name}" is not declared in the schema \u2014 sqlite rendering needs the target schema's enums for the CHECK constraint`,
6981
7153
  meta: { field: field.name, enum: enumName }
@@ -7010,7 +7182,7 @@ function requireSchemas(params) {
7010
7182
  const { step, fromNorm, toNorm } = params;
7011
7183
  if (fromNorm !== undefined && toNorm !== undefined)
7012
7184
  return;
7013
- throw new VibeError32({
7185
+ throw new VibeError33({
7014
7186
  code: "VIBE_MIGRATION",
7015
7187
  message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
7016
7188
  meta: { step: step.kind }
@@ -7021,7 +7193,7 @@ function q3(name) {
7021
7193
  }
7022
7194
 
7023
7195
  // packages/migrate/src/ddl/mysql.ts
7024
- import { VibeError as VibeError33, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
7196
+ import { VibeError as VibeError34, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
7025
7197
  import { mysqlDialect as mysqlDialect3 } from "@vibeorm/sql";
7026
7198
  function renderMysqlSteps(params) {
7027
7199
  for (const step of params.steps) {
@@ -7169,12 +7341,12 @@ ${lines.join(`,
7169
7341
  ];
7170
7342
  case "addExclusion":
7171
7343
  case "dropExclusion":
7172
- throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
7344
+ throw new VibeError34({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
7173
7345
  case "dropCheck":
7174
7346
  return [`ALTER TABLE ${q4(step.table)} DROP CHECK ${q4(step.name)};`];
7175
7347
  case "dropIndex": {
7176
7348
  if (step.table === undefined) {
7177
- throw new VibeError33({
7349
+ throw new VibeError34({
7178
7350
  code: "VIBE_MIGRATION",
7179
7351
  message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
7180
7352
  meta: { index: step.name }
@@ -7204,8 +7376,8 @@ ${lines.join(`,
7204
7376
  case "dropForeignKey":
7205
7377
  return [`ALTER TABLE ${q4(step.table)} DROP FOREIGN KEY ${q4(step.constraintName)};`];
7206
7378
  case "createJoinTable": {
7207
- const aType = mysqlColumnTypeSql({ field: { ...step.a.field, isList: false }, enums });
7208
- const bType = mysqlColumnTypeSql({ field: { ...step.b.field, isList: false }, enums });
7379
+ const aType = joinColumnTypeSql({ field: step.a.field, enums });
7380
+ const bType = joinColumnTypeSql({ field: step.b.field, enums });
7209
7381
  const names = implicitJoinTableObjectNames4({ table: step.table });
7210
7382
  return [
7211
7383
  `CREATE TABLE ${q4(step.table)} (
@@ -7226,12 +7398,17 @@ ${lines.join(`,
7226
7398
  return [`ALTER TABLE ${q4(step.table)} DROP PRIMARY KEY;`];
7227
7399
  }
7228
7400
  }
7401
+ function joinColumnTypeSql(params) {
7402
+ const { field, enums } = params;
7403
+ const type = mysqlColumnTypeSql({ field: { ...field, isList: false }, enums });
7404
+ return field.collation === undefined ? type : `${type} COLLATE ${q4(field.collation)}`;
7405
+ }
7229
7406
  function columnDefSql3(params) {
7230
7407
  const { field, enums } = params;
7231
7408
  const name = q4(field.dbName ?? field.name);
7232
7409
  const autoincrement = field.default?.kind === "autoincrement";
7233
7410
  if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
7234
- throw new VibeError33({
7411
+ throw new VibeError34({
7235
7412
  code: "VIBE_MIGRATION",
7236
7413
  message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
7237
7414
  meta: { field: field.name }
@@ -7253,7 +7430,7 @@ function renderAlterColumn2(params) {
7253
7430
  const { step, enums } = params;
7254
7431
  assertEnumStringTransition({ from: step.from, to: step.to, dialect: "mysql", table: step.table, column: step.column });
7255
7432
  if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
7256
- throw new VibeError33({
7433
+ throw new VibeError34({
7257
7434
  code: "VIBE_MIGRATION",
7258
7435
  message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
7259
7436
  meta: { table: step.table, column: step.column }
@@ -7293,24 +7470,24 @@ function declaredSqlitePreservation(params) {
7293
7470
  continue;
7294
7471
  const facts = object.expected?.facts;
7295
7472
  if (facts === undefined)
7296
- throw new VibeError34({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
7473
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
7297
7474
  if (identity.kind === "trigger" || identity.kind === "index") {
7298
7475
  if (identity.kind === "index" && to.models.some((model) => (model.dbName ?? model.name) === identity.table.name && model.indexes.some((index) => index.name === identity.name)))
7299
7476
  continue;
7300
7477
  if (typeof facts.definition !== "string")
7301
- throw new VibeError34({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
7478
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
7302
7479
  const statements = restore.get(identity.table.name) ?? [];
7303
7480
  statements.push(facts.definition);
7304
7481
  restore.set(identity.table.name, statements);
7305
7482
  } else if (identity.kind === "column" || identity.kind === "generatedColumn") {
7306
7483
  if (typeof facts.columnDefinition !== "string")
7307
- throw new VibeError34({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
7484
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
7308
7485
  const fields = columns.get(identity.table.name) ?? new Map;
7309
7486
  fields.set(identity.column, { sql: facts.columnDefinition, generated: Number(facts.generated) > 1 });
7310
7487
  columns.set(identity.table.name, fields);
7311
7488
  } else if (identity.kind === "columnDefault") {
7312
7489
  if (facts.definition !== null && typeof facts.definition !== "string")
7313
- throw new VibeError34({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
7490
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
7314
7491
  const expression = facts.definition;
7315
7492
  to = { ...to, models: to.models.map((model) => (model.dbName ?? model.name) !== identity.table.name ? model : { ...model, fields: model.fields.map((field) => {
7316
7493
  if ((field.dbName ?? field.name) !== identity.column)
@@ -7347,7 +7524,7 @@ function renderSteps(params) {
7347
7524
  }
7348
7525
 
7349
7526
  // packages/migrate/src/extensions.ts
7350
- import { extensionDeclarations } from "@vibeorm/schema";
7527
+ import { extensionDeclarations, providerToDialect as providerToDialect4, qualifiedSqlName as qualifiedSqlName10, SQL_NAMESPACES } from "@vibeorm/schema";
7351
7528
  function collectExtensionArtifacts(params) {
7352
7529
  const { schema, dialect, extensions } = params;
7353
7530
  const setup = [];
@@ -7414,6 +7591,10 @@ function stripExtensionArtifacts(params) {
7414
7591
  const models = [];
7415
7592
  for (const model of schema.models) {
7416
7593
  const tableName = model.dbName ?? model.name;
7594
+ if (providerToDialect4({ provider: schema.datasource.provider }) === "postgres" && qualifiedSqlName10({ name: tableName, namespace: model.namespace }).namespace !== SQL_NAMESPACES.postgres) {
7595
+ models.push(model);
7596
+ continue;
7597
+ }
7417
7598
  if (tables.exact.has(tableName) || tables.prefixes.some((prefix) => tableName.startsWith(prefix))) {
7418
7599
  continue;
7419
7600
  }
@@ -7437,6 +7618,8 @@ function planMissingArtifacts(params) {
7437
7618
  return [];
7438
7619
  const modelByTable = new Map;
7439
7620
  for (const model of introspected.models) {
7621
+ if (providerToDialect4({ provider: introspected.datasource.provider }) === "postgres" && qualifiedSqlName10({ name: model.dbName ?? model.name, namespace: model.namespace }).namespace !== SQL_NAMESPACES.postgres)
7622
+ continue;
7440
7623
  modelByTable.set(model.dbName ?? model.name, model);
7441
7624
  }
7442
7625
  return artifacts.filter((artifact) => {
@@ -7595,10 +7778,10 @@ function assertNativeMigrationMetadata(params) {
7595
7778
  if (!native)
7596
7779
  return;
7597
7780
  if (!DIALECT_CAPABILITIES3[params.dialect].nativeRowLevelSecurity) {
7598
- throw new VibeError35({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
7781
+ throw new VibeError36({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
7599
7782
  }
7600
7783
  if (params.migration.rls === undefined) {
7601
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Native RLS migration files require trusted rls.from/rls.to schema metadata. SQL alone cannot prove policy ownership or drift; regenerate with preserved security metadata.", meta: { reason: "rls-migration-metadata-required", migration: params.migration.name } });
7784
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Native RLS migration files require trusted rls.from/rls.to schema metadata. SQL alone cannot prove policy ownership or drift; regenerate with preserved security metadata.", meta: { reason: "rls-migration-metadata-required", migration: params.migration.name } });
7602
7785
  }
7603
7786
  }
7604
7787
  var PG_ADVISORY_LOCK_KEY = 1447645253;
@@ -7679,12 +7862,12 @@ function generateMigration(params) {
7679
7862
  }),
7680
7863
  ...orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).flatMap((object) => {
7681
7864
  if (object.dialect !== dialect)
7682
- throw new VibeError35({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
7865
+ throw new VibeError36({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
7683
7866
  const previous = (params.desiredState?.from.schema.migrationObjects ?? from.migrationObjects)?.find((entry) => entry.id === object.id);
7684
7867
  if (previous === undefined)
7685
7868
  return [...object.create];
7686
7869
  if (JSON.stringify(previous) !== JSON.stringify(object))
7687
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration-owned object ${object.id} changed; supply a reviewed forward SQL migration`, meta: { reason: "migration-object-replacement-required", shadowReusable: true, id: object.id } });
7870
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration-owned object ${object.id} changed; supply a reviewed forward SQL migration`, meta: { reason: "migration-object-replacement-required", shadowReusable: true, id: object.id } });
7688
7871
  return [];
7689
7872
  })
7690
7873
  ];
@@ -7710,8 +7893,8 @@ async function withMigrationLock(params) {
7710
7893
  }, (cause) => releaseLock({ executor, dialect: params.dialect }).then(() => {
7711
7894
  throw cause;
7712
7895
  }, (cleanupCause) => {
7713
- if (cause instanceof VibeError35)
7714
- throw new VibeError35({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
7896
+ if (cause instanceof VibeError36)
7897
+ throw new VibeError36({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
7715
7898
  throw cause;
7716
7899
  }));
7717
7900
  };
@@ -7741,11 +7924,11 @@ function assertMigrationMetadata(params) {
7741
7924
  assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
7742
7925
  }
7743
7926
  if (migration.desiredStatePending === true)
7744
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration ${migration.name} has unfinalized desired state; finalize its reviewed before/after declarations before applying`, meta: { reason: "desired-state-unfinalized", migration: migration.name } });
7927
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration ${migration.name} has unfinalized desired state; finalize its reviewed before/after declarations before applying`, meta: { reason: "desired-state-unfinalized", migration: migration.name } });
7745
7928
  if (migration.desiredState !== undefined) {
7746
7929
  assertDesiredState({ value: migration.desiredState, migration: migration.name });
7747
7930
  if (migration.desiredState.dialect !== dialect)
7748
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
7931
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
7749
7932
  }
7750
7933
  assertNativeMigrationMetadata({ migration, sql: migration.sql, dialect });
7751
7934
  assertExecutable({ migration, execution: migration.execution, sql: migration.sql, dialect });
@@ -7755,7 +7938,7 @@ async function applyMigrations(params) {
7755
7938
  const dialect = params.dialect ?? "postgres";
7756
7939
  const trustedReplay = isShadowReplayExecutor({ executor });
7757
7940
  if (params.history !== undefined && params.history !== "complete" && params.history !== "subset")
7758
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
7941
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
7759
7942
  if (params.history === "complete")
7760
7943
  assertDistinctMigrationNames({ migrations });
7761
7944
  for (const migration of migrations)
@@ -7771,7 +7954,7 @@ async function applyMigrations(params) {
7771
7954
  assertModuleUpgradeHistory({ migrations, records: moduleRecords });
7772
7955
  const namespace = await migrationNamespace({ executor, dialect });
7773
7956
  if (dialect !== "postgres" && migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
7774
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
7957
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
7775
7958
  }
7776
7959
  if (params.history === "complete")
7777
7960
  assertCompleteHistory({ migrations, recorded, dialect });
@@ -7782,7 +7965,7 @@ async function applyMigrations(params) {
7782
7965
  });
7783
7966
  const adopted = pending.find((migration) => migration.moduleAdoption !== undefined);
7784
7967
  if (adopted !== undefined && !trustedReplay)
7785
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "An adopted source must be recorded by the verified adoption workflow, never executed by ordinary apply", meta: { reason: "module-adoption-recording-required", migration: adopted.name } });
7968
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "An adopted source must be recorded by the verified adoption workflow, never executed by ordinary apply", meta: { reason: "module-adoption-recording-required", migration: adopted.name } });
7786
7969
  if (external !== undefined && !trustedReplay)
7787
7970
  for (const migration of pending)
7788
7971
  await assertExternalMigrationMutation({ executor, migration, dialect, namespace: await migrationNamespace({ executor, dialect }), external });
@@ -7798,7 +7981,7 @@ async function applyMigrations(params) {
7798
7981
  await assertLiveModuleOwnership({ executor, origin: migration.moduleOrigin, resume: stored !== undefined });
7799
7982
  if (stored !== undefined) {
7800
7983
  if (stored.statementIndex < 0) {
7801
- throw new VibeError35({
7984
+ throw new VibeError36({
7802
7985
  code: "VIBE_MIGRATION",
7803
7986
  message: `migration "${migration.name}" is mid-rollback (${-stored.statementIndex - 1} down statement(s) done) \u2014 finish it with \`migrate down\` or repair with resolveMigration before applying`,
7804
7987
  meta: { migration: migration.name, statementIndex: stored.statementIndex }
@@ -7956,7 +8139,7 @@ async function repairMigration(params) {
7956
8139
  const { executor, name, dialect } = params;
7957
8140
  const moduleRecord = (await readModuleJournal({ executor, dialect })).get(name);
7958
8141
  if (moduleRecord?.entry !== undefined)
7959
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Module history cannot be marked applied or deleted without verified module recovery", meta: { reason: "module-resolution-required", moduleId: moduleRecord.entry.migration.moduleOrigin.moduleId } });
8142
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Module history cannot be marked applied or deleted without verified module recovery", meta: { reason: "module-resolution-required", moduleId: moduleRecord.entry.migration.moduleOrigin.moduleId } });
7960
8143
  await ensureMigrationsTable({ executor, dialect });
7961
8144
  if (params.as === "applied") {
7962
8145
  const insert = `INSERT INTO ${q5(dialect, MIGRATIONS_TABLE_NAME)} (${q5(dialect, "name")}, ${q5(dialect, "checksum")}, ${q5(dialect, "statement_index")}) VALUES (${ph(dialect, 1)}, ${ph(dialect, 2)}, ${RESOLVED_STATEMENT_INDEX})`;
@@ -7978,7 +8161,7 @@ async function rollbackMigration(params) {
7978
8161
  const stored = assertRollbackRecord({ migration, stored: recorded.get(migration.name), dialect });
7979
8162
  const newest = [...recorded.keys()].at(-1);
7980
8163
  if (newest !== migration.name)
7981
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration "${migration.name}" is not the newest recorded migration; select rollback again`, meta: { reason: "rollback-not-latest", migration: migration.name, newest } });
8164
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${migration.name}" is not the newest recorded migration; select rollback again`, meta: { reason: "rollback-not-latest", migration: migration.name, newest } });
7982
8165
  await validateRollbackRecovery({ executor, migration, downSql, stored, dialect });
7983
8166
  await executeRollback({ ...params, executor, dialect, downSql, stored });
7984
8167
  } });
@@ -7996,7 +8179,7 @@ async function rollbackMigrations(params) {
7996
8179
  const target = journal.findIndex((row) => row.name === params.to);
7997
8180
  const row = journal[target];
7998
8181
  if (row?.state !== "applied")
7999
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Rollback target "${params.to}" is not a known applied migration`, meta: { reason: "rollback-target-invalid", to: params.to, state: row?.state } });
8182
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Rollback target "${params.to}" is not a known applied migration`, meta: { reason: "rollback-target-invalid", to: params.to, state: row?.state } });
8000
8183
  assertMigrationMetadata({ migration: byName.get(row.name), dialect });
8001
8184
  start = target + 1;
8002
8185
  }
@@ -8004,7 +8187,7 @@ async function rollbackMigrations(params) {
8004
8187
  for (const row of journal.slice(start).reverse()) {
8005
8188
  const migration = byName.get(row.name);
8006
8189
  if (migration === undefined)
8007
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
8190
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
8008
8191
  const stored = assertRollbackRecord({ migration, stored: recorded.get(row.name), dialect });
8009
8192
  const downSql = validateRollbackFile({ ...params, migration, dialect });
8010
8193
  plan.push({ migration, downSql, stored });
@@ -8023,21 +8206,21 @@ function validateRollbackFile(params) {
8023
8206
  const { migration, dialect } = params;
8024
8207
  assertModuleAdoption({ migration });
8025
8208
  if (migration.moduleAdoption !== undefined)
8026
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Reversal across verified module adoption is refused; it would execute destructive source down SQL against pre-existing data", meta: { reason: "module-adoption-reverse-refused", migration: migration.name } });
8209
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Reversal across verified module adoption is refused; it would execute destructive source down SQL against pre-existing data", meta: { reason: "module-adoption-reverse-refused", migration: migration.name } });
8027
8210
  if (migration.desiredStatePending === true)
8028
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
8211
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
8029
8212
  if (migration.moduleOrigin !== undefined) {
8030
8213
  assertMaterializedModuleMigration({ migration });
8031
8214
  assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
8032
8215
  }
8033
8216
  const downSql = migration.downSql;
8034
8217
  if (downSql === undefined)
8035
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" has no down.sql; roll it back manually, then repair with resolveMigration (as: "rolled-back")`, meta: { reason: "rollback-down-missing", migration: migration.name } });
8218
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" has no down.sql; roll it back manually, then repair with resolveMigration (as: "rolled-back")`, meta: { reason: "rollback-down-missing", migration: migration.name } });
8036
8219
  assertProbeMode({ dialect, probe: params.probe });
8037
8220
  if (migration.desiredState !== undefined) {
8038
8221
  assertDesiredState({ value: migration.desiredState, migration: migration.name });
8039
8222
  if (migration.desiredState.dialect !== dialect)
8040
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
8223
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
8041
8224
  assertRemovalAuthorization({ steps: diffSchemas({ from: migration.desiredState.to.schema, to: (migration.desiredState.down ?? migration.desiredState.from).schema, dialect }), previous: migration.desiredState.to, dialect, namespace: dialect === "sqlite" ? "main" : "public", ...params.acceptIntegrityLoss === undefined ? {} : { acceptIntegrityLoss: params.acceptIntegrityLoss } });
8042
8225
  }
8043
8226
  assertNativeMigrationMetadata({ migration, sql: downSql, dialect });
@@ -8050,10 +8233,10 @@ function validateRollbackFile(params) {
8050
8233
  function assertRollbackRecord(params) {
8051
8234
  const { migration, stored, dialect } = params;
8052
8235
  if (stored === undefined)
8053
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
8236
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
8054
8237
  assertChecksumMatches({ migration, stored: stored.checksum });
8055
8238
  if (stored.statementIndex >= 0 && (!isCompleteRecord({ dialect, migration, stored }) || stored.error !== undefined))
8056
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is only partially applied or has an unresolved error; its down mirrors the FULL up. Finish or repair with resolveMigration`, meta: { reason: "rollback-partial-up", migration: migration.name, statementIndex: stored.statementIndex } });
8239
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is only partially applied or has an unresolved error; its down mirrors the FULL up. Finish or repair with resolveMigration`, meta: { reason: "rollback-partial-up", migration: migration.name, statementIndex: stored.statementIndex } });
8057
8240
  return stored;
8058
8241
  }
8059
8242
  async function validateRollbackRecovery(params) {
@@ -8062,7 +8245,7 @@ async function validateRollbackRecovery(params) {
8062
8245
  return;
8063
8246
  const start = -stored.statementIndex - 1;
8064
8247
  if (start > downSql.length)
8065
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
8248
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
8066
8249
  if (dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql }))
8067
8250
  assertSelfManagedResumeSafe({ sql: downSql, start, migration: migration.name, direction: "down" });
8068
8251
  if (!marksInflight({ dialect, execution: migration.downExecution }) || start >= downSql.length)
@@ -8128,7 +8311,7 @@ async function runTransactionalDown(params) {
8128
8311
  } else {
8129
8312
  await cleanupSelfManagedFailure({ executor });
8130
8313
  }
8131
- throw new VibeError35({
8314
+ throw new VibeError36({
8132
8315
  code: "VIBE_MIGRATION",
8133
8316
  message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
8134
8317
  meta: { migration: migration.name, ...timeoutDiagnostic({ error }) },
@@ -8211,7 +8394,7 @@ async function runNonTransactionalDown(params) {
8211
8394
  values: [marks ? `${INFLIGHT_PREFIX}${index}` : error instanceof Error ? error.message : String(error), migration.name]
8212
8395
  });
8213
8396
  } catch {}
8214
- throw new VibeError35({
8397
+ throw new VibeError36({
8215
8398
  code: "VIBE_MIGRATION",
8216
8399
  message: `rollback of "${migration.name}" failed at down statement ${index + 1} of ${downSql.length}: ${error instanceof Error ? error.message : String(error)}. ${marks ? "The statement outcome is unknown; inspect its effects before resolving or retrying." : "Progress is recorded; re-running the rollback resumes from this statement."}`,
8217
8400
  meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeoutDiagnostic({ error }) },
@@ -8273,7 +8456,7 @@ async function runTransactional(params) {
8273
8456
  } else {
8274
8457
  await cleanupSelfManagedFailure({ executor });
8275
8458
  }
8276
- throw new VibeError35({
8459
+ throw new VibeError36({
8277
8460
  code: "VIBE_MIGRATION",
8278
8461
  message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
8279
8462
  meta: { migration: migration.name },
@@ -8369,7 +8552,7 @@ async function runNonTransactional(params) {
8369
8552
  } catch {}
8370
8553
  const sqlState = sqlStateOf(error);
8371
8554
  const timeout = sqlState === "55P03" ? "lock-timeout" : sqlState === "57014" ? "statement-timeout" : undefined;
8372
- throw new VibeError35({
8555
+ throw new VibeError36({
8373
8556
  code: "VIBE_MIGRATION",
8374
8557
  message: timeout === "lock-timeout" ? `migration "${migration.name}" gave up waiting for a lock at statement ${index + 1} of ${migration.sql.length} (lock_timeout ${String(execution?.lockTimeoutMs ?? 0)}ms, SQLSTATE 55P03). Another session holds a conflicting lock; inspect the declared operation before retrying.` : timeout === "statement-timeout" ? `migration "${migration.name}" exceeded its statement_timeout at statement ${index + 1} of ${migration.sql.length} (${String(execution?.statementTimeoutMs ?? 0)}ms, SQLSTATE 57014). Inspect the declared operation before retrying.` : `migration "${migration.name}" failed at statement ${index + 1} of ${migration.sql.length}: ${error instanceof Error ? error.message : String(error)}. ${marks ? "The statement outcome is unknown; inspect its effects before resolving or retrying." : "Progress is recorded; re-running applyMigrations resumes from this statement."}`,
8375
8558
  meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeout === undefined ? {} : { reason: timeout, sqlState } },
@@ -8397,7 +8580,7 @@ async function acquireLock(params) {
8397
8580
  });
8398
8581
  const acquired = Number(rows[0]?.vibe_lock) === 1;
8399
8582
  if (!acquired) {
8400
- throw new VibeError35({
8583
+ throw new VibeError36({
8401
8584
  code: "VIBE_MIGRATION",
8402
8585
  message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
8403
8586
  meta: { lock: MYSQL_LOCK_NAME }
@@ -8432,7 +8615,7 @@ function uncertainStatement(params) {
8432
8615
  const subject = direction === "down" ? `rollback of "${migration}"` : `migration "${migration}"`;
8433
8616
  const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${statements.length}`;
8434
8617
  const why = dialect === "mysql" ? "MySQL commits every statement on its own, so nothing outside the statement records whether it finished." : "That statement declares no operation whose effect recovery can read back from the catalog.";
8435
- return new VibeError35({
8618
+ return new VibeError36({
8436
8619
  code: "VIBE_MIGRATION",
8437
8620
  message: `${subject} was interrupted while ${position} was running. ${why} ` + "It may or may not have committed, and replaying it could apply it twice, so VibeORM stops here instead of guessing. " + 'Inspect what that statement did to the database, then follow "Recovering from a stopped migration" in docs/migrations.md: ' + `clear the \`error\` column of this migration's journal row if the statement did NOT land, set \`statement_index\` to ${direction === "down" ? -(index + 2) : index + 1} if it DID, ` + "or finish the whole migration by hand and record that with `vibeorm migrate resolve --applied`. " + `The statement was: ${statements[index].slice(0, 200)}`,
8438
8621
  meta: {
@@ -8450,7 +8633,7 @@ function assertExecutable(params) {
8450
8633
  if (execution === undefined || isDefaultExecution({ execution }))
8451
8634
  return;
8452
8635
  if (dialect !== "postgres") {
8453
- throw new VibeError35({
8636
+ throw new VibeError36({
8454
8637
  code: "VIBE_UNSUPPORTED_CAPABILITY",
8455
8638
  message: `migration "${migration.name}" declares explicit execution semantics (transaction: "${execution.transaction}"), which only the postgres runner implements \u2014 ${dialect} has no separate nontransactional regime to opt into`,
8456
8639
  meta: { reason: "nontransactional-unsupported", dialect, migration: migration.name, feature: "nontransactionalMigrations" }
@@ -8526,7 +8709,7 @@ function assertChecksumMatches(params) {
8526
8709
  const actual = checksumOf({ migration });
8527
8710
  if (stored === actual)
8528
8711
  return;
8529
- throw new VibeError35({
8712
+ throw new VibeError36({
8530
8713
  code: "VIBE_MIGRATION",
8531
8714
  message: `checksum mismatch for migration "${migration.name}" \u2014 its SQL changed after it was applied (recorded ${stored.slice(0, 12)}\u2026, current ${actual.slice(0, 12)}\u2026)`,
8532
8715
  meta: { migration: migration.name, recorded: stored, current: actual }
@@ -8537,7 +8720,7 @@ function assertResumable(params) {
8537
8720
  const actual = checksumOf({ migration });
8538
8721
  if (stored.checksum === actual)
8539
8722
  return;
8540
- throw new VibeError35({
8723
+ throw new VibeError36({
8541
8724
  code: "VIBE_MIGRATION",
8542
8725
  message: `migration "${migration.name}" is partially applied (${stored.statementIndex} statement(s) done) and its SQL has CHANGED since \u2014 refusing to resume; restore the original SQL or repair with resolveMigration`,
8543
8726
  meta: {
@@ -8557,7 +8740,7 @@ function assertDistinctMigrationNames(params) {
8557
8740
  const names = new Set;
8558
8741
  for (const migration of params.migrations) {
8559
8742
  if (names.has(migration.name))
8560
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
8743
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
8561
8744
  names.add(migration.name);
8562
8745
  }
8563
8746
  }
@@ -8565,15 +8748,15 @@ function assertCompleteHistory(params) {
8565
8748
  const byName = new Map(params.migrations.map((migration) => [migration.name, migration]));
8566
8749
  for (const name of params.recorded.keys()) {
8567
8750
  if (!byName.has(name))
8568
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
8751
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
8569
8752
  }
8570
8753
  let index = 0;
8571
8754
  for (const [name, stored] of params.recorded) {
8572
8755
  const migration = params.migrations[index];
8573
8756
  if (migration?.name !== name)
8574
- throw new VibeError35({ code: "VIBE_MIGRATION", message: "Recorded migration order is not a prefix of the supplied complete chain", meta: { reason: "history-order-conflict", migration: name, expected: migration?.name, position: index } });
8757
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Recorded migration order is not a prefix of the supplied complete chain", meta: { reason: "history-order-conflict", migration: name, expected: migration?.name, position: index } });
8575
8758
  if (stored.statementIndex >= 0 && !isCompleteRecord({ dialect: params.dialect, migration, stored }) && index !== params.recorded.size - 1)
8576
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
8759
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
8577
8760
  index++;
8578
8761
  }
8579
8762
  }
@@ -8582,16 +8765,16 @@ async function assertApplyHistory(params) {
8582
8765
  const byName = new Map(migrations.map((migration) => [migration.name, migration]));
8583
8766
  for (const [name, stored] of recorded) {
8584
8767
  if (stored.statementIndex < 0) {
8585
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration "${name}" is mid-rollback; finish or resolve it before applying`, meta: { reason: "history-rolling-back", migration: name, statementIndex: stored.statementIndex } });
8768
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${name}" is mid-rollback; finish or resolve it before applying`, meta: { reason: "history-rolling-back", migration: name, statementIndex: stored.statementIndex } });
8586
8769
  }
8587
8770
  const migration = byName.get(name);
8588
8771
  if (migration === undefined) {
8589
8772
  if (stored.error !== undefined)
8590
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error but no supplied file`, meta: { reason: "history-unknown-dirty", migration: name } });
8773
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error but no supplied file`, meta: { reason: "history-unknown-dirty", migration: name } });
8591
8774
  continue;
8592
8775
  }
8593
8776
  if (isCompleteRecord({ dialect, migration, stored }) && stored.error !== undefined)
8594
- throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
8777
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
8595
8778
  if (isCompleteRecord({ dialect, migration, stored }))
8596
8779
  assertChecksumMatches({ migration, stored: stored.checksum });
8597
8780
  else
@@ -8627,7 +8810,7 @@ function absent(error) {
8627
8810
  return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
8628
8811
  }
8629
8812
  function failure(params) {
8630
- return new VibeError36({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
8813
+ return new VibeError37({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
8631
8814
  }
8632
8815
  async function optionalFile(params) {
8633
8816
  return readFile(params.path, "utf-8").catch((cause) => {
@@ -8641,13 +8824,13 @@ function rlsMetadata(params) {
8641
8824
  const metadata = JSON.parse(params.text);
8642
8825
  const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
8643
8826
  if (metadata?.version !== 1 || typeof metadata.destructive !== "boolean" || metadata.rls === undefined || !strings(metadata.securityChanges) || !strings(metadata.downSecurityChanges) || metadata.sqlChecksum !== migrationChecksum({ sql: params.migration.sql }) || metadata.downChecksum !== migrationChecksum({ sql: params.migration.downSql ?? [] })) {
8644
- throw new VibeError36({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
8827
+ throw new VibeError37({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
8645
8828
  }
8646
8829
  assertValidSchemaIR4({ schema: metadata.rls.from });
8647
8830
  assertValidSchemaIR4({ schema: metadata.rls.to });
8648
8831
  return { ...params.migration, rls: metadata.rls, destructive: metadata.destructive, securityChanges: metadata.securityChanges, downSecurityChanges: metadata.downSecurityChanges };
8649
8832
  }).catch((cause) => {
8650
- throw new VibeError36({ code: "VIBE_MIGRATION", message: `Invalid native RLS metadata for migration "${params.migration.name}"`, meta: { reason: "rls-migration-metadata-invalid", migration: params.migration.name }, cause });
8833
+ throw new VibeError37({ code: "VIBE_MIGRATION", message: `Invalid native RLS metadata for migration "${params.migration.name}"`, meta: { reason: "rls-migration-metadata-invalid", migration: params.migration.name }, cause });
8651
8834
  });
8652
8835
  }
8653
8836
  async function loadMigrationFiles(params) {
@@ -8679,10 +8862,7 @@ async function loadMigrationFiles(params) {
8679
8862
  migration = { ...migration, ...parseExecutionSidecar({ text: execution, migration: name, statements: migration.sql.length, downStatements: migration.downSql?.length ?? 0 }) };
8680
8863
  const desired = await optionalFile({ path: join(directory, DESIRED_STATE_METADATA.filename) });
8681
8864
  if (desired !== undefined) {
8682
- const pending = await Promise.resolve().then(() => isUnfinalizedDesiredState({ text: desired })).catch((cause) => {
8683
- throw new VibeError36({ code: "VIBE_MIGRATION", message: `Malformed desired state for ${name}`, meta: { reason: "desired-state-invalid", migration: name }, cause });
8684
- });
8685
- migration = pending ? { ...migration, desiredStatePending: true } : { ...migration, desiredState: parseDesiredStateSidecar({ text: desired, migration: name }) };
8865
+ migration = { ...migration, ...loadDesiredStateSidecar({ text: desired, migration: name }) };
8686
8866
  }
8687
8867
  const origin = await optionalFile({ path: join(directory, MODULE_ORIGIN_METADATA.filename) });
8688
8868
  if (origin !== undefined)
@@ -8701,4 +8881,4 @@ export {
8701
8881
  splitSqlStatements
8702
8882
  };
8703
8883
 
8704
- //# debugId=91A1E2329350564C64756E2164756E21
8884
+ //# debugId=73DFB0227E02518164756E2164756E21