@vibeorm/migrate 2.5.0 → 2.7.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 (82) hide show
  1. package/README.md +2 -0
  2. package/dist/compare-sqlite-primary-key.d.ts +1 -1
  3. package/dist/compare-sqlite-primary-key.d.ts.map +1 -1
  4. package/dist/ddl/mysql.d.ts.map +1 -1
  5. package/dist/ddl/postgres.d.ts +1 -1
  6. package/dist/ddl/rls.d.ts.map +1 -1
  7. package/dist/ddl/sqlite.d.ts.map +1 -1
  8. package/dist/differ.d.ts.map +1 -1
  9. package/dist/down.d.ts.map +1 -1
  10. package/dist/enum-values.d.ts +1 -0
  11. package/dist/enum-values.d.ts.map +1 -1
  12. package/dist/exclusions.d.ts +1 -0
  13. package/dist/exclusions.d.ts.map +1 -1
  14. package/dist/external-dependencies.d.ts +37 -0
  15. package/dist/external-dependencies.d.ts.map +1 -0
  16. package/dist/external-objects.d.ts +122 -0
  17. package/dist/external-objects.d.ts.map +1 -0
  18. package/dist/fs.d.ts.map +1 -1
  19. package/dist/fs.js +1303 -662
  20. package/dist/fs.js.map +34 -30
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3791 -2738
  24. package/dist/index.js.map +55 -49
  25. package/dist/inspect-objects.d.ts +5 -0
  26. package/dist/inspect-objects.d.ts.map +1 -1
  27. package/dist/introspect/postgres-rls.d.ts.map +1 -1
  28. package/dist/introspect/postgres.d.ts +4 -2
  29. package/dist/introspect/postgres.d.ts.map +1 -1
  30. package/dist/introspect/shared.d.ts +2 -1
  31. package/dist/introspect/shared.d.ts.map +1 -1
  32. package/dist/inventory.d.ts.map +1 -1
  33. package/dist/migration-checksum.d.ts +2 -0
  34. package/dist/migration-checksum.d.ts.map +1 -1
  35. package/dist/module-adoption-record.d.ts +22 -0
  36. package/dist/module-adoption-record.d.ts.map +1 -0
  37. package/dist/module-adoption-reference.d.ts +27 -0
  38. package/dist/module-adoption-reference.d.ts.map +1 -0
  39. package/dist/module-adoption.d.ts +26 -0
  40. package/dist/module-adoption.d.ts.map +1 -0
  41. package/dist/module-bundle.d.ts.map +1 -1
  42. package/dist/module-export.d.ts +6 -0
  43. package/dist/module-export.d.ts.map +1 -1
  44. package/dist/module-history.d.ts.map +1 -1
  45. package/dist/module-origin.d.ts +1 -1
  46. package/dist/module-origin.d.ts.map +1 -1
  47. package/dist/module-ownership.d.ts.map +1 -1
  48. package/dist/module-pending.d.ts +6 -0
  49. package/dist/module-pending.d.ts.map +1 -1
  50. package/dist/module-readiness.d.ts +5 -0
  51. package/dist/module-readiness.d.ts.map +1 -1
  52. package/dist/normalize.d.ts +34 -3
  53. package/dist/normalize.d.ts.map +1 -1
  54. package/dist/object-ownership.d.ts.map +1 -1
  55. package/dist/operator-classes.d.ts +9 -0
  56. package/dist/operator-classes.d.ts.map +1 -0
  57. package/dist/planner.d.ts +5 -0
  58. package/dist/planner.d.ts.map +1 -1
  59. package/dist/postgres-types.d.ts +24 -0
  60. package/dist/postgres-types.d.ts.map +1 -1
  61. package/dist/push.d.ts +3 -0
  62. package/dist/push.d.ts.map +1 -1
  63. package/dist/reference-state.d.ts.map +1 -1
  64. package/dist/relations.d.ts +1 -0
  65. package/dist/relations.d.ts.map +1 -1
  66. package/dist/renames.d.ts.map +1 -1
  67. package/dist/replay-plan.d.ts.map +1 -1
  68. package/dist/replay-reference.d.ts +10 -0
  69. package/dist/replay-reference.d.ts.map +1 -1
  70. package/dist/runner.d.ts +3 -0
  71. package/dist/runner.d.ts.map +1 -1
  72. package/dist/shadow-dedicated.d.ts.map +1 -1
  73. package/dist/shadow-guard.d.ts +4 -0
  74. package/dist/shadow-guard.d.ts.map +1 -1
  75. package/dist/shadow-types.d.ts +10 -0
  76. package/dist/shadow-types.d.ts.map +1 -1
  77. package/dist/shadow.d.ts.map +1 -1
  78. package/dist/types.d.ts +22 -1
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/verify-history.d.ts +4 -0
  81. package/dist/verify-history.d.ts.map +1 -1
  82. package/package.json +3 -3
package/dist/fs.js CHANGED
@@ -15,8 +15,8 @@ function hasMysqlGlobalCatalogVisibility(params) {
15
15
  }
16
16
 
17
17
  // packages/migrate/src/module-ownership.ts
18
- import { VibeError as VibeError17, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
19
- import { getSqlDialect as getSqlDialect3, renderModuleDependencyQuery } from "@vibeorm/sql";
18
+ import { VibeError as VibeError18, sqlObjectKey as sqlObjectKey7, sqlObjectNamespace as sqlObjectNamespace3 } from "@vibeorm/schema";
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";
@@ -115,14 +115,11 @@ function parseMigrationMetadata(params) {
115
115
  }
116
116
  }
117
117
 
118
- // packages/migrate/src/object-ownership.ts
119
- import { VibeError as VibeError6, defaultPrimaryKeyName, enumStorageOf as enumStorageOf2, implicitJoinTableObjectNames, postgresGeneratedObjectName, sqlObjectKey as sqlObjectKey2 } from "@vibeorm/schema";
120
-
121
118
  // packages/migrate/src/normalize.ts
122
- import { VibeError as VibeError5, defaultForeignKeyName, defaultIndexName, enumStorageOf, isEnumType as isEnumType5, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
119
+ import { assertIndexOperatorClasses, VibeError as VibeError5, defaultForeignKeyName, defaultIndexName, enumStorageOf, isEnumType as isEnumType5, qualifiedSqlName as qualifiedSqlName3, assertSqlNamespace, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
123
120
 
124
121
  // packages/migrate/src/relations.ts
125
- import { implicitJoinTable, isImplicitManyToManyRelation } from "@vibeorm/schema";
122
+ import { implicitJoinTable, isImplicitManyToManyRelation, qualifiedSqlName, unsupportedCapability, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
126
123
  function detectImplicitM2MPairs(params) {
127
124
  const { schema } = params;
128
125
  const pairs = [];
@@ -138,14 +135,20 @@ function detectImplicitM2MPairs(params) {
138
135
  modelB: relation.targetModel,
139
136
  ...relation.relationName === undefined ? {} : { relationName: relation.relationName }
140
137
  });
141
- if (seen.has(joinTable.tableName))
138
+ const target = schema.models.find((entry) => entry.name === relation.targetModel);
139
+ const namespace = qualifiedSqlName({ name: model.name, namespace: model.namespace }).namespace;
140
+ if (namespace !== qualifiedSqlName({ name: target.name, namespace: target.namespace }).namespace)
141
+ throw unsupportedCapability({ dialect: providerToDialect2({ provider: schema.datasource.provider }), feature: "implicitJoinNamespaces", hint: "Implicit join parents must share one physical namespace" });
142
+ const key = JSON.stringify([namespace, joinTable.tableName]);
143
+ if (seen.has(key))
142
144
  continue;
143
- seen.add(joinTable.tableName);
145
+ seen.add(key);
144
146
  pairs.push({
145
147
  modelA: joinTable.modelA,
146
148
  modelB: joinTable.modelB,
147
149
  ...relation.relationName === undefined ? {} : { relationName: relation.relationName },
148
- tableName: joinTable.tableName
150
+ tableName: joinTable.tableName,
151
+ ...model.namespace === undefined && target.namespace === undefined ? {} : { namespace }
149
152
  });
150
153
  }
151
154
  }
@@ -153,7 +156,7 @@ function detectImplicitM2MPairs(params) {
153
156
  }
154
157
 
155
158
  // packages/migrate/src/postgres-types.ts
156
- import { VibeError as VibeError2, isEnumType, scalarListDefault, serializeJsonDefault } from "@vibeorm/schema";
159
+ import { VibeError as VibeError2, isEnumType, scalarListDefault, serializeJsonDefault, qualifiedSqlName as qualifiedSqlName2 } from "@vibeorm/schema";
157
160
  import { POSTGRES_CODECS, postgresDialect, toPgArrayLiteral, parsePostgresTypeName, renderPostgresTypeName } from "@vibeorm/sql";
158
161
  var SCALAR_TO_PG = {
159
162
  String: "text",
@@ -169,6 +172,9 @@ var SCALAR_TO_PG = {
169
172
  var CANONICAL_EQUIVALENTS = {
170
173
  DateTime: ["timestamptz(3)", "timestamptz(6)"]
171
174
  };
175
+ var EXACT_CANONICAL_EQUIVALENTS = {
176
+ DateTime: ["timestamptz(6)"]
177
+ };
172
178
  var CANONICAL_UDT = {
173
179
  text: "String",
174
180
  bool: "Boolean",
@@ -237,13 +243,15 @@ var NATIVE_ALIASES = {
237
243
  var IMPLICIT_MODIFIERS = {
238
244
  bit: "(1)"
239
245
  };
246
+ var DEFAULT_PRECISION_SIX = new Set(["timestamp", "time", "timetz"]);
240
247
  function normalizeNativeType(params) {
241
248
  const compact = params.raw.toLowerCase().replace(/\s+/g, "");
242
249
  const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(\[\])?$/);
243
250
  if (match === null)
244
251
  return compact;
245
252
  const base = NATIVE_ALIASES[match[1]] ?? match[1];
246
- return `${base}${match[2] ?? IMPLICIT_MODIFIERS[base] ?? ""}${match[3] ?? ""}`;
253
+ const modifier = DEFAULT_PRECISION_SIX.has(base) && match[2] === "(6)" ? "" : match[2] ?? IMPLICIT_MODIFIERS[base] ?? "";
254
+ return `${base}${modifier}${match[3] ?? ""}`;
247
255
  }
248
256
  function parseUnsupportedPostgresType(params) {
249
257
  const temporal = /^\s*(time(?:stamp)?)\s*(\(\s*\d+\s*\))?\s+(with|without)\s+time\s+zone\s*((?:\[\s*\]\s*)*)$/i.exec(params.raw);
@@ -256,7 +264,7 @@ function parseUnsupportedPostgresType(params) {
256
264
  return NATIVE_ALIASES[base] === undefined ? undefined : parsePostgresTypeName({ raw: normalizeNativeType(params) });
257
265
  }
258
266
  function columnTypeKey(params) {
259
- const { field } = params;
267
+ const { field, exact } = params;
260
268
  const listSuffix = field.isList ? "[]" : "";
261
269
  if (field.isUnsupported === true) {
262
270
  const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
@@ -267,22 +275,48 @@ function columnTypeKey(params) {
267
275
  const scalar = namespace === undefined || namespace === "pg_catalog" ? scalarForBuiltinType({ name: parsed.name }) : undefined;
268
276
  if (scalar !== undefined) {
269
277
  const { isUnsupported: _unsupported, nativeTypeIdentity: _identity, ...rest } = field;
270
- return columnTypeKey({ field: { ...rest, type: scalar, nativeType: `${parsed.name}${parsed.modifier ?? ""}`, isList: parsed.array || field.isList } });
278
+ return columnTypeKey({ field: { ...rest, type: scalar, nativeType: `${parsed.name}${parsed.modifier ?? ""}`, isList: parsed.array || field.isList }, ...exact === undefined ? {} : { exact } });
271
279
  }
272
280
  return `u:${JSON.stringify([namespace ?? null, parsed.name, parsed.modifier, parsed.array || field.isList])}`;
273
281
  }
274
282
  if (isEnumType(field.type)) {
275
- return `e:${field.type.enum}${listSuffix}`;
283
+ const identity = qualifiedSqlName2({ name: field.type.enum, namespace: field.type.namespace });
284
+ return `e:${JSON.stringify([identity.namespace, identity.name])}${listSuffix}`;
276
285
  }
277
286
  if (field.nativeType !== undefined) {
278
287
  const normalized = normalizeNativeType({ raw: field.nativeType });
279
288
  const canonical = normalizeNativeType({ raw: SCALAR_TO_PG[field.type] });
280
- const isCanonical = normalized === canonical || (CANONICAL_EQUIVALENTS[field.type]?.includes(normalized) ?? false);
289
+ const equivalents = exact === true ? EXACT_CANONICAL_EQUIVALENTS : CANONICAL_EQUIVALENTS;
290
+ const isCanonical = normalized === canonical || (equivalents[field.type]?.includes(normalized) ?? false);
281
291
  if (!isCanonical)
282
292
  return `n:${normalized}${listSuffix}`;
283
293
  }
284
294
  return `s:${field.type}${listSuffix}`;
285
295
  }
296
+ function declaresExactColumnType(params) {
297
+ const { field } = params;
298
+ if (field.isUnsupported === true || field.nativeType === undefined || isEnumType(field.type))
299
+ return false;
300
+ return CANONICAL_EQUIVALENTS[field.type]?.includes(normalizeNativeType({ raw: field.nativeType })) ?? false;
301
+ }
302
+ function timestamptzPrecision(params) {
303
+ const { field } = params;
304
+ if (field.type !== "DateTime" || field.isUnsupported === true)
305
+ return;
306
+ const normalized = field.nativeType === undefined ? "timestamptz" : normalizeNativeType({ raw: field.nativeType });
307
+ if (normalized === "timestamptz")
308
+ return 6;
309
+ const match = /^timestamptz\((\d)\)$/.exec(normalized);
310
+ return match === null ? undefined : Number(match[1]);
311
+ }
312
+ function isTimestamptzPrecisionWidening(params) {
313
+ const { from, to } = params;
314
+ if (from.isList !== to.isList)
315
+ return false;
316
+ const before = timestamptzPrecision({ field: from });
317
+ const after = timestamptzPrecision({ field: to });
318
+ return before !== undefined && after !== undefined && after >= before;
319
+ }
286
320
  function isScalarToArrayChange(params) {
287
321
  const { from, to } = params;
288
322
  if (from.isList || !to.isList)
@@ -309,7 +343,7 @@ function columnTypeSql(params) {
309
343
  }
310
344
  const listSuffix = field.isList ? "[]" : "";
311
345
  if (isEnumType(field.type)) {
312
- return `${postgresDialect.quoteIdent({ name: "public" })}.${postgresDialect.quoteIdent({ name: field.type.enum })}${listSuffix}`;
346
+ return `${postgresDialect.quoteIdent({ name: field.type.namespace ?? "public" })}.${postgresDialect.quoteIdent({ name: field.type.enum })}${listSuffix}`;
313
347
  }
314
348
  if (field.nativeType !== undefined) {
315
349
  const normalized = normalizeNativeType({ raw: field.nativeType });
@@ -426,18 +460,18 @@ function quotePgString(params) {
426
460
  }
427
461
 
428
462
  // packages/migrate/src/sqlite-types.ts
429
- import { VibeError as VibeError3, isEnumType as isEnumType3, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
463
+ import { VibeError as VibeError3, isEnumType as isEnumType3, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
430
464
  import { ParamCollector, sqliteDialect } from "@vibeorm/sql";
431
465
 
432
466
  // packages/migrate/src/enum-values.ts
433
- import { isEnumType as isEnumType2, unsupportedCapability } from "@vibeorm/schema";
467
+ import { isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
434
468
  function resolveEnumValues(params) {
435
469
  const { name, enums } = params;
436
470
  const direct = enums.get(name);
437
471
  if (direct !== undefined)
438
472
  return direct.values;
439
473
  for (const enumDef of enums.values()) {
440
- if (enumDef.name === name)
474
+ if ((enumDef.name === name || enumDef.dbName === name) && (params.namespace === undefined || (enumDef.namespace ?? "public") === params.namespace))
441
475
  return enumDef.values;
442
476
  }
443
477
  return;
@@ -452,7 +486,7 @@ function assertEnumStringTransition(params) {
452
486
  return;
453
487
  const reason = from.isList !== to.isList ? "change scalar/list shape separately from enum storage" : from.default?.kind === "dbgenerated" || to.default?.kind === "dbgenerated" ? "database-expression defaults may depend on the native enum type \u2014 migrate the default explicitly first" : dialect === "mysql" && isEnumType2(to.type) ? "non-strict mysql can silently replace out-of-list strings with '' \u2014 validate and migrate this column manually in strict mode" : undefined;
454
488
  if (reason !== undefined) {
455
- throw unsupportedCapability({ dialect, feature: `enum storage conversion on "${table}"."${column}"`, hint: reason });
489
+ throw unsupportedCapability2({ dialect, feature: `enum storage conversion on "${table}"."${column}"`, hint: reason });
456
490
  }
457
491
  }
458
492
 
@@ -469,7 +503,7 @@ var SCALAR_TO_SQLITE = {
469
503
  Bytes: "BLOB"
470
504
  };
471
505
  function refuseSqliteScalarList(params) {
472
- throw unsupportedCapability2({
506
+ throw unsupportedCapability3({
473
507
  dialect: "sqlite",
474
508
  feature: `scalar list column "${params.field.name}"`,
475
509
  hint: "sqlite has no array column type \u2014 model the list as a separate table or a Json column"
@@ -605,7 +639,7 @@ function parseSqliteEnumChecks(params) {
605
639
  }
606
640
 
607
641
  // packages/migrate/src/mysql-types.ts
608
- import { VibeError as VibeError4, isEnumType as isEnumType4, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
642
+ import { VibeError as VibeError4, isEnumType as isEnumType4, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
609
643
  import { mysqlDialect } from "@vibeorm/sql";
610
644
  var SCALAR_TO_MYSQL = {
611
645
  String: `varchar(${mysqlDialect.conflictKeyEquality.defaultStringMaxLength})`,
@@ -619,7 +653,7 @@ var SCALAR_TO_MYSQL = {
619
653
  Bytes: "longblob"
620
654
  };
621
655
  function refuseMysqlScalarList(params) {
622
- throw unsupportedCapability3({
656
+ throw unsupportedCapability4({
623
657
  dialect: "mysql",
624
658
  feature: `scalar list column "${params.field.name}"`,
625
659
  hint: "mysql has no array column type \u2014 model the list as a separate table or a JSON column"
@@ -691,7 +725,7 @@ function mysqlColumnTypeSql(params) {
691
725
  });
692
726
  }
693
727
  if (values.some((value) => /[\\\x00-\x1f\x7f]/.test(value)))
694
- throw unsupportedCapability3({ dialect: "mysql", feature: "mysqlEnumLiteralEscaping", hint: "ENUM labels containing backslashes or control characters require a reviewed SQL-mode-specific migration" });
728
+ throw unsupportedCapability4({ dialect: "mysql", feature: "mysqlEnumLiteralEscaping", hint: "ENUM labels containing backslashes or control characters require a reviewed SQL-mode-specific migration" });
695
729
  return `ENUM(${values.map((value) => quotePgString({ value })).join(", ")})`;
696
730
  }
697
731
  if (field.nativeType !== undefined) {
@@ -750,25 +784,31 @@ function parseMysqlEnumColumnType(params) {
750
784
  function normalizeSchema(params) {
751
785
  const { schema } = params;
752
786
  const dialect = params.dialect ?? "postgres";
787
+ assertIndexOperatorClasses({ schema, dialect, physical: true });
753
788
  const enums = new Map;
754
789
  for (const enumDef of schema.enums) {
790
+ if (enumDef.namespace !== undefined)
791
+ assertSqlNamespace({ namespace: enumDef.namespace, dialect });
755
792
  if (enumStorageOf({ enumDef }) === "string")
756
793
  continue;
757
- enums.set(enumDef.dbName ?? enumDef.name, enumDef);
794
+ enums.set(physicalNameKey({ name: enumDef.dbName ?? enumDef.name, namespace: enumDef.namespace, dialect }), enumDef);
758
795
  }
759
796
  const tables = new Map;
760
797
  const views = new Map;
761
798
  for (const model of schema.models) {
799
+ if (model.namespace !== undefined)
800
+ assertSqlNamespace({ namespace: model.namespace, dialect });
762
801
  if (model.isView) {
763
- views.set(model.dbName ?? model.name, model);
802
+ views.set(physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect }), model);
764
803
  continue;
765
804
  }
766
805
  const table = normalizeModel({ model, schema, dialect, enums });
767
- tables.set(table.table, table);
806
+ tables.set(physicalNameKey({ name: table.table, namespace: table.namespace, dialect }), table);
768
807
  }
769
808
  const joinTables = new Map;
770
809
  for (const pair of detectImplicitM2MPairs({ schema })) {
771
- joinTables.set(pair.tableName, normalizeJoinTable({ pair, schema, dialect }));
810
+ const join = normalizeJoinTable({ pair, schema, dialect });
811
+ joinTables.set(physicalNameKey({ name: join.table, namespace: join.namespace, dialect }), join);
772
812
  }
773
813
  return { tables, joinTables, enums, views };
774
814
  }
@@ -776,12 +816,15 @@ function physicalEnumField(params) {
776
816
  const { field, schema, dialect, table } = params;
777
817
  if (!isEnumType5(field.type))
778
818
  return field;
779
- const name = field.type.enum;
780
- const enumDef = schema.enums.find((candidate) => candidate.name === name || candidate.dbName === name);
819
+ const type = field.type;
820
+ if (type.namespace !== undefined)
821
+ assertSqlNamespace({ namespace: type.namespace, dialect });
822
+ const name = type.enum;
823
+ const enumDef = schema.enums.find((candidate) => (candidate.name === name || candidate.dbName === name) && (type.namespace === undefined || qualifiedSqlName3({ name, namespace: candidate.namespace }).namespace === type.namespace));
781
824
  if (enumDef === undefined)
782
825
  return field;
783
826
  if (enumStorageOf({ enumDef }) !== "string") {
784
- return dialect === "postgres" ? { ...field, type: { enum: enumDef.dbName ?? enumDef.name } } : field;
827
+ return dialect === "postgres" ? { ...field, type: { enum: enumDef.dbName ?? enumDef.name, ...enumDef.namespace === undefined ? {} : { namespace: enumDef.namespace } } } : field;
785
828
  }
786
829
  const physical = {
787
830
  ...field,
@@ -808,7 +851,7 @@ function assertStringEnumLabelsFit(params) {
808
851
  const charLimit = varchar?.[1] === undefined ? undefined : Number(varchar[1]);
809
852
  const tooLong = values.some((value) => charLimit !== undefined && [...value].length > charLimit || textBytes !== undefined && new TextEncoder().encode(value).length > textBytes);
810
853
  if (!supported || tooLong) {
811
- throw unsupportedCapability4({
854
+ throw unsupportedCapability5({
812
855
  dialect,
813
856
  feature: `string enum column "${table}"."${field.dbName ?? field.name}" stored as ${type}`,
814
857
  hint: supported ? "an enum label exceeds the physical column limit \u2014 choose a wider varchar or text type" : "use text or varchar storage; other types cannot preserve arbitrary enum labels verbatim"
@@ -816,16 +859,20 @@ function assertStringEnumLabelsFit(params) {
816
859
  }
817
860
  }
818
861
  function columnTypeKeyFor(params) {
819
- const { field, dialect, enums } = params;
862
+ const { field, dialect, enums, exact } = params;
820
863
  switch (dialect) {
821
864
  case "postgres":
822
- return columnTypeKey({ field });
865
+ return columnTypeKey({ field, ...exact === undefined ? {} : { exact } });
823
866
  case "sqlite":
824
867
  return sqliteColumnTypeKey({ field, enums });
825
868
  case "mysql":
826
869
  return mysqlColumnTypeKey({ field, enums });
827
870
  }
828
871
  }
872
+ function columnTypeChanged(params) {
873
+ const { declared, actual } = params;
874
+ return declared.pinsExactType ? declared.exactTypeKey !== actual.exactTypeKey : declared.typeKey !== actual.typeKey;
875
+ }
829
876
  function defaultFingerprintFor(params) {
830
877
  const { field, dialect } = params;
831
878
  switch (dialect) {
@@ -848,6 +895,8 @@ function normalizeModel(params) {
848
895
  name,
849
896
  field,
850
897
  typeKey: columnTypeKeyFor({ field, dialect, enums }),
898
+ exactTypeKey: columnTypeKeyFor({ field, dialect, enums, exact: true }),
899
+ pinsExactType: dialect === "postgres" && declaresExactColumnType({ field }),
851
900
  nullable: field.isOptional,
852
901
  defaultKey: defaultFingerprintFor({ field, dialect })
853
902
  });
@@ -858,7 +907,7 @@ function normalizeModel(params) {
858
907
  const checks = new Map;
859
908
  for (const check of model.checks ?? [])
860
909
  checks.set(check.name, check.expression);
861
- return { model, table, columns, pk, indexes, fks, checks };
910
+ return { model, table, ...model.namespace === undefined ? {} : { namespace: model.namespace }, columns, pk, indexes, fks, checks };
862
911
  }
863
912
  function normalizePk(params) {
864
913
  const { model } = params;
@@ -879,7 +928,8 @@ function normalizeIndexes(params) {
879
928
  return {
880
929
  name: fieldDbName({ model, name: field.name }),
881
930
  desc: field.sort === "desc",
882
- ...decimal ? { decimal: true } : {}
931
+ ...decimal ? { decimal: true } : {},
932
+ ...field.operatorClass === undefined ? {} : { operatorClass: field.operatorClass }
883
933
  };
884
934
  });
885
935
  const where = index.where;
@@ -910,7 +960,7 @@ function normalizeIndexes(params) {
910
960
  }
911
961
  function indexComparisonKey(params) {
912
962
  const { unique, kind, columns, where, nullsNotDistinct } = params;
913
- const cols = columns.map((column) => `${column.name}${column.desc ? " desc" : ""}`).join(",");
963
+ const cols = columns.map((column) => `${column.name}${column.desc ? " desc" : ""}${column.operatorClass === undefined ? "" : ` ops:${JSON.stringify([column.operatorClass.namespace, column.operatorClass.name])}`}`).join(",");
914
964
  return `${unique ? "u" : "i"}${nullsNotDistinct === true ? "n" : ""}:${kind}:${cols}:${normalizeWhere({ where })}`;
915
965
  }
916
966
  function normalizeWhere(params) {
@@ -1088,7 +1138,7 @@ function normalizeFks(params) {
1088
1138
  const field = model.fields.find((candidate) => candidate.name === name);
1089
1139
  return field === undefined ? true : !field.isOptional;
1090
1140
  });
1091
- const key = `${columns.join(",")}\u2192${refTable}.${refColumns.join(",")}`;
1141
+ const key = `${columns.join(",")}\u2192${physicalNameKey({ name: refTable, namespace: target?.namespace, dialect: params.dialect })}.${refColumns.join(",")}`;
1092
1142
  if (result.has(key))
1093
1143
  continue;
1094
1144
  result.set(key, {
@@ -1096,6 +1146,7 @@ function normalizeFks(params) {
1096
1146
  constraintName: relation.constraintName ?? defaultForeignKeyName({ table, columns, dialect: params.dialect }),
1097
1147
  columns,
1098
1148
  refTable,
1149
+ ...target?.namespace === undefined ? {} : { refNamespace: target.namespace },
1099
1150
  refColumns,
1100
1151
  onDelete: resolveOnDelete({ explicit: relation.onDelete, required }),
1101
1152
  onUpdate: resolveOnUpdate({ explicit: relation.onUpdate })
@@ -1107,6 +1158,7 @@ function normalizeJoinTable(params) {
1107
1158
  const { pair, schema, dialect } = params;
1108
1159
  return {
1109
1160
  table: pair.tableName,
1161
+ ...pair.namespace === undefined ? {} : { namespace: pair.namespace },
1110
1162
  a: joinTableEnd({ modelName: pair.modelA, schema, dialect, joinTable: pair.tableName }),
1111
1163
  b: joinTableEnd({ modelName: pair.modelB, schema, dialect, joinTable: pair.tableName })
1112
1164
  };
@@ -1132,10 +1184,24 @@ function joinTableEnd(params) {
1132
1184
  }
1133
1185
  return {
1134
1186
  table: model.dbName ?? model.name,
1187
+ ...model.namespace === undefined ? {} : { namespace: model.namespace },
1135
1188
  column: pkField.dbName ?? pkField.name,
1136
1189
  field: physicalEnumField({ field: pkField, schema, dialect: params.dialect, table: model.dbName ?? model.name })
1137
1190
  };
1138
1191
  }
1192
+ function physicalNameKey(params) {
1193
+ if (params.dialect !== undefined && params.dialect !== "postgres")
1194
+ return params.name;
1195
+ const identity = qualifiedSqlName3(params);
1196
+ return JSON.stringify([identity.namespace, identity.name]);
1197
+ }
1198
+ function schemaNamespaces(params) {
1199
+ return [...new Set([
1200
+ ...params.schema.models.flatMap((model) => model.namespace === undefined || model.namespace === "public" ? [] : [model.namespace]),
1201
+ ...params.schema.enums.flatMap((entry) => enumStorageOf({ enumDef: entry }) === "string" || entry.namespace === undefined || entry.namespace === "public" ? [] : [entry.namespace]),
1202
+ ...(params.schema.migrationObjects ?? []).flatMap((object) => object.identity.kind === "namespace" ? [object.identity.name] : [])
1203
+ ])];
1204
+ }
1139
1205
  function fieldDbName(params) {
1140
1206
  const { model, name } = params;
1141
1207
  const field = model.fields.find((candidate) => candidate.name === name);
@@ -1143,6 +1209,7 @@ function fieldDbName(params) {
1143
1209
  }
1144
1210
 
1145
1211
  // packages/migrate/src/object-ownership.ts
1212
+ import { VibeError as VibeError6, defaultPrimaryKeyName, enumStorageOf as enumStorageOf2, implicitJoinTableObjectNames, postgresGeneratedObjectName, qualifiedSqlName as qualifiedSqlName4, sqlObjectKey as sqlObjectKey2 } from "@vibeorm/schema";
1146
1213
  function legacyMigrationObjects(params) {
1147
1214
  return params.artifacts.map((artifact) => {
1148
1215
  const owner = artifact.owns;
@@ -1166,13 +1233,14 @@ function legacyMigrationObjects(params) {
1166
1233
  }
1167
1234
  function preserveMigrationObjects(params) {
1168
1235
  const namespace = params.namespace ?? (params.to.datasource.provider === "sqlite" ? "main" : "public");
1169
- const objects = params.objects.filter((object) => ("table" in object.identity ? object.identity.table.namespace : object.identity.name.namespace) === namespace);
1236
+ const objects = params.objects;
1170
1237
  const models = params.from.models.flatMap((model) => {
1171
1238
  const table = model.dbName ?? model.name;
1172
- const target = params.to.models.find((entry) => (entry.dbName ?? entry.name) === table);
1173
- if (objects.some((object) => (object.identity.kind === "table" || object.identity.kind === "view") && object.identity.name.name === table))
1239
+ const placement = qualifiedSqlName4({ name: table, namespace: model.namespace, defaultNamespace: namespace });
1240
+ const target = params.to.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect: params.to.datasource.provider === "sqlite" ? "sqlite" : params.to.datasource.provider === "mysql" ? "mysql" : "postgres" }) === physicalNameKey({ ...placement, dialect: params.to.datasource.provider === "sqlite" ? "sqlite" : params.to.datasource.provider === "mysql" ? "mysql" : "postgres" }));
1241
+ if (objects.some((object) => (object.identity.kind === "table" || object.identity.kind === "view") && object.identity.name.name === table && object.identity.name.namespace === placement.namespace))
1174
1242
  return [];
1175
- const owners = objects.map((object) => object.identity).filter((identity) => ("table" in identity) && identity.table.name === table);
1243
+ const owners = objects.map((object) => object.identity).filter((identity) => ("table" in identity) && identity.table.name === table && identity.table.namespace === placement.namespace);
1176
1244
  return [{
1177
1245
  ...model,
1178
1246
  fields: model.fields.flatMap((field) => {
@@ -1194,7 +1262,7 @@ function preserveMigrationObjects(params) {
1194
1262
  relations: model.relations.filter((relation) => !owners.some((owner) => owner.kind === "constraint" && owner.constraintType === "foreignKey" && owner.name === relation.constraintName))
1195
1263
  }];
1196
1264
  });
1197
- const enums = params.from.enums.filter((enumDef) => enumStorageOf2({ enumDef }) === "string" || !objects.some((object) => object.identity.kind === "enum" && object.identity.name.name === (enumDef.dbName ?? enumDef.name)));
1265
+ const enums = params.from.enums.filter((enumDef) => enumStorageOf2({ enumDef }) === "string" || !objects.some((object) => object.identity.kind === "enum" && object.identity.name.name === (enumDef.dbName ?? enumDef.name) && object.identity.name.namespace === (enumDef.namespace ?? namespace)));
1198
1266
  return { ...params.from, models, enums };
1199
1267
  }
1200
1268
  function sqlitePreservedSchema(params) {
@@ -1213,7 +1281,8 @@ function sqlitePreservedSchema(params) {
1213
1281
  }) };
1214
1282
  }
1215
1283
  function removalIdentity(params) {
1216
- const { step, namespace } = params;
1284
+ const { step } = params;
1285
+ const namespace = step.namespace ?? params.namespace;
1217
1286
  const table = "table" in step && step.table !== undefined ? { namespace, name: step.table } : undefined;
1218
1287
  switch (step.kind) {
1219
1288
  case "dropEnum":
@@ -1244,18 +1313,22 @@ function removalIdentity(params) {
1244
1313
  function schemaObjectIdentities(params) {
1245
1314
  const normalized = normalizeSchema({ schema: params.schema, dialect: params.dialect });
1246
1315
  const result = [];
1247
- if (params.dialect === "postgres")
1248
- for (const name of normalized.enums.keys())
1249
- result.push({ kind: "enum", name: { namespace: params.namespace, name } });
1250
- for (const [name, model] of normalized.tables) {
1251
- const table = { namespace: params.namespace, name };
1316
+ if (params.dialect === "postgres") {
1317
+ for (const namespace of schemaNamespaces({ schema: params.schema }))
1318
+ result.push({ kind: "namespace", name: namespace });
1319
+ for (const enumDef of normalized.enums.values())
1320
+ result.push({ kind: "enum", name: qualifiedSqlName4({ name: enumDef.dbName ?? enumDef.name, namespace: enumDef.namespace, defaultNamespace: params.namespace }) });
1321
+ }
1322
+ for (const model of normalized.tables.values()) {
1323
+ const name = model.table;
1324
+ const table = qualifiedSqlName4({ name, namespace: model.namespace, defaultNamespace: params.namespace });
1252
1325
  result.push({ kind: "table", name: table });
1253
1326
  for (const [column, definition] of model.columns) {
1254
1327
  result.push({ kind: "column", table, column });
1255
1328
  if (definition.defaultKey !== null)
1256
1329
  result.push({ kind: "columnDefault", table, column });
1257
1330
  if (params.dialect === "postgres" && definition.field.default?.kind === "autoincrement")
1258
- result.push({ kind: "sequence", name: { namespace: params.namespace, name: postgresGeneratedObjectName({ name1: name, name2: column, label: "seq" }) } });
1331
+ result.push({ kind: "sequence", name: { namespace: table.namespace, name: postgresGeneratedObjectName({ name1: name, name2: column, label: "seq" }) } });
1259
1332
  }
1260
1333
  for (const exclusion of model.model.exclusions ?? [])
1261
1334
  result.push({ kind: "constraint", constraintType: "exclusion", table, name: exclusion.name });
@@ -1271,16 +1344,17 @@ function schemaObjectIdentities(params) {
1271
1344
  if (model.pk.length > 0)
1272
1345
  result.push({ kind: "constraint", constraintType: "primaryKey", table, name: model.model.primaryKeyConstraintName ?? (params.dialect === "mysql" ? "PRIMARY" : defaultPrimaryKeyName({ table: name, dialect: params.dialect })) });
1273
1346
  }
1274
- for (const name of normalized.joinTables.keys()) {
1275
- const table = { namespace: params.namespace, name };
1347
+ for (const join of normalized.joinTables.values()) {
1348
+ const name = join.table;
1349
+ const table = qualifiedSqlName4({ name, namespace: join.namespace, defaultNamespace: params.namespace });
1276
1350
  result.push({ kind: "table", name: table });
1277
1351
  const derived = implicitJoinTableObjectNames({ table: name });
1278
1352
  result.push({ kind: "column", table, column: "A" }, { kind: "constraint", constraintType: "foreignKey", table, name: derived.foreignKeyA });
1279
1353
  result.push({ kind: "column", table, column: "B" }, { kind: "constraint", constraintType: "foreignKey", table, name: derived.foreignKeyB });
1280
1354
  result.push({ kind: "index", table, name: derived.uniqueIndex }, { kind: "index", table, name: derived.indexB });
1281
1355
  }
1282
- for (const name of normalized.views.keys())
1283
- result.push({ kind: "view", name: { namespace: params.namespace, name } });
1356
+ for (const view of normalized.views.values())
1357
+ result.push({ kind: "view", name: qualifiedSqlName4({ name: view.dbName ?? view.name, namespace: view.namespace, defaultNamespace: params.namespace }) });
1284
1358
  return result;
1285
1359
  }
1286
1360
  function assertRetainedMigrationObjects(params) {
@@ -1340,7 +1414,7 @@ function assertRemovalAuthorization(params) {
1340
1414
  integrity.push(identity);
1341
1415
  if (step.kind === "dropTable" || step.kind === "dropJoinTable") {
1342
1416
  for (const child of [...declared, ...params.removeUnmanaged ?? []]) {
1343
- if ((child.kind === "constraint" || child.kind === "index") && child.table.namespace === params.namespace && child.table.name === step.table)
1417
+ if ((child.kind === "constraint" || child.kind === "index") && child.table.namespace === (step.namespace ?? params.namespace) && child.table.name === step.table)
1344
1418
  integrity.push(child);
1345
1419
  }
1346
1420
  }
@@ -1352,22 +1426,311 @@ function assertRemovalAuthorization(params) {
1352
1426
  }
1353
1427
 
1354
1428
  // packages/migrate/src/inventory.ts
1355
- import { VibeError as VibeError16, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey5 } from "@vibeorm/schema";
1356
- import { getSqlDialect as getSqlDialect2, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
1429
+ import { VibeError as VibeError17, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
1430
+ import { getSqlDialect as getSqlDialect3, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
1431
+
1432
+ // packages/migrate/src/external-objects.ts
1433
+ import { IDENTIFIER_LIMITS, VibeError as VibeError7, identifierLength, sqlNamespaceIssue, sqlObjectKey as sqlObjectKey3, sqlObjectNamespace, qualifiedSqlName as qualifiedSqlName5, enumStorageOf as enumStorageOf3, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
1434
+ import { getSqlDialect, renderExternalOwnershipCatalog, renderReplayCoverageGaps } from "@vibeorm/sql";
1435
+ var partitionPatterns = new WeakMap;
1436
+ function invalid2(params) {
1437
+ return new VibeError7({ code: "VIBE_CONFIG", message: `Invalid external ownership declaration: ${params.detail}`, meta: { reason: "external-declaration-invalid", ...params.owner === undefined ? {} : { owner: params.owner } }, ...params.cause === undefined ? {} : { cause: params.cause } });
1438
+ }
1439
+ function record2(value) {
1440
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1441
+ }
1442
+ function identifier(value) {
1443
+ return typeof value === "string" && value.trim().length > 0 && !value.includes("\x00") && identifierLength({ name: value, dialect: "postgres" }) <= IDENTIFIER_LIMITS.postgres.max;
1444
+ }
1445
+ function names(params) {
1446
+ if (params.value === undefined)
1447
+ return;
1448
+ if (!Array.isArray(params.value) || !params.value.every(identifier) || new Set(params.value).size !== params.value.length)
1449
+ throw invalid2({ detail: `${params.key} must contain unique non-empty PostgreSQL identifiers` });
1450
+ return [...params.value];
1451
+ }
1452
+ async function parseExternalObjects(params) {
1453
+ return Promise.resolve().then(() => {
1454
+ if (!Array.isArray(params.value))
1455
+ throw invalid2({ detail: "external must be an array" });
1456
+ if (params.value.length > 0 && params.dialect !== undefined && params.dialect !== "postgres")
1457
+ throw unsupportedCapability6({ dialect: params.dialect, feature: "externalObjects" });
1458
+ const owners = new Set;
1459
+ const claims = new Set;
1460
+ const compiled = [];
1461
+ const tableClaims = [];
1462
+ const result = [];
1463
+ for (const value of params.value) {
1464
+ if (!record2(value) || typeof value.owner !== "string" || value.owner.trim().length === 0 || value.owner.includes("\x00"))
1465
+ throw invalid2({ detail: "each entry needs a non-empty owner" });
1466
+ const owner = value.owner;
1467
+ if (owners.has(owner))
1468
+ throw invalid2({ detail: "an owner must have one declaration", owner });
1469
+ owners.add(owner);
1470
+ const allowed = value.schema === undefined ? ["owner", "tables", "partitions", "functions"] : ["owner", "schema"];
1471
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
1472
+ throw invalid2({ detail: "whole-schema and named-object declarations are exclusive; unknown keys refuse", owner });
1473
+ const claim = (options) => {
1474
+ const { kind, name } = options;
1475
+ const key = JSON.stringify([kind, name]);
1476
+ if (claims.has(key))
1477
+ throw invalid2({ detail: `${kind} ${JSON.stringify(name)} has conflicting owners`, owner });
1478
+ claims.add(key);
1479
+ };
1480
+ if (value.schema !== undefined) {
1481
+ const issue = sqlNamespaceIssue({ namespace: value.schema, allowPublic: false });
1482
+ if (issue !== undefined || typeof value.schema !== "string")
1483
+ throw invalid2({ detail: issue ?? "schema must be a string", owner });
1484
+ claim({ kind: "namespace", name: value.schema });
1485
+ result.push(Object.freeze({ owner, schema: value.schema }));
1486
+ continue;
1487
+ }
1488
+ const tables = names({ value: value.tables, key: "tables" });
1489
+ const functions = names({ value: value.functions, key: "functions" });
1490
+ const partitions = [];
1491
+ const ownerPatterns = [];
1492
+ if (value.partitions !== undefined) {
1493
+ if (!Array.isArray(value.partitions))
1494
+ throw invalid2({ detail: "partitions must be an array", owner });
1495
+ for (const partition of value.partitions) {
1496
+ if (!record2(partition) || Object.keys(partition).some((key) => key !== "parent" && key !== "pattern") || !identifier(partition.parent) || typeof partition.pattern !== "string" || partition.pattern.length === 0 || !tables?.includes(partition.parent))
1497
+ throw invalid2({ detail: "partition patterns require a parent in the same owner's tables", owner });
1498
+ const pattern = new RegExp(`^(?:${partition.pattern})$`);
1499
+ compiled.push({ owner, pattern });
1500
+ ownerPatterns.push({ parent: partition.parent, pattern });
1501
+ partitions.push(Object.freeze({ parent: partition.parent, pattern: partition.pattern }));
1502
+ }
1503
+ }
1504
+ if ((tables?.length ?? 0) + (functions?.length ?? 0) + partitions.length === 0)
1505
+ throw invalid2({ detail: "named ownership needs at least one table or function", owner });
1506
+ for (const name of tables ?? []) {
1507
+ if (name === "_vibe_migrations")
1508
+ throw invalid2({ detail: "the migration journal cannot be claimed externally", owner });
1509
+ claim({ kind: "table", name });
1510
+ tableClaims.push({ owner, name });
1511
+ }
1512
+ for (const name of functions ?? [])
1513
+ claim({ kind: "function", name });
1514
+ const declaration = Object.freeze({ owner, ...tables === undefined ? {} : { tables: Object.freeze(tables) }, ...functions === undefined ? {} : { functions: Object.freeze(functions) }, ...value.partitions === undefined ? {} : { partitions: Object.freeze(partitions) } });
1515
+ partitionPatterns.set(declaration, ownerPatterns);
1516
+ result.push(declaration);
1517
+ }
1518
+ for (const table of tableClaims)
1519
+ for (const pattern of compiled) {
1520
+ if (table.owner !== pattern.owner && pattern.pattern.exec(table.name)?.[0] === table.name)
1521
+ throw invalid2({ detail: "a full-name pattern claims another owner's named table", owner: pattern.owner });
1522
+ }
1523
+ return Object.freeze(result);
1524
+ }).catch((cause) => {
1525
+ if (cause instanceof VibeError7)
1526
+ throw cause;
1527
+ throw invalid2({ detail: "regular-expression pattern cannot be compiled", cause });
1528
+ });
1529
+ }
1530
+ function nameKey(name) {
1531
+ return sqlObjectKey3({ identity: { kind: "table", name } });
1532
+ }
1533
+ function schemaOwner(params) {
1534
+ return params.external.find((entry) => entry.schema === params.namespace)?.owner;
1535
+ }
1536
+ function declaredTableOwner(params) {
1537
+ const whole = schemaOwner({ external: params.external, namespace: params.name.namespace });
1538
+ if (whole !== undefined || params.name.namespace !== "public")
1539
+ return whole;
1540
+ const matches = params.external.filter((entry) => {
1541
+ if (entry.tables?.includes(params.name.name))
1542
+ return true;
1543
+ const patterns = partitionPatterns.get(entry);
1544
+ if ((entry.partitions?.length ?? 0) > 0 && patterns === undefined)
1545
+ throw invalid2({ detail: "resolve partition declarations returned by parseExternalObjects", owner: entry.owner });
1546
+ return patterns?.some(({ pattern }) => pattern.exec(params.name.name)?.[0] === params.name.name) === true;
1547
+ });
1548
+ if (matches.length > 1)
1549
+ throw new VibeError7({ code: "VIBE_CONFIG", message: "A table is claimed by more than one external owner", meta: { reason: "external-owner-collision", identity: { kind: "table", name: params.name }, owners: matches.map((entry) => entry.owner) } });
1550
+ return matches[0]?.owner;
1551
+ }
1552
+ function externalObjectOwner(params) {
1553
+ const { identity, external } = params;
1554
+ const whole = schemaOwner({ external: external.declarations, namespace: sqlObjectNamespace({ identity }) });
1555
+ if (whole !== undefined)
1556
+ return whole;
1557
+ if (identity.kind === "table" || "table" in identity) {
1558
+ const name = "table" in identity ? identity.table : identity.name;
1559
+ return external.tables.find((entry) => nameKey(entry.name) === nameKey(name))?.owner ?? declaredTableOwner({ external: external.declarations, name });
1560
+ }
1561
+ if (identity.kind === "sequence")
1562
+ return external.sequences.find((entry) => nameKey(entry.name) === nameKey(identity.name))?.owner;
1563
+ if (identity.kind === "function" && identity.name.namespace === "public")
1564
+ return external.declarations.find((entry) => entry.functions?.includes(identity.name.name))?.owner;
1565
+ return;
1566
+ }
1567
+ function resolveExternalObjectOwnership(params) {
1568
+ if (params.external.length > 0 && params.dialect !== "postgres")
1569
+ throw unsupportedCapability6({ dialect: params.dialect, feature: "externalObjects" });
1570
+ if (params.catalog.inventory.some((object) => object.inspectionUnavailable !== undefined))
1571
+ throw new VibeError7({ code: "VIBE_MIGRATION", message: "External ownership requires complete catalog visibility", meta: { reason: "external-inventory-unavailable", objects: params.catalog.inventory.filter((object) => object.inspectionUnavailable !== undefined) } });
1572
+ const tables = new Map;
1573
+ const assign = (entry) => {
1574
+ const key = nameKey(entry.name);
1575
+ const prior = tables.get(key)?.owner ?? declaredTableOwner({ external: params.external, name: entry.name });
1576
+ if (prior !== undefined && prior !== entry.owner)
1577
+ throw new VibeError7({ code: "VIBE_CONFIG", message: "An inherited table has conflicting external owners", meta: { reason: "external-owner-collision", identity: { kind: "table", name: entry.name }, owners: [prior, entry.owner] } });
1578
+ if (tables.has(key))
1579
+ return false;
1580
+ tables.set(key, entry);
1581
+ return true;
1582
+ };
1583
+ for (const declaration of params.external)
1584
+ for (const name of declaration.tables ?? [])
1585
+ assign({ owner: declaration.owner, name: { namespace: "public", name } });
1586
+ for (const object of params.catalog.inventory) {
1587
+ if (object.identity.kind !== "table")
1588
+ continue;
1589
+ const owner = declaredTableOwner({ external: params.external, name: object.identity.name });
1590
+ if (owner !== undefined)
1591
+ assign({ owner, name: object.identity.name });
1592
+ }
1593
+ let changed = true;
1594
+ while (changed) {
1595
+ changed = false;
1596
+ for (const edge of params.catalog.inheritance) {
1597
+ const owner = tables.get(nameKey(edge.parent))?.owner;
1598
+ if (owner !== undefined && assign({ owner, name: edge.child }))
1599
+ changed = true;
1600
+ }
1601
+ }
1602
+ const sequences = params.catalog.ownedSequences.flatMap((edge) => {
1603
+ const owner = tables.get(nameKey(edge.table))?.owner;
1604
+ if (owner === undefined)
1605
+ return [];
1606
+ const whole = schemaOwner({ external: params.external, namespace: edge.sequence.namespace });
1607
+ if (whole !== undefined && whole !== owner)
1608
+ throw new VibeError7({ code: "VIBE_CONFIG", message: "An owned sequence has conflicting external owners", meta: { reason: "external-owner-collision", identity: { kind: "sequence", name: edge.sequence }, owners: [whole, owner] } });
1609
+ return [{ owner, name: edge.sequence }];
1610
+ });
1611
+ const closure = { declarations: params.external, catalog: params.catalog, inventory: params.catalog.inventory, tables: [...tables.values()], sequences, owners: [], identities: [] };
1612
+ for (const edge of params.catalog.foreignKeys) {
1613
+ const owner = externalObjectOwner({ external: closure, identity: { kind: "table", name: edge.table } });
1614
+ const targetOwner = externalObjectOwner({ external: closure, identity: { kind: "table", name: edge.target } });
1615
+ if (owner !== targetOwner && (owner !== undefined || targetOwner !== undefined))
1616
+ throw new VibeError7({ code: "VIBE_MIGRATION", message: "Foreign keys cannot cross external ownership boundaries", meta: { reason: "external-cross-owner-foreign-key", name: edge.name, table: edge.table, target: edge.target, owner: owner ?? "application", targetOwner: targetOwner ?? "application" } });
1617
+ }
1618
+ const observed = new Map;
1619
+ for (const namespace of params.catalog.namespaces) {
1620
+ if (schemaOwner({ external: params.external, namespace }) !== undefined)
1621
+ observed.set(sqlObjectKey3({ identity: { kind: "namespace", name: namespace } }), { kind: "namespace", name: namespace });
1622
+ }
1623
+ for (const object of params.catalog.inventory)
1624
+ observed.set(sqlObjectKey3({ identity: object.identity }), object.identity);
1625
+ const owners = params.external.map((entry) => {
1626
+ const identities = [...observed.values()].filter((identity) => externalObjectOwner({ identity, external: closure }) === entry.owner);
1627
+ const missing = [];
1628
+ if (entry.schema !== undefined && !params.catalog.namespaces.includes(entry.schema))
1629
+ missing.push({ kind: "namespace", name: entry.schema });
1630
+ for (const name of entry.tables ?? [])
1631
+ if (!params.catalog.inventory.some(({ identity }) => identity.kind === "table" && identity.name.namespace === "public" && identity.name.name === name))
1632
+ missing.push({ kind: "table", name: { namespace: "public", name } });
1633
+ for (const name of entry.functions ?? [])
1634
+ if (!params.catalog.inventory.some(({ identity }) => identity.kind === "function" && identity.name.namespace === "public" && identity.name.name === name))
1635
+ missing.push({ kind: "function", name: { namespace: "public", name } });
1636
+ return { owner: entry.owner, state: "excluded", count: identities.length, identities, missing };
1637
+ });
1638
+ return { ...closure, owners, identities: owners.flatMap((owner) => owner.identities) };
1639
+ }
1640
+ function catalogName(params) {
1641
+ const namespace = params.row[params.namespace];
1642
+ const name = params.row[params.name];
1643
+ if (!identifier(namespace) || !identifier(name))
1644
+ throw new VibeError7({ code: "VIBE_MIGRATION", message: "Malformed external ownership catalog endpoint", meta: { reason: "external-inventory-unavailable" } });
1645
+ return { namespace, name };
1646
+ }
1647
+ async function inspectExternalObjects(params) {
1648
+ const external = await parseExternalObjects({ value: params.external, dialect: params.dialect });
1649
+ if (params.dialect !== "postgres")
1650
+ return resolveExternalObjectOwnership({ external, dialect: params.dialect, catalog: { namespaces: [], inventory: params.inventory ?? [], inheritance: [], ownedSequences: [], foreignKeys: [] } });
1651
+ const read = async (operation) => {
1652
+ const query = renderExternalOwnershipCatalog({ dialect: getSqlDialect({ dialect: params.dialect }), operation });
1653
+ return params.executor({ text: query.text, values: [...query.values] });
1654
+ };
1655
+ const namespaceRows = await read("namespaces");
1656
+ if (namespaceRows.some((row) => !identifier(row.namespace) || row.visible !== true))
1657
+ throw new VibeError7({ code: "VIBE_MIGRATION", message: "External ownership cannot prove visibility of every inspected namespace", meta: { reason: "external-inventory-unavailable" } });
1658
+ const namespaces = namespaceRows.map((row) => String(row.namespace));
1659
+ const [inherited, owned, foreignKeys] = await Promise.all([read("inheritance"), read("ownedSequences"), read("foreignKeys")]);
1660
+ const inventory = params.inventory ?? (await Promise.all(namespaces.map((namespace) => inspectMigrationInventory({ executor: params.executor, dialect: params.dialect, namespace })))).flat();
1661
+ const coverageGaps = (await Promise.all(namespaces.map(async (namespace) => {
1662
+ const query = renderReplayCoverageGaps({ dialect: getSqlDialect({ dialect: params.dialect }), namespace });
1663
+ return params.executor({ text: query.text, values: [...query.values] });
1664
+ }))).flat();
1665
+ return resolveExternalObjectOwnership({ external, dialect: params.dialect, catalog: {
1666
+ namespaces,
1667
+ inventory,
1668
+ coverageGaps,
1669
+ inheritance: inherited.map((row) => ({ parent: catalogName({ row, namespace: "parentNamespace", name: "parentName" }), child: catalogName({ row, namespace: "namespace", name: "name" }) })),
1670
+ ownedSequences: owned.map((row) => ({ table: catalogName({ row, namespace: "tableNamespace", name: "tableName" }), sequence: catalogName({ row, namespace: "namespace", name: "name" }) })),
1671
+ foreignKeys: foreignKeys.map((row) => ({ table: catalogName({ row, namespace: "namespace", name: "table" }), target: catalogName({ row, namespace: "refNamespace", name: "refTable" }), name: catalogName({ row, namespace: "namespace", name: "name" }).name }))
1672
+ } });
1673
+ }
1674
+ function assertExternalOwnershipClaims(params) {
1675
+ for (const identity of params.identities) {
1676
+ const owner = externalObjectOwner({ identity, external: params.external }) ?? (identity.kind === "namespace" ? [...params.external.tables, ...params.external.sequences].find((entry) => entry.name.namespace === identity.name)?.owner : undefined);
1677
+ if (owner !== undefined)
1678
+ throw new VibeError7({ code: "VIBE_CONFIG", message: "Application or module declarations collide with external ownership", meta: { reason: "external-owned-object-collision", owner, identity } });
1679
+ }
1680
+ }
1681
+ function projectExternalSchema(params) {
1682
+ const { schema, external } = params;
1683
+ const models = schema.models.filter((model) => externalObjectOwner({ external, identity: { kind: model.isView ? "view" : "table", name: qualifiedSqlName5({ name: model.dbName ?? model.name, namespace: model.namespace }) } }) === undefined);
1684
+ const removed = new Set(schema.models.filter((model) => !models.includes(model)).map((model) => model.name));
1685
+ for (const model of models)
1686
+ for (const relation of model.relations)
1687
+ if (removed.has(relation.targetModel))
1688
+ throw new VibeError7({ code: "VIBE_MIGRATION", message: "Application relations cannot depend on externally owned tables", meta: { reason: "external-cross-owner-foreign-key", model: model.name, relation: relation.name } });
1689
+ return {
1690
+ ...schema,
1691
+ models,
1692
+ enums: schema.enums.filter((entry) => enumStorageOf3({ enumDef: entry }) === "string" || externalObjectOwner({ external, identity: { kind: "enum", name: qualifiedSqlName5({ name: entry.dbName ?? entry.name, namespace: entry.namespace }) } }) === undefined),
1693
+ ...schema.migrationObjects === undefined ? {} : { migrationObjects: schema.migrationObjects.filter((object) => externalObjectOwner({ external, identity: object.identity }) === undefined) }
1694
+ };
1695
+ }
1696
+ function projectExternalState(params) {
1697
+ return { ...params.state, schema: projectExternalSchema({ schema: params.state.schema, external: params.external }), definitions: params.state.definitions.filter((definition) => externalObjectOwner({ external: params.external, identity: definition.identity }) === undefined) };
1698
+ }
1699
+ function projectExternalInventory(params) {
1700
+ return params.inventory.filter((object) => externalObjectOwner({ identity: object.identity, external: params.external }) === undefined);
1701
+ }
1702
+ function migrationCoverageGaps(params) {
1703
+ const recognized = new Set([...params.namespaces, ...params.external?.tables.map((entry) => entry.name.namespace) ?? [], ...params.external?.sequences.map((entry) => entry.name.namespace) ?? []]);
1704
+ const kept = params.gaps.filter((gap) => {
1705
+ const namespace = typeof gap.namespace === "string" ? gap.namespace : params.namespace;
1706
+ if (gap.kind === "namespace" && typeof gap.name === "string" && recognized.has(gap.name))
1707
+ return false;
1708
+ const external = params.external;
1709
+ if (external === undefined)
1710
+ return true;
1711
+ if (external.declarations.some((entry) => entry.schema === namespace || gap.kind === "namespace" && entry.schema === gap.name))
1712
+ return false;
1713
+ const name = gap.kind === "rule" ? gap.parent : gap.name;
1714
+ if ((gap.kind === "relation-layout" || gap.kind === "rule") && typeof name === "string" && externalObjectOwner({ external, identity: { kind: "table", name: { namespace, name } } }) !== undefined)
1715
+ return false;
1716
+ return true;
1717
+ });
1718
+ return [...new Map(kept.map((gap) => [JSON.stringify(gap), gap])).values()];
1719
+ }
1357
1720
 
1358
1721
  // packages/migrate/src/exclusions.ts
1359
1722
  import { randomUUID } from "crypto";
1360
- import { VibeError as VibeError7, sqlObjectKey as sqlObjectKey3 } from "@vibeorm/schema";
1723
+ import { VibeError as VibeError8, sqlObjectKey as sqlObjectKey4 } from "@vibeorm/schema";
1361
1724
  import { postgresDialect as postgresDialect2, renderExclusionCatalog, renderExclusionProbe } from "@vibeorm/sql";
1362
1725
  async function deparseExclusion(params) {
1363
1726
  const probe = `vibe_exclusion_${randomUUID().replaceAll("-", "")}`;
1364
1727
  await params.executor({ text: "SAVEPOINT vibe_exclusion_probe" });
1365
1728
  try {
1366
- for (const text of renderExclusionProbe({ dialect: postgresDialect2, table: params.table, probe, definition: params.definition }))
1729
+ for (const text of renderExclusionProbe({ dialect: postgresDialect2, table: params.table, ...params.namespace === undefined ? {} : { namespace: params.namespace }, probe, definition: params.definition }))
1367
1730
  await params.executor({ text });
1368
1731
  const rows = await params.executor({ text: renderExclusionCatalog({ dialect: postgresDialect2, probe }) });
1369
1732
  if (typeof rows[0]?.definition !== "string")
1370
- throw new VibeError7({ code: "VIBE_MIGRATION", message: "Exclusion probe unavailable", meta: { reason: "exclusion-inspection-unavailable" } });
1733
+ throw new VibeError8({ code: "VIBE_MIGRATION", message: "Exclusion probe unavailable", meta: { reason: "exclusion-inspection-unavailable" } });
1371
1734
  return rows[0].definition;
1372
1735
  } finally {
1373
1736
  await params.executor({ text: "ROLLBACK TO SAVEPOINT vibe_exclusion_probe" });
@@ -1378,7 +1741,7 @@ async function prepareExclusionDiff(params) {
1378
1741
  const models = [];
1379
1742
  for (const model of params.from.models) {
1380
1743
  const table = model.dbName ?? model.name;
1381
- const target = params.to.models.find((entry) => (entry.dbName ?? entry.name) === table);
1744
+ const target = params.to.models.find((entry) => (entry.dbName ?? entry.name) === table && (entry.namespace ?? "public") === (model.namespace ?? "public"));
1382
1745
  const exclusions = [];
1383
1746
  for (const before of model.exclusions ?? []) {
1384
1747
  const next = target?.exclusions?.find((entry) => entry.name === before.name);
@@ -1386,22 +1749,22 @@ async function prepareExclusionDiff(params) {
1386
1749
  exclusions.push(before);
1387
1750
  continue;
1388
1751
  }
1389
- const identity = { kind: "constraint", constraintType: "exclusion", table: { namespace: "public", name: table }, name: before.name };
1752
+ const identity = { kind: "constraint", constraintType: "exclusion", table: { namespace: model.namespace ?? "public", name: table }, name: before.name };
1390
1753
  if (params.probe === "rollback") {
1391
- const normalized = await deparseExclusion({ executor: params.executor, table, definition: next.definition });
1754
+ const normalized = await deparseExclusion({ executor: params.executor, table, ...model.namespace === undefined ? {} : { namespace: model.namespace }, definition: next.definition });
1392
1755
  exclusions.push(normalized === before.definition ? next : { name: before.name, definition: before.definition, requiredExtensions: before.requiredExtensions });
1393
1756
  continue;
1394
1757
  }
1395
- const previous = params.previous?.schema.models.find((entry) => (entry.dbName ?? entry.name) === table)?.exclusions?.find((entry) => entry.name === before.name);
1758
+ const previous = params.previous?.schema.models.find((entry) => (entry.dbName ?? entry.name) === table && (entry.namespace ?? "public") === (model.namespace ?? "public"))?.exclusions?.find((entry) => entry.name === before.name);
1396
1759
  if (previous !== undefined) {
1397
1760
  exclusions.push(previous);
1398
1761
  continue;
1399
1762
  }
1400
- if (params.replaceChecks?.some((entry) => sqlObjectKey3({ identity: entry }) === sqlObjectKey3({ identity }))) {
1763
+ if (params.replaceChecks?.some((entry) => sqlObjectKey4({ identity: entry }) === sqlObjectKey4({ identity }))) {
1401
1764
  exclusions.push({ name: before.name, definition: before.definition, requiredExtensions: before.requiredExtensions });
1402
1765
  continue;
1403
1766
  }
1404
- throw new VibeError7({ code: "VIBE_MIGRATION", message: "Exclusion equivalence requires a rollback probe or explicit replacement", meta: { reason: "explicit-replacement-required", identity } });
1767
+ throw new VibeError8({ code: "VIBE_MIGRATION", message: "Exclusion equivalence requires a rollback probe or explicit replacement", meta: { reason: "explicit-replacement-required", identity } });
1405
1768
  }
1406
1769
  models.push({ ...model, exclusions });
1407
1770
  }
@@ -1410,20 +1773,20 @@ async function prepareExclusionDiff(params) {
1410
1773
  async function verifyExclusions(params) {
1411
1774
  if (!params.expected.schema.models.some((model) => (model.exclusions?.length ?? 0) > 0))
1412
1775
  return [];
1413
- const rows = await params.executor({ text: renderExclusionCatalog({ dialect: postgresDialect2 }) });
1776
+ const rows = (await Promise.all([...new Set(params.expected.schema.models.map((model) => model.namespace ?? "public"))].map(async (namespace) => (await params.executor({ text: renderExclusionCatalog({ dialect: postgresDialect2, namespace }) })).map((row) => ({ ...row, namespace }))))).flat();
1414
1777
  const objects = [];
1415
1778
  for (const model of params.expected.schema.models)
1416
1779
  for (const exclusion of model.exclusions ?? []) {
1417
1780
  const table = model.dbName ?? model.name;
1418
- const identity = { kind: "constraint", constraintType: "exclusion", table: { namespace: "public", name: table }, name: exclusion.name };
1419
- const row = rows.find((entry) => entry.table_name === table && entry.constraint_name === exclusion.name);
1781
+ const identity = { kind: "constraint", constraintType: "exclusion", table: { namespace: model.namespace ?? "public", name: table }, name: exclusion.name };
1782
+ const row = rows.find((entry) => entry.namespace === (model.namespace ?? "public") && entry.table_name === table && entry.constraint_name === exclusion.name);
1420
1783
  if (row === undefined) {
1421
1784
  objects.push({ identity, ownership: "schema", state: "missing", differences: [] });
1422
1785
  continue;
1423
1786
  }
1424
1787
  const differences = ["validated", "valid", "ready"].flatMap((attribute) => row[attribute] === true ? [] : [{ attribute, expected: true, actual: row[attribute] }]);
1425
- const independentlyVerified = params.expected.definitions.some((entry) => sqlObjectKey3({ identity: entry.identity }) === sqlObjectKey3({ identity }));
1426
- const definition = independentlyVerified ? row.definition : params.probe === "rollback" ? await deparseExclusion({ executor: params.executor, table, definition: exclusion.definition }) : (exclusion.normalizationSource === exclusion.definition ? exclusion.normalizedDefinition : undefined) ?? exclusion.definition;
1788
+ const independentlyVerified = params.expected.definitions.some((entry) => sqlObjectKey4({ identity: entry.identity }) === sqlObjectKey4({ identity }));
1789
+ const definition = independentlyVerified ? row.definition : params.probe === "rollback" ? await deparseExclusion({ executor: params.executor, table, ...model.namespace === undefined ? {} : { namespace: model.namespace }, definition: exclusion.definition }) : (exclusion.normalizationSource === exclusion.definition ? exclusion.normalizedDefinition : undefined) ?? exclusion.definition;
1427
1790
  if (definition !== row.definition)
1428
1791
  differences.push({ attribute: "definition", expected: definition, actual: row.definition });
1429
1792
  objects.push({ identity, ownership: "schema", state: differences.length === 0 ? "matching" : params.probe === "rollback" ? "changed" : "unavailable", differences });
@@ -1433,22 +1796,19 @@ async function verifyExclusions(params) {
1433
1796
 
1434
1797
  // packages/migrate/src/inspect-objects.ts
1435
1798
  import { randomUUID as randomUUID2 } from "crypto";
1436
- import { VibeError as VibeError15, sqlObjectKey as sqlObjectKey4, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2 } from "@vibeorm/schema";
1437
- import { getSqlDialect, renderMigrationObjectBatch, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
1799
+ import { VibeError as VibeError16, sqlObjectKey as sqlObjectKey5, sqlObjectNamespace as sqlObjectNamespace2, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2, qualifiedSqlName as qualifiedSqlName8 } from "@vibeorm/schema";
1800
+ import { getSqlDialect as getSqlDialect2, renderMigrationObjectBatch, renderMigrationObjectQuery as renderMigrationObjectQuery2, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
1438
1801
 
1439
1802
  // packages/migrate/src/introspect/postgres.ts
1440
1803
  import { renderExclusionCatalog as renderExclusionCatalog2 } from "@vibeorm/sql";
1441
1804
  import { parsePgArrayLiteral } from "@vibeorm/sql";
1442
1805
  import { scalarListDefault as scalarListDefault2 } from "@vibeorm/schema";
1443
1806
  import { exactIntegerLiteral } from "@vibeorm/schema";
1444
- import { postgresDialect as postgresDialect6, renderRlsReadinessQuery } from "@vibeorm/sql";
1445
- import { VibeError as VibeError12 } from "@vibeorm/schema";
1446
-
1447
- // packages/migrate/src/journal-table.ts
1448
- var MIGRATIONS_TABLE_NAME = "_vibe_migrations";
1807
+ import { postgresDialect as postgresDialect6, renderRlsReadinessQuery, renderPostgresPhysicalCatalog, renderPostgresIndexKeys, renderMigrationObjectQuery } from "@vibeorm/sql";
1808
+ import { VibeError as VibeError13, assertSqlNamespace as assertSqlNamespace2, isReservedClientName } from "@vibeorm/schema";
1449
1809
 
1450
1810
  // packages/migrate/src/introspect/shared.ts
1451
- import { VibeError as VibeError8 } from "@vibeorm/schema";
1811
+ import { VibeError as VibeError9 } from "@vibeorm/schema";
1452
1812
  function detectJoinTable(params) {
1453
1813
  const { table, columnNames, fkGroups, indexes } = params;
1454
1814
  if (!table.startsWith("_"))
@@ -1466,8 +1826,8 @@ function detectJoinTable(params) {
1466
1826
  if (fkA.refTable > fkB.refTable || tableFks.some((fk) => !fk.required || fk.onDelete !== "Cascade" || fk.onUpdate !== "Cascade"))
1467
1827
  return null;
1468
1828
  const tableIndexes = indexes.filter((index) => index.table === table);
1469
- const canonicalAb = (index) => index.unique && index.kind === "btree" && index.where === undefined && index.nullsNotDistinct !== true && index.columns.length === 2 && index.columns[0]?.name === "A" && index.columns[1]?.name === "B" && index.columns.every((column) => !column.desc);
1470
- const canonicalB = (index) => !index.unique && index.kind === "btree" && index.where === undefined && index.columns.length === 1 && index.columns[0]?.name === "B" && !index.columns[0].desc;
1829
+ const canonicalAb = (index) => index.unique && index.kind === "btree" && index.where === undefined && index.nullsNotDistinct !== true && index.columns.length === 2 && index.columns[0]?.name === "A" && index.columns[1]?.name === "B" && index.columns.every((column) => !column.desc && column.operatorClass === undefined);
1830
+ const canonicalB = (index) => !index.unique && index.kind === "btree" && index.where === undefined && index.columns.length === 1 && index.columns[0]?.name === "B" && !index.columns[0].desc && index.columns[0].operatorClass === undefined;
1471
1831
  if (tableIndexes.length !== 2 || tableIndexes.filter(canonicalAb).length !== 1 || tableIndexes.filter(canonicalB).length !== 1)
1472
1832
  return null;
1473
1833
  const sorted = [fkA.refTable, fkB.refTable].sort();
@@ -1487,7 +1847,7 @@ function addFkRelations(params) {
1487
1847
  const primary = target?.primaryKey?.fields ?? target?.fields.filter((field) => field.isId).map((field) => field.name) ?? [];
1488
1848
  const columns = primary.map((name) => target?.fields.find((field) => field.name === name)?.dbName ?? name);
1489
1849
  if (columns.length !== 1 || fk.refColumns.length !== 1 || columns[0] !== fk.refColumns[0])
1490
- throw new VibeError8({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Join table "${table}" does not reference the single-column primary key of "${fk.refTable}"; automatic join compression is unavailable`, meta: { feature: "implicitJoinReferencedKey", table, target: fk.refTable } });
1850
+ throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Join table "${table}" does not reference the single-column primary key of "${fk.refTable}"; automatic join compression is unavailable`, meta: { feature: "implicitJoinReferencedKey", table, target: fk.refTable } });
1491
1851
  }
1492
1852
  const relationsByModel = new Map;
1493
1853
  const takeName = nameAllocator({ models });
@@ -1499,9 +1859,9 @@ function addFkRelations(params) {
1499
1859
  const target = modelsByName.get(group.refTable);
1500
1860
  if (owner === undefined || target === undefined)
1501
1861
  continue;
1502
- const relationName = ambiguousPairs.has(modelPairKey({ a: group.table, b: group.refTable })) ? `${group.table}_${group.columns.join("_")}To${group.refTable}` : undefined;
1862
+ const relationName = ambiguousPairs.has(modelPairKey({ a: group.table, b: group.refTable })) ? `${owner.dbName ?? owner.name}_${group.columns.join("_")}To${target.dbName ?? target.name}` : undefined;
1503
1863
  const fkSide = {
1504
- name: takeName({ model: owner.name, base: lowerFirst(group.refTable) }),
1864
+ name: takeName({ model: owner.name, base: lowerFirst(target.dbName ?? target.name) }),
1505
1865
  model: owner.name,
1506
1866
  targetModel: group.refTable,
1507
1867
  type: "manyToOne",
@@ -1516,7 +1876,7 @@ function addFkRelations(params) {
1516
1876
  };
1517
1877
  pushRelation({ relationsByModel, relation: fkSide });
1518
1878
  const listSide = {
1519
- name: takeName({ model: target.name, base: lowerFirst(group.table) }),
1879
+ name: takeName({ model: target.name, base: lowerFirst(owner.dbName ?? owner.name) }),
1520
1880
  model: target.name,
1521
1881
  targetModel: group.table,
1522
1882
  type: "oneToMany",
@@ -1542,7 +1902,7 @@ function addJoinTableRelations(params) {
1542
1902
  pushRelation({
1543
1903
  relationsByModel,
1544
1904
  relation: {
1545
- name: takeName({ model: modelA.name, base: lowerFirst(modelB.name) }),
1905
+ name: takeName({ model: modelA.name, base: lowerFirst(modelB.dbName ?? modelB.name) }),
1546
1906
  model: modelA.name,
1547
1907
  targetModel: modelB.name,
1548
1908
  type: "oneToMany",
@@ -1554,7 +1914,7 @@ function addJoinTableRelations(params) {
1554
1914
  pushRelation({
1555
1915
  relationsByModel,
1556
1916
  relation: {
1557
- name: takeName({ model: modelB.name, base: lowerFirst(modelA.name) }),
1917
+ name: takeName({ model: modelB.name, base: lowerFirst(modelA.dbName ?? modelA.name) }),
1558
1918
  model: modelB.name,
1559
1919
  targetModel: modelA.name,
1560
1920
  type: "oneToMany",
@@ -1635,14 +1995,14 @@ function lowerFirst(value) {
1635
1995
  }
1636
1996
 
1637
1997
  // packages/migrate/src/postgres-type-identity.ts
1638
- import { VibeError as VibeError9 } from "@vibeorm/schema";
1998
+ import { VibeError as VibeError10, qualifiedSqlName as qualifiedSqlName6 } from "@vibeorm/schema";
1639
1999
  import { postgresDialect as postgresDialect3, renderPostgresTypeIdentity } from "@vibeorm/sql";
1640
2000
  async function resolvePostgresTypeIdentities(params) {
1641
2001
  const unavailable = [];
1642
2002
  const models = [];
1643
2003
  for (const model of params.schema.models) {
1644
- const table = { namespace: "public", name: model.dbName ?? model.name };
1645
- const previous = params.previous?.models.find((entry) => (entry.dbName ?? entry.name) === table.name);
2004
+ const table = qualifiedSqlName6({ name: model.dbName ?? model.name, namespace: model.namespace });
2005
+ const previous = params.previous?.models.find((entry) => (entry.dbName ?? entry.name) === table.name && (entry.namespace ?? "public") === table.namespace);
1646
2006
  const fields = [];
1647
2007
  for (const field of model.fields) {
1648
2008
  if (field.isUnsupported !== true) {
@@ -1653,7 +2013,7 @@ async function resolvePostgresTypeIdentities(params) {
1653
2013
  const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
1654
2014
  const evidence = field.nativeTypeIdentity;
1655
2015
  if (evidence !== undefined && (evidence.source !== field.nativeType || parsed?.namespace !== undefined && parsed.namespace !== evidence.namespace)) {
1656
- throw new VibeError9({ code: "VIBE_MIGRATION", message: "PostgreSQL type evidence no longer matches its declaration; recapture the independent reference", meta: { reason: "type-reference-invalid", shadowReusable: true, table: table.name, column } });
2016
+ throw new VibeError10({ code: "VIBE_MIGRATION", message: "PostgreSQL type evidence no longer matches its declaration; recapture the independent reference", meta: { reason: "type-reference-invalid", shadowReusable: true, table: table.name, column } });
1657
2017
  }
1658
2018
  const prior = previous?.fields.find((entry) => (entry.dbName ?? entry.name) === column && entry.isUnsupported === true);
1659
2019
  const priorType = parseUnsupportedPostgresType({ raw: prior?.nativeType ?? "" });
@@ -1663,7 +2023,7 @@ async function resolvePostgresTypeIdentities(params) {
1663
2023
  const source = params.mode === "catalog" ? { table, column } : { type: { name: parsed?.name ?? "", ...namespace === undefined && params.mode === "reference" ? {} : { namespace: namespace ?? "pg_catalog" } } };
1664
2024
  const query = renderPostgresTypeIdentity({ dialect: postgresDialect3, source });
1665
2025
  const rows = parsed === undefined && params.mode !== "catalog" ? [] : await params.executor({ text: query.text, values: [...query.values] }).then((rows) => rows, (cause) => {
1666
- throw new VibeError9({ code: "VIBE_MIGRATION", message: "PostgreSQL type identity inspection failed", meta: { reason: "type-inspection-unavailable", table: table.name, column }, cause });
2026
+ throw new VibeError10({ code: "VIBE_MIGRATION", message: "PostgreSQL type identity inspection failed", meta: { reason: "type-inspection-unavailable", table: table.name, column }, cause });
1667
2027
  });
1668
2028
  const resolved = rows.length === 1 ? rows[0] : undefined;
1669
2029
  namespace = typeof resolved?.namespace === "string" && (resolved.name === parsed?.name || parsed === undefined && params.mode === "catalog") ? resolved.namespace : undefined;
@@ -1681,11 +2041,11 @@ async function resolvePostgresTypeIdentities(params) {
1681
2041
  }
1682
2042
 
1683
2043
  // packages/migrate/src/introspect/postgres-rls.ts
1684
- import { VibeError as VibeError11, assertValidSchemaIR as assertValidSchemaIR3 } from "@vibeorm/schema";
2044
+ import { VibeError as VibeError12, assertValidSchemaIR as assertValidSchemaIR3 } from "@vibeorm/schema";
1685
2045
  import { NATIVE_RLS, postgresDialect as postgresDialect5, renderRlsPolicy as renderRlsPolicy2 } from "@vibeorm/sql";
1686
2046
 
1687
2047
  // packages/migrate/src/ddl/rls.ts
1688
- import { VibeError as VibeError10, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
2048
+ import { VibeError as VibeError11, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
1689
2049
  import * as sql from "@vibeorm/sql";
1690
2050
  function isRlsProtected(params) {
1691
2051
  return params.model.rls !== undefined && params.model.rls !== "unprotected";
@@ -1715,7 +2075,18 @@ function assertRlsDialect(params) {
1715
2075
  if (params.schema.rls === undefined && !params.schema.models.some((model) => model.rls !== undefined))
1716
2076
  return;
1717
2077
  if (!sql.DIALECT_CAPABILITIES[params.dialect].nativeRowLevelSecurity) {
1718
- throw unsupportedCapability5({ dialect: params.dialect, feature: "nativeRowLevelSecurity" });
2078
+ throw unsupportedCapability7({ dialect: params.dialect, feature: "nativeRowLevelSecurity" });
2079
+ }
2080
+ for (const model of params.schema.models) {
2081
+ if (model.namespace !== undefined && model.namespace !== "public" && isRlsProtected({ model }))
2082
+ throw unsupportedCapability7({ dialect: params.dialect, feature: "fixedNamespaceNativeRls", hint: "Named package schemas do not support native RLS placement in this contract" });
2083
+ const policy = model.rls;
2084
+ if (isRlsProtected({ model }) && typeof policy === "object" && "through" in policy) {
2085
+ const relation = model.relations.find((entry) => entry.name === policy.through);
2086
+ const parent = params.schema.models.find((entry) => entry.name === relation?.targetModel);
2087
+ if (parent?.namespace !== undefined && parent.namespace !== "public")
2088
+ throw unsupportedCapability7({ dialect: params.dialect, feature: "fixedNamespaceNativeRls" });
2089
+ }
1719
2090
  }
1720
2091
  assertValidSchemaIR2({ schema: params.schema });
1721
2092
  }
@@ -1723,8 +2094,9 @@ function withRlsSteps(params) {
1723
2094
  const { from, to, steps } = params;
1724
2095
  const before = orderedRlsModels({ schema: from });
1725
2096
  const after = orderedRlsModels({ schema: to });
1726
- const beforeByTable = new Map(before.map((model) => [model.dbName ?? model.name, model]));
1727
- const afterByTable = new Map(after.map((model) => [model.dbName ?? model.name, model]));
2097
+ const tableKey = (params) => physicalNameKey(params);
2098
+ const beforeByTable = new Map(before.map((model) => [tableKey({ name: model.dbName ?? model.name, namespace: model.namespace }), model]));
2099
+ const afterByTable = new Map(after.map((model) => [tableKey({ name: model.dbName ?? model.name, namespace: model.namespace }), model]));
1728
2100
  const changed = new Set;
1729
2101
  const policyChanges = new Set;
1730
2102
  for (const [table, model] of beforeByTable) {
@@ -1736,7 +2108,7 @@ function withRlsSteps(params) {
1736
2108
  }
1737
2109
  for (const step of steps) {
1738
2110
  if (step.kind === "dropTable" || step.kind === "dropColumn" || step.kind === "alterColumn" || step.kind === "dropForeignKey" || step.kind === "dropPrimaryKey")
1739
- changed.add(step.table);
2111
+ changed.add(tableKey({ name: step.table, namespace: step.namespace }));
1740
2112
  }
1741
2113
  for (const schema of [from, to]) {
1742
2114
  for (const model of orderedRlsModels({ schema })) {
@@ -1745,9 +2117,9 @@ function withRlsSteps(params) {
1745
2117
  const through = model.rls.through;
1746
2118
  const relation = model.relations.find((entry) => entry.name === through);
1747
2119
  const parent = schema.models.find((entry) => entry.name === relation?.targetModel);
1748
- if (parent !== undefined && changed.has(parent.dbName ?? parent.name)) {
1749
- changed.add(model.dbName ?? model.name);
1750
- policyChanges.add(model.dbName ?? model.name);
2120
+ if (parent !== undefined && changed.has(tableKey({ name: parent.dbName ?? parent.name, namespace: parent.namespace }))) {
2121
+ changed.add(tableKey({ name: model.dbName ?? model.name, namespace: model.namespace }));
2122
+ policyChanges.add(tableKey({ name: model.dbName ?? model.name, namespace: model.namespace }));
1751
2123
  }
1752
2124
  }
1753
2125
  }
@@ -1756,20 +2128,22 @@ function withRlsSteps(params) {
1756
2128
  const removals = [];
1757
2129
  for (const model of [...before].reverse()) {
1758
2130
  const table = model.dbName ?? model.name;
1759
- if (!changed.has(table))
2131
+ const key = tableKey({ name: table, namespace: model.namespace });
2132
+ if (!changed.has(key))
1760
2133
  continue;
1761
- const target = afterByTable.get(table);
1762
- const securityChange = target === undefined ? `remove native RLS protection from "${table}"` : policyChanges.has(table) && target.rls !== "deny" ? `replace native RLS policy on "${table}" (access may widen)` : undefined;
2134
+ const target = afterByTable.get(key);
2135
+ const securityChange = target === undefined ? `remove native RLS protection from "${table}"` : policyChanges.has(key) && target.rls !== "deny" ? `replace native RLS policy on "${table}" (access may widen)` : undefined;
1763
2136
  drops.push({ kind: "dropPolicy", table, destructive: false, ...securityChange === undefined ? {} : { securityChange } });
1764
- if (target === undefined && to.models.some((entry) => (entry.dbName ?? entry.name) === table)) {
2137
+ if (target === undefined && to.models.some((entry) => tableKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace }) === key)) {
1765
2138
  removals.push({ kind: "noForceRls", table, destructive: false }, { kind: "disableRls", table, destructive: false });
1766
2139
  }
1767
2140
  }
1768
2141
  for (const model of after) {
1769
2142
  const table = model.dbName ?? model.name;
1770
- if (beforeByTable.has(table) && !changed.has(table))
2143
+ const key = tableKey({ name: table, namespace: model.namespace });
2144
+ if (beforeByTable.has(key) && !changed.has(key))
1771
2145
  continue;
1772
- if (!beforeByTable.has(table))
2146
+ if (!beforeByTable.has(key))
1773
2147
  installs.push({ kind: "enableRls", table, destructive: false }, { kind: "forceRls", table, destructive: false });
1774
2148
  installs.push({ kind: "createPolicy", table, schema: to, model: model.name, destructive: false });
1775
2149
  }
@@ -1781,7 +2155,7 @@ function isRlsStep(params) {
1781
2155
  function renderRlsStep(params) {
1782
2156
  const { step, dialect } = params;
1783
2157
  if (!sql.DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity)
1784
- throw unsupportedCapability5({ dialect, feature: "nativeRowLevelSecurity" });
2158
+ throw unsupportedCapability7({ dialect, feature: "nativeRowLevelSecurity" });
1785
2159
  const common = { dialect: sql.postgresDialect, table: "table" in step ? step.table ?? "" : "" };
1786
2160
  switch (step.kind) {
1787
2161
  case "enableRls":
@@ -1797,11 +2171,11 @@ function renderRlsStep(params) {
1797
2171
  const renderers = sql;
1798
2172
  const renderer = renderers[name];
1799
2173
  if (renderer === undefined)
1800
- throw unsupportedCapability5({ dialect, feature: "nativeRowLevelSecurity", hint: `The SQL package must provide ${name} before this migration can execute` });
2174
+ throw unsupportedCapability7({ dialect, feature: "nativeRowLevelSecurity", hint: `The SQL package must provide ${name} before this migration can execute` });
1801
2175
  return [renderer(common).text];
1802
2176
  }
1803
2177
  default:
1804
- throw new VibeError10({ code: "VIBE_MIGRATION", message: "Expected a native RLS migration step" });
2178
+ throw new VibeError11({ code: "VIBE_MIGRATION", message: "Expected a native RLS migration step" });
1805
2179
  }
1806
2180
  }
1807
2181
  function securityChangesOf(params) {
@@ -1810,7 +2184,7 @@ function securityChangesOf(params) {
1810
2184
  function assertSecurityAcknowledgement(params) {
1811
2185
  if (params.securityChanges.length === 0 || params.acceptSecurityChanges === true)
1812
2186
  return;
1813
- throw new VibeError10({
2187
+ throw new VibeError11({
1814
2188
  code: "VIBE_MIGRATION",
1815
2189
  message: `Security changes require acceptSecurityChanges: true; acceptDataLoss does not authorize them:
1816
2190
  ${params.securityChanges.join(`
@@ -1849,7 +2223,7 @@ async function readPostgresRls(params) {
1849
2223
  return [...tables.values()];
1850
2224
  }
1851
2225
  function conflict(params) {
1852
- throw new VibeError11({
2226
+ throw new VibeError12({
1853
2227
  code: "VIBE_MIGRATION",
1854
2228
  message: `Native RLS on public.${params.table} cannot be managed safely (${params.attributes.join(", ")}). Review the live policies and restore the deployed declaration, or remove conflicting policies explicitly with administrator credentials. Supply rlsSchema/previousSchema for the deployed policy; no policy is deleted or adopted automatically.`,
1855
2229
  meta: params
@@ -1860,10 +2234,11 @@ async function verifyPostgresRls(params) {
1860
2234
  if (schema !== undefined && schema.rls !== undefined)
1861
2235
  assertValidSchemaIR3({ schema });
1862
2236
  await executor({ text: "SET LOCAL search_path = ''" });
1863
- const declared = new Map((schema?.models ?? []).map((model) => [model.dbName ?? model.name, model]));
2237
+ const declared = (schema?.models ?? []).filter((model) => model.namespace === undefined || model.namespace === "public");
2238
+ const byTable = new Map(declared.map((model) => [model.dbName ?? model.name, model]));
1864
2239
  let tables = await readPostgresRls({ executor });
1865
2240
  for (const state of tables) {
1866
- const model = declared.get(state.table);
2241
+ const model = byTable.get(state.table);
1867
2242
  if (!state.enabled && !state.force && state.policies.length === 0 && (model === undefined || !isRlsProtected({ model })))
1868
2243
  continue;
1869
2244
  await executor({ text: `LOCK TABLE "public".${postgresDialect5.quoteIdent({ name: state.table })} IN ACCESS EXCLUSIVE MODE` });
@@ -1871,7 +2246,7 @@ async function verifyPostgresRls(params) {
1871
2246
  tables = await readPostgresRls({ executor });
1872
2247
  const matched = new Set;
1873
2248
  for (const state of tables) {
1874
- const model = declared.get(state.table);
2249
+ const model = byTable.get(state.table);
1875
2250
  const active = state.enabled || state.force || state.policies.length > 0;
1876
2251
  if (schema === undefined && active)
1877
2252
  conflict({ table: state.table, reason: "rls-unmanaged", attributes: ["security metadata cannot be exported losslessly without a trusted declaration"] });
@@ -1918,7 +2293,7 @@ async function verifyPostgresRls(params) {
1918
2293
  matched.add(state.table);
1919
2294
  }
1920
2295
  if (params.requireInstalled === true) {
1921
- for (const [table, model] of declared) {
2296
+ for (const [table, model] of byTable) {
1922
2297
  if (isRlsProtected({ model }) && !matched.has(table))
1923
2298
  conflict({ table, reason: "rls-drift", attributes: ["missing table"] });
1924
2299
  }
@@ -1930,8 +2305,9 @@ function projectPostgresRls(params) {
1930
2305
  if (declared?.rls === undefined || matched.size === 0)
1931
2306
  return physical;
1932
2307
  const models = physical.models.map((model) => {
1933
- const source = declared.models.find((entry) => (entry.dbName ?? entry.name) === model.name);
1934
- if (source === undefined || !matched.has(model.name))
2308
+ const physicalName = model.dbName ?? model.name;
2309
+ const source = declared.models.find((entry) => (entry.dbName ?? entry.name) === physicalName && (entry.namespace ?? "public") === (model.namespace ?? "public"));
2310
+ if (source === undefined || model.namespace !== undefined && model.namespace !== "public" || !matched.has(physicalName))
1935
2311
  return { ...model, rls: "unprotected" };
1936
2312
  const sourcePolicy = source.rls;
1937
2313
  let rls = "deny";
@@ -1942,7 +2318,8 @@ function projectPostgresRls(params) {
1942
2318
  const target = declared.models.find((entry) => entry.name === sourceRelation.targetModel);
1943
2319
  const fields = sourceRelation.fields.map((name) => source.fields.find((entry) => entry.name === name)?.dbName ?? name);
1944
2320
  const references = sourceRelation.references.map((name) => target.fields.find((entry) => entry.name === name)?.dbName ?? name);
1945
- const relation = model.relations.find((entry) => entry.targetModel === (target.dbName ?? target.name) && JSON.stringify(entry.fields) === JSON.stringify(fields) && JSON.stringify(entry.references) === JSON.stringify(references));
2321
+ const physicalTarget = physical.models.find((entry) => (entry.dbName ?? entry.name) === (target.dbName ?? target.name) && (entry.namespace ?? "public") === (target.namespace ?? "public"));
2322
+ const relation = model.relations.find((entry) => entry.targetModel === physicalTarget?.name && JSON.stringify(entry.fields) === JSON.stringify(fields) && JSON.stringify(entry.references) === JSON.stringify(references));
1946
2323
  if (relation === undefined)
1947
2324
  conflict({ table: model.name, reason: "rls-drift", attributes: ["through foreign key"] });
1948
2325
  rls = { through: relation.name };
@@ -1953,146 +2330,6 @@ function projectPostgresRls(params) {
1953
2330
  }
1954
2331
 
1955
2332
  // packages/migrate/src/introspect/postgres.ts
1956
- var EXTENSION_OWNED_FILTER = `NOT EXISTS (
1957
- SELECT 1
1958
- FROM pg_catalog.pg_depend d
1959
- JOIN pg_catalog.pg_class c ON c.oid = d.objid
1960
- JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1961
- WHERE d.classid = 'pg_class'::regclass
1962
- AND d.deptype = 'e'
1963
- AND n.nspname = 'public'
1964
- AND c.relname = table_name
1965
- )`;
1966
- var TABLES_QUERY = `
1967
- SELECT table_name
1968
- FROM information_schema.tables
1969
- WHERE table_schema = 'public'
1970
- AND table_type = 'BASE TABLE'
1971
- AND table_name <> '${MIGRATIONS_TABLE_NAME}'
1972
- AND ${EXTENSION_OWNED_FILTER}
1973
- ORDER BY table_name;
1974
- `;
1975
- var VIEWS_QUERY = `
1976
- SELECT table_name
1977
- FROM information_schema.views
1978
- WHERE table_schema = 'public'
1979
- AND ${EXTENSION_OWNED_FILTER}
1980
- ORDER BY table_name;
1981
- `;
1982
- var COLUMNS_QUERY = `
1983
- SELECT
1984
- table_name,
1985
- column_name,
1986
- data_type,
1987
- udt_name,
1988
- is_nullable,
1989
- is_identity,
1990
- is_generated,
1991
- collation_name,
1992
- column_default,
1993
- datetime_precision,
1994
- character_maximum_length,
1995
- numeric_precision,
1996
- numeric_scale,
1997
- ordinal_position,
1998
- (
1999
- SELECT format_type(a.atttypid, a.atttypmod)
2000
- FROM pg_attribute a
2001
- JOIN pg_class c ON c.oid = a.attrelid
2002
- JOIN pg_namespace n ON n.oid = c.relnamespace
2003
- WHERE n.nspname = 'public'
2004
- AND c.relname = columns.table_name
2005
- AND a.attname = columns.column_name
2006
- ) AS full_type
2007
- FROM information_schema.columns AS columns
2008
- WHERE table_schema = 'public'
2009
- ORDER BY table_name, ordinal_position;
2010
- `;
2011
- var CONSTRAINTS_QUERY = `
2012
- SELECT
2013
- c.conname AS constraint_name,
2014
- CASE c.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type,
2015
- cl.relname AS table_name,
2016
- att.attname AS column_name
2017
- FROM pg_catalog.pg_constraint c
2018
- JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid
2019
- JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
2020
- JOIN unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
2021
- JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = k.attnum
2022
- WHERE c.contype IN ('p', 'u')
2023
- AND n.nspname = 'public'
2024
- ORDER BY cl.relname, c.conname, k.ord;
2025
- `;
2026
- var CHECKS_QUERY = `
2027
- SELECT
2028
- cl.relname AS table_name,
2029
- c.conname AS constraint_name,
2030
- pg_get_constraintdef(c.oid) AS definition
2031
- FROM pg_catalog.pg_constraint c
2032
- JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid
2033
- JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
2034
- WHERE c.contype = 'c'
2035
- AND n.nspname = 'public'
2036
- ORDER BY cl.relname, c.conname;
2037
- `;
2038
- var FOREIGN_KEYS_QUERY = `
2039
- SELECT
2040
- c.conname AS constraint_name,
2041
- cl.relname AS table_name,
2042
- att.attname AS column_name,
2043
- fcl.relname AS foreign_table_name,
2044
- fatt.attname AS foreign_column_name,
2045
- c.confdeltype AS on_delete_char,
2046
- c.confupdtype AS on_update_char,
2047
- c.convalidated AS validated,
2048
- c.condeferrable AS deferrable,
2049
- (SELECT bool_and(t.tgenabled IN ('O', 'A')) FROM pg_catalog.pg_trigger t WHERE t.tgconstraint = c.oid) AS triggers_enabled
2050
- FROM pg_catalog.pg_constraint c
2051
- JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid
2052
- JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
2053
- JOIN pg_catalog.pg_class fcl ON fcl.oid = c.confrelid
2054
- JOIN pg_catalog.pg_namespace fn ON fn.oid = fcl.relnamespace
2055
- JOIN unnest(c.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true
2056
- JOIN unnest(c.confkey) WITH ORDINALITY AS rk(attnum, ord) ON lk.ord = rk.ord
2057
- JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = lk.attnum
2058
- JOIN pg_catalog.pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = rk.attnum
2059
- WHERE c.contype = 'f'
2060
- AND n.nspname = 'public'
2061
- AND fn.nspname = 'public'
2062
- ORDER BY cl.relname, c.conname, lk.ord;
2063
- `;
2064
- var ENUMS_QUERY = `
2065
- SELECT
2066
- t.typname AS type_name,
2067
- e.enumlabel AS enum_value,
2068
- e.enumsortorder AS sort_order
2069
- FROM pg_type t
2070
- JOIN pg_enum e ON t.oid = e.enumtypid
2071
- JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
2072
- WHERE n.nspname = 'public'
2073
- ORDER BY t.typname, e.enumsortorder;
2074
- `;
2075
- var INDEXES_QUERY = `
2076
- SELECT
2077
- tablename AS table_name,
2078
- indexname AS index_name,
2079
- indexdef AS index_def,
2080
- con.conname AS backing_constraint,
2081
- con.contype AS backing_constraint_type,
2082
- health.indisvalid AS valid,
2083
- health.indisready AS ready,
2084
- health.indimmediate AS immediate,
2085
- health.indexprs IS NULL AS plain_columns,
2086
- health.indnkeyatts AS key_count,
2087
- health.indnatts AS attribute_count
2088
- FROM pg_indexes ix
2089
- JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname
2090
- JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname
2091
- JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid
2092
- LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x')
2093
- WHERE schemaname = 'public'
2094
- ORDER BY tablename, indexname;
2095
- `;
2096
2333
  async function introspectPostgres(params) {
2097
2334
  const { executor } = params;
2098
2335
  if (params.inTransaction !== true)
@@ -2101,14 +2338,14 @@ async function introspectPostgres(params) {
2101
2338
  const savedPath = await executor(renderRlsReadinessQuery({ dialect: postgresDialect6, query: "readSearchPath" }));
2102
2339
  const searchPath = savedPath[0]?.searchPath;
2103
2340
  if (typeof searchPath !== "string")
2104
- throw new VibeError12({ code: "VIBE_MIGRATION", message: "Cannot read the migration search_path" });
2341
+ throw new VibeError13({ code: "VIBE_MIGRATION", message: "Cannot read the migration search_path" });
2105
2342
  const matched = await verifyPostgresRls({ executor, ...params.rlsSchema === undefined ? {} : { schema: params.rlsSchema }, ...params.requireInstalledRls === undefined ? {} : { requireInstalled: params.requireInstalledRls } }).then((verified) => verified, (error) => {
2106
- if (error instanceof VibeError12)
2343
+ if (error instanceof VibeError13)
2107
2344
  throw error;
2108
- throw new VibeError12({ code: "VIBE_MIGRATION", message: "Native RLS verification could not complete. Check migration privileges and supply the deployed schema as previousSchema/rlsSchema before changing referenced columns.", meta: { reason: "rls-verification-unavailable" }, cause: error });
2345
+ throw new VibeError13({ code: "VIBE_MIGRATION", message: "Native RLS verification could not complete. Check migration privileges and supply the deployed schema as previousSchema/rlsSchema before changing referenced columns.", meta: { reason: "rls-verification-unavailable" }, cause: error });
2109
2346
  });
2110
2347
  await executor(renderRlsReadinessQuery({ dialect: postgresDialect6, query: "setSearchPath", searchPath }));
2111
- const physical = await introspectPhysicalPostgres({ executor });
2348
+ const physical = await introspectPhysicalPostgres({ executor, namespaces: params.namespaces ?? ["public", ...params.rlsSchema?.models.flatMap((model) => model.namespace === undefined ? [] : [model.namespace]) ?? []] });
2112
2349
  return projectPostgresRls({ physical, ...params.rlsSchema === undefined ? {} : { declared: params.rlsSchema }, matched });
2113
2350
  } finally {
2114
2351
  if (params.inTransaction !== true)
@@ -2117,17 +2354,51 @@ async function introspectPostgres(params) {
2117
2354
  }
2118
2355
  async function introspectPhysicalPostgres(params) {
2119
2356
  const { executor } = params;
2357
+ const namespaces = [...new Set(params.namespaces ?? ["public"])];
2358
+ const physicalNames = new Map;
2359
+ const enumIdentities = new Map;
2360
+ const namespaceObjects = [];
2361
+ const read = async (query) => {
2362
+ const groups = await Promise.all(namespaces.map(async (namespace) => {
2363
+ assertSqlNamespace2({ namespace, dialect: "postgres" });
2364
+ const sql = renderPostgresPhysicalCatalog({ dialect: postgresDialect6, namespace, query });
2365
+ const rows = await executor({ text: sql.text, values: [...sql.values] });
2366
+ return rows.map((row) => {
2367
+ const name = typeof row.table_name === "string" ? row.table_name : undefined;
2368
+ const table = name === undefined ? undefined : physicalNameKey({ namespace, name });
2369
+ if (table !== undefined && name !== undefined)
2370
+ physicalNames.set(table, { namespace, name });
2371
+ const typeName = typeof row.type_name === "string" ? row.type_name : undefined;
2372
+ const enumKey = typeName === undefined ? undefined : physicalNameKey({ namespace, name: typeName });
2373
+ if (enumKey !== undefined && typeName !== undefined)
2374
+ enumIdentities.set(enumKey, { namespace, name: typeName });
2375
+ return { ...row, ...table === undefined ? {} : { table_name: table }, ...enumKey === undefined ? {} : { type_name: enumKey }, ...typeof row.foreign_table_name === "string" ? { foreign_table_name: physicalNameKey({ namespace: typeof row.foreign_namespace === "string" ? row.foreign_namespace : namespace, name: row.foreign_table_name }) } : {} };
2376
+ });
2377
+ }));
2378
+ return groups.flat();
2379
+ };
2380
+ for (const namespace of namespaces) {
2381
+ if (namespace === "public")
2382
+ continue;
2383
+ const query = renderMigrationObjectQuery({ dialect: postgresDialect6, identity: { kind: "namespace", name: namespace } });
2384
+ if ((await executor({ text: query.text, values: [...query.values] })).length > 0)
2385
+ namespaceObjects.push({ id: `namespace:${namespace}`, dialect: "postgres", identity: { kind: "namespace", name: namespace }, create: [], drop: [], dependsOn: [] });
2386
+ }
2120
2387
  const [tableRows, viewRows, columnRows, constraintRows, fkRows, enumRows, indexRows, checkRows, exclusionRows] = await Promise.all([
2121
- executor({ text: TABLES_QUERY }),
2122
- executor({ text: VIEWS_QUERY }),
2123
- executor({ text: COLUMNS_QUERY }),
2124
- executor({ text: CONSTRAINTS_QUERY }),
2125
- executor({ text: FOREIGN_KEYS_QUERY }),
2126
- executor({ text: ENUMS_QUERY }),
2127
- executor({ text: INDEXES_QUERY }),
2128
- executor({ text: CHECKS_QUERY }),
2129
- executor({ text: renderExclusionCatalog2({ dialect: postgresDialect6 }) })
2388
+ read("tables"),
2389
+ read("views"),
2390
+ read("columns"),
2391
+ read("constraints"),
2392
+ read("foreignKeys"),
2393
+ read("enums"),
2394
+ read("indexes"),
2395
+ read("checks"),
2396
+ 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())
2130
2397
  ]);
2398
+ const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6 });
2399
+ const indexKeys = await executor({ text: keysQuery.text, values: [...keysQuery.values] });
2400
+ 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))
2401
+ throw new VibeError13({ code: "VIBE_MIGRATION", message: "PostgreSQL index-key identity is incomplete", meta: { reason: "index-inspection-unavailable" } });
2131
2402
  const tables = tableRows.map((row) => row.table_name);
2132
2403
  const views = viewRows.map((row) => row.table_name);
2133
2404
  const columns = columnRows.map((row) => ({
@@ -2135,6 +2406,7 @@ async function introspectPhysicalPostgres(params) {
2135
2406
  name: row.column_name,
2136
2407
  dataType: row.data_type,
2137
2408
  udtName: row.udt_name,
2409
+ udtNamespace: typeof row.udt_schema === "string" ? row.udt_schema : "public",
2138
2410
  nullable: row.is_nullable === "YES",
2139
2411
  identity: row.is_identity === "YES",
2140
2412
  columnDefault: row.column_default ?? null,
@@ -2167,7 +2439,7 @@ async function introspectPhysicalPostgres(params) {
2167
2439
  const table = row.table_name;
2168
2440
  const constraintName = row.constraint_name;
2169
2441
  const columnName = row.column_name;
2170
- constraintNames.add(constraintName);
2442
+ constraintNames.add(JSON.stringify([table, constraintName]));
2171
2443
  if (row.constraint_type === "PRIMARY KEY") {
2172
2444
  pkNames.set(table, constraintName);
2173
2445
  const list = pkByTable.get(table);
@@ -2176,9 +2448,10 @@ async function introspectPhysicalPostgres(params) {
2176
2448
  else
2177
2449
  list.push(columnName);
2178
2450
  } else {
2179
- const entry = uniquesByConstraint.get(constraintName);
2451
+ const constraintKey = JSON.stringify([table, constraintName]);
2452
+ const entry = uniquesByConstraint.get(constraintKey);
2180
2453
  if (entry === undefined)
2181
- uniquesByConstraint.set(constraintName, { table, columns: [columnName] });
2454
+ uniquesByConstraint.set(constraintKey, { table, name: constraintName, columns: [columnName] });
2182
2455
  else
2183
2456
  entry.columns.push(columnName);
2184
2457
  }
@@ -2225,17 +2498,35 @@ async function introspectPhysicalPostgres(params) {
2225
2498
  for (const row of indexRows) {
2226
2499
  if (row.backing_constraint_type === "x")
2227
2500
  continue;
2228
- const parsed = parseIndexDef({
2229
- table: row.table_name,
2230
- name: row.index_name,
2231
- def: row.index_def
2232
- });
2233
- if (parsed !== null)
2234
- indexes.push(parsed);
2501
+ 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);
2502
+ if (row.plain_columns === true && operands.length !== Number(row.key_count))
2503
+ 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 } });
2504
+ if (operands.length > 0) {
2505
+ if (operands.every((key) => typeof key.column_name === "string"))
2506
+ indexes.push({
2507
+ table: String(row.table_name),
2508
+ name: String(row.index_name),
2509
+ unique: operands[0].unique === true,
2510
+ kind: String(operands[0].method),
2511
+ columns: operands.map((key) => ({ name: String(key.column_name), desc: key.descending === true, ...key.exact_default === true ? {} : { operatorClass: { namespace: String(key.operator_namespace), name: String(key.operator_name) } } })),
2512
+ ...typeof operands[0].predicate === "string" ? { where: operands[0].predicate } : {},
2513
+ ...operands[0].nulls_not_distinct === true ? { nullsNotDistinct: true } : {}
2514
+ });
2515
+ else {
2516
+ const parsed = parseIndexDef({ table: String(row.table_name), name: String(row.index_name), def: String(row.index_def) });
2517
+ if (parsed !== null)
2518
+ indexes.push(parsed);
2519
+ }
2520
+ } else {
2521
+ const parsed = parseIndexDef({ table: String(row.table_name), name: String(row.index_name), def: String(row.index_def) });
2522
+ if (parsed !== null)
2523
+ indexes.push(parsed);
2524
+ }
2235
2525
  }
2236
2526
  const joinTables = new Map;
2237
2527
  for (const table of tables) {
2238
- if (!table.startsWith("_"))
2528
+ const physicalTable = physicalNames.get(table);
2529
+ if (!physicalTable.name.startsWith("_"))
2239
2530
  continue;
2240
2531
  const tableFks = fkRows.filter((row) => row.table_name === table);
2241
2532
  const tableIndexes = indexRows.filter((row) => row.table_name === table);
@@ -2251,14 +2542,16 @@ async function introspectPhysicalPostgres(params) {
2251
2542
  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;
2252
2543
  }))
2253
2544
  continue;
2545
+ if (tableFks.some((row) => row.foreign_namespace !== undefined && row.foreign_namespace !== physicalTable.namespace))
2546
+ continue;
2254
2547
  const detected = detectJoinTable({
2255
- table,
2548
+ table: physicalTable.name,
2256
2549
  columnNames: columns.filter((column) => column.table === table).map((column) => column.name),
2257
- fkGroups,
2258
- indexes
2550
+ fkGroups: fkGroups.filter((group) => group.table === table).map((group) => ({ ...group, table: physicalTable.name, refTable: physicalNames.get(group.refTable)?.name ?? group.refTable })),
2551
+ indexes: indexes.filter((index) => index.table === table).map((index) => ({ ...index, table: physicalTable.name }))
2259
2552
  });
2260
2553
  if (detected !== null)
2261
- joinTables.set(table, detected);
2554
+ 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 }) });
2262
2555
  }
2263
2556
  const models = [];
2264
2557
  for (const table of tables) {
@@ -2266,6 +2559,7 @@ async function introspectPhysicalPostgres(params) {
2266
2559
  continue;
2267
2560
  models.push(buildModel({
2268
2561
  table,
2562
+ physicalTable: physicalNames.get(table).name,
2269
2563
  isView: false,
2270
2564
  columns,
2271
2565
  pkByTable,
@@ -2281,6 +2575,7 @@ async function introspectPhysicalPostgres(params) {
2281
2575
  for (const view of views) {
2282
2576
  models.push(buildModel({
2283
2577
  table: view,
2578
+ physicalTable: physicalNames.get(view).name,
2284
2579
  isView: true,
2285
2580
  columns,
2286
2581
  pkByTable,
@@ -2294,14 +2589,59 @@ async function introspectPhysicalPostgres(params) {
2294
2589
  }
2295
2590
  addFkRelations({ models, fkGroups, joinTables });
2296
2591
  addJoinTableRelations({ models, joinTables });
2592
+ const logicalNames = new Set([...physicalNames.values(), ...enumIdentities.values()].map((entry) => entry.name));
2593
+ const logicalName = (params) => {
2594
+ if (!params.duplicates && /^[$_\p{ID_Start}][$_\u200c\u200d\p{ID_Continue}]*$/u.test(params.name) && !isReservedClientName({ name: params.name.charAt(0).toLowerCase() + params.name.slice(1) }))
2595
+ return params.name;
2596
+ const hex = Array.from(new TextEncoder().encode(params.key), (byte) => byte.toString(16).padStart(2, "0")).join("");
2597
+ let name = `VibePhysical_${hex}`;
2598
+ while (logicalNames.has(name))
2599
+ name += "_";
2600
+ logicalNames.add(name);
2601
+ return name;
2602
+ };
2603
+ const modelNames = new Map(models.map((model) => {
2604
+ const identity = physicalNames.get(model.name);
2605
+ const duplicates = [...physicalNames.values()].filter((entry) => entry.name === identity.name).length > 1;
2606
+ return [model.name, logicalName({ key: model.name, name: identity.name, duplicates })];
2607
+ }));
2608
+ const enumLogicalNames = new Map(enums.map((entry) => {
2609
+ const identity = enumIdentities.get(entry.name);
2610
+ const duplicates = [...enumIdentities.values()].filter((candidate) => candidate.name === identity.name).length > 1;
2611
+ return [entry.name, logicalName({ key: entry.name, name: identity.name, duplicates })];
2612
+ }));
2613
+ const qualifiedModels = models.map((model) => {
2614
+ const identity = physicalNames.get(model.name);
2615
+ const name = modelNames.get(model.name);
2616
+ const { dbName: _physicalName, ...metadata } = model;
2617
+ return {
2618
+ ...metadata,
2619
+ name,
2620
+ ...name === identity.name ? {} : { dbName: identity.name },
2621
+ ...identity.namespace === "public" ? {} : { namespace: identity.namespace },
2622
+ fields: model.fields.map((field) => {
2623
+ if (typeof field.type === "string")
2624
+ return field;
2625
+ const identity = enumIdentities.get(field.type.enum);
2626
+ return { ...field, type: { enum: enumLogicalNames.get(field.type.enum), ...identity.namespace === "public" ? {} : { namespace: identity.namespace } } };
2627
+ }),
2628
+ relations: model.relations.map((relation) => ({ ...relation, name: /^[$_\p{ID_Start}][$_\u200c\u200d\p{ID_Continue}]*$/u.test(relation.name) ? relation.name : `relation_${Array.from(new TextEncoder().encode(relation.name), (byte) => byte.toString(16).padStart(2, "0")).join("")}`, model: name, targetModel: modelNames.get(relation.targetModel) ?? relation.targetModel }))
2629
+ };
2630
+ });
2631
+ const qualifiedEnums = enums.map((entry) => {
2632
+ const identity = enumIdentities.get(entry.name);
2633
+ const name = enumLogicalNames.get(entry.name);
2634
+ return { ...entry, name, ...name === identity.name ? {} : { dbName: identity.name }, ...identity.namespace === "public" ? {} : { namespace: identity.namespace } };
2635
+ });
2297
2636
  const resolved = await resolvePostgresTypeIdentities({ executor, mode: "catalog", schema: {
2298
2637
  datasource: { provider: "postgresql" },
2299
- models,
2300
- enums,
2301
- extensions: []
2638
+ models: qualifiedModels,
2639
+ enums: qualifiedEnums,
2640
+ extensions: [],
2641
+ ...namespaceObjects.length === 0 ? {} : { migrationObjects: namespaceObjects }
2302
2642
  } });
2303
2643
  if (resolved.unavailable.length > 0)
2304
- throw new VibeError12({ code: "VIBE_MIGRATION", message: "PostgreSQL column type identity could not be read", meta: { reason: "type-inspection-unavailable", columns: resolved.unavailable } });
2644
+ throw new VibeError13({ code: "VIBE_MIGRATION", message: "PostgreSQL column type identity could not be read", meta: { reason: "type-inspection-unavailable", columns: resolved.unavailable } });
2305
2645
  return resolved.schema;
2306
2646
  }
2307
2647
  function buildModel(params) {
@@ -2310,18 +2650,19 @@ function buildModel(params) {
2310
2650
  const pk = pkByTable.get(table) ?? [];
2311
2651
  const uniqueSingles = new Set;
2312
2652
  const multiUniques = [];
2313
- for (const [name, unique] of uniquesByConstraint) {
2653
+ for (const unique of uniquesByConstraint.values()) {
2654
+ const name = unique.name;
2314
2655
  if (unique.table !== table)
2315
2656
  continue;
2316
2657
  if (unique.columns.length === 1)
2317
2658
  uniqueSingles.add(unique.columns[0]);
2318
2659
  multiUniques.push({ name, columns: unique.columns });
2319
2660
  }
2320
- const tableIndexes = indexes.filter((index) => index.table === table && !constraintNames.has(index.name));
2661
+ const tableIndexes = indexes.filter((index) => index.table === table && !constraintNames.has(JSON.stringify([table, index.name])));
2321
2662
  for (const index of tableIndexes) {
2322
2663
  if (!index.unique)
2323
2664
  continue;
2324
- if (index.columns.length === 1 && index.where === undefined && index.nullsNotDistinct !== true) {
2665
+ if (index.columns.length === 1 && index.where === undefined && index.nullsNotDistinct !== true && index.columns[0]?.operatorClass === undefined) {
2325
2666
  uniqueSingles.add(index.columns[0].name);
2326
2667
  }
2327
2668
  }
@@ -2336,7 +2677,7 @@ function buildModel(params) {
2336
2677
  });
2337
2678
  }
2338
2679
  for (const index of tableIndexes) {
2339
- if (index.unique && index.columns.length === 1 && index.where === undefined && index.nullsNotDistinct !== true && index.name === `${table}_${index.columns[0].name}_key`) {
2680
+ if (index.unique && index.columns.length === 1 && index.where === undefined && index.nullsNotDistinct !== true && index.columns[0]?.operatorClass === undefined && index.name === `${params.physicalTable}_${index.columns[0].name}_key`) {
2340
2681
  continue;
2341
2682
  }
2342
2683
  modelIndexes.push({
@@ -2344,7 +2685,8 @@ function buildModel(params) {
2344
2685
  kind: index.unique ? "unique" : index.kind,
2345
2686
  fields: index.columns.map((column) => ({
2346
2687
  name: column.name,
2347
- ...column.desc ? { sort: "desc" } : {}
2688
+ ...column.desc ? { sort: "desc" } : {},
2689
+ ...column.operatorClass === undefined ? {} : { operatorClass: column.operatorClass }
2348
2690
  })),
2349
2691
  ...index.where === undefined ? {} : { where: index.where },
2350
2692
  ...index.nullsNotDistinct === true ? { nullsNotDistinct: true } : {}
@@ -2352,6 +2694,7 @@ function buildModel(params) {
2352
2694
  }
2353
2695
  return {
2354
2696
  name: table,
2697
+ dbName: params.physicalTable,
2355
2698
  fields,
2356
2699
  relations: [],
2357
2700
  indexes: modelIndexes,
@@ -2389,8 +2732,9 @@ function reverseMapType(params) {
2389
2732
  const { column, enumNames } = params;
2390
2733
  const isList = column.dataType === "ARRAY";
2391
2734
  const udt = isList ? column.udtName.replace(/^_/, "") : column.udtName;
2392
- if (enumNames.has(udt)) {
2393
- return { type: { enum: udt }, isList };
2735
+ const enumKey = physicalNameKey({ namespace: column.udtNamespace, name: udt });
2736
+ if (enumNames.has(enumKey)) {
2737
+ return { type: { enum: enumKey }, isList };
2394
2738
  }
2395
2739
  const canonical = CANONICAL_UDT[udt];
2396
2740
  if (canonical !== undefined) {
@@ -2412,8 +2756,9 @@ function reverseMapType(params) {
2412
2756
  function nonCanonicalModifier(params) {
2413
2757
  const { column, udt, scalar } = params;
2414
2758
  if (column.dataType === "ARRAY" && column.fullType !== null) {
2415
- const element = normalizeNativeType({ raw: column.fullType }).replace(/\[\]$/, "");
2416
- if (/^(numeric|timestamptz)\([\d,]+\)$/.test(element))
2759
+ const temporal = parseUnsupportedPostgresType({ raw: column.fullType });
2760
+ const element = temporal?.name === "timestamptz" ? `timestamptz${temporal.modifier}` : normalizeNativeType({ raw: column.fullType }).replace(/\[\]$/, "");
2761
+ if (element !== "timestamptz(6)" && /^(numeric|timestamptz)\([\d,]+\)$/.test(element))
2417
2762
  return element;
2418
2763
  }
2419
2764
  if (scalar === "DateTime" && udt === "timestamptz") {
@@ -2430,9 +2775,9 @@ function nonCanonicalModifier(params) {
2430
2775
  }
2431
2776
  function withModifier(params) {
2432
2777
  const { column, udt } = params;
2433
- if (udt === "varchar" && column.dataType === "ARRAY" && column.fullType !== null) {
2778
+ if ((udt === "varchar" || udt === "bpchar" || udt === "bit" || udt === "varbit") && column.dataType === "ARRAY" && column.fullType !== null) {
2434
2779
  const elementType = normalizeNativeType({ raw: column.fullType }).replace(/\[\]$/, "");
2435
- if (/^varchar\(\d+\)$/.test(elementType))
2780
+ if (/^(varchar|char|bit|varbit)\(\d+\)$/.test(elementType))
2436
2781
  return elementType;
2437
2782
  }
2438
2783
  if ((udt === "varchar" || udt === "bpchar") && column.charMaxLength !== null) {
@@ -2442,6 +2787,10 @@ function withModifier(params) {
2442
2787
  return "char";
2443
2788
  if ((udt === "bit" || udt === "varbit") && column.charMaxLength !== null)
2444
2789
  return `${udt}(${column.charMaxLength})`;
2790
+ if ((udt === "timestamp" || udt === "time" || udt === "timetz") && column.dataType === "ARRAY" && column.fullType !== null) {
2791
+ const element = parseUnsupportedPostgresType({ raw: column.fullType });
2792
+ return element === undefined || element.modifier === "" || element.modifier === "(6)" ? udt : `${udt}${element.modifier}`;
2793
+ }
2445
2794
  if ((udt === "timestamp" || udt === "time" || udt === "timetz") && column.datetimePrecision !== null && column.datetimePrecision !== 6) {
2446
2795
  return `${udt}(${column.datetimePrecision})`;
2447
2796
  }
@@ -2596,7 +2945,12 @@ function toNumberOrNull(value) {
2596
2945
  // packages/migrate/src/introspect/mysql.ts
2597
2946
  import { exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
2598
2947
  import { mysqlDialect as mysqlDialect2 } from "@vibeorm/sql";
2599
- var TABLES_QUERY2 = `
2948
+
2949
+ // packages/migrate/src/journal-table.ts
2950
+ var MIGRATIONS_TABLE_NAME = "_vibe_migrations";
2951
+
2952
+ // packages/migrate/src/introspect/mysql.ts
2953
+ var TABLES_QUERY = `
2600
2954
  SELECT table_name, engine
2601
2955
  FROM information_schema.tables
2602
2956
  WHERE table_schema = DATABASE()
@@ -2604,13 +2958,13 @@ var TABLES_QUERY2 = `
2604
2958
  AND table_name <> '${MIGRATIONS_TABLE_NAME}'
2605
2959
  ORDER BY table_name;
2606
2960
  `;
2607
- var VIEWS_QUERY2 = `
2961
+ var VIEWS_QUERY = `
2608
2962
  SELECT table_name
2609
2963
  FROM information_schema.views
2610
2964
  WHERE table_schema = DATABASE()
2611
2965
  ORDER BY table_name;
2612
2966
  `;
2613
- var COLUMNS_QUERY2 = `
2967
+ var COLUMNS_QUERY = `
2614
2968
  SELECT
2615
2969
  table_name,
2616
2970
  column_name,
@@ -2641,7 +2995,7 @@ var STATISTICS_QUERY = `
2641
2995
  WHERE table_schema = DATABASE()
2642
2996
  ORDER BY table_name, index_name, seq_in_index;
2643
2997
  `;
2644
- var FOREIGN_KEYS_QUERY2 = `
2998
+ var FOREIGN_KEYS_QUERY = `
2645
2999
  SELECT
2646
3000
  constraint_name,
2647
3001
  table_name,
@@ -2661,7 +3015,7 @@ var REFERENTIAL_QUERY = `
2661
3015
  FROM information_schema.referential_constraints
2662
3016
  WHERE constraint_schema = DATABASE();
2663
3017
  `;
2664
- var CHECKS_QUERY2 = `
3018
+ var CHECKS_QUERY = `
2665
3019
  SELECT
2666
3020
  tc.table_name AS table_name,
2667
3021
  tc.constraint_name AS constraint_name,
@@ -2688,13 +3042,13 @@ function catalogField(params) {
2688
3042
  async function introspectMysql(params) {
2689
3043
  const { executor } = params;
2690
3044
  const [tableRows, viewRows, columnRows, statisticRows, fkRows, refRows, checkRows] = await Promise.all([
2691
- executor({ text: TABLES_QUERY2 }),
2692
- executor({ text: VIEWS_QUERY2 }),
2693
- executor({ text: COLUMNS_QUERY2 }),
3045
+ executor({ text: TABLES_QUERY }),
3046
+ executor({ text: VIEWS_QUERY }),
3047
+ executor({ text: COLUMNS_QUERY }),
2694
3048
  executor({ text: STATISTICS_QUERY }),
2695
- executor({ text: FOREIGN_KEYS_QUERY2 }),
3049
+ executor({ text: FOREIGN_KEYS_QUERY }),
2696
3050
  executor({ text: REFERENTIAL_QUERY }),
2697
- executor({ text: CHECKS_QUERY2 })
3051
+ executor({ text: CHECKS_QUERY })
2698
3052
  ]);
2699
3053
  const checksByTable = new Map;
2700
3054
  for (const row of checkRows) {
@@ -3448,7 +3802,7 @@ function q(name) {
3448
3802
  }
3449
3803
 
3450
3804
  // packages/migrate/src/execute.ts
3451
- import { VibeError as VibeError13 } from "@vibeorm/schema";
3805
+ import { VibeError as VibeError14 } from "@vibeorm/schema";
3452
3806
  var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
3453
3807
  var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
3454
3808
  function usesSelfManagedTransactions(params) {
@@ -3489,7 +3843,7 @@ function assertSelfManagedResumeSafe(params) {
3489
3843
  const completedIndex = params.direction === "up" ? params.start + 1 : -(params.start + 2);
3490
3844
  const rewind = selfManagedResumeStart({ sql: params.sql, start: params.start });
3491
3845
  const retryIndex = params.direction === "up" ? rewind : -(rewind + 1);
3492
- throw new VibeError13({
3846
+ throw new VibeError14({
3493
3847
  code: "VIBE_MIGRATION",
3494
3848
  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.`,
3495
3849
  meta: { reason: "uncertain-statement", migration: params.migration, statementIndex: params.start, direction: params.direction, dialect: "sqlite" }
@@ -3518,7 +3872,7 @@ function unsupportedScript(params) {
3518
3872
  const { migration, direction, index, problem, found } = params;
3519
3873
  const subject = direction === "down" ? `the down script of migration "${migration}"` : `migration "${migration}"`;
3520
3874
  const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${params.statements}`;
3521
- return new VibeError13({
3875
+ return new VibeError14({
3522
3876
  code: "VIBE_MIGRATION",
3523
3877
  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.',
3524
3878
  meta: { reason: "unsupported-self-managed-script", problem, migration, statementIndex: index, direction }
@@ -3574,7 +3928,7 @@ async function executeStatement(params) {
3574
3928
  const rows = await executor({ text: statement, values: [] });
3575
3929
  if (rows.length > 0) {
3576
3930
  const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
3577
- throw new VibeError13({
3931
+ throw new VibeError14({
3578
3932
  code: "VIBE_MIGRATION",
3579
3933
  message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
3580
3934
  meta: { violations: rows }
@@ -3593,9 +3947,44 @@ async function cleanupSelfManagedFailure(params) {
3593
3947
  } catch {}
3594
3948
  }
3595
3949
 
3950
+ // packages/migrate/src/operator-classes.ts
3951
+ import { defaultIndexName as defaultIndexName2, qualifiedSqlName as qualifiedSqlName7 } from "@vibeorm/schema";
3952
+ import { postgresDialect as postgresDialect7, renderPostgresIndexKeys as renderPostgresIndexKeys2 } from "@vibeorm/sql";
3953
+ async function prepareOperatorClassDiff(params) {
3954
+ const models = [];
3955
+ for (const live of params.from.models) {
3956
+ const table = qualifiedSqlName7({ name: live.dbName ?? live.name, namespace: live.namespace });
3957
+ const authored = params.to.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace }) === physicalNameKey(table));
3958
+ if (authored === undefined || !authored.indexes.some((index) => index.fields.some((field) => field.operatorClass !== undefined))) {
3959
+ models.push(live);
3960
+ continue;
3961
+ }
3962
+ const query = renderPostgresIndexKeys2({ dialect: postgresDialect7, table });
3963
+ const rows = await params.executor({ text: query.text, values: [...query.values] });
3964
+ const indexes = live.indexes.map((index) => {
3965
+ const declared = authored.indexes.find((entry) => (entry.name ?? defaultIndexName2({ table: table.name, columns: entry.fields.map((field) => authored.fields.find((candidate) => candidate.name === field.name)?.dbName ?? field.name), unique: entry.kind === "unique", dialect: "postgres" })) === index.name && entry.kind === index.kind);
3966
+ if (declared === undefined)
3967
+ return index;
3968
+ return { ...index, fields: index.fields.map((field, position) => {
3969
+ const requested = declared.fields[position];
3970
+ const operatorClass = requested?.operatorClass;
3971
+ const physicalColumn = authored.fields.find((entry) => entry.name === requested?.name)?.dbName ?? requested?.name;
3972
+ if (field.operatorClass !== undefined || operatorClass === undefined || physicalColumn !== field.name)
3973
+ return field;
3974
+ const row = rows.find((entry) => entry.index_name === index.name && Number(entry.ordinal) === position + 1 && entry.column_name === field.name);
3975
+ if (row?.exact_default !== true || row.operator_namespace !== operatorClass.namespace || row.operator_name !== operatorClass.name || row.method !== (index.kind === "unique" ? "btree" : index.kind))
3976
+ return field;
3977
+ return { ...field, operatorClass };
3978
+ }) };
3979
+ });
3980
+ models.push({ ...live, indexes });
3981
+ }
3982
+ return { ...params.from, models };
3983
+ }
3984
+
3596
3985
  // packages/migrate/src/postgres-default-equivalence.ts
3597
- import { VibeError as VibeError14 } from "@vibeorm/schema";
3598
- import { postgresDialect as postgresDialect7, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
3986
+ import { VibeError as VibeError15 } from "@vibeorm/schema";
3987
+ import { postgresDialect as postgresDialect8, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
3599
3988
  var IDENTIFIER = '(?:"(?:[^"\\u0000]|"")+"|[A-Za-z_][A-Za-z_0-9$]*)';
3600
3989
  var CALL = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*\\(\\s*\\)\\s*$`);
3601
3990
  function parseCall(params) {
@@ -3611,9 +4000,9 @@ async function equivalentPostgresDefaultFunction(params) {
3611
4000
  const observed = parseCall({ expression: params.actual });
3612
4001
  if (expected?.namespace === undefined || observed === undefined)
3613
4002
  return false;
3614
- const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect7, table: params.table, column: params.column });
4003
+ const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect8, table: params.table, column: params.column });
3615
4004
  const rows = await params.executor({ text: query.text, values: [...query.values] }).catch((cause) => {
3616
- throw new VibeError14({ code: "VIBE_MIGRATION", message: "Cannot inspect the stored default function identity", meta: { reason: "default-inspection-unavailable", table: params.table, column: params.column }, cause });
4005
+ 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 });
3617
4006
  });
3618
4007
  if (rows.length !== 1)
3619
4008
  return false;
@@ -3641,8 +4030,8 @@ function factsOf(params) {
3641
4030
  }
3642
4031
  async function inspectSqlObject(params) {
3643
4032
  return Promise.resolve().then(async () => {
3644
- const namespace = "table" in params.identity ? params.identity.table.namespace : params.identity.name.namespace;
3645
- const query = renderMigrationObjectQuery({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
4033
+ const namespace = sqlObjectNamespace2({ identity: params.identity });
4034
+ const query = renderMigrationObjectQuery2({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: params.identity });
3646
4035
  if (params.dialect === "mysql")
3647
4036
  await assertCatalogVisibility({ executor: params.executor, dialect: params.dialect, namespace });
3648
4037
  const rows = await params.executor({ text: query.text, values: [...query.values] });
@@ -3666,7 +4055,7 @@ async function inspectSqlObject(params) {
3666
4055
  const constraintType = params.identity.constraintType;
3667
4056
  const constraint = sqliteTableConstraints2({ sql: String(facts.definition ?? "") }).find((entry) => entry.name === name && entry.type === constraintType);
3668
4057
  if (constraint === undefined && constraintType === "primaryKey" && name === `${params.identity.table.name}_pkey`) {
3669
- const query = renderSqliteColumnInventory({ dialect: getSqlDialect({ dialect: "sqlite" }), namespace: params.identity.table.namespace, table: params.identity.table.name });
4058
+ const query = renderSqliteColumnInventory({ dialect: getSqlDialect2({ dialect: "sqlite" }), namespace: params.identity.table.namespace, table: params.identity.table.name });
3670
4059
  const columns = (await params.executor({ text: query.text, values: [...query.values] })).filter((column) => Number(column.pk) > 0).sort((a, b) => Number(a.pk) - Number(b.pk));
3671
4060
  if (columns.length === 0)
3672
4061
  return { state: "missing" };
@@ -3689,12 +4078,12 @@ async function inspectSqlObject(params) {
3689
4078
  if (params.identity.kind === "columnDefault")
3690
4079
  facts = { definition: facts.definition ?? null };
3691
4080
  if (params.dialect === "postgres") {
3692
- const query = renderMigrationDependencies({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
4081
+ const query = renderMigrationDependencies({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: params.identity });
3693
4082
  const rows = await params.executor({ text: query.text, values: [...query.values] });
3694
4083
  return postgresInspection({ identity: params.identity, facts, addresses: rows.map((row) => row.address) });
3695
4084
  }
3696
4085
  if (params.dialect === "mysql") {
3697
- const query = renderMysqlObjectMembers({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
4086
+ const query = renderMysqlObjectMembers({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: params.identity });
3698
4087
  if (query !== undefined) {
3699
4088
  const rows = await params.executor({ text: query.text, values: [...query.values] });
3700
4089
  if (params.identity.kind === "function" && rows.length === 0)
@@ -3708,7 +4097,7 @@ async function inspectSqlObject(params) {
3708
4097
  }
3709
4098
  }
3710
4099
  return { state: "present", definition: { identity: params.identity, facts, dependencies: [], format: "catalog-v2" } };
3711
- }).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError15 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
4100
+ }).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError16 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
3712
4101
  }
3713
4102
  function postgresInspection(params) {
3714
4103
  if (!params.addresses.every((address) => typeof address === "string"))
@@ -3723,7 +4112,7 @@ function postgresInspection(params) {
3723
4112
  return { state: "present", definition: { identity: params.identity, facts, dependencies, format: "catalog-v2" } };
3724
4113
  }
3725
4114
  function unavailableReason(params) {
3726
- return params.error instanceof VibeError15 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
4115
+ return params.error instanceof VibeError16 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
3727
4116
  }
3728
4117
  async function readBatch(params) {
3729
4118
  const positions = new Set(params.batch.positions);
@@ -3731,7 +4120,7 @@ async function readBatch(params) {
3731
4120
  const rows = new Map;
3732
4121
  for (const row of await params.executor({ text: params.batch.text, values: [...params.batch.values] })) {
3733
4122
  if (typeof row.position !== "number" || !Number.isInteger(row.position) || !positions.has(row.position))
3734
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
4123
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
3735
4124
  const matches = rows.get(row.position) ?? [];
3736
4125
  matches.push(row);
3737
4126
  rows.set(row.position, matches);
@@ -3759,7 +4148,7 @@ async function inspectSqlObjects(params) {
3759
4148
  }
3760
4149
  return Promise.resolve().then(async () => {
3761
4150
  const results = params.identities.map(() => ({ state: "unavailable", reason: "catalog-query-failed" }));
3762
- for (const batch of renderMigrationObjectBatch({ dialect: getSqlDialect({ dialect: params.dialect }), identities: params.identities })) {
4151
+ for (const batch of renderMigrationObjectBatch({ dialect: getSqlDialect2({ dialect: params.dialect }), identities: params.identities })) {
3763
4152
  const outcome = await readBatch({ executor: params.executor, batch });
3764
4153
  for (const position of batch.positions) {
3765
4154
  results[position] = outcome.ok ? batchedInspection({ identity: params.identities[position], matches: outcome.rows.get(position) ?? [] }) : { state: "unavailable", reason: outcome.reason };
@@ -3771,12 +4160,12 @@ async function inspectSqlObjects(params) {
3771
4160
  async function assertCatalogVisibility(params) {
3772
4161
  if (params.dialect !== "mysql")
3773
4162
  return;
3774
- const query = renderMigrationVisibility({ dialect: getSqlDialect({ dialect: params.dialect }), namespace: params.namespace });
4163
+ const query = renderMigrationVisibility({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace });
3775
4164
  const rows = await params.executor({ text: query.text, values: [...query.values] });
3776
4165
  const grants = new Set(rows.map((row) => row.privilege));
3777
4166
  const missing = ["SELECT", "TRIGGER", "SHOW VIEW", "EXECUTE"].filter((privilege) => !grants.has(privilege));
3778
4167
  if (missing.length > 0)
3779
- throw new VibeError15({ 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 } });
4168
+ 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 } });
3780
4169
  }
3781
4170
  var CATALOG_V1_OMITTED_FACTS = Object.freeze({
3782
4171
  function: ["definerOwner"],
@@ -3794,27 +4183,29 @@ function compare(params) {
3794
4183
  if (expected === undefined !== (actual === undefined) || canonicalMigrationJson({ value: expected }) !== canonicalMigrationJson({ value: actual }))
3795
4184
  differences.push({ attribute: key, expected, actual });
3796
4185
  }
3797
- const expected = params.expected.dependencies.map((identity) => sqlObjectKey4({ identity })).sort();
3798
- const actual = params.live.dependencies.map((identity) => sqlObjectKey4({ identity })).sort();
4186
+ const expected = params.expected.dependencies.map((identity) => sqlObjectKey5({ identity })).sort();
4187
+ const actual = params.live.dependencies.map((identity) => sqlObjectKey5({ identity })).sort();
3799
4188
  if (JSON.stringify(expected) !== JSON.stringify(actual))
3800
4189
  differences.push({ attribute: "dependencies", expected, actual });
3801
4190
  return differences;
3802
4191
  }
3803
4192
  function assertProbeMode(params) {
3804
4193
  if (params.probe !== undefined && params.probe !== "none" && DIALECT_CAPABILITIES2[params.dialect].checkNormalization !== params.probe)
3805
- throw new VibeError15({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
4194
+ throw new VibeError16({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
3806
4195
  }
3807
4196
  async function verifyMigrationState(params) {
3808
4197
  return verifyState(params);
3809
4198
  }
3810
4199
  async function verifyState(params) {
4200
+ if (params.external !== undefined)
4201
+ params = { ...params, expected: projectExternalState({ state: params.expected, external: params.external }) };
3811
4202
  assertProbeMode({ dialect: params.dialect, probe: params.probe });
3812
4203
  const declarations = params.expected.schema.migrationObjects ?? [];
3813
- const owned = new Set(declarations.map((object) => sqlObjectKey4({ identity: object.identity })));
3814
- const definitions = new Map(params.expected.definitions.map((definition) => [sqlObjectKey4({ identity: definition.identity }), definition]));
3815
- const identities = new Map(params.expected.definitions.map((definition) => [sqlObjectKey4({ identity: definition.identity }), definition.identity]));
4204
+ const owned = new Set(declarations.map((object) => sqlObjectKey5({ identity: object.identity })));
4205
+ const definitions = new Map(params.expected.definitions.map((definition) => [sqlObjectKey5({ identity: definition.identity }), definition]));
4206
+ const identities = new Map(params.expected.definitions.map((definition) => [sqlObjectKey5({ identity: definition.identity }), definition.identity]));
3816
4207
  for (const object of declarations)
3817
- identities.set(sqlObjectKey4({ identity: object.identity }), object.identity);
4208
+ identities.set(sqlObjectKey5({ identity: object.identity }), object.identity);
3818
4209
  const objects = [];
3819
4210
  const inspections = params.batch === true ? await inspectSqlObjects({ ...params, identities: [...identities.values()] }) : undefined;
3820
4211
  let position = 0;
@@ -3825,6 +4216,8 @@ async function verifyState(params) {
3825
4216
  position++;
3826
4217
  if (live.state !== "present") {
3827
4218
  objects.push({ identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
4219
+ } else if (expected === undefined && identity.kind === "namespace") {
4220
+ objects.push({ identity, ownership, state: "matching", differences: [] });
3828
4221
  } else if (expected === undefined) {
3829
4222
  objects.push({ identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] });
3830
4223
  } else if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined)) {
@@ -3834,23 +4227,31 @@ async function verifyState(params) {
3834
4227
  objects.push({ identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences });
3835
4228
  }
3836
4229
  }
3837
- if (params.expected.schema.models.length > 0 || params.expected.schema.enums.length > 0)
4230
+ if (params.dialect === "postgres" && schemaNamespaces({ schema: params.expected.schema }).length > 0 || params.expected.schema.models.length > 0 || params.expected.schema.enums.length > 0)
3838
4231
  objects.push(...await verifySchemaObjects(params));
3839
4232
  if (params.dialect === "postgres")
3840
4233
  objects.push(...await verifyExclusions(params));
3841
4234
  return { inSync: objects.every((object) => object.state === "matching"), objects };
3842
4235
  }
3843
4236
  async function verifySchemaObjects(params) {
3844
- const physical = params.dialect === "postgres" ? await introspectPhysicalPostgres({ executor: params.executor }) : params.dialect === "mysql" ? await introspectMysql({ executor: params.executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor: params.executor }) : await introspectSqlite({ executor: params.executor });
4237
+ const physical = params.dialect === "postgres" ? await introspectPhysicalPostgres({ executor: params.executor, namespaces: ["public", ...params.namespaces ?? [], ...schemaNamespaces({ schema: params.expected.schema })] }) : params.dialect === "mysql" ? await introspectMysql({ executor: params.executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor: params.executor }) : await introspectSqlite({ executor: params.executor });
3845
4238
  const resolved = params.dialect === "postgres" ? await resolvePostgresTypeIdentities({ executor: params.executor, schema: params.expected.schema, mode: "verify" }) : { schema: params.expected.schema, unavailable: [] };
3846
4239
  const expected = normalizeSchema({ schema: resolved.schema, dialect: params.dialect });
3847
- const actual = normalizeSchema({ schema: physical, dialect: params.dialect });
4240
+ const physicalComparison = params.external === undefined ? physical : projectExternalSchema({ schema: physical, external: params.external });
4241
+ const comparable = params.dialect === "postgres" ? await prepareOperatorClassDiff({ executor: params.executor, from: physicalComparison, to: resolved.schema }) : physicalComparison;
4242
+ const actual = normalizeSchema({ schema: comparable, dialect: params.dialect });
3848
4243
  const objects = resolved.unavailable.map((identity) => ({ identity, ownership: "schema", state: "unavailable", reason: "type-reference-required", differences: [] }));
3849
4244
  const namespace = await migrationNamespace({ executor: params.executor, dialect: params.dialect });
3850
4245
  const sqliteTableOptions = params.dialect === "sqlite" && expected.tables.size > 0 ? await params.executor({ text: "PRAGMA main.table_list;", values: [] }) : undefined;
3851
4246
  if (params.dialect === "postgres")
3852
- for (const [name, desired] of expected.enums) {
3853
- const identity = { kind: "enum", name: { namespace, name } };
4247
+ for (const name of schemaNamespaces({ schema: params.expected.schema })) {
4248
+ const identity = { kind: "namespace", name };
4249
+ const inspection = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
4250
+ objects.push({ identity, ownership: "schema", state: inspection.state === "present" ? "matching" : inspection.state, differences: [], ...inspection.state === "unavailable" ? { reason: inspection.reason } : {} });
4251
+ }
4252
+ if (params.dialect === "postgres")
4253
+ for (const desired of expected.enums.values()) {
4254
+ const identity = { kind: "enum", name: qualifiedSqlName8({ name: desired.dbName ?? desired.name, namespace: desired.namespace, defaultNamespace: namespace }) };
3854
4255
  const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
3855
4256
  if (live.state !== "present") {
3856
4257
  objects.push({ identity, ownership: "schema", state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
@@ -3864,9 +4265,10 @@ async function verifySchemaObjects(params) {
3864
4265
  const matching = canonicalMigrationJson({ value: desired.values }) === canonicalMigrationJson({ value: values });
3865
4266
  objects.push({ identity, ownership: "schema", state: matching ? "matching" : "changed", differences: matching ? [] : [{ attribute: "values", expected: desired.values, actual: values }] });
3866
4267
  }
3867
- for (const [name, desired] of expected.tables) {
3868
- const table = { namespace, name };
3869
- const live = actual.tables.get(name);
4268
+ for (const [key, desired] of expected.tables) {
4269
+ const name = desired.table;
4270
+ const table = qualifiedSqlName8({ name, namespace: desired.namespace, defaultNamespace: namespace });
4271
+ const live = actual.tables.get(key);
3870
4272
  if (live === undefined) {
3871
4273
  const presence = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity: { kind: "table", name: table } });
3872
4274
  objects.push({ identity: { kind: "table", name: table }, ownership: "schema", state: presence.state === "missing" ? "missing" : "unavailable", ...presence.state === "missing" ? {} : { reason: "schema-definition-not-visible" }, differences: [] });
@@ -3887,12 +4289,13 @@ async function verifySchemaObjects(params) {
3887
4289
  pin("primaryKey", desired.pk, live.pk);
3888
4290
  for (const [column, field] of desired.columns) {
3889
4291
  const stored = live.columns.get(column);
3890
- pin(`columns.${column}.type`, field.typeKey, stored?.typeKey);
4292
+ const exactType = field.pinsExactType;
4293
+ pin(`columns.${column}.type`, exactType ? field.exactTypeKey : field.typeKey, exactType ? stored?.exactTypeKey : stored?.typeKey);
3891
4294
  pin(`columns.${column}.nullable`, field.nullable, stored?.nullable);
3892
4295
  if (params.dialect === "mysql" && field.field.collation !== undefined) {
3893
4296
  pin(`columns.${column}.collation`, field.field.collation.toLowerCase(), stored?.field.collation?.toLowerCase());
3894
4297
  }
3895
- const ownedDefault = params.expected.schema.migrationObjects?.some((object) => (object.identity.kind === "columnDefault" || object.identity.kind === "generatedColumn") && object.identity.table.namespace === namespace && object.identity.table.name === name && object.identity.column === column);
4298
+ const ownedDefault = params.expected.schema.migrationObjects?.some((object) => (object.identity.kind === "columnDefault" || object.identity.kind === "generatedColumn") && object.identity.table.namespace === table.namespace && object.identity.table.name === name && object.identity.column === column);
3896
4299
  if (!ownedDefault) {
3897
4300
  const desiredDefault = field.field.default;
3898
4301
  const actualDefault = stored?.field.default;
@@ -3903,10 +4306,10 @@ async function verifySchemaObjects(params) {
3903
4306
  }
3904
4307
  for (const [key, fk] of desired.fks) {
3905
4308
  const stored = live.fks.get(key);
3906
- pin(`foreignKeys.${fk.constraintName}`, { columns: fk.columns, refTable: fk.refTable, refColumns: fk.refColumns, onDelete: fk.onDelete, onUpdate: fk.onUpdate }, stored === undefined ? undefined : { columns: stored.columns, refTable: stored.refTable, refColumns: stored.refColumns, onDelete: stored.onDelete, onUpdate: stored.onUpdate });
4309
+ pin(`foreignKeys.${fk.constraintName}`, { columns: fk.columns, refTable: fk.refTable, refNamespace: fk.refNamespace ?? namespace, refColumns: fk.refColumns, onDelete: fk.onDelete, onUpdate: fk.onUpdate }, stored === undefined ? undefined : { columns: stored.columns, refTable: stored.refTable, refNamespace: stored.refNamespace ?? namespace, refColumns: stored.refColumns, onDelete: stored.onDelete, onUpdate: stored.onUpdate });
3907
4310
  if (params.dialect === "postgres" && stored !== undefined) {
3908
4311
  const identity = { kind: "constraint", constraintType: "foreignKey", table, name: stored.constraintName };
3909
- if (params.expected.definitions.some((definition) => sqlObjectKey4({ identity: definition.identity }) === sqlObjectKey4({ identity })))
4312
+ if (params.expected.definitions.some((definition) => sqlObjectKey5({ identity: definition.identity }) === sqlObjectKey5({ identity })))
3910
4313
  continue;
3911
4314
  const inspection = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
3912
4315
  if (inspection.state !== "present") {
@@ -3922,7 +4325,7 @@ async function verifySchemaObjects(params) {
3922
4325
  if (params.dialect !== "postgres" || !live.indexes.has(key))
3923
4326
  continue;
3924
4327
  const identity = { kind: "index", table, name: live.indexes.get(key).name };
3925
- if (params.expected.definitions.some((definition) => sqlObjectKey4({ identity: definition.identity }) === sqlObjectKey4({ identity })))
4328
+ if (params.expected.definitions.some((definition) => sqlObjectKey5({ identity: definition.identity }) === sqlObjectKey5({ identity })))
3926
4329
  continue;
3927
4330
  const inspection = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
3928
4331
  if (inspection.state !== "present") {
@@ -3935,7 +4338,7 @@ async function verifySchemaObjects(params) {
3935
4338
  objects.push({ identity: { kind: "table", name: table }, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
3936
4339
  for (const [check, expression] of desired.checks) {
3937
4340
  const identity = { kind: "constraint", constraintType: "check", table, name: check };
3938
- if (params.expected.definitions.some((definition) => sqlObjectKey4({ identity: definition.identity }) === sqlObjectKey4({ identity })))
4341
+ if (params.expected.definitions.some((definition) => sqlObjectKey5({ identity: definition.identity }) === sqlObjectKey5({ identity })))
3939
4342
  continue;
3940
4343
  const stored = live.checks.get(check);
3941
4344
  if (stored === undefined) {
@@ -3975,13 +4378,14 @@ async function verifySchemaObjects(params) {
3975
4378
  }
3976
4379
  for (const [name, join] of expected.joinTables) {
3977
4380
  const stored = actual.joinTables.get(name);
3978
- const shape = (value) => value === undefined ? undefined : { a: { table: value.a.table, column: value.a.column }, b: { table: value.b.table, column: value.b.column } };
4381
+ const shape = (value) => value === undefined ? undefined : { a: { table: value.a.table, namespace: value.a.namespace ?? namespace, column: value.a.column }, b: { table: value.b.table, namespace: value.b.namespace ?? namespace, column: value.b.column } };
3979
4382
  const matching = canonicalMigrationJson({ value: shape(join) }) === canonicalMigrationJson({ value: shape(stored) });
3980
- objects.push({ identity: { kind: "table", name: { namespace, name } }, ownership: "schema", state: stored === undefined ? "missing" : matching ? "matching" : "changed", differences: matching ? [] : [{ attribute: "joinTable", expected: shape(join), actual: shape(stored) }] });
4383
+ objects.push({ identity: { kind: "table", name: qualifiedSqlName8({ name: join.table, namespace: join.namespace, defaultNamespace: namespace }) }, ownership: "schema", state: stored === undefined ? "missing" : matching ? "matching" : "changed", differences: matching ? [] : [{ attribute: "joinTable", expected: shape(join), actual: shape(stored) }] });
3981
4384
  }
3982
- for (const [name] of expected.views) {
3983
- if (!params.expected.definitions.some((definition) => definition.identity.kind === "view" && definition.identity.name.namespace === namespace && definition.identity.name.name === name))
3984
- objects.push({ identity: { kind: "view", name: { namespace, name } }, ownership: "schema", state: actual.views.has(name) ? "unavailable" : "missing", reason: "independent-definition-required", differences: [] });
4385
+ for (const [key, view] of expected.views) {
4386
+ const name = qualifiedSqlName8({ name: view.dbName ?? view.name, namespace: view.namespace, defaultNamespace: namespace });
4387
+ if (!params.expected.definitions.some((definition) => definition.identity.kind === "view" && definition.identity.name.namespace === name.namespace && definition.identity.name.name === name.name))
4388
+ objects.push({ identity: { kind: "view", name }, ownership: "schema", state: actual.views.has(key) ? "unavailable" : "missing", reason: "independent-definition-required", differences: [] });
3985
4389
  }
3986
4390
  return objects;
3987
4391
  }
@@ -3990,16 +4394,25 @@ async function verifyMigrationTransition(params) {
3990
4394
  }
3991
4395
  async function verifySqliteTransitionBeforeRestore(params) {
3992
4396
  if (params.dialect !== "sqlite")
3993
- throw new VibeError15({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
4397
+ throw new VibeError16({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
3994
4398
  await executeStatement({ executor: params.executor, dialect: "sqlite", statement: "PRAGMA foreign_key_check;" });
3995
4399
  return verifyTransition({ ...params, sqliteRestorePending: true });
3996
4400
  }
3997
4401
  async function verifyTransition(params) {
3998
- const report = await verifyState({ executor: params.executor, dialect: params.dialect, expected: params.to, ...params.sqliteRestorePending === true ? { sqliteRestorePending: true } : {}, ...params.probe === undefined ? {} : { probe: params.probe } });
4402
+ const external = params.external;
4403
+ if (external !== undefined) {
4404
+ for (const identity of params.removals ?? []) {
4405
+ const owner = externalObjectOwner({ identity, external });
4406
+ if (owner !== undefined)
4407
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Transition removals cannot target externally owned objects", meta: { reason: "external-owned-removal", owner, identity } });
4408
+ }
4409
+ params = { ...params, from: projectExternalState({ state: params.from, external }), to: projectExternalState({ state: params.to, external }) };
4410
+ }
4411
+ const report = await verifyState({ executor: params.executor, dialect: params.dialect, expected: params.to, ...params.namespaces === undefined ? {} : { namespaces: params.namespaces }, ...external === undefined ? {} : { external }, ...params.sqliteRestorePending === true ? { sqliteRestorePending: true } : {}, ...params.probe === undefined ? {} : { probe: params.probe } });
3999
4412
  const namespace = await migrationNamespace(params);
4000
4413
  const identities = (state) => [...schemaObjectIdentities({ schema: state.schema, dialect: params.dialect, namespace }), ...(state.schema.migrationObjects ?? []).map((object) => object.identity), ...state.definitions.map((definition) => definition.identity)];
4001
- const retained = new Set(identities(params.to).map((identity) => sqlObjectKey4({ identity })));
4002
- const removed = new Map([...identities(params.from).filter((identity) => !retained.has(sqlObjectKey4({ identity }))), ...params.removals ?? []].map((identity) => [sqlObjectKey4({ identity }), identity]));
4414
+ const retained = new Set(identities(params.to).map((identity) => sqlObjectKey5({ identity })));
4415
+ const removed = new Map([...identities(params.from).filter((identity) => identity.kind !== "namespace" && !retained.has(sqlObjectKey5({ identity }))), ...params.removals ?? []].map((identity) => [sqlObjectKey5({ identity }), identity]));
4003
4416
  const objects = [...report.objects];
4004
4417
  for (const identity of removed.values()) {
4005
4418
  const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
@@ -4012,16 +4425,16 @@ async function verifyTransition(params) {
4012
4425
  async function migrationNamespace(params) {
4013
4426
  if (params.dialect !== "mysql")
4014
4427
  return params.dialect === "sqlite" ? "main" : "public";
4015
- const query = renderMigrationNamespace({ dialect: getSqlDialect({ dialect: params.dialect }) });
4428
+ const query = renderMigrationNamespace({ dialect: getSqlDialect2({ dialect: params.dialect }) });
4016
4429
  const rows = await params.executor({ text: query.text, values: [] });
4017
4430
  const namespace = rows[0]?.namespace;
4018
4431
  if (typeof namespace !== "string" || namespace.length === 0)
4019
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
4432
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
4020
4433
  return namespace;
4021
4434
  }
4022
4435
  function assertMigrationState(params) {
4023
4436
  if (!params.report.inSync)
4024
- throw new VibeError15({ 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") } });
4437
+ 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") } });
4025
4438
  }
4026
4439
  async function deparsePostgresCheck(params) {
4027
4440
  const definition = await deparsePostgresCheckDefinition({ ...params, constraint: "vibeorm_check_probe" });
@@ -4031,11 +4444,11 @@ async function deparsePostgresCheckDefinition(params) {
4031
4444
  const name = "vibeorm_check_probe";
4032
4445
  await params.executor({ text: "SAVEPOINT vibeorm_check_probe" });
4033
4446
  try {
4034
- const query = renderCheckProbe({ dialect: getSqlDialect({ dialect: "postgres" }), table: params.table, expression: params.expression, name });
4447
+ const query = renderCheckProbe({ dialect: getSqlDialect2({ dialect: "postgres" }), table: params.table, expression: params.expression, name });
4035
4448
  await params.executor({ text: query.text });
4036
4449
  const result = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: { kind: "constraint", constraintType: "check", table: params.table, name } });
4037
4450
  if (result.state !== "present" || typeof result.definition.facts.definition !== "string")
4038
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
4451
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
4039
4452
  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 } };
4040
4453
  } finally {
4041
4454
  await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_check_probe" });
@@ -4044,7 +4457,7 @@ async function deparsePostgresCheckDefinition(params) {
4044
4457
  }
4045
4458
  async function deparseMysqlCheck(params) {
4046
4459
  const probe = `vibe_check_${randomUUID2().replaceAll("-", "")}`;
4047
- const dialect = getSqlDialect({ dialect: "mysql" });
4460
+ const dialect = getSqlDialect2({ dialect: "mysql" });
4048
4461
  const clone = renderMysqlCheckProbe({ dialect, probe, operation: "clone", table: params.table });
4049
4462
  await params.executor({ text: clone.text });
4050
4463
  let created = true;
@@ -4053,7 +4466,7 @@ async function deparseMysqlCheck(params) {
4053
4466
  const source = await params.executor({ text: read.text, values: [] });
4054
4467
  const definition = source[0]?.["Create Table"];
4055
4468
  if (typeof definition !== "string")
4056
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
4469
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
4057
4470
  const drop = renderMysqlCheckProbe({ dialect, probe, operation: "drop" });
4058
4471
  await params.executor({ text: drop.text });
4059
4472
  created = false;
@@ -4064,7 +4477,7 @@ async function deparseMysqlCheck(params) {
4064
4477
  const normalizedDefinition = rows[0]?.["Create Table"];
4065
4478
  const check = typeof normalizedDefinition === "string" ? parseSqliteNamedChecks({ tableSql: normalizedDefinition }).find((entry) => entry.name === probe) : undefined;
4066
4479
  if (check === undefined)
4067
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
4480
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
4068
4481
  return check.expression;
4069
4482
  } finally {
4070
4483
  if (created) {
@@ -4078,7 +4491,7 @@ async function prepareCheckDiff(params) {
4078
4491
  const models = [];
4079
4492
  for (const model of params.from.models) {
4080
4493
  const table = model.dbName ?? model.name;
4081
- const target = params.to.models.find((entry) => (entry.dbName ?? entry.name) === table);
4494
+ const target = params.to.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect: params.dialect }) === physicalNameKey({ name: table, namespace: model.namespace, dialect: params.dialect }));
4082
4495
  const checks = [];
4083
4496
  for (const check of model.checks ?? []) {
4084
4497
  const next = target?.checks?.find((entry) => entry.name === check.name);
@@ -4086,12 +4499,12 @@ async function prepareCheckDiff(params) {
4086
4499
  checks.push({ name: check.name, expression: check.expression });
4087
4500
  continue;
4088
4501
  }
4089
- const identity = { kind: "constraint", constraintType: "check", table: { namespace: await migrationNamespace({ executor: params.executor, dialect: params.dialect }), name: table }, name: check.name };
4502
+ const identity = { kind: "constraint", constraintType: "check", table: qualifiedSqlName8({ name: table, namespace: model.namespace, defaultNamespace: await migrationNamespace({ executor: params.executor, dialect: params.dialect }) }), name: check.name };
4090
4503
  if (params.dialect === "postgres" && params.probe === "rollback") {
4091
4504
  const desired = await deparsePostgresCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "rollback" });
4092
4505
  const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
4093
4506
  if (live.state !== "present")
4094
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4507
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4095
4508
  if (live.definition.facts.definition === desired) {
4096
4509
  checks.push(next);
4097
4510
  continue;
@@ -4103,20 +4516,20 @@ async function prepareCheckDiff(params) {
4103
4516
  const desired = await deparseMysqlCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "temporary" });
4104
4517
  const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
4105
4518
  if (live.state !== "present")
4106
- throw new VibeError15({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4519
+ throw new VibeError16({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
4107
4520
  checks.push(desired === live.definition.facts.definition ? next : { name: check.name, expression: check.expression });
4108
4521
  continue;
4109
4522
  }
4110
- const previous = params.previous?.schema.models.find((entry) => (entry.dbName ?? entry.name) === table)?.checks?.find((entry) => entry.name === check.name);
4523
+ const previous = params.previous?.schema.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect: params.dialect }) === physicalNameKey({ name: table, namespace: model.namespace, dialect: params.dialect }))?.checks?.find((entry) => entry.name === check.name);
4111
4524
  if (previous !== undefined) {
4112
4525
  checks.push(previous.expression === next.expression ? next : { name: check.name, expression: check.expression });
4113
4526
  continue;
4114
4527
  }
4115
- if (params.replaceChecks?.some((entry) => sqlObjectKey4({ identity: entry }) === sqlObjectKey4({ identity }))) {
4528
+ if (params.replaceChecks?.some((entry) => sqlObjectKey5({ identity: entry }) === sqlObjectKey5({ identity }))) {
4116
4529
  checks.push({ name: check.name, expression: check.expression });
4117
4530
  continue;
4118
4531
  }
4119
- throw new VibeError15({ 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 } });
4532
+ 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 } });
4120
4533
  }
4121
4534
  models.push({ ...model, checks });
4122
4535
  }
@@ -4126,7 +4539,7 @@ async function prepareCheckDiff(params) {
4126
4539
  // packages/migrate/src/inventory.ts
4127
4540
  async function inspectMigrationInventory(params) {
4128
4541
  await assertCatalogVisibility(params);
4129
- const query = renderMigrationInventory({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace });
4542
+ const query = renderMigrationInventory({ dialect: getSqlDialect3({ dialect: params.dialect }), namespace: params.namespace });
4130
4543
  const rows = await params.executor({ text: query.text, values: [...query.values] });
4131
4544
  const objects = [];
4132
4545
  const kinds = {
@@ -4143,14 +4556,18 @@ async function inspectMigrationInventory(params) {
4143
4556
  for (const row of rows) {
4144
4557
  const name = row.name;
4145
4558
  if (typeof name !== "string" || typeof row.kind !== "string")
4146
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
4559
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
4147
4560
  const qualified = { namespace: params.namespace, name };
4148
4561
  const table = { namespace: params.namespace, name: String(row.parent ?? "") };
4149
4562
  let identity;
4150
4563
  switch (row.kind) {
4564
+ case "namespace":
4565
+ identity = { kind: "namespace", name };
4566
+ break;
4151
4567
  case "table":
4152
4568
  case "view":
4153
4569
  case "sequence":
4570
+ case "enum":
4154
4571
  identity = { kind: row.kind, name: qualified };
4155
4572
  break;
4156
4573
  case "trigger":
@@ -4165,27 +4582,27 @@ async function inspectMigrationInventory(params) {
4165
4582
  case "constraint": {
4166
4583
  const constraintType = kinds[String(row.subtype)];
4167
4584
  if (constraintType === undefined)
4168
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
4585
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
4169
4586
  identity = { kind: "constraint", table, name, constraintType };
4170
4587
  break;
4171
4588
  }
4172
4589
  case "function": {
4173
4590
  let argumentTypes = JSON.parse(String(row.signature));
4174
4591
  if (params.dialect === "mysql") {
4175
- const query = renderMysqlObjectMembers2({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
4592
+ const query = renderMysqlObjectMembers2({ dialect: getSqlDialect3({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
4176
4593
  const members = await params.executor({ text: query.text, values: [...query.values] });
4177
4594
  if (members.length === 0)
4178
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
4595
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
4179
4596
  argumentTypes = members.filter((member) => member.mode !== null).map((member) => ({ namespace: "mysql", name: String(member.type) }));
4180
4597
  }
4181
4598
  const candidate = { kind: "function", name: qualified, argumentTypes };
4182
4599
  if (!isSqlObjectIdentity2(candidate))
4183
- throw new VibeError16({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
4600
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
4184
4601
  identity = candidate;
4185
4602
  break;
4186
4603
  }
4187
4604
  default:
4188
- throw new VibeError16({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
4605
+ throw new VibeError17({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
4189
4606
  }
4190
4607
  objects.push({ identity, ...typeof row.backing === "string" ? { backingConstraint: row.backing } : {} });
4191
4608
  if (params.dialect === "sqlite" && row.kind === "table" && typeof row.definition === "string" && !/^\s*CREATE\s+VIRTUAL\s+TABLE\b/i.test(row.definition)) {
@@ -4201,11 +4618,11 @@ async function inspectMigrationInventory(params) {
4201
4618
  objects.push({ identity: { kind: "constraint", constraintType: "check", table: qualified, name: check.name } });
4202
4619
  if ([...row.definition.matchAll(/\bCHECK\s*\(/gi)].length > checks.length)
4203
4620
  objects.push({ identity: { kind: "table", name: qualified }, inspectionUnavailable: "sqlite-unnamed-check-definition-unavailable" });
4204
- const query = renderSqliteColumnInventory2({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace, table: name });
4621
+ const query = renderSqliteColumnInventory2({ dialect: getSqlDialect3({ dialect: params.dialect }), namespace: params.namespace, table: name });
4205
4622
  const columns = await params.executor({ text: query.text, values: [...query.values] });
4206
4623
  if (columns.some((column) => Number(column.pk) > 0) && !clauses.some((clause) => clause.type === "primaryKey"))
4207
4624
  objects.push({ identity: { kind: "constraint", constraintType: "primaryKey", table: qualified, name: defaultPrimaryKeyName2({ table: qualified.name, dialect: "sqlite" }) } });
4208
- const fkQuery = renderSqliteForeignKeyInventory({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace, table: name });
4625
+ const fkQuery = renderSqliteForeignKeyInventory({ dialect: getSqlDialect3({ dialect: params.dialect }), namespace: params.namespace, table: name });
4209
4626
  const foreignKeys = await params.executor({ text: fkQuery.text, values: [...fkQuery.values] });
4210
4627
  const groups = new Map;
4211
4628
  for (const fk of foreignKeys)
@@ -4224,48 +4641,47 @@ async function inspectMigrationInventory(params) {
4224
4641
  }
4225
4642
  }
4226
4643
  }
4227
- return [...new Map(objects.map((object) => [sqlObjectKey5({ identity: object.identity }), object])).values()];
4644
+ return [...new Map(objects.map((object) => [sqlObjectKey6({ identity: object.identity }), object])).values()];
4228
4645
  }
4229
4646
 
4230
4647
  // packages/migrate/src/module-ownership.ts
4231
4648
  function refusal(params) {
4232
- return new VibeError17({ code: "VIBE_MIGRATION", message: "Module ownership or dependencies cannot be reconciled with the declared and live schema; no package SQL was authorized", meta: params });
4649
+ 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 });
4233
4650
  }
4234
4651
  function identities(params) {
4235
4652
  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)];
4236
4653
  }
4237
4654
  function moduleNamespace(params) {
4238
- if (params.origin.dialect !== "mysql")
4239
- return params.origin.dialect === "sqlite" ? "main" : "public";
4240
- const first = params.origin.owned[0];
4241
- if (first === undefined)
4655
+ const namespaces = [...new Set(params.origin.owned.map((identity) => sqlObjectNamespace3({ identity })))];
4656
+ if (namespaces.length > 1 || namespaces.length === 0 && params.origin.dialect === "mysql")
4242
4657
  throw refusal({ reason: "module-namespace-required", moduleId: params.origin.moduleId });
4243
- return "table" in first ? first.table.namespace : first.name.namespace;
4658
+ return namespaces[0] ?? (params.origin.dialect === "sqlite" ? "main" : "public");
4244
4659
  }
4245
4660
  function moduleOwnedObjects(params) {
4246
4661
  const { desiredState } = params;
4662
+ if (params.namespace === undefined && desiredState.dialect === "mysql")
4663
+ throw refusal({ reason: "module-namespace-required" });
4247
4664
  const namespace = params.namespace ?? (desiredState.dialect === "sqlite" ? "main" : "public");
4248
4665
  const all = [desiredState.from, desiredState.to, ...desiredState.down === undefined ? [] : [desiredState.down]].flatMap((state) => identities({ state, dialect: desiredState.dialect, namespace }));
4249
- return [...new Map(all.map((identity) => [sqlObjectKey6({ identity }), identity])).values()].sort((a, b) => sqlObjectKey6({ identity: a }).localeCompare(sqlObjectKey6({ identity: b })));
4666
+ return [...new Map(all.map((identity) => [sqlObjectKey7({ identity }), identity])).values()].sort((a, b) => sqlObjectKey7({ identity: a }).localeCompare(sqlObjectKey7({ identity: b })));
4250
4667
  }
4251
4668
  function assertModuleOwnership(params) {
4252
4669
  const { origin } = params;
4253
4670
  const state = origin.source.desiredState;
4254
4671
  const namespace = moduleNamespace({ origin });
4255
4672
  const expected = moduleOwnedObjects({ desiredState: state, namespace });
4256
- const owned = new Set(origin.owned.map((identity) => sqlObjectKey6({ identity })));
4257
- if (expected.length !== owned.size || expected.some((identity) => !owned.has(sqlObjectKey6({ identity }))))
4673
+ const owned = new Set(origin.owned.map((identity) => sqlObjectKey7({ identity })));
4674
+ if (expected.length !== owned.size || expected.some((identity) => !owned.has(sqlObjectKey7({ identity }))))
4258
4675
  throw refusal({ reason: "module-ownership-invalid", moduleId: origin.moduleId });
4259
4676
  for (const identity of expected) {
4260
- const name = "table" in identity ? identity.table : typeof identity.name === "object" ? identity.name : undefined;
4261
- if (name?.namespace !== namespace || name.name === "_vibe_migrations")
4677
+ if (sqlObjectNamespace3({ identity }) !== namespace || ("table" in identity ? identity.table.name : typeof identity.name === "object" ? identity.name.name : undefined) === "_vibe_migrations")
4262
4678
  throw refusal({ reason: "module-ownership-invalid", moduleId: origin.moduleId, identity });
4263
4679
  }
4264
4680
  for (const target of [state.from, state.to, ...state.down === undefined ? [] : [state.down]]) {
4265
4681
  if (target.schema.rls !== undefined || target.schema.extensions.length > 0)
4266
4682
  throw refusal({ reason: "module-schema-unsupported", moduleId: origin.moduleId });
4267
4683
  for (const dependency of [...target.definitions.flatMap((definition) => definition.dependencies), ...(target.schema.migrationObjects ?? []).flatMap((object) => object.dependsOn), ...state.unmanagedRemovals ?? []]) {
4268
- if (!owned.has(sqlObjectKey6({ identity: dependency })))
4684
+ if (!owned.has(sqlObjectKey7({ identity: dependency })))
4269
4685
  throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId, identity: dependency });
4270
4686
  }
4271
4687
  }
@@ -4273,28 +4689,29 @@ function assertModuleOwnership(params) {
4273
4689
  return;
4274
4690
  const local = params.desiredState;
4275
4691
  const rest = (actual, source) => {
4276
- const tables = new Set(source.schema.models.map((model) => model.dbName ?? model.name));
4277
- const enums = new Set(source.schema.enums.map((entry) => entry.dbName ?? entry.name));
4278
- const selected = actual.schema.models.filter((model) => tables.has(model.dbName ?? model.name));
4279
- const selectedEnums = actual.schema.enums.filter((entry) => enums.has(entry.dbName ?? entry.name));
4692
+ const keyOf = (entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect: origin.dialect });
4693
+ const tables = new Set(source.schema.models.map(keyOf));
4694
+ const enums = new Set(source.schema.enums.map(keyOf));
4695
+ const selected = actual.schema.models.filter((model) => tables.has(keyOf(model)));
4696
+ const selectedEnums = actual.schema.enums.filter((entry) => enums.has(keyOf(entry)));
4280
4697
  if (canonicalMigrationJson({ value: selected }) !== canonicalMigrationJson({ value: source.schema.models }) || canonicalMigrationJson({ value: selectedEnums }) !== canonicalMigrationJson({ value: source.schema.enums }))
4281
4698
  throw refusal({ reason: "module-source-state-mismatch", moduleId: origin.moduleId });
4282
- const selectedObjects = (actual.schema.migrationObjects ?? []).filter((object) => owned.has(sqlObjectKey6({ identity: object.identity })));
4283
- const selectedDefinitions = actual.definitions.filter((definition) => owned.has(sqlObjectKey6({ identity: definition.identity })));
4699
+ const selectedObjects = (actual.schema.migrationObjects ?? []).filter((object) => owned.has(sqlObjectKey7({ identity: object.identity })));
4700
+ const selectedDefinitions = actual.definitions.filter((definition) => owned.has(sqlObjectKey7({ identity: definition.identity })));
4284
4701
  if (canonicalMigrationJson({ value: selectedObjects }) !== canonicalMigrationJson({ value: source.schema.migrationObjects ?? [] }) || canonicalMigrationJson({ value: selectedDefinitions }) !== canonicalMigrationJson({ value: source.definitions }))
4285
4702
  throw refusal({ reason: "module-source-state-mismatch", moduleId: origin.moduleId });
4286
- return { ...actual, schema: { ...actual.schema, models: actual.schema.models.filter((model) => !tables.has(model.dbName ?? model.name)), enums: actual.schema.enums.filter((entry) => !enums.has(entry.dbName ?? entry.name)), migrationObjects: (actual.schema.migrationObjects ?? []).filter((object) => !owned.has(sqlObjectKey6({ identity: object.identity }))) }, definitions: actual.definitions.filter((definition) => !owned.has(sqlObjectKey6({ identity: definition.identity }))) };
4703
+ return { ...actual, schema: { ...actual.schema, models: actual.schema.models.filter((model) => !tables.has(keyOf(model))), enums: actual.schema.enums.filter((entry) => !enums.has(keyOf(entry))), migrationObjects: (actual.schema.migrationObjects ?? []).filter((object) => !owned.has(sqlObjectKey7({ identity: object.identity }))) }, definitions: actual.definitions.filter((definition) => !owned.has(sqlObjectKey7({ identity: definition.identity }))) };
4287
4704
  };
4288
4705
  const before = canonicalMigrationJson({ value: rest(local.from, state.from) });
4289
4706
  if (before !== canonicalMigrationJson({ value: rest(local.to, state.to) }) || before !== canonicalMigrationJson({ value: rest(local.down ?? local.from, state.down ?? state.from) }))
4290
4707
  throw refusal({ reason: "module-mutates-application-state", moduleId: origin.moduleId });
4291
4708
  }
4292
4709
  function dependencyUnavailable(params) {
4293
- return new VibeError17({ 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 } });
4710
+ 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 } });
4294
4711
  }
4295
4712
  async function assertCrossNamespaceModuleDependencies(params) {
4296
4713
  const { executor, origin } = params;
4297
- const dialect = getSqlDialect3({ dialect: origin.dialect });
4714
+ const dialect = getSqlDialect4({ dialect: origin.dialect });
4298
4715
  const query = renderModuleDependencyQuery({ dialect, operation: { kind: "foreignKeys", namespace: params.namespace, tables: [...params.tables] } });
4299
4716
  if (query === undefined)
4300
4717
  return;
@@ -4321,15 +4738,15 @@ async function assertCrossNamespaceModuleDependencies(params) {
4321
4738
  async function assertLiveModuleOwnership(params) {
4322
4739
  const { executor, origin } = params;
4323
4740
  const namespace = moduleNamespace({ origin });
4324
- if (await migrationNamespace({ executor, dialect: origin.dialect }) !== namespace)
4741
+ if (origin.dialect !== "postgres" && await migrationNamespace({ executor, dialect: origin.dialect }) !== namespace)
4325
4742
  throw refusal({ reason: "module-placement-mismatch", moduleId: origin.moduleId });
4326
4743
  const inventory = await inspectMigrationInventory({ executor, dialect: origin.dialect, namespace });
4327
- const before = new Set(identities({ state: origin.source.desiredState.from, dialect: origin.dialect, namespace }).map((identity) => sqlObjectKey6({ identity })));
4328
- const owned = new Set(origin.owned.map((identity) => sqlObjectKey6({ identity })));
4744
+ const before = new Set(identities({ state: origin.source.desiredState.from, dialect: origin.dialect, namespace }).map((identity) => sqlObjectKey7({ identity })));
4745
+ const owned = new Set(origin.owned.map((identity) => sqlObjectKey7({ identity })));
4329
4746
  const tables = new Set(origin.owned.flatMap((identity) => identity.kind === "table" ? [identity.name.name] : []));
4330
4747
  await assertCrossNamespaceModuleDependencies({ executor, origin, namespace, tables });
4331
4748
  for (const object of inventory) {
4332
- const key = sqlObjectKey6({ identity: object.identity });
4749
+ const key = sqlObjectKey7({ identity: object.identity });
4333
4750
  if (params.resume !== true && owned.has(key) && !before.has(key))
4334
4751
  throw refusal({ reason: "module-object-collision", moduleId: origin.moduleId, identity: object.identity });
4335
4752
  if (!owned.has(key) && (["function", "view", "trigger"].includes(object.identity.kind) || ("table" in object.identity) && tables.has(object.identity.table.name) && object.backingConstraint === undefined))
@@ -4337,7 +4754,7 @@ async function assertLiveModuleOwnership(params) {
4337
4754
  if (object.inspectionUnavailable !== undefined && (owned.has(key) || ("table" in object.identity) && tables.has(object.identity.table.name)))
4338
4755
  throw refusal({ reason: "module-dependency-unavailable", moduleId: origin.moduleId, identity: object.identity });
4339
4756
  }
4340
- const physical = origin.dialect === "postgres" ? await introspectPhysicalPostgres({ executor }) : origin.dialect === "mysql" ? await introspectMysql({ executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor }) : await introspectSqlite({ executor });
4757
+ const physical = origin.dialect === "postgres" ? await introspectPhysicalPostgres({ executor, namespaces: [namespace] }) : origin.dialect === "mysql" ? await introspectMysql({ executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor }) : await introspectSqlite({ executor });
4341
4758
  for (const model of physical.models)
4342
4759
  for (const relation of model.relations) {
4343
4760
  const target = physical.models.find((candidate) => candidate.name === relation.targetModel);
@@ -4352,12 +4769,51 @@ async function assertApplicationModuleState(params) {
4352
4769
  const options = { executor: params.executor, dialect: origin.dialect, from: state.from, to: state.to, removals: state.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } };
4353
4770
  const report = params.sqliteRestorePending === true ? await verifySqliteTransitionBeforeRestore(options) : await verifyMigrationTransition(options);
4354
4771
  if (!report.inSync)
4355
- throw new VibeError17({ 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 } });
4772
+ 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 } });
4356
4773
  }
4357
4774
  }
4358
4775
 
4776
+ // packages/migrate/src/module-adoption.ts
4777
+ import { VibeError as VibeError19 } from "@vibeorm/schema";
4778
+ var MODULE_ADOPTION_METADATA = Object.freeze({ filename: "module-adoption.json", version: 1 });
4779
+ function invalid3() {
4780
+ 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" } });
4781
+ }
4782
+ function assertMarker(params) {
4783
+ const value = params.value;
4784
+ if (value === null || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== 3 || !("version" in value) || value.version !== 1 || !("reverse" in value) || value.reverse !== "refuse" || !("referenceFingerprint" in value) || typeof value.referenceFingerprint !== "string" || !/^[a-f0-9]{64}$/.test(value.referenceFingerprint))
4785
+ throw invalid3();
4786
+ }
4787
+ function assertModuleAdoption(params) {
4788
+ if (params.migration.moduleAdoption === undefined)
4789
+ return;
4790
+ assertMarker({ value: params.migration.moduleAdoption });
4791
+ if (params.migration.moduleOrigin === undefined)
4792
+ throw invalid3();
4793
+ assertModuleOrigin({ value: params.migration.moduleOrigin });
4794
+ }
4795
+ function parseModuleAdoption(params) {
4796
+ return parseMigrationMetadata({ text: params.text, failure: invalid3, parse: (value) => {
4797
+ const checked = { value };
4798
+ assertMarker(checked);
4799
+ return Object.freeze(checked.value);
4800
+ } });
4801
+ }
4802
+ function moduleAdoptionChecksumSuffix(params) {
4803
+ if (params.moduleAdoption === undefined)
4804
+ return "";
4805
+ assertModuleAdoption({ migration: params });
4806
+ return `
4807
+ -- vibeorm:module-adoption ${canonicalMigrationJson({ value: params.moduleAdoption })}`;
4808
+ }
4809
+ function serializeModuleAdoption(params) {
4810
+ assertModuleAdoption(params);
4811
+ return params.migration.moduleAdoption === undefined ? undefined : JSON.stringify(params.migration.moduleAdoption, null, 2) + `
4812
+ `;
4813
+ }
4814
+
4359
4815
  // packages/migrate/src/sql-statements.ts
4360
- import { VibeError as VibeError18 } from "@vibeorm/schema";
4816
+ import { VibeError as VibeError20 } from "@vibeorm/schema";
4361
4817
  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;
4362
4818
  var BLOCK_OPENERS = new Set(["BEGIN", "CASE", "IF", "LOOP", "WHILE", "REPEAT"]);
4363
4819
  function splitSqlStatements(params) {
@@ -4433,7 +4889,7 @@ function splitSqlStatements(params) {
4433
4889
  continue;
4434
4890
  }
4435
4891
  if (dialect === "mysql" || dialect === undefined) {
4436
- throw new VibeError18({ 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 } });
4892
+ 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 } });
4437
4893
  }
4438
4894
  }
4439
4895
  if (value === endQuote) {
@@ -4507,15 +4963,15 @@ function joinSqlStatements(params) {
4507
4963
 
4508
4964
  // packages/migrate/src/module-origin.ts
4509
4965
  import { createHash } from "crypto";
4510
- import { VibeError as VibeError19, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey7 } from "@vibeorm/schema";
4966
+ import { VibeError as VibeError21, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
4511
4967
  var MODULE_ORIGIN_METADATA = Object.freeze({ filename: "module-origin.json", version: 1 });
4512
- function invalid2() {
4513
- return new VibeError19({ 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" } });
4968
+ function invalid4() {
4969
+ 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" } });
4514
4970
  }
4515
- function record2(value) {
4971
+ function record3(value) {
4516
4972
  return value !== null && typeof value === "object" && !Array.isArray(value);
4517
4973
  }
4518
- function identifier(value) {
4974
+ function identifier2(value) {
4519
4975
  return typeof value === "string" && value.trim().length > 0 && !value.includes("\x00");
4520
4976
  }
4521
4977
  function strings(value) {
@@ -4533,17 +4989,17 @@ function freezeData(params) {
4533
4989
  }
4534
4990
  function assertModuleOrigin(params) {
4535
4991
  const value = params.value;
4536
- if (!record2(value) || value.version !== 1 || !identifier(value.moduleId) || !identifier(value.revision) || !(value.previousRevision === null || identifier(value.previousRevision)) || value.previousRevision === value.revision || !["postgres", "sqlite", "mysql"].includes(String(value.dialect)) || !strings(value.contracts) || value.contracts.length === 0 || !value.contracts.every(identifier) || new Set(value.contracts).size !== value.contracts.length || !Array.isArray(value.owned) || !value.owned.every(isSqlObjectIdentity3) || !record2(value.source) || "moduleOrigin" in value.source || !identifier(value.source.name) || !strings(value.source.sql) || typeof value.source.destructive !== "boolean" || value.source.downSql !== undefined && !strings(value.source.downSql) || value.source.desiredState === undefined || value.source.desiredStatePending !== undefined || typeof value.artifactChecksum !== "string" || !/^[a-f0-9]{64}$/.test(value.artifactChecksum))
4537
- throw invalid2();
4538
- if (new Set(value.owned.map((identity) => sqlObjectKey7({ identity }))).size !== value.owned.length)
4539
- throw invalid2();
4992
+ if (!record3(value) || value.version !== 1 || !identifier2(value.moduleId) || !identifier2(value.revision) || !(value.previousRevision === null || identifier2(value.previousRevision)) || value.previousRevision === value.revision || !["postgres", "sqlite", "mysql"].includes(String(value.dialect)) || !strings(value.contracts) || value.contracts.length === 0 || !value.contracts.every(identifier2) || new Set(value.contracts).size !== value.contracts.length || !Array.isArray(value.owned) || !value.owned.every(isSqlObjectIdentity3) || !record3(value.source) || "moduleOrigin" in value.source || "moduleAdoption" in value.source || !identifier2(value.source.name) || !strings(value.source.sql) || typeof value.source.destructive !== "boolean" || value.source.downSql !== undefined && !strings(value.source.downSql) || value.source.desiredState === undefined || value.source.desiredStatePending !== undefined || typeof value.artifactChecksum !== "string" || !/^[a-f0-9]{64}$/.test(value.artifactChecksum))
4993
+ throw invalid4();
4994
+ if (new Set(value.owned.map((identity) => sqlObjectKey8({ identity }))).size !== value.owned.length)
4995
+ throw invalid4();
4540
4996
  const desired = { value: value.source.desiredState, migration: value.source.name };
4541
4997
  assertDesiredState(desired);
4542
4998
  if (desired.value.dialect !== value.dialect)
4543
- throw invalid2();
4999
+ throw invalid4();
4544
5000
  const { artifactChecksum, ...payload } = value;
4545
5001
  if (checksum({ value: payload }) !== artifactChecksum)
4546
- throw invalid2();
5002
+ throw invalid4();
4547
5003
  assertModuleOwnership({ origin: value });
4548
5004
  }
4549
5005
  function createModuleOrigin(params) {
@@ -4566,7 +5022,7 @@ function parseModuleOrigin(params) {
4566
5022
  const checked = { value };
4567
5023
  assertModuleOrigin(checked);
4568
5024
  return checked.value;
4569
- }, failure: invalid2 });
5025
+ }, failure: invalid4 });
4570
5026
  }
4571
5027
  function assertMaterializedModuleMigration(params) {
4572
5028
  const { migration } = params;
@@ -4574,7 +5030,7 @@ function assertMaterializedModuleMigration(params) {
4574
5030
  return;
4575
5031
  const restored = restoreModuleMigration({ migration, origin: migration.moduleOrigin });
4576
5032
  if (migration.desiredState === undefined || canonicalMigrationJson({ value: restored }) !== canonicalMigrationJson({ value: migration }))
4577
- throw new VibeError19({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
5033
+ 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 } });
4578
5034
  }
4579
5035
  function restoreModuleMigration(params) {
4580
5036
  const { migration, origin } = params;
@@ -4585,25 +5041,27 @@ function restoreModuleMigration(params) {
4585
5041
  continue;
4586
5042
  const normalized = (sql) => sql.flatMap((text) => splitSqlStatements({ sql: text, dialect: origin.dialect }));
4587
5043
  if (canonicalMigrationJson({ value: normalized(actual) }) !== canonicalMigrationJson({ value: normalized(origin.source[key] ?? []) }))
4588
- throw invalid2();
5044
+ throw invalid4();
4589
5045
  }
4590
5046
  for (const key of ["execution", "downExecution", "rls", "securityChanges", "downSecurityChanges"]) {
4591
5047
  if (migration[key] !== undefined && canonicalMigrationJson({ value: migration[key] }) !== canonicalMigrationJson({ value: origin.source[key] }))
4592
- throw invalid2();
5048
+ throw invalid4();
4593
5049
  }
4594
- return { ...origin.source, name: migration.name, ...migration.desiredState === undefined ? {} : { desiredState: migration.desiredState }, moduleOrigin: origin };
5050
+ const restored = { ...origin.source, name: migration.name, ...migration.desiredState === undefined ? {} : { desiredState: migration.desiredState }, moduleOrigin: origin, ...migration.moduleAdoption === undefined ? {} : { moduleAdoption: migration.moduleAdoption } };
5051
+ assertModuleAdoption({ migration: restored });
5052
+ return restored;
4595
5053
  }
4596
5054
 
4597
5055
  // packages/migrate/src/fs.ts
4598
5056
  import { readFile, readdir } from "fs/promises";
4599
5057
  import { join } from "path";
4600
- import { VibeError as VibeError33, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
5058
+ import { VibeError as VibeError36, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
4601
5059
 
4602
5060
  // packages/migrate/src/migration-checksum.ts
4603
5061
  import { createHash as createHash2 } from "crypto";
4604
5062
 
4605
5063
  // packages/migrate/src/execution.ts
4606
- import { VibeError as VibeError20 } from "@vibeorm/schema";
5064
+ import { VibeError as VibeError22 } from "@vibeorm/schema";
4607
5065
  var EXECUTION_SIDECAR_FILENAME = "execution.json";
4608
5066
  var EXECUTION_SIDECAR_VERSION = 1;
4609
5067
  function isDefaultExecution(params) {
@@ -4641,8 +5099,8 @@ function executionChecksumSuffix(params) {
4641
5099
  ${lines.join(`
4642
5100
  `)}`;
4643
5101
  }
4644
- function invalid3(params) {
4645
- return new VibeError20({
5102
+ function invalid5(params) {
5103
+ return new VibeError22({
4646
5104
  code: "VIBE_MIGRATION",
4647
5105
  message: `invalid execution metadata for migration "${params.migration}": ${params.detail}`,
4648
5106
  meta: { reason: "execution-metadata-invalid", migration: params.migration }
@@ -4653,22 +5111,22 @@ function assertValidExecution(params) {
4653
5111
  if (execution === undefined)
4654
5112
  return;
4655
5113
  if (execution.transaction !== "default" && execution.transaction !== "none") {
4656
- throw invalid3({ migration, detail: `unknown transaction mode ${JSON.stringify(execution.transaction)}` });
5114
+ throw invalid5({ migration, detail: `unknown transaction mode ${JSON.stringify(execution.transaction)}` });
4657
5115
  }
4658
5116
  for (const [label, value] of [["lockTimeoutMs", execution.lockTimeoutMs], ["statementTimeoutMs", execution.statementTimeoutMs]]) {
4659
5117
  if (value === undefined)
4660
5118
  continue;
4661
5119
  if (!Number.isSafeInteger(value) || value < 0) {
4662
- throw invalid3({ migration, detail: `${label} must be a non-negative integer number of milliseconds` });
5120
+ throw invalid5({ migration, detail: `${label} must be a non-negative integer number of milliseconds` });
4663
5121
  }
4664
5122
  }
4665
5123
  const seen = new Set;
4666
5124
  for (const binding of execution.operations ?? []) {
4667
5125
  if (!Number.isSafeInteger(binding.statement) || binding.statement < 0 || binding.statement >= statements) {
4668
- throw invalid3({ migration, detail: `operation refers to statement ${String(binding.statement)}, which is outside the migration's ${String(statements)} statement(s)` });
5126
+ throw invalid5({ migration, detail: `operation refers to statement ${String(binding.statement)}, which is outside the migration's ${String(statements)} statement(s)` });
4669
5127
  }
4670
5128
  if (seen.has(binding.statement)) {
4671
- throw invalid3({ migration, detail: `statement ${String(binding.statement)} carries more than one declared operation` });
5129
+ throw invalid5({ migration, detail: `statement ${String(binding.statement)} carries more than one declared operation` });
4672
5130
  }
4673
5131
  seen.add(binding.statement);
4674
5132
  }
@@ -4693,7 +5151,7 @@ function parseExecutionSidecar(params) {
4693
5151
  try {
4694
5152
  parsed = JSON.parse(params.text);
4695
5153
  } catch (error) {
4696
- throw new VibeError20({
5154
+ throw new VibeError22({
4697
5155
  code: "VIBE_MIGRATION",
4698
5156
  message: `invalid execution metadata for migration "${migration}": not valid JSON`,
4699
5157
  meta: { reason: "execution-metadata-invalid", migration },
@@ -4701,10 +5159,10 @@ function parseExecutionSidecar(params) {
4701
5159
  });
4702
5160
  }
4703
5161
  if (parsed === null || typeof parsed !== "object")
4704
- throw invalid3({ migration, detail: "expected a JSON object" });
5162
+ throw invalid5({ migration, detail: "expected a JSON object" });
4705
5163
  const body = parsed;
4706
5164
  if (body.version !== EXECUTION_SIDECAR_VERSION) {
4707
- throw invalid3({ migration, detail: `unsupported sidecar version ${String(body.version)} (this build reads version ${String(EXECUTION_SIDECAR_VERSION)})` });
5165
+ throw invalid5({ migration, detail: `unsupported sidecar version ${String(body.version)} (this build reads version ${String(EXECUTION_SIDECAR_VERSION)})` });
4708
5166
  }
4709
5167
  const up = body.up === undefined ? undefined : body.up;
4710
5168
  const down = body.down === undefined ? undefined : body.down;
@@ -4786,22 +5244,25 @@ function describeMigrationRisks(params) {
4786
5244
  function migrationChecksum(params) {
4787
5245
  const suffix = executionChecksumSuffix({ ...params.execution === undefined ? {} : { execution: params.execution } });
4788
5246
  return createHash2("sha256").update(params.sql.join(`
4789
- `) + suffix + moduleOriginChecksumSuffix(params) + (params.desiredStatePending === true ? `
5247
+ `) + suffix + moduleOriginChecksumSuffix(params) + moduleAdoptionChecksumSuffix(params) + (params.desiredStatePending === true ? `
4790
5248
  -- vibeorm:desired-state unfinalized-v1` : "") + desiredStateChecksumSuffix({ ...params.desiredState === undefined ? {} : { desiredState: params.desiredState } }) + (params.desiredState === undefined ? "" : `
4791
5249
  -- vibeorm:down ${JSON.stringify(params.downSql ?? null)}${executionChecksumSuffix({ ...params.downExecution === undefined ? {} : { execution: params.downExecution } })}`)).digest("hex");
4792
5250
  }
4793
5251
 
4794
5252
  // packages/migrate/src/shadow-guard.ts
4795
- import { VibeError as VibeError21 } from "@vibeorm/schema";
4796
- import { getSqlDialect as getSqlDialect4, renderShadowOperation } from "@vibeorm/sql";
5253
+ import { VibeError as VibeError23 } from "@vibeorm/schema";
5254
+ import { getSqlDialect as getSqlDialect5, renderShadowOperation } from "@vibeorm/sql";
4797
5255
  var replayExecutors = new WeakSet;
5256
+ function isShadowReplayExecutor(params) {
5257
+ return replayExecutors.has(params.executor);
5258
+ }
4798
5259
  async function assertDatabaseNotShadow(params) {
4799
5260
  if (params.dialect === "sqlite" || replayExecutors.has(params.executor))
4800
5261
  return;
4801
- const [query] = renderShadowOperation({ dialect: getSqlDialect4({ dialect: params.dialect }), operation: { kind: "markerExists" } });
5262
+ const [query] = renderShadowOperation({ dialect: getSqlDialect5({ dialect: params.dialect }), operation: { kind: "markerExists" } });
4802
5263
  const rows = await params.executor({ text: query.text, values: [...query.values] });
4803
5264
  if (rows.length !== 0)
4804
- throw new VibeError21({ 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" } });
5265
+ 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" } });
4805
5266
  }
4806
5267
  function runShadowReplay(params) {
4807
5268
  replayExecutors.add(params.executor);
@@ -4810,10 +5271,95 @@ function runShadowReplay(params) {
4810
5271
  });
4811
5272
  }
4812
5273
 
5274
+ // packages/migrate/src/external-dependencies.ts
5275
+ import { VibeError as VibeError24, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
5276
+ import { getSqlDialect as getSqlDialect6, renderExternalDependents } from "@vibeorm/sql";
5277
+ function externalDependencyTargets(params) {
5278
+ return params.steps.flatMap((step) => {
5279
+ const namespace = step.namespace ?? params.namespace;
5280
+ switch (step.kind) {
5281
+ case "dropEnum":
5282
+ return [{ kind: "type", namespace, name: step.enumName, dependency: { kind: "enum", name: { namespace, name: step.enumName } } }];
5283
+ case "dropTable":
5284
+ case "dropJoinTable":
5285
+ return [{ kind: "relation", namespace, name: step.table, dependency: { kind: "table", name: { namespace, name: step.table } } }];
5286
+ case "dropView":
5287
+ return [{ kind: "relation", namespace, name: step.table, dependency: { kind: "view", name: { namespace, name: step.table } } }];
5288
+ case "dropColumn":
5289
+ return [{ kind: "column", namespace, name: step.table, column: step.column, dependency: { kind: "column", table: { namespace, name: step.table }, column: step.column } }];
5290
+ case "alterColumn":
5291
+ return step.changes.includes("type") ? [{ kind: "column", namespace, name: step.table, column: step.column, dependency: { kind: "column", table: { namespace, name: step.table }, column: step.column } }] : [];
5292
+ default:
5293
+ return [];
5294
+ }
5295
+ });
5296
+ }
5297
+ function unavailable() {
5298
+ return new VibeError24({ code: "VIBE_MIGRATION", message: "Malformed catalog dependency row", meta: { reason: "external-inventory-unavailable" } });
5299
+ }
5300
+ function text(params) {
5301
+ const value = params.row[params.key];
5302
+ if (value === null || value === undefined)
5303
+ return;
5304
+ if (typeof value !== "string")
5305
+ throw unavailable();
5306
+ return value;
5307
+ }
5308
+ function dependentOwner(params) {
5309
+ const { row, external } = params;
5310
+ const relationNamespace = text({ row, key: "relationNamespace" });
5311
+ const relationName = text({ row, key: "relationName" });
5312
+ const relationKind = text({ row, key: "relationKind" });
5313
+ const column = text({ row, key: "columnName" });
5314
+ const dependentClass = text({ row, key: "dependentClass" });
5315
+ const objectNamespace = text({ row, key: "objectNamespace" });
5316
+ const objectName = text({ row, key: "objectName" });
5317
+ if (relationNamespace !== undefined && relationName !== undefined) {
5318
+ const name = { namespace: relationNamespace, name: relationName };
5319
+ const anchor = relationKind === "r" || relationKind === "p" || relationKind === "f" ? { kind: "table", name } : relationKind === "v" || relationKind === "m" ? { kind: "view", name } : relationKind === "S" ? { kind: "sequence", name } : { kind: "namespace", name: relationNamespace };
5320
+ const owner = externalObjectOwner({ identity: anchor, external });
5321
+ const identity = anchor.kind === "table" && column !== undefined && dependentClass === "pg_attrdef" ? { kind: "columnDefault", table: name, column } : anchor.kind === "table" && column !== undefined ? { kind: "column", table: name, column } : anchor.kind === "namespace" ? undefined : anchor;
5322
+ return { ...owner === undefined ? {} : { owner }, ...identity === undefined ? {} : { identity } };
5323
+ }
5324
+ const anchor = dependentClass === "pg_proc" && objectNamespace !== undefined && objectName !== undefined ? { kind: "function", name: { namespace: objectNamespace, name: objectName }, argumentTypes: [] } : objectNamespace === undefined ? undefined : { kind: "namespace", name: objectNamespace };
5325
+ const owner = anchor === undefined ? undefined : externalObjectOwner({ identity: anchor, external });
5326
+ return owner === undefined ? {} : { owner };
5327
+ }
5328
+ function assertNoExternalDependents(params) {
5329
+ for (const row of params.rows) {
5330
+ const target = typeof row.position === "number" ? params.targets[row.position] : undefined;
5331
+ const type = text({ row, key: "dependentType" });
5332
+ const identity = text({ row, key: "dependentIdentity" });
5333
+ const referenced = text({ row, key: "referenced" });
5334
+ if (target === undefined || type === undefined || identity === undefined || referenced === undefined)
5335
+ throw unavailable();
5336
+ const dependent = dependentOwner({ row, external: params.external });
5337
+ if (dependent.owner === undefined)
5338
+ continue;
5339
+ throw new VibeError24({
5340
+ code: "VIBE_MIGRATION",
5341
+ message: "An externally owned object depends on an application object this change drops or retypes; automatic DDL is refused",
5342
+ meta: { reason: "external-owned-dependency", owner: dependent.owner, ...dependent.identity === undefined ? {} : { identity: dependent.identity }, dependent: { type, identity }, dependency: target.dependency, referenced }
5343
+ });
5344
+ }
5345
+ }
5346
+ async function refuseExternalDependents(params) {
5347
+ if (params.external.declarations.length === 0)
5348
+ return;
5349
+ if (params.dialect !== "postgres")
5350
+ throw unsupportedCapability8({ dialect: params.dialect, feature: "externalObjects" });
5351
+ const targets = externalDependencyTargets({ steps: params.steps, namespace: params.namespace });
5352
+ if (targets.length === 0)
5353
+ return;
5354
+ const query = renderExternalDependents({ dialect: getSqlDialect6({ dialect: params.dialect }), targets: targets.map(({ dependency, ...target }) => target) });
5355
+ const rows = await params.executor({ text: query.text, values: [...query.values] });
5356
+ assertNoExternalDependents({ external: params.external, targets, rows });
5357
+ }
5358
+
4813
5359
  // packages/migrate/src/module-history.ts
4814
- import { VibeError as VibeError22, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
5360
+ import { VibeError as VibeError25, sqlObjectKey as sqlObjectKey9, providerToDialect as providerToDialect3 } from "@vibeorm/schema";
4815
5361
  function refusal2(params) {
4816
- return new VibeError22({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} history, ownership or predecessor state is inconsistent; restore or complete the reviewed upgrade first`, meta: params });
5362
+ 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 });
4817
5363
  }
4818
5364
  function moduleHistoryHeads(params) {
4819
5365
  const heads = new Map;
@@ -4848,20 +5394,22 @@ function applicationModuleOrigins(params) {
4848
5394
  return origins;
4849
5395
  }
4850
5396
  function preserves(params) {
4851
- const tables = new Set(params.expected.schema.models.map((model) => model.dbName ?? model.name));
4852
- const enums = new Set(params.expected.schema.enums.map((entry) => entry.dbName ?? entry.name));
4853
- const models = params.actual.schema.models.filter((model) => tables.has(model.dbName ?? model.name));
4854
- const declaredEnums = params.actual.schema.enums.filter((entry) => enums.has(entry.dbName ?? entry.name));
5397
+ const dialect = providerToDialect3({ provider: params.expected.schema.datasource.provider });
5398
+ const keyOf = (entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect });
5399
+ const tables = new Set(params.expected.schema.models.map(keyOf));
5400
+ const enums = new Set(params.expected.schema.enums.map(keyOf));
5401
+ const models = params.actual.schema.models.filter((model) => tables.has(keyOf(model)));
5402
+ const declaredEnums = params.actual.schema.enums.filter((entry) => enums.has(keyOf(entry)));
4855
5403
  if (canonicalMigrationJson({ value: models }) !== canonicalMigrationJson({ value: params.expected.schema.models }) || canonicalMigrationJson({ value: declaredEnums }) !== canonicalMigrationJson({ value: params.expected.schema.enums }))
4856
5404
  throw refusal2({ reason: "module-application-mutation", moduleId: params.moduleId });
4857
- const objectKeys = new Set((params.expected.schema.migrationObjects ?? []).map((object) => sqlObjectKey8({ identity: object.identity })));
4858
- const definitionKeys = new Set(params.expected.definitions.map((definition) => sqlObjectKey8({ identity: definition.identity })));
4859
- if (canonicalMigrationJson({ value: (params.actual.schema.migrationObjects ?? []).filter((object) => objectKeys.has(sqlObjectKey8({ identity: object.identity }))) }) !== canonicalMigrationJson({ value: params.expected.schema.migrationObjects ?? [] }) || canonicalMigrationJson({ value: params.actual.definitions.filter((definition) => definitionKeys.has(sqlObjectKey8({ identity: definition.identity }))) }) !== canonicalMigrationJson({ value: params.expected.definitions }))
5405
+ const objectKeys = new Set((params.expected.schema.migrationObjects ?? []).map((object) => sqlObjectKey9({ identity: object.identity })));
5406
+ const definitionKeys = new Set(params.expected.definitions.map((definition) => sqlObjectKey9({ identity: definition.identity })));
5407
+ if (canonicalMigrationJson({ value: (params.actual.schema.migrationObjects ?? []).filter((object) => objectKeys.has(sqlObjectKey9({ identity: object.identity }))) }) !== canonicalMigrationJson({ value: params.expected.schema.migrationObjects ?? [] }) || canonicalMigrationJson({ value: params.actual.definitions.filter((definition) => definitionKeys.has(sqlObjectKey9({ identity: definition.identity }))) }) !== canonicalMigrationJson({ value: params.expected.definitions }))
4860
5408
  throw refusal2({ reason: "module-application-mutation", moduleId: params.moduleId });
4861
5409
  for (const model of params.actual.schema.models)
4862
5410
  for (const relation of model.relations) {
4863
5411
  const target = params.actual.schema.models.find((candidate) => candidate.name === relation.targetModel);
4864
- if (target !== undefined && tables.has(model.dbName ?? model.name) !== tables.has(target.dbName ?? target.name))
5412
+ if (target !== undefined && tables.has(keyOf(model)) !== tables.has(keyOf(target)))
4865
5413
  throw refusal2({ reason: "module-external-dependency", moduleId: params.moduleId });
4866
5414
  }
4867
5415
  }
@@ -4871,7 +5419,7 @@ function assertModuleUpgradeHistory(params) {
4871
5419
  const register = (migration) => {
4872
5420
  const origin = migration.moduleOrigin;
4873
5421
  for (const identity of origin.owned) {
4874
- const key = sqlObjectKey8({ identity });
5422
+ const key = sqlObjectKey9({ identity });
4875
5423
  if (owners.has(key) && owners.get(key) !== origin.moduleId)
4876
5424
  throw refusal2({ reason: "module-object-collision", moduleId: origin.moduleId });
4877
5425
  owners.set(key, origin.moduleId);
@@ -4918,11 +5466,11 @@ function assertModuleUpgradeHistory(params) {
4918
5466
  }
4919
5467
 
4920
5468
  // packages/migrate/src/module-journal.ts
4921
- import { VibeError as VibeError24 } from "@vibeorm/schema";
4922
- import { getSqlDialect as getSqlDialect5, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
5469
+ import { VibeError as VibeError27 } from "@vibeorm/schema";
5470
+ import { getSqlDialect as getSqlDialect7, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
4923
5471
 
4924
5472
  // packages/migrate/src/journal-read.ts
4925
- import { VibeError as VibeError23 } from "@vibeorm/schema";
5473
+ import { VibeError as VibeError26 } from "@vibeorm/schema";
4926
5474
  var MISSING_TABLE_IDENTIFIERS = new Set(["42P01", "42S02", "ER_NO_SUCH_TABLE"]);
4927
5475
  function isMissingTable(params) {
4928
5476
  let current = params.cause;
@@ -4946,7 +5494,7 @@ async function readJournalRows(params) {
4946
5494
  if (presence.state === "missing")
4947
5495
  return;
4948
5496
  }
4949
- throw new VibeError23({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
5497
+ throw new VibeError26({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
4950
5498
  });
4951
5499
  if (rows === undefined)
4952
5500
  return;
@@ -4954,7 +5502,7 @@ async function readJournalRows(params) {
4954
5502
  for (const row of rows) {
4955
5503
  const index = row.statement_index;
4956
5504
  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)) {
4957
- throw new VibeError23({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
5505
+ throw new VibeError26({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
4958
5506
  }
4959
5507
  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 } : {} });
4960
5508
  }
@@ -4962,41 +5510,41 @@ async function readJournalRows(params) {
4962
5510
  }
4963
5511
 
4964
5512
  // packages/migrate/src/module-journal.ts
4965
- function invalid4() {
4966
- return new VibeError24({ 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" } });
5513
+ function invalid6() {
5514
+ 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" } });
4967
5515
  }
4968
5516
  function parseModuleJournalEntry(params) {
4969
- return parseMigrationMetadata({ text: params.text, failure: invalid4, parse: (value) => {
5517
+ return parseMigrationMetadata({ text: params.text, failure: invalid6, parse: (value) => {
4970
5518
  if (value === null || typeof value !== "object" || !("version" in value) || value.version !== 1 || !("complete" in value) || typeof value.complete !== "boolean" || !("migration" in value) || value.migration === null || typeof value.migration !== "object")
4971
- throw invalid4();
5519
+ throw invalid6();
4972
5520
  const migration = value.migration;
4973
5521
  assertModuleOrigin({ value: migration.moduleOrigin });
4974
5522
  if (migration.name !== params.name || migrationChecksum(migration) !== params.checksum)
4975
- throw invalid4();
5523
+ throw invalid6();
4976
5524
  assertMaterializedModuleMigration({ migration });
4977
5525
  assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
4978
5526
  return { version: 1, complete: value.complete, migration };
4979
5527
  } });
4980
5528
  }
4981
5529
  async function readModuleJournal(params) {
4982
- const query = renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: { kind: "read" } });
5530
+ const query = renderModuleJournal({ dialect: getSqlDialect7({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: { kind: "read" } });
4983
5531
  const rows = await readJournalRows({ ...params, query });
4984
5532
  return new Map([...rows ?? []].map(([name, row]) => {
4985
5533
  const entry = row.moduleEntry === undefined ? undefined : parseModuleJournalEntry({ text: row.moduleEntry, name, checksum: row.checksum });
4986
5534
  if (entry !== undefined && entry.migration.moduleOrigin.dialect !== params.dialect)
4987
- throw invalid4();
5535
+ throw invalid6();
4988
5536
  return [name, { row, ...entry === undefined ? {} : { entry } }];
4989
5537
  }));
4990
5538
  }
4991
5539
  async function ensureModuleJournal(params) {
4992
- const dialect = getSqlDialect5({ dialect: params.dialect });
5540
+ const dialect = getSqlDialect7({ dialect: params.dialect });
4993
5541
  const columns = await params.executor(renderModuleJournal({ dialect, table: MIGRATIONS_TABLE_NAME, operation: { kind: "columns" } }));
4994
5542
  if (columns.some((row) => row.name === MODULE_JOURNAL_COLUMN))
4995
5543
  return;
4996
5544
  await params.executor(renderModuleJournal({ dialect, table: MIGRATIONS_TABLE_NAME, operation: { kind: "addColumn" } }));
4997
5545
  }
4998
5546
  async function insertModuleJournal(params) {
4999
- await params.executor(renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
5547
+ await params.executor(renderModuleJournal({ dialect: getSqlDialect7({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
5000
5548
  kind: "insert",
5001
5549
  name: params.migration.name,
5002
5550
  checksum: migrationChecksum(params.migration),
@@ -5005,7 +5553,7 @@ async function insertModuleJournal(params) {
5005
5553
  } }));
5006
5554
  }
5007
5555
  async function completeModuleJournal(params) {
5008
- await params.executor(renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
5556
+ await params.executor(renderModuleJournal({ dialect: getSqlDialect7({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
5009
5557
  kind: "complete",
5010
5558
  name: params.migration.name,
5011
5559
  entry: canonicalMigrationJson({ value: { version: 1, migration: params.migration, complete: true } })
@@ -5014,27 +5562,27 @@ async function completeModuleJournal(params) {
5014
5562
  function moduleRecordComplete(params) {
5015
5563
  const { migration, stored } = params;
5016
5564
  if (stored.moduleEntry === undefined || stored.checksum === "")
5017
- throw invalid4();
5565
+ throw invalid6();
5018
5566
  const entry = parseModuleJournalEntry({ text: stored.moduleEntry, name: migration.name, checksum: stored.checksum });
5019
5567
  if (migrationChecksum(migration) !== stored.checksum)
5020
- throw invalid4();
5568
+ throw invalid6();
5021
5569
  return entry.complete && stored.error === undefined && stored.statementIndex === migration.sql.length;
5022
5570
  }
5023
5571
 
5024
5572
  // packages/migrate/src/runner.ts
5025
- import { VibeError as VibeError32 } from "@vibeorm/schema";
5573
+ import { VibeError as VibeError35 } from "@vibeorm/schema";
5026
5574
  import {
5027
5575
  DIALECT_CAPABILITIES as DIALECT_CAPABILITIES3,
5028
- getSqlDialect as getSqlDialect7,
5576
+ getSqlDialect as getSqlDialect9,
5029
5577
  renderMigrationSessionRead,
5030
5578
  renderMigrationSessionRestore,
5031
5579
  renderMigrationSessionSet
5032
5580
  } from "@vibeorm/sql";
5033
5581
 
5034
5582
  // packages/migrate/src/recovery.ts
5035
- import { VibeError as VibeError25 } from "@vibeorm/schema";
5583
+ import { VibeError as VibeError28 } from "@vibeorm/schema";
5036
5584
  import {
5037
- getSqlDialect as getSqlDialect6,
5585
+ getSqlDialect as getSqlDialect8,
5038
5586
  renderColumnStateQuery,
5039
5587
  renderConstraintStateQuery,
5040
5588
  renderIndexStateQuery,
@@ -5048,7 +5596,7 @@ function constraintUnverifiable(params) {
5048
5596
  });
5049
5597
  }
5050
5598
  function conflict2(params) {
5051
- return new VibeError25({
5599
+ return new VibeError28({
5052
5600
  code: "VIBE_MIGRATION",
5053
5601
  message: params.message,
5054
5602
  meta: { reason: params.reason, ...params.meta }
@@ -5073,7 +5621,7 @@ function sameShape(params) {
5073
5621
  }
5074
5622
  async function establishOperationState(params) {
5075
5623
  const { executor, operation } = params;
5076
- const dialect = getSqlDialect6({ dialect: params.dialect });
5624
+ const dialect = getSqlDialect8({ dialect: params.dialect });
5077
5625
  const run = async (query) => executor({ text: query.text, values: [...query.values] });
5078
5626
  switch (operation.kind) {
5079
5627
  case "createIndexConcurrently": {
@@ -5152,11 +5700,11 @@ async function establishOperationState(params) {
5152
5700
  }
5153
5701
 
5154
5702
  // packages/migrate/src/differ.ts
5155
- import { isEnumType as isEnumType6, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
5156
- import { VibeError as VibeError27 } from "@vibeorm/schema";
5703
+ import { assertIndexOperatorClasses as assertIndexOperatorClasses2, isEnumType as isEnumType6, qualifiedSqlName as qualifiedSqlName9, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
5704
+ import { VibeError as VibeError30 } from "@vibeorm/schema";
5157
5705
 
5158
5706
  // packages/migrate/src/renames.ts
5159
- import { VibeError as VibeError26 } from "@vibeorm/schema";
5707
+ import { VibeError as VibeError29, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
5160
5708
  function tableOf(model) {
5161
5709
  return model.dbName ?? model.name;
5162
5710
  }
@@ -5167,7 +5715,7 @@ function hasColumn(params) {
5167
5715
  return params.model.fields.some((field) => (field.dbName ?? field.name) === params.column);
5168
5716
  }
5169
5717
  function ambiguous(params) {
5170
- return new VibeError26({
5718
+ return new VibeError29({
5171
5719
  code: "VIBE_MIGRATION",
5172
5720
  message: `rename refused: ${params.detail}`,
5173
5721
  meta: { reason: "rename-ambiguous", ...params.meta }
@@ -5177,6 +5725,8 @@ function planRenames(params) {
5177
5725
  const { to, dialect } = params;
5178
5726
  if (params.renames.length === 0)
5179
5727
  return { steps: [], from: params.from };
5728
+ if ([params.from, params.to].some((schema) => schema.models.some((model) => model.namespace !== undefined && model.namespace !== "public" && params.renames.some((intent) => intent.kind === "table" ? [intent.from, intent.to].includes(tableOf(model)) : intent.table === tableOf(model)))))
5729
+ throw unsupportedCapability9({ dialect, feature: "qualifiedRenameIntent", hint: "Use reviewed namespace-qualified SQL for a physical rename in a fixed namespace" });
5180
5730
  const renamedTables = new Map;
5181
5731
  const renamedColumns = new Map;
5182
5732
  const steps = [];
@@ -5268,9 +5818,19 @@ function planRenames(params) {
5268
5818
  // packages/migrate/src/differ.ts
5269
5819
  function diffSchemas(params) {
5270
5820
  const dialect = params.dialect ?? "postgres";
5821
+ assertIndexOperatorClasses2({ schema: params.to, dialect });
5822
+ for (const kind of ["models", "enums"])
5823
+ for (const entry of params.to[kind]) {
5824
+ const key = physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect });
5825
+ 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 })));
5826
+ 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))
5827
+ throw unsupportedCapability10({ dialect, feature: "automaticNamespaceMove", hint: "Moving populated objects to a fixed namespace requires an explicit reviewed migration" });
5828
+ }
5829
+ for (const schema of [params.from, params.to])
5830
+ normalizeSchema({ schema, dialect });
5271
5831
  for (const schema of [params.from, params.to])
5272
5832
  if (dialect !== "postgres" && schema.models.some((model) => (model.exclusions?.length ?? 0) > 0))
5273
- throw unsupportedCapability6({ dialect, feature: "exclusionConstraints" });
5833
+ throw unsupportedCapability10({ dialect, feature: "exclusionConstraints" });
5274
5834
  assertRlsDialect({ schema: params.from, dialect });
5275
5835
  assertRlsDialect({ schema: params.to, dialect });
5276
5836
  if (dialect !== "postgres")
@@ -5280,6 +5840,7 @@ function diffSchemas(params) {
5280
5840
  const from = normalizeSchema({ schema: params.from, dialect });
5281
5841
  const to = normalizeSchema({ schema: params.to, dialect });
5282
5842
  const buckets = {
5843
+ createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !schemaNamespaces({ schema: params.from }).includes(namespace)).map((namespace) => ({ kind: "createNamespace", namespace, destructive: false })) : [],
5283
5844
  dropExclusion: [],
5284
5845
  addExclusion: [],
5285
5846
  dropView: [],
@@ -5309,16 +5870,17 @@ function diffSchemas(params) {
5309
5870
  diffTables({ from, to, buckets, dialect });
5310
5871
  for (const model of params.to.models) {
5311
5872
  const table = model.dbName ?? model.name;
5312
- const previous = params.from.models.find((entry) => (entry.dbName ?? entry.name) === table);
5873
+ const placement = model.namespace === undefined ? {} : { namespace: model.namespace };
5874
+ const previous = params.from.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect }) === physicalNameKey({ name: table, namespace: model.namespace, dialect }));
5313
5875
  for (const exclusion of model.exclusions ?? []) {
5314
5876
  const before = previous?.exclusions?.find((entry) => entry.name === exclusion.name);
5315
5877
  if (before?.definition === exclusion.definition)
5316
5878
  continue;
5317
5879
  if (before?.catalogDefinition)
5318
- throw new VibeError27({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
5880
+ throw new VibeError30({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
5319
5881
  if (before !== undefined)
5320
- buckets.dropExclusion.push({ kind: "dropExclusion", table, exclusion: before, destructive: false });
5321
- buckets.addExclusion.push({ kind: "addExclusion", table, exclusion, destructive: false });
5882
+ buckets.dropExclusion.push({ kind: "dropExclusion", table, ...placement, exclusion: before, destructive: false });
5883
+ buckets.addExclusion.push({ kind: "addExclusion", table, ...placement, exclusion, destructive: false });
5322
5884
  }
5323
5885
  for (const exclusion of previous?.exclusions ?? [])
5324
5886
  if (!model.exclusions?.some((entry) => entry.name === exclusion.name))
@@ -5326,6 +5888,7 @@ function diffSchemas(params) {
5326
5888
  }
5327
5889
  refuseDependentEnumStorageChanges({ from, to, buckets, dialect });
5328
5890
  return withRlsSteps({ from: params.from, to: params.to, steps: [
5891
+ ...buckets.createNamespace,
5329
5892
  ...renamed.steps,
5330
5893
  ...buckets.dropView,
5331
5894
  ...buckets.createEnum,
@@ -5358,7 +5921,7 @@ function refuseScalarListColumns(params) {
5358
5921
  for (const field of model.fields) {
5359
5922
  if (!field.isList || field.isUnsupported === true)
5360
5923
  continue;
5361
- throw unsupportedCapability6({
5924
+ throw unsupportedCapability10({
5362
5925
  dialect,
5363
5926
  feature: `scalar list column "${model.name}.${field.name}"`,
5364
5927
  hint: `${dialect} has no array column type \u2014 model the list as a separate table or a ${dialect === "mysql" ? "JSON" : "Json"} column`
@@ -5378,12 +5941,12 @@ function diffEnums(params) {
5378
5941
  const currentValues = new Set(enumDef.values);
5379
5942
  for (const value of enumDef.values) {
5380
5943
  if (!previousValues.has(value)) {
5381
- buckets.addEnumValue.push({ kind: "addEnumValue", enumName: name, value, destructive: false });
5944
+ buckets.addEnumValue.push({ kind: "addEnumValue", enumName: enumDef.dbName ?? enumDef.name, ...enumDef.namespace === undefined ? {} : { namespace: enumDef.namespace }, value, destructive: false });
5382
5945
  }
5383
5946
  }
5384
5947
  for (const value of previous.values) {
5385
5948
  if (!currentValues.has(value)) {
5386
- throw unsupportedCapability6({
5949
+ throw unsupportedCapability10({
5387
5950
  dialect: "postgres",
5388
5951
  feature: `removing value "${value}" from enum "${name}"`,
5389
5952
  hint: "postgres cannot drop enum values \u2014 recreate the type manually (create new type, ALTER COLUMN ... USING, drop old type)"
@@ -5391,9 +5954,9 @@ function diffEnums(params) {
5391
5954
  }
5392
5955
  }
5393
5956
  }
5394
- for (const [name] of from.enums) {
5957
+ for (const [name, enumDef] of from.enums) {
5395
5958
  if (!to.enums.has(name)) {
5396
- buckets.dropEnum.push({ kind: "dropEnum", enumName: name, destructive: true });
5959
+ buckets.dropEnum.push({ kind: "dropEnum", enumName: enumDef.dbName ?? enumDef.name, ...enumDef.namespace === undefined ? {} : { namespace: enumDef.namespace }, destructive: true });
5397
5960
  }
5398
5961
  }
5399
5962
  }
@@ -5403,10 +5966,10 @@ function refuseDependentEnumStorageChanges(params) {
5403
5966
  if (step.kind !== "alterColumn" || !isEnumStringTransition({ from: step.from, to: step.to }))
5404
5967
  continue;
5405
5968
  for (const normalized of [from, to]) {
5406
- const hasFk = [...normalized.tables.values()].some((table) => [...table.fks.values()].some((fk) => table.table === step.table && fk.columns.includes(step.column) || fk.refTable === step.table && fk.refColumns.includes(step.column)));
5407
- const hasJoin = [...normalized.joinTables.values()].some((join) => [join.a, join.b].some((end) => end.table === step.table && end.column === step.column));
5969
+ const hasFk = [...normalized.tables.values()].some((table) => [...table.fks.values()].some((fk) => table.table === step.table && (table.namespace ?? "public") === (step.namespace ?? "public") && fk.columns.includes(step.column) || fk.refTable === step.table && (fk.refNamespace ?? "public") === (step.namespace ?? "public") && fk.refColumns.includes(step.column)));
5970
+ const hasJoin = [...normalized.joinTables.values()].some((join) => [join.a, join.b].some((end) => end.table === step.table && (end.namespace ?? "public") === (step.namespace ?? "public") && end.column === step.column));
5408
5971
  if (hasFk || hasJoin) {
5409
- throw unsupportedCapability6({
5972
+ throw unsupportedCapability10({
5410
5973
  dialect,
5411
5974
  feature: `enum storage conversion on "${step.table}"."${step.column}" with foreign-key dependencies`,
5412
5975
  hint: "migrate the referencing columns and constraints together in a reviewed manual migration"
@@ -5417,28 +5980,31 @@ function refuseDependentEnumStorageChanges(params) {
5417
5980
  }
5418
5981
  function diffViews(params) {
5419
5982
  const { from, to, buckets } = params;
5420
- for (const [name] of from.views) {
5983
+ for (const [name, view] of from.views) {
5421
5984
  if (!to.views.has(name)) {
5422
- buckets.dropView.push({ kind: "dropView", table: name, destructive: true });
5985
+ buckets.dropView.push({ kind: "dropView", table: view.dbName ?? view.name, ...view.namespace === undefined ? {} : { namespace: view.namespace }, destructive: true });
5423
5986
  }
5424
5987
  }
5425
5988
  }
5426
5989
  function diffJoinTables(params) {
5427
5990
  const { from, to, buckets } = params;
5428
- for (const [table, joinTable] of to.joinTables) {
5429
- if (!from.joinTables.has(table)) {
5991
+ for (const [key, joinTable] of to.joinTables) {
5992
+ if (!from.joinTables.has(key)) {
5993
+ const table = joinTable.table;
5994
+ const placement = joinTable.namespace === undefined ? {} : { namespace: joinTable.namespace };
5430
5995
  buckets.createJoinTable.push({
5431
5996
  kind: "createJoinTable",
5432
5997
  table,
5998
+ ...placement,
5433
5999
  a: joinTable.a,
5434
6000
  b: joinTable.b,
5435
6001
  destructive: false
5436
6002
  });
5437
6003
  }
5438
6004
  }
5439
- for (const [table] of from.joinTables) {
5440
- if (!to.joinTables.has(table)) {
5441
- buckets.dropJoinTable.push({ kind: "dropJoinTable", table, destructive: true });
6005
+ for (const [key, joinTable] of from.joinTables) {
6006
+ if (!to.joinTables.has(key)) {
6007
+ buckets.dropJoinTable.push({ kind: "dropJoinTable", table: joinTable.table, ...joinTable.namespace === undefined ? {} : { namespace: joinTable.namespace }, destructive: true });
5442
6008
  }
5443
6009
  }
5444
6010
  }
@@ -5463,9 +6029,9 @@ function diffTables(params) {
5463
6029
  continue;
5464
6030
  if (dialect !== "sqlite")
5465
6031
  for (const fk of table.fks.values()) {
5466
- buckets.dropForeignKey.push({ kind: "dropForeignKey", table: name, constraintName: fk.constraintName, destructive: false });
6032
+ buckets.dropForeignKey.push({ kind: "dropForeignKey", table: table.table, ...table.namespace === undefined ? {} : { namespace: table.namespace }, constraintName: fk.constraintName, destructive: false });
5467
6033
  }
5468
- buckets.dropTable.push({ kind: "dropTable", table: name, destructive: true });
6034
+ buckets.dropTable.push({ kind: "dropTable", table: table.table, ...table.namespace === undefined ? {} : { namespace: table.namespace }, destructive: true });
5469
6035
  }
5470
6036
  }
5471
6037
  function createWholeTable(params) {
@@ -5473,6 +6039,7 @@ function createWholeTable(params) {
5473
6039
  buckets.createTable.push({
5474
6040
  kind: "createTable",
5475
6041
  table: table.table,
6042
+ ...table.namespace === undefined ? {} : { namespace: table.namespace },
5476
6043
  columns: [...table.columns.values()].map((column) => column.field),
5477
6044
  primaryKey: table.pk,
5478
6045
  ...table.model.primaryKeyConstraintName === undefined ? {} : { primaryKeyConstraintName: table.model.primaryKeyConstraintName },
@@ -5483,6 +6050,7 @@ function createWholeTable(params) {
5483
6050
  buckets.createIndex.push({
5484
6051
  kind: "createIndex",
5485
6052
  table: table.table,
6053
+ ...table.namespace === undefined ? {} : { namespace: table.namespace },
5486
6054
  name: index.name,
5487
6055
  indexKind: index.kind,
5488
6056
  unique: index.unique,
@@ -5496,6 +6064,7 @@ function createWholeTable(params) {
5496
6064
  buckets.addForeignKey.push({
5497
6065
  kind: "addForeignKey",
5498
6066
  table: table.table,
6067
+ ...table.namespace === undefined ? {} : { namespace: table.namespace },
5499
6068
  constraintName: fk.constraintName,
5500
6069
  columns: fk.columns,
5501
6070
  refTable: fk.refTable,
@@ -5509,14 +6078,15 @@ function createWholeTable(params) {
5509
6078
  function diffOneTable(params) {
5510
6079
  const { previous, current, buckets, dialect, fromEnums, toEnums } = params;
5511
6080
  const table = current.table;
6081
+ const placement = current.namespace === undefined ? {} : { namespace: current.namespace };
5512
6082
  for (const [name, column] of current.columns) {
5513
6083
  const before = previous.columns.get(name);
5514
6084
  if (before === undefined) {
5515
- buckets.addColumn.push({ kind: "addColumn", table, column: column.field, destructive: false });
6085
+ buckets.addColumn.push({ kind: "addColumn", table, ...placement, column: column.field, destructive: false });
5516
6086
  continue;
5517
6087
  }
5518
6088
  const changes = [];
5519
- if (before.typeKey !== column.typeKey)
6089
+ if (columnTypeChanged({ declared: column, actual: before }))
5520
6090
  changes.push("type");
5521
6091
  if (before.nullable !== column.nullable)
5522
6092
  changes.push("nullability");
@@ -5528,25 +6098,25 @@ function diffOneTable(params) {
5528
6098
  if (changes.length === 0)
5529
6099
  continue;
5530
6100
  if (changes.includes("type") && [...previous.checks].some(([name, expression]) => current.checks.get(name) === expression)) {
5531
- throw unsupportedCapability6({ dialect, feature: "alterColumnWithRetainedCheck", hint: "CHECK dependencies and type compatibility are not proven; use a reviewed migration that replaces and revalidates the affected constraints" });
6101
+ throw unsupportedCapability10({ dialect, feature: "alterColumnWithRetainedCheck", hint: "CHECK dependencies and type compatibility are not proven; use a reviewed migration that replaces and revalidates the affected constraints" });
5532
6102
  }
5533
6103
  const storageChange = isEnumStringTransition({ from: before.field, to: column.field });
5534
6104
  if (storageChange) {
5535
6105
  assertEnumStringTransition({ from: before.field, to: column.field, dialect, table, column: name });
5536
6106
  if (isEnumType6(before.field.type) && column.field.type === "String") {
5537
- const values = resolveEnumValues({ name: before.field.type.enum, enums: fromEnums });
6107
+ const values = resolveEnumValues({ name: before.field.type.enum, namespace: before.field.type.namespace, enums: fromEnums });
5538
6108
  if (values !== undefined)
5539
6109
  assertStringEnumLabelsFit({ field: column.field, values, dialect, table });
5540
6110
  }
5541
6111
  }
5542
- let narrowingType = changes.includes("type") && !isScalarToArrayChange({ from: before.field, to: column.field }) && !(storageChange && column.field.type === "String");
6112
+ 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");
5543
6113
  if (dialect !== "postgres" && changes.includes("type") && isEnumType6(before.field.type) && isEnumType6(column.field.type)) {
5544
6114
  const fromValues = resolveEnumValues({ name: before.field.type.enum, enums: fromEnums });
5545
6115
  const toValues = resolveEnumValues({ name: column.field.type.enum, enums: toEnums });
5546
6116
  if (fromValues !== undefined && toValues !== undefined) {
5547
6117
  const removed = fromValues.filter((value) => !toValues.includes(value));
5548
6118
  if (removed.length > 0) {
5549
- throw unsupportedCapability6({
6119
+ throw unsupportedCapability10({
5550
6120
  dialect,
5551
6121
  feature: `removing value(s) ${removed.map((value) => `"${value}"`).join(", ")} from enum column "${table}"."${name}"`,
5552
6122
  hint: "migrate the stored values away first, then recreate the column with the narrower enum"
@@ -5558,6 +6128,7 @@ function diffOneTable(params) {
5558
6128
  buckets.alterColumn.push({
5559
6129
  kind: "alterColumn",
5560
6130
  table,
6131
+ ...placement,
5561
6132
  column: name,
5562
6133
  from: before.field,
5563
6134
  to: column.field,
@@ -5567,19 +6138,20 @@ function diffOneTable(params) {
5567
6138
  }
5568
6139
  for (const [name] of previous.columns) {
5569
6140
  if (!current.columns.has(name)) {
5570
- buckets.dropColumn.push({ kind: "dropColumn", table, column: name, destructive: true });
6141
+ buckets.dropColumn.push({ kind: "dropColumn", table, ...placement, column: name, destructive: true });
5571
6142
  }
5572
6143
  }
5573
6144
  const pkBefore = previous.pk.join(",");
5574
6145
  const pkAfter = current.pk.join(",");
5575
6146
  if (pkBefore !== pkAfter) {
5576
6147
  if (previous.pk.length > 0) {
5577
- buckets.dropPrimaryKey.push({ kind: "dropPrimaryKey", table, destructive: false, ...previous.model.primaryKeyConstraintName === undefined ? {} : { constraintName: previous.model.primaryKeyConstraintName } });
6148
+ buckets.dropPrimaryKey.push({ kind: "dropPrimaryKey", table, ...placement, destructive: false, ...previous.model.primaryKeyConstraintName === undefined ? {} : { constraintName: previous.model.primaryKeyConstraintName } });
5578
6149
  }
5579
6150
  if (current.pk.length > 0) {
5580
6151
  buckets.addPrimaryKey.push({
5581
6152
  kind: "addPrimaryKey",
5582
6153
  table,
6154
+ ...placement,
5583
6155
  columns: current.pk,
5584
6156
  ...current.model.primaryKeyConstraintName === undefined ? {} : { constraintName: current.model.primaryKeyConstraintName },
5585
6157
  destructive: false
@@ -5591,15 +6163,15 @@ function diffOneTable(params) {
5591
6163
  if (before === expression)
5592
6164
  continue;
5593
6165
  if (before !== undefined && previous.model.checks?.some((check) => check.name === name && check.catalogExpression === true))
5594
- throw new VibeError27({ 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 } });
6166
+ 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 } });
5595
6167
  if (before !== undefined)
5596
- buckets.dropCheck.push({ kind: "dropCheck", table, name, destructive: false });
5597
- buckets.addCheck.push({ kind: "addCheck", table, name, expression, destructive: false });
6168
+ buckets.dropCheck.push({ kind: "dropCheck", table, ...placement, name, destructive: false });
6169
+ buckets.addCheck.push({ kind: "addCheck", table, ...placement, name, expression, destructive: false });
5598
6170
  }
5599
6171
  for (const [name] of previous.checks) {
5600
6172
  if (current.checks.has(name))
5601
6173
  continue;
5602
- buckets.dropCheck.push({ kind: "dropCheck", table, name, destructive: false });
6174
+ buckets.dropCheck.push({ kind: "dropCheck", table, ...placement, name, destructive: false });
5603
6175
  }
5604
6176
  for (const [key, index] of current.indexes) {
5605
6177
  if (previous.indexes.has(key))
@@ -5607,6 +6179,7 @@ function diffOneTable(params) {
5607
6179
  buckets.createIndex.push({
5608
6180
  kind: "createIndex",
5609
6181
  table,
6182
+ ...placement,
5610
6183
  name: index.name,
5611
6184
  indexKind: index.kind,
5612
6185
  unique: index.unique,
@@ -5619,7 +6192,7 @@ function diffOneTable(params) {
5619
6192
  for (const [key, index] of previous.indexes) {
5620
6193
  if (current.indexes.has(key))
5621
6194
  continue;
5622
- buckets.dropIndex.push({ kind: "dropIndex", name: index.name, table, destructive: false, ...index.constraintName === undefined ? {} : { constraintName: index.constraintName } });
6195
+ buckets.dropIndex.push({ kind: "dropIndex", name: index.name, table, ...placement, destructive: false, ...index.constraintName === undefined ? {} : { constraintName: index.constraintName } });
5623
6196
  }
5624
6197
  for (const [key, fk] of current.fks) {
5625
6198
  const before = previous.fks.get(key);
@@ -5627,9 +6200,11 @@ function diffOneTable(params) {
5627
6200
  buckets.addForeignKey.push({
5628
6201
  kind: "addForeignKey",
5629
6202
  table,
6203
+ ...placement,
5630
6204
  constraintName: fk.constraintName,
5631
6205
  columns: fk.columns,
5632
6206
  refTable: fk.refTable,
6207
+ ...fk.refNamespace === undefined ? {} : { refNamespace: fk.refNamespace },
5633
6208
  refColumns: fk.refColumns,
5634
6209
  onDelete: fk.onDelete,
5635
6210
  onUpdate: fk.onUpdate,
@@ -5641,15 +6216,18 @@ function diffOneTable(params) {
5641
6216
  buckets.dropForeignKey.push({
5642
6217
  kind: "dropForeignKey",
5643
6218
  table,
6219
+ ...placement,
5644
6220
  constraintName: before.constraintName,
5645
6221
  destructive: false
5646
6222
  });
5647
6223
  buckets.addForeignKey.push({
5648
6224
  kind: "addForeignKey",
5649
6225
  table,
6226
+ ...placement,
5650
6227
  constraintName: fk.constraintName,
5651
6228
  columns: fk.columns,
5652
6229
  refTable: fk.refTable,
6230
+ ...fk.refNamespace === undefined ? {} : { refNamespace: fk.refNamespace },
5653
6231
  refColumns: fk.refColumns,
5654
6232
  onDelete: fk.onDelete,
5655
6233
  onUpdate: fk.onUpdate,
@@ -5663,6 +6241,7 @@ function diffOneTable(params) {
5663
6241
  buckets.dropForeignKey.push({
5664
6242
  kind: "dropForeignKey",
5665
6243
  table,
6244
+ ...placement,
5666
6245
  constraintName: fk.constraintName,
5667
6246
  destructive: false
5668
6247
  });
@@ -5670,12 +6249,12 @@ function diffOneTable(params) {
5670
6249
  }
5671
6250
 
5672
6251
  // packages/migrate/src/ddl/render.ts
5673
- import { VibeError as VibeError31, sqlObjectKey as sqlObjectKey9 } from "@vibeorm/schema";
6252
+ import { VibeError as VibeError34, sqlObjectKey as sqlObjectKey10 } from "@vibeorm/schema";
5674
6253
 
5675
6254
  // packages/migrate/src/ddl/postgres.ts
5676
6255
  import { renderExclusionSetup } from "@vibeorm/sql";
5677
- import { VibeError as VibeError28, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
5678
- import { postgresDialect as postgresDialect8, renderRlsPolicyPath } from "@vibeorm/sql";
6256
+ import { VibeError as VibeError31, assertSqlNamespace as assertSqlNamespace3, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
6257
+ import { postgresDialect as postgresDialect9, renderRlsPolicyPath, renderIndexColumns } from "@vibeorm/sql";
5679
6258
  function renderPostgresSteps(params) {
5680
6259
  refuseSameTransactionEnumUse({ steps: params.steps });
5681
6260
  return params.steps.flatMap((step) => renderStep({ step }));
@@ -5685,19 +6264,21 @@ function refuseSameTransactionEnumUse(params) {
5685
6264
  for (const step of params.steps) {
5686
6265
  if (step.kind !== "addEnumValue")
5687
6266
  continue;
5688
- const values = added.get(step.enumName) ?? new Set;
6267
+ const key = JSON.stringify([step.namespace ?? "public", step.enumName]);
6268
+ const values = added.get(key) ?? new Set;
5689
6269
  values.add(step.value);
5690
- added.set(step.enumName, values);
6270
+ added.set(key, values);
5691
6271
  }
5692
6272
  if (added.size === 0)
5693
6273
  return;
5694
6274
  const use = (field) => {
5695
6275
  if (!isEnumType7(field.type))
5696
6276
  return;
5697
- const enumName = field.type.enum;
6277
+ const type = field.type;
6278
+ const enumName = type.enum;
5698
6279
  const def = field.default;
5699
6280
  const candidates = def?.kind === "list" ? def.values : def?.kind === "enumValue" || def?.kind === "literal" ? [def.value] : [];
5700
- const value = candidates.find((entry) => typeof entry === "string" && added.get(enumName)?.has(entry) === true);
6281
+ const value = candidates.find((entry) => typeof entry === "string" && added.get(JSON.stringify([type.namespace ?? "public", enumName]))?.has(entry) === true);
5701
6282
  return value === undefined ? undefined : { enumName, value };
5702
6283
  };
5703
6284
  for (const step of params.steps) {
@@ -5709,7 +6290,7 @@ function refuseSameTransactionEnumUse(params) {
5709
6290
  const found = use(field);
5710
6291
  if (found === undefined)
5711
6292
  continue;
5712
- throw new VibeError28({
6293
+ throw new VibeError31({
5713
6294
  code: "VIBE_MIGRATION",
5714
6295
  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`,
5715
6296
  meta: { enum: found.enumName, value: found.value, table: step.table, column: field.dbName ?? field.name, rule: "enum-value-used-in-adding-transaction" }
@@ -5720,27 +6301,30 @@ function refuseSameTransactionEnumUse(params) {
5720
6301
  function renderStep(params) {
5721
6302
  const { step } = params;
5722
6303
  switch (step.kind) {
6304
+ case "createNamespace":
6305
+ assertSqlNamespace3({ namespace: step.namespace, dialect: "postgres", allowPublic: false });
6306
+ return [`CREATE SCHEMA ${q2(step.namespace)};`];
5723
6307
  case "addExclusion":
5724
- return [...renderExclusionSetup({ dialect: postgresDialect8, exclusion: step.exclusion }), `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.exclusion.name)} ${step.exclusion.definition};`];
6308
+ return [...renderExclusionSetup({ dialect: postgresDialect9, exclusion: step.exclusion }), `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(step.exclusion.name)} ${step.exclusion.definition};`];
5725
6309
  case "dropExclusion":
5726
- return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.exclusion.name)};`];
6310
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.exclusion.name)};`];
5727
6311
  case "enableRls":
5728
6312
  case "forceRls":
5729
6313
  case "disableRls":
5730
6314
  case "noForceRls":
5731
6315
  case "createPolicy":
5732
6316
  case "dropPolicy":
5733
- return step.kind === "createPolicy" ? [renderRlsPolicyPath({ dialect: postgresDialect8, stage: "save" }).text, renderRlsPolicyPath({ dialect: postgresDialect8, stage: "empty" }).text, ...renderRlsStep({ step, dialect: "postgres" }), renderRlsPolicyPath({ dialect: postgresDialect8, stage: "restore" }).text] : renderRlsStep({ step, dialect: "postgres" });
6317
+ return step.kind === "createPolicy" ? [renderRlsPolicyPath({ dialect: postgresDialect9, stage: "save" }).text, renderRlsPolicyPath({ dialect: postgresDialect9, stage: "empty" }).text, ...renderRlsStep({ step, dialect: "postgres" }), renderRlsPolicyPath({ dialect: postgresDialect9, stage: "restore" }).text] : renderRlsStep({ step, dialect: "postgres" });
5734
6318
  case "createEnum": {
5735
6319
  const name = step.enumDef.dbName ?? step.enumDef.name;
5736
6320
  const values = step.enumDef.values.map((value) => quotePgString({ value })).join(", ");
5737
- return [`CREATE TYPE ${publicName(name)} AS ENUM (${values});`];
6321
+ return [`CREATE TYPE ${qualifiedName(name, step.enumDef.namespace)} AS ENUM (${values});`];
5738
6322
  }
5739
6323
  case "dropEnum":
5740
- return [`DROP TYPE ${publicName(step.enumName)};`];
6324
+ return [`DROP TYPE ${qualifiedName(step.enumName, step.namespace)};`];
5741
6325
  case "addEnumValue":
5742
6326
  return [
5743
- `ALTER TYPE ${publicName(step.enumName)} ADD VALUE IF NOT EXISTS ${quotePgString({ value: step.value })};`
6327
+ `ALTER TYPE ${qualifiedName(step.enumName, step.namespace)} ADD VALUE IF NOT EXISTS ${quotePgString({ value: step.value })};`
5744
6328
  ];
5745
6329
  case "createTable": {
5746
6330
  const lines = step.columns.map((column) => ` ${columnDefSql({ field: column })}`);
@@ -5751,101 +6335,101 @@ function renderStep(params) {
5751
6335
  for (const check of step.checks ?? []) {
5752
6336
  lines.push(` CONSTRAINT ${q2(check.name)} CHECK (${check.expression})`);
5753
6337
  }
5754
- return [`CREATE TABLE ${publicName(step.table)} (
6338
+ return [`CREATE TABLE ${qualifiedName(step.table, step.namespace)} (
5755
6339
  ${lines.join(`,
5756
6340
  `)}
5757
6341
  );`];
5758
6342
  }
5759
6343
  case "dropTable":
5760
6344
  case "dropJoinTable":
5761
- return [`DROP TABLE ${publicName(step.table)};`];
6345
+ return [`DROP TABLE ${qualifiedName(step.table, step.namespace)};`];
5762
6346
  case "dropView":
5763
- return [`DROP VIEW IF EXISTS ${publicName(step.table)};`];
6347
+ return [`DROP VIEW IF EXISTS ${qualifiedName(step.table, step.namespace)};`];
5764
6348
  case "addColumn":
5765
- return [`ALTER TABLE ${publicName(step.table)} ADD COLUMN ${columnDefSql({ field: step.column })};`];
6349
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD COLUMN ${columnDefSql({ field: step.column })};`];
5766
6350
  case "dropColumn":
5767
- return [`ALTER TABLE ${publicName(step.table)} DROP COLUMN ${q2(step.column)};`];
6351
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP COLUMN ${q2(step.column)};`];
5768
6352
  case "alterColumn":
5769
6353
  return renderAlterColumn({ step });
5770
6354
  case "createIndex": {
5771
6355
  const unique = step.unique ? "UNIQUE " : "";
5772
6356
  const using = !step.unique && step.indexKind !== "btree" ? ` USING ${step.indexKind}` : "";
5773
- const cols = step.columns.map((column) => `${q2(column.name)}${column.desc ? " DESC" : ""}`).join(", ");
6357
+ const cols = renderIndexColumns({ dialect: postgresDialect9, columns: step.columns, unique: step.unique }).join(", ");
5774
6358
  const nulls = step.nullsNotDistinct === true ? " NULLS NOT DISTINCT" : "";
5775
6359
  const where = step.where === undefined ? "" : ` WHERE ${step.where}`;
5776
6360
  return [
5777
- `CREATE ${unique}INDEX ${q2(step.name)} ON ${publicName(step.table)}${using} (${cols})${nulls}${where};`
6361
+ `CREATE ${unique}INDEX ${q2(step.name)} ON ${qualifiedName(step.table, step.namespace)}${using} (${cols})${nulls}${where};`
5778
6362
  ];
5779
6363
  }
5780
6364
  case "dropIndex":
5781
6365
  if (step.constraintName !== undefined) {
5782
6366
  if (step.table === undefined)
5783
- throw new VibeError28({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
5784
- return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName)};`];
6367
+ throw new VibeError31({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
6368
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.constraintName)};`];
5785
6369
  }
5786
- return [`DROP INDEX ${publicName(step.name)};`];
6370
+ return [`DROP INDEX ${qualifiedName(step.name, step.namespace)};`];
5787
6371
  case "addCheck":
5788
6372
  return [
5789
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.name)} CHECK (${step.expression});`
6373
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(step.name)} CHECK (${step.expression});`
5790
6374
  ];
5791
6375
  case "dropCheck":
5792
- return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.name)};`];
6376
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.name)};`];
5793
6377
  case "addForeignKey": {
5794
6378
  const cols = step.columns.map((column) => q2(column)).join(", ");
5795
6379
  const refCols = step.refColumns.map((column) => q2(column)).join(", ");
5796
6380
  return [
5797
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.constraintName)} FOREIGN KEY (${cols}) REFERENCES ${publicName(step.refTable)} (${refCols}) ON DELETE ${CASCADE_ACTION_SQL[step.onDelete]} ON UPDATE ${CASCADE_ACTION_SQL[step.onUpdate]};`
6381
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(step.constraintName)} FOREIGN KEY (${cols}) REFERENCES ${qualifiedName(step.refTable, step.refNamespace)} (${refCols}) ON DELETE ${CASCADE_ACTION_SQL[step.onDelete]} ON UPDATE ${CASCADE_ACTION_SQL[step.onUpdate]};`
5798
6382
  ];
5799
6383
  }
5800
6384
  case "addForeignKeyNotValid": {
5801
6385
  const cols = step.columns.map((column) => q2(column)).join(", ");
5802
6386
  const refCols = step.refColumns.map((column) => q2(column)).join(", ");
5803
6387
  return [
5804
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.constraintName)} FOREIGN KEY (${cols}) REFERENCES ${publicName(step.refTable)} (${refCols}) ON DELETE ${CASCADE_ACTION_SQL[step.onDelete]} ON UPDATE ${CASCADE_ACTION_SQL[step.onUpdate]} NOT VALID;`
6388
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(step.constraintName)} FOREIGN KEY (${cols}) REFERENCES ${qualifiedName(step.refTable, step.refNamespace)} (${refCols}) ON DELETE ${CASCADE_ACTION_SQL[step.onDelete]} ON UPDATE ${CASCADE_ACTION_SQL[step.onUpdate]} NOT VALID;`
5805
6389
  ];
5806
6390
  }
5807
6391
  case "addCheckNotValid":
5808
6392
  return [
5809
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.name)} CHECK (${step.expression}) NOT VALID;`
6393
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(step.name)} CHECK (${step.expression}) NOT VALID;`
5810
6394
  ];
5811
6395
  case "validateConstraint":
5812
- return [`ALTER TABLE ${publicName(step.table)} VALIDATE CONSTRAINT ${q2(step.name)};`];
6396
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} VALIDATE CONSTRAINT ${q2(step.name)};`];
5813
6397
  case "renameTable":
5814
- return [`ALTER TABLE ${publicName(step.from)} RENAME TO ${q2(step.to)};`];
6398
+ return [`ALTER TABLE ${qualifiedName(step.from, step.namespace)} RENAME TO ${q2(step.to)};`];
5815
6399
  case "renameColumn":
5816
- return [`ALTER TABLE ${publicName(step.table)} RENAME COLUMN ${q2(step.from)} TO ${q2(step.to)};`];
6400
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} RENAME COLUMN ${q2(step.from)} TO ${q2(step.to)};`];
5817
6401
  case "dropForeignKey":
5818
- return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName)};`];
6402
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.constraintName)};`];
5819
6403
  case "createJoinTable": {
5820
6404
  const aType = columnTypeSql({ field: { ...step.a.field, isList: false } });
5821
6405
  const bType = columnTypeSql({ field: { ...step.b.field, isList: false } });
5822
6406
  const names = implicitJoinTableObjectNames2({ table: step.table });
5823
6407
  return [
5824
- `CREATE TABLE ${publicName(step.table)} (
6408
+ `CREATE TABLE ${qualifiedName(step.table, step.namespace)} (
5825
6409
  "A" ${aType} NOT NULL,
5826
6410
  "B" ${bType} NOT NULL
5827
6411
  );`,
5828
- `CREATE UNIQUE INDEX ${q2(names.uniqueIndex)} ON ${publicName(step.table)} ("A", "B");`,
5829
- `CREATE INDEX ${q2(names.indexB)} ON ${publicName(step.table)} ("B");`,
5830
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(names.foreignKeyA)} FOREIGN KEY ("A") REFERENCES ${publicName(step.a.table)} (${q2(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`,
5831
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(names.foreignKeyB)} FOREIGN KEY ("B") REFERENCES ${publicName(step.b.table)} (${q2(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`
6412
+ `CREATE UNIQUE INDEX ${q2(names.uniqueIndex)} ON ${qualifiedName(step.table, step.namespace)} ("A", "B");`,
6413
+ `CREATE INDEX ${q2(names.indexB)} ON ${qualifiedName(step.table, step.namespace)} ("B");`,
6414
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(names.foreignKeyA)} FOREIGN KEY ("A") REFERENCES ${qualifiedName(step.a.table, step.a.namespace)} (${q2(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`,
6415
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(names.foreignKeyB)} FOREIGN KEY ("B") REFERENCES ${qualifiedName(step.b.table, step.b.namespace)} (${q2(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`
5832
6416
  ];
5833
6417
  }
5834
6418
  case "addPrimaryKey": {
5835
6419
  const cols = step.columns.map((column) => q2(column)).join(", ");
5836
6420
  return [
5837
- `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.constraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))} PRIMARY KEY (${cols});`
6421
+ `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ADD CONSTRAINT ${q2(step.constraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))} PRIMARY KEY (${cols});`
5838
6422
  ];
5839
6423
  }
5840
6424
  case "dropPrimaryKey":
5841
- return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))};`];
6425
+ return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.constraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))};`];
5842
6426
  }
5843
6427
  }
5844
6428
  function columnDefSql(params) {
5845
6429
  const { field } = params;
5846
6430
  const name = q2(field.dbName ?? field.name);
5847
6431
  if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
5848
- throw new VibeError28({
6432
+ throw new VibeError31({
5849
6433
  code: "VIBE_MIGRATION",
5850
6434
  message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
5851
6435
  meta: { field: field.name }
@@ -5865,7 +6449,7 @@ function renderAlterColumn(params) {
5865
6449
  const { step } = params;
5866
6450
  const statements = [];
5867
6451
  const column = q2(step.column);
5868
- const alterPrefix = `ALTER TABLE ${publicName(step.table)} ALTER COLUMN ${column}`;
6452
+ const alterPrefix = `ALTER TABLE ${qualifiedName(step.table, step.namespace)} ALTER COLUMN ${column}`;
5869
6453
  const storageChange = step.changes.includes("type") && isEnumStringTransition({ from: step.from, to: step.to });
5870
6454
  if (storageChange)
5871
6455
  assertEnumStringTransition({ from: step.from, to: step.to, dialect: "postgres", table: step.table, column: step.column });
@@ -5881,7 +6465,7 @@ function renderAlterColumn(params) {
5881
6465
  }
5882
6466
  if (step.changes.includes("default") || droppedDefault) {
5883
6467
  if (step.to.default?.kind === "autoincrement") {
5884
- throw new VibeError28({
6468
+ throw new VibeError31({
5885
6469
  code: "VIBE_MIGRATION",
5886
6470
  message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
5887
6471
  meta: { table: step.table, column: step.column }
@@ -5892,15 +6476,16 @@ function renderAlterColumn(params) {
5892
6476
  }
5893
6477
  return statements;
5894
6478
  }
5895
- function publicName(name) {
5896
- return `${q2("public")}.${q2(name)}`;
6479
+ function qualifiedName(name, namespace = "public") {
6480
+ assertSqlNamespace3({ namespace, dialect: "postgres" });
6481
+ return `${q2(namespace)}.${q2(name)}`;
5897
6482
  }
5898
6483
  function q2(name) {
5899
- return postgresDialect8.quoteIdent({ name });
6484
+ return postgresDialect9.quoteIdent({ name });
5900
6485
  }
5901
6486
 
5902
6487
  // packages/migrate/src/ddl/sqlite.ts
5903
- import { VibeError as VibeError29, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
6488
+ import { VibeError as VibeError32, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
5904
6489
  import { sqliteDialect as sqliteDialect3 } from "@vibeorm/sql";
5905
6490
 
5906
6491
  // packages/migrate/src/ddl/drop-order.ts
@@ -5950,6 +6535,8 @@ function orderDropsChildrenFirst(params) {
5950
6535
  function describeStep(params) {
5951
6536
  const { step } = params;
5952
6537
  switch (step.kind) {
6538
+ case "createNamespace":
6539
+ return `create namespace ${JSON.stringify(step.namespace)}`;
5953
6540
  case "addExclusion":
5954
6541
  return `add exclusion "${step.exclusion.name}" on "${step.table}"`;
5955
6542
  case "dropExclusion":
@@ -6020,6 +6607,12 @@ function describeStep(params) {
6020
6607
  // packages/migrate/src/ddl/sqlite.ts
6021
6608
  function renderSqliteSteps(params) {
6022
6609
  const { steps } = params;
6610
+ for (const step of steps) {
6611
+ if (step.kind === "createIndex" && step.columns.some((column) => column.operatorClass !== undefined))
6612
+ throw unsupportedCapability11({ dialect: "sqlite", feature: "indexOperatorClasses" });
6613
+ if (step.kind === "createNamespace" || step.namespace !== undefined || step.refNamespace !== undefined)
6614
+ throw unsupportedCapability11({ dialect: "sqlite", feature: "fixedNamespaces" });
6615
+ }
6023
6616
  for (const step of steps)
6024
6617
  if (isRlsStep({ step }))
6025
6618
  renderRlsStep({ step, dialect: "sqlite" });
@@ -6040,7 +6633,7 @@ function renderSqliteSteps(params) {
6040
6633
  continue;
6041
6634
  const awaitsNewParent = [...toNorm?.tables.get(table)?.fks.values() ?? []].some((fk) => createdTables.has(fk.refTable));
6042
6635
  if (awaitsNewParent) {
6043
- throw new VibeError29({
6636
+ throw new VibeError32({
6044
6637
  code: "VIBE_MIGRATION",
6045
6638
  message: `cannot order this sqlite migration safely: rebuilding "${table}" must happen BEFORE the dropped table(s) it references (${[
6046
6639
  ...droppedTables
@@ -6069,7 +6662,7 @@ function renderSqliteSteps(params) {
6069
6662
  switch (step.kind) {
6070
6663
  case "addExclusion":
6071
6664
  case "dropExclusion":
6072
- throw new VibeError29({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6665
+ throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6073
6666
  case "createEnum":
6074
6667
  case "dropEnum":
6075
6668
  case "addEnumValue":
@@ -6155,7 +6748,7 @@ function renderSqliteSteps(params) {
6155
6748
  case "renameColumn": {
6156
6749
  const renamed = step.kind === "renameTable" ? step.from : step.table;
6157
6750
  if (rebuildSet.has(renamed) || step.kind === "renameTable" && rebuildSet.has(step.to)) {
6158
- throw new VibeError29({
6751
+ throw new VibeError32({
6159
6752
  code: "VIBE_MIGRATION",
6160
6753
  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`,
6161
6754
  meta: { reason: "rename-ambiguous", table: renamed, dialect: "sqlite" }
@@ -6167,7 +6760,7 @@ function renderSqliteSteps(params) {
6167
6760
  case "addForeignKeyNotValid":
6168
6761
  case "addCheckNotValid":
6169
6762
  case "validateConstraint":
6170
- throw unsupportedCapability7({
6763
+ throw unsupportedCapability11({
6171
6764
  dialect: "sqlite",
6172
6765
  feature: "stagedConstraints",
6173
6766
  hint: "sqlite applies constraint changes by rebuilding the table; add the constraint in one step"
@@ -6209,7 +6802,7 @@ function classifyRebuilds(params) {
6209
6802
  switch (step.kind) {
6210
6803
  case "addExclusion":
6211
6804
  case "dropExclusion":
6212
- throw new VibeError29({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6805
+ throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
6213
6806
  case "alterColumn":
6214
6807
  case "dropForeignKey":
6215
6808
  case "addPrimaryKey":
@@ -6261,7 +6854,7 @@ async function assertSqliteRebuildTableOptions(params) {
6261
6854
  for (const table of params.tables) {
6262
6855
  const row = rows.find((entry) => entry.schema === "main" && entry.name === table);
6263
6856
  if (row === undefined || Number(row.strict) !== 0 || Number(row.wr) !== 0) {
6264
- throw unsupportedCapability7({ dialect: "sqlite", feature: "sqliteRebuildTableOptions", hint: `Cannot reproduce the existing table options of "${table}"; use a reviewed migration that preserves STRICT and WITHOUT ROWID` });
6857
+ throw unsupportedCapability11({ dialect: "sqlite", feature: "sqliteRebuildTableOptions", hint: `Cannot reproduce the existing table options of "${table}"; use a reviewed migration that preserves STRICT and WITHOUT ROWID` });
6265
6858
  }
6266
6859
  }
6267
6860
  }
@@ -6270,7 +6863,7 @@ function renderTableRebuild(params) {
6270
6863
  const source = from.tables.get(table);
6271
6864
  const target = to.tables.get(table);
6272
6865
  if (source === undefined || target === undefined) {
6273
- throw new VibeError29({
6866
+ throw new VibeError32({
6274
6867
  code: "VIBE_MIGRATION",
6275
6868
  message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
6276
6869
  meta: { table }
@@ -6297,7 +6890,7 @@ function renderTableRebuild(params) {
6297
6890
  }));
6298
6891
  const common = [...new Set([...target.columns.keys(), ...params.preservedColumns.keys()])].filter((name) => params.preservedColumns.get(name)?.generated !== true && (source.columns.has(name) || params.preservedColumns.has(name)));
6299
6892
  if (common.length === 0) {
6300
- throw unsupportedCapability7({ dialect: "sqlite", feature: "sqliteRebuildWithoutRowTransfer", hint: `Rebuilding "${table}" has no transferable columns and would discard every row; use explicit renames or a reviewed data migration` });
6893
+ throw unsupportedCapability11({ dialect: "sqlite", feature: "sqliteRebuildWithoutRowTransfer", hint: `Rebuilding "${table}" has no transferable columns and would discard every row; use explicit renames or a reviewed data migration` });
6301
6894
  }
6302
6895
  const cols = common.map((name) => q3(name)).join(", ");
6303
6896
  statements.push(`INSERT INTO ${q3(tmp)} (${cols}) SELECT ${cols} FROM ${q3(table)};`);
@@ -6405,7 +6998,7 @@ function columnDefSql2(params) {
6405
6998
  const { field, constraintTable, enums, inlinePkAutoincrement } = params;
6406
6999
  const name = field.dbName ?? field.name;
6407
7000
  if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
6408
- throw new VibeError29({
7001
+ throw new VibeError32({
6409
7002
  code: "VIBE_MIGRATION",
6410
7003
  message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
6411
7004
  meta: { field: field.name }
@@ -6424,7 +7017,7 @@ function columnDefSql2(params) {
6424
7017
  const enumName = field.type.enum;
6425
7018
  const values = resolveEnumValues({ name: enumName, enums });
6426
7019
  if (values === undefined) {
6427
- throw new VibeError29({
7020
+ throw new VibeError32({
6428
7021
  code: "VIBE_MIGRATION",
6429
7022
  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`,
6430
7023
  meta: { field: field.name, enum: enumName }
@@ -6438,14 +7031,14 @@ function columnDefSql2(params) {
6438
7031
  function createIndexSql(params) {
6439
7032
  const { table, name, unique, kind, columns, where } = params;
6440
7033
  if (params.nullsNotDistinct === true) {
6441
- throw unsupportedCapability7({
7034
+ throw unsupportedCapability11({
6442
7035
  dialect: "sqlite",
6443
7036
  feature: `UNIQUE NULLS NOT DISTINCT (index "${name}")`,
6444
7037
  hint: "sqlite treats NULLs as distinct in unique indexes and has no switch \u2014 gate the flag per dialect"
6445
7038
  });
6446
7039
  }
6447
7040
  if (!unique && kind !== "btree") {
6448
- throw unsupportedCapability7({
7041
+ throw unsupportedCapability11({
6449
7042
  dialect: "sqlite",
6450
7043
  feature: `index kind "${kind}" (index "${name}")`,
6451
7044
  hint: "sqlite indexes are btree only; use an extension-specific virtual table for others"
@@ -6459,7 +7052,7 @@ function requireSchemas(params) {
6459
7052
  const { step, fromNorm, toNorm } = params;
6460
7053
  if (fromNorm !== undefined && toNorm !== undefined)
6461
7054
  return;
6462
- throw new VibeError29({
7055
+ throw new VibeError32({
6463
7056
  code: "VIBE_MIGRATION",
6464
7057
  message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
6465
7058
  meta: { step: step.kind }
@@ -6470,10 +7063,14 @@ function q3(name) {
6470
7063
  }
6471
7064
 
6472
7065
  // packages/migrate/src/ddl/mysql.ts
6473
- import { VibeError as VibeError30, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
7066
+ import { VibeError as VibeError33, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
6474
7067
  import { mysqlDialect as mysqlDialect3 } from "@vibeorm/sql";
6475
7068
  function renderMysqlSteps(params) {
6476
7069
  for (const step of params.steps) {
7070
+ if (step.namespace !== undefined || step.refNamespace !== undefined || step.kind === "createNamespace")
7071
+ throw unsupportedCapability12({ dialect: "mysql", feature: "fixedNamespaces" });
7072
+ if (step.kind === "createIndex" && step.columns.some((column) => column.operatorClass !== undefined))
7073
+ throw unsupportedCapability12({ dialect: "mysql", feature: "indexOperatorClasses" });
6477
7074
  if (isRlsStep({ step }))
6478
7075
  renderRlsStep({ step, dialect: "mysql" });
6479
7076
  if (step.kind !== "alterColumn" || !step.changes.some((change) => change === "type" || change === "nullability" || change === "collation"))
@@ -6483,7 +7080,7 @@ function renderMysqlSteps(params) {
6483
7080
  return object.dialect === "mysql" && (identity.kind === "columnDefault" || identity.kind === "generatedColumn" || identity.kind === "column") && identity.table.name === step.table && identity.column === step.column;
6484
7081
  });
6485
7082
  if (owner !== undefined)
6486
- throw unsupportedCapability8({ dialect: "mysql", feature: "mysqlOwnedColumnRewrite", hint: `MODIFY COLUMN would overwrite migration-owned ${owner.id}; use a reviewed migration that preserves the complete column definition` });
7083
+ throw unsupportedCapability12({ dialect: "mysql", feature: "mysqlOwnedColumnRewrite", hint: `MODIFY COLUMN would overwrite migration-owned ${owner.id}; use a reviewed migration that preserves the complete column definition` });
6487
7084
  }
6488
7085
  const enums = new Map;
6489
7086
  for (const enumDef of params.to?.enums ?? []) {
@@ -6503,7 +7100,7 @@ function renderMysqlSteps(params) {
6503
7100
  continue;
6504
7101
  const retainedIndex = [...table.indexes.values()].some((index) => index.columns[0]?.name === auto && !params.steps.some((candidate) => candidate.kind === "dropIndex" && candidate.table === step.table && candidate.name === index.name));
6505
7102
  if (!retainedIndex)
6506
- throw unsupportedCapability8({ dialect: "mysql", feature: "mysqlAutoIncrementKeyRemoval", hint: `Removing the last leading key of AUTO_INCREMENT column "${step.table}.${auto}" requires a reviewed combined ALTER` });
7103
+ throw unsupportedCapability12({ dialect: "mysql", feature: "mysqlAutoIncrementKeyRemoval", hint: `Removing the last leading key of AUTO_INCREMENT column "${step.table}.${auto}" requires a reviewed combined ALTER` });
6507
7104
  }
6508
7105
  let dropCursor = 0;
6509
7106
  const inlineIndexes = new Map;
@@ -6512,7 +7109,7 @@ function renderMysqlSteps(params) {
6512
7109
  const columns = step.kind === "createTable" ? step.columns : step.kind === "addColumn" ? [step.column] : [];
6513
7110
  const counters = columns.filter((field) => field.default?.kind === "autoincrement");
6514
7111
  if (counters.length > 1)
6515
- throw unsupportedCapability8({ dialect: "mysql", feature: "multiple auto-increment columns" });
7112
+ throw unsupportedCapability12({ dialect: "mysql", feature: "multiple auto-increment columns" });
6516
7113
  for (const column of counters) {
6517
7114
  const name = column.dbName ?? column.name;
6518
7115
  if (step.kind === "createTable" && step.primaryKey[0] === name)
@@ -6521,7 +7118,7 @@ function renderMysqlSteps(params) {
6521
7118
  continue;
6522
7119
  const key = params.steps.find((candidate) => candidate.kind === "createIndex" && candidate.table === step.table && candidate.columns[0]?.name === name && !candidate.columns[0]?.desc && candidate.where === undefined && candidate.nullsNotDistinct !== true && (candidate.unique || candidate.indexKind === "btree"));
6523
7120
  if (key === undefined)
6524
- throw unsupportedCapability8({ dialect: "mysql", feature: `auto-increment column ${step.table}.${name} without an accompanying leading index` });
7121
+ throw unsupportedCapability12({ dialect: "mysql", feature: `auto-increment column ${step.table}.${name} without an accompanying leading index` });
6525
7122
  inlineIndexes.set(step, key);
6526
7123
  consumed.add(key);
6527
7124
  }
@@ -6540,6 +7137,8 @@ function renderMysqlSteps(params) {
6540
7137
  function renderStep2(params) {
6541
7138
  const { step, enums } = params;
6542
7139
  switch (step.kind) {
7140
+ case "createNamespace":
7141
+ throw unsupportedCapability12({ dialect: "mysql", feature: "fixedNamespaces" });
6543
7142
  case "enableRls":
6544
7143
  case "forceRls":
6545
7144
  case "disableRls":
@@ -6581,21 +7180,21 @@ ${lines.join(`,
6581
7180
  return renderAlterColumn2({ step, enums });
6582
7181
  case "createIndex": {
6583
7182
  if (step.where !== undefined) {
6584
- throw unsupportedCapability8({
7183
+ throw unsupportedCapability12({
6585
7184
  dialect: "mysql",
6586
7185
  feature: `partial index "${step.name}"`,
6587
7186
  hint: "mysql has no partial indexes \u2014 drop the WHERE predicate or gate the index per dialect"
6588
7187
  });
6589
7188
  }
6590
7189
  if (step.nullsNotDistinct === true) {
6591
- throw unsupportedCapability8({
7190
+ throw unsupportedCapability12({
6592
7191
  dialect: "mysql",
6593
7192
  feature: `UNIQUE NULLS NOT DISTINCT (index "${step.name}")`,
6594
7193
  hint: "mysql treats NULLs as distinct in unique indexes and has no switch \u2014 gate the flag per dialect"
6595
7194
  });
6596
7195
  }
6597
7196
  if (!step.unique && step.indexKind !== "btree") {
6598
- throw unsupportedCapability8({
7197
+ throw unsupportedCapability12({
6599
7198
  dialect: "mysql",
6600
7199
  feature: `index kind "${step.indexKind}" (index "${step.name}")`,
6601
7200
  hint: "mysql secondary indexes are btree only"
@@ -6612,12 +7211,12 @@ ${lines.join(`,
6612
7211
  ];
6613
7212
  case "addExclusion":
6614
7213
  case "dropExclusion":
6615
- throw new VibeError30({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
7214
+ throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
6616
7215
  case "dropCheck":
6617
7216
  return [`ALTER TABLE ${q4(step.table)} DROP CHECK ${q4(step.name)};`];
6618
7217
  case "dropIndex": {
6619
7218
  if (step.table === undefined) {
6620
- throw new VibeError30({
7219
+ throw new VibeError33({
6621
7220
  code: "VIBE_MIGRATION",
6622
7221
  message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
6623
7222
  meta: { index: step.name }
@@ -6639,7 +7238,7 @@ ${lines.join(`,
6639
7238
  case "addForeignKeyNotValid":
6640
7239
  case "addCheckNotValid":
6641
7240
  case "validateConstraint":
6642
- throw unsupportedCapability8({
7241
+ throw unsupportedCapability12({
6643
7242
  dialect: "mysql",
6644
7243
  feature: "stagedConstraints",
6645
7244
  hint: "MySQL validates a constraint when it is added; add it in one step, or take the outage window that scan needs"
@@ -6674,7 +7273,7 @@ function columnDefSql3(params) {
6674
7273
  const name = q4(field.dbName ?? field.name);
6675
7274
  const autoincrement = field.default?.kind === "autoincrement";
6676
7275
  if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
6677
- throw new VibeError30({
7276
+ throw new VibeError33({
6678
7277
  code: "VIBE_MIGRATION",
6679
7278
  message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
6680
7279
  meta: { field: field.name }
@@ -6696,7 +7295,7 @@ function renderAlterColumn2(params) {
6696
7295
  const { step, enums } = params;
6697
7296
  assertEnumStringTransition({ from: step.from, to: step.to, dialect: "mysql", table: step.table, column: step.column });
6698
7297
  if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
6699
- throw new VibeError30({
7298
+ throw new VibeError33({
6700
7299
  code: "VIBE_MIGRATION",
6701
7300
  message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
6702
7301
  meta: { table: step.table, column: step.column }
@@ -6729,31 +7328,31 @@ function declaredSqlitePreservation(params) {
6729
7328
  if (params.from === undefined || to === undefined || (params.from.migrationObjects?.length ?? 0) === 0)
6730
7329
  return { to, restore, columns };
6731
7330
  const invalid = sqliteInvalidatedTables({ steps: params.steps, from: params.from });
6732
- const retained = new Set((to.migrationObjects ?? []).map((object) => sqlObjectKey9({ identity: object.identity })));
7331
+ const retained = new Set((to.migrationObjects ?? []).map((object) => sqlObjectKey10({ identity: object.identity })));
6733
7332
  for (const object of params.from.migrationObjects ?? []) {
6734
7333
  const identity = object.identity;
6735
- if (!("table" in identity) || identity.table.namespace !== "main" || !invalid.rebuilt.has(identity.table.name) || !retained.has(sqlObjectKey9({ identity })))
7334
+ if (!("table" in identity) || identity.table.namespace !== "main" || !invalid.rebuilt.has(identity.table.name) || !retained.has(sqlObjectKey10({ identity })))
6736
7335
  continue;
6737
7336
  const facts = object.expected?.facts;
6738
7337
  if (facts === undefined)
6739
- throw new VibeError31({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
7338
+ throw new VibeError34({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
6740
7339
  if (identity.kind === "trigger" || identity.kind === "index") {
6741
7340
  if (identity.kind === "index" && to.models.some((model) => (model.dbName ?? model.name) === identity.table.name && model.indexes.some((index) => index.name === identity.name)))
6742
7341
  continue;
6743
7342
  if (typeof facts.definition !== "string")
6744
- throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
7343
+ throw new VibeError34({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
6745
7344
  const statements = restore.get(identity.table.name) ?? [];
6746
7345
  statements.push(facts.definition);
6747
7346
  restore.set(identity.table.name, statements);
6748
7347
  } else if (identity.kind === "column" || identity.kind === "generatedColumn") {
6749
7348
  if (typeof facts.columnDefinition !== "string")
6750
- throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
7349
+ throw new VibeError34({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
6751
7350
  const fields = columns.get(identity.table.name) ?? new Map;
6752
7351
  fields.set(identity.column, { sql: facts.columnDefinition, generated: Number(facts.generated) > 1 });
6753
7352
  columns.set(identity.table.name, fields);
6754
7353
  } else if (identity.kind === "columnDefault") {
6755
7354
  if (facts.definition !== null && typeof facts.definition !== "string")
6756
- throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
7355
+ throw new VibeError34({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
6757
7356
  const expression = facts.definition;
6758
7357
  to = { ...to, models: to.models.map((model) => (model.dbName ?? model.name) !== identity.table.name ? model : { ...model, fields: model.fields.map((field) => {
6759
7358
  if ((field.dbName ?? field.name) !== identity.column)
@@ -7038,10 +7637,10 @@ function assertNativeMigrationMetadata(params) {
7038
7637
  if (!native)
7039
7638
  return;
7040
7639
  if (!DIALECT_CAPABILITIES3[params.dialect].nativeRowLevelSecurity) {
7041
- throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
7640
+ throw new VibeError35({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
7042
7641
  }
7043
7642
  if (params.migration.rls === undefined) {
7044
- throw new VibeError32({ 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 } });
7643
+ 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 } });
7045
7644
  }
7046
7645
  }
7047
7646
  var PG_ADVISORY_LOCK_KEY = 1447645253;
@@ -7086,11 +7685,11 @@ async function assertForwardStart(params) {
7086
7685
  const state = params.migration.desiredState;
7087
7686
  if (state === undefined)
7088
7687
  return;
7089
- const report = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.from, ...params.probe === undefined ? {} : { probe: params.probe } });
7688
+ const report = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.from, external: params.external, ...params.probe === undefined ? {} : { probe: params.probe } });
7090
7689
  if (report.inSync)
7091
7690
  return;
7092
7691
  if (state.down !== undefined) {
7093
- const restored = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.down, ...params.probe === undefined ? {} : { probe: params.probe } });
7692
+ const restored = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.down, external: params.external, ...params.probe === undefined ? {} : { probe: params.probe } });
7094
7693
  if (restored.inSync)
7095
7694
  return;
7096
7695
  }
@@ -7122,12 +7721,12 @@ function generateMigration(params) {
7122
7721
  }),
7123
7722
  ...orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).flatMap((object) => {
7124
7723
  if (object.dialect !== dialect)
7125
- throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
7724
+ throw new VibeError35({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
7126
7725
  const previous = (params.desiredState?.from.schema.migrationObjects ?? from.migrationObjects)?.find((entry) => entry.id === object.id);
7127
7726
  if (previous === undefined)
7128
7727
  return [...object.create];
7129
7728
  if (JSON.stringify(previous) !== JSON.stringify(object))
7130
- throw new VibeError32({ 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 } });
7729
+ 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 } });
7131
7730
  return [];
7132
7731
  })
7133
7732
  ];
@@ -7153,8 +7752,8 @@ async function withMigrationLock(params) {
7153
7752
  }, (cause) => releaseLock({ executor, dialect: params.dialect }).then(() => {
7154
7753
  throw cause;
7155
7754
  }, (cleanupCause) => {
7156
- if (cause instanceof VibeError32)
7157
- throw new VibeError32({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
7755
+ if (cause instanceof VibeError35)
7756
+ throw new VibeError35({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
7158
7757
  throw cause;
7159
7758
  }));
7160
7759
  };
@@ -7178,16 +7777,17 @@ function validateMigrationReplay(params) {
7178
7777
  }
7179
7778
  function assertMigrationMetadata(params) {
7180
7779
  const { migration, dialect } = params;
7780
+ assertModuleAdoption({ migration });
7181
7781
  if (migration.moduleOrigin !== undefined) {
7182
7782
  assertMaterializedModuleMigration({ migration });
7183
7783
  assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
7184
7784
  }
7185
7785
  if (migration.desiredStatePending === true)
7186
- throw new VibeError32({ 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 } });
7786
+ 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 } });
7187
7787
  if (migration.desiredState !== undefined) {
7188
7788
  assertDesiredState({ value: migration.desiredState, migration: migration.name });
7189
7789
  if (migration.desiredState.dialect !== dialect)
7190
- throw new VibeError32({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
7790
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
7191
7791
  }
7192
7792
  assertNativeMigrationMetadata({ migration, sql: migration.sql, dialect });
7193
7793
  assertExecutable({ migration, execution: migration.execution, sql: migration.sql, dialect });
@@ -7195,20 +7795,25 @@ function assertMigrationMetadata(params) {
7195
7795
  async function applyMigrations(params) {
7196
7796
  const { executor, migrations } = params;
7197
7797
  const dialect = params.dialect ?? "postgres";
7798
+ const trustedReplay = isShadowReplayExecutor({ executor });
7198
7799
  if (params.history !== undefined && params.history !== "complete" && params.history !== "subset")
7199
- throw new VibeError32({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
7800
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
7200
7801
  if (params.history === "complete")
7201
7802
  assertDistinctMigrationNames({ migrations });
7202
7803
  for (const migration of migrations)
7203
7804
  assertMigrationMetadata({ migration, dialect });
7805
+ const externalDeclarations = await parseExternalObjects({ value: params.external ?? [], dialect });
7204
7806
  return withMigrationLock({ executor, dialect, run: async (executor) => {
7807
+ const external = externalDeclarations.length === 0 ? undefined : await inspectExternalObjects({ executor, dialect, external: externalDeclarations });
7205
7808
  let moduleRecords = await readModuleJournal({ executor, dialect });
7206
7809
  const recorded = new Map([...moduleRecords].map(([name, record]) => [name, record.row]));
7810
+ if (external !== undefined)
7811
+ assertExternalOwnershipClaims({ external, identities: [...migrations.flatMap((migration) => migration.moduleOrigin?.owned ?? []), ...[...moduleRecords.values()].flatMap((record) => record.entry?.migration.moduleOrigin?.owned ?? [])] });
7207
7812
  if (migrations.some((migration) => migration.moduleOrigin !== undefined) || [...moduleRecords.values()].some((record) => record.entry !== undefined)) {
7208
7813
  assertModuleUpgradeHistory({ migrations, records: moduleRecords });
7209
7814
  const namespace = await migrationNamespace({ executor, dialect });
7210
- if (migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
7211
- throw new VibeError32({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
7815
+ if (dialect !== "postgres" && migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
7816
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
7212
7817
  }
7213
7818
  if (params.history === "complete")
7214
7819
  assertCompleteHistory({ migrations, recorded, dialect });
@@ -7217,6 +7822,12 @@ async function applyMigrations(params) {
7217
7822
  const stored = recorded.get(migration.name);
7218
7823
  return stored === undefined || !isCompleteRecord({ dialect, migration, stored });
7219
7824
  });
7825
+ const adopted = pending.find((migration) => migration.moduleAdoption !== undefined);
7826
+ if (adopted !== undefined && !trustedReplay)
7827
+ 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 } });
7828
+ if (external !== undefined && !trustedReplay)
7829
+ for (const migration of pending)
7830
+ await assertExternalMigrationMutation({ executor, migration, dialect, namespace: await migrationNamespace({ executor, dialect }), external });
7220
7831
  validateMigrationReplay({ ...params, migrations: pending, dialect });
7221
7832
  await ensureMigrationsTable({ executor, dialect });
7222
7833
  if (migrations.some((migration) => migration.moduleOrigin !== undefined))
@@ -7229,7 +7840,7 @@ async function applyMigrations(params) {
7229
7840
  await assertLiveModuleOwnership({ executor, origin: migration.moduleOrigin, resume: stored !== undefined });
7230
7841
  if (stored !== undefined) {
7231
7842
  if (stored.statementIndex < 0) {
7232
- throw new VibeError32({
7843
+ throw new VibeError35({
7233
7844
  code: "VIBE_MIGRATION",
7234
7845
  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`,
7235
7846
  meta: { migration: migration.name, statementIndex: stored.statementIndex }
@@ -7251,6 +7862,7 @@ async function applyMigrations(params) {
7251
7862
  migration,
7252
7863
  start: stored.statementIndex,
7253
7864
  moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }),
7865
+ external,
7254
7866
  ...params.probe === undefined ? {} : { probe: params.probe },
7255
7867
  insertRecord: false,
7256
7868
  ...stored.error === undefined ? {} : { storedError: stored.error }
@@ -7266,10 +7878,10 @@ async function applyMigrations(params) {
7266
7878
  executor,
7267
7879
  dialect,
7268
7880
  ...migration.execution === undefined ? {} : { execution: migration.execution },
7269
- run: () => runNonTransactional({ executor, dialect, migration, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), start: 0, insertRecord: true, ...params.probe === undefined ? {} : { probe: params.probe } })
7881
+ run: () => runNonTransactional({ executor, dialect, migration, external, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), start: 0, insertRecord: true, ...params.probe === undefined ? {} : { probe: params.probe } })
7270
7882
  });
7271
7883
  } else {
7272
- await runTransactional({ executor, dialect, migration, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), ...params.probe === undefined ? {} : { probe: params.probe } });
7884
+ await runTransactional({ executor, dialect, migration, external, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), ...params.probe === undefined ? {} : { probe: params.probe } });
7273
7885
  }
7274
7886
  if (migration.moduleOrigin !== undefined)
7275
7887
  moduleRecords = await readModuleJournal({ executor, dialect });
@@ -7277,10 +7889,11 @@ async function applyMigrations(params) {
7277
7889
  }
7278
7890
  const final = migrations.at(-1);
7279
7891
  if (final?.desiredState !== undefined) {
7892
+ const finalExternal = externalDeclarations.length === 0 ? undefined : await inspectExternalObjects({ executor, dialect, external: externalDeclarations });
7280
7893
  if (params.probe === "rollback")
7281
7894
  await executor({ text: "BEGIN" });
7282
7895
  try {
7283
- assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: final.desiredState.from, to: final.desiredState.to, removals: final.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
7896
+ assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: final.desiredState.from, to: final.desiredState.to, external: finalExternal, removals: comparisonRemovals({ migration: final, external: finalExternal }), ...params.probe === undefined ? {} : { probe: params.probe } }) });
7284
7897
  } finally {
7285
7898
  if (params.probe === "rollback")
7286
7899
  await executor({ text: "ROLLBACK" });
@@ -7289,6 +7902,27 @@ async function applyMigrations(params) {
7289
7902
  return { applied, skipped };
7290
7903
  } });
7291
7904
  }
7905
+ function comparisonRemovals(params) {
7906
+ return params.migration.desiredState?.unmanagedRemovals?.filter((identity) => params.external === undefined || externalObjectOwner({ identity, external: params.external }) === undefined);
7907
+ }
7908
+ async function assertExternalMigrationMutation(params) {
7909
+ assertExternalOwnershipClaims({ external: params.external, identities: params.migration.desiredState?.unmanagedRemovals ?? [] });
7910
+ const state = params.migration.desiredState;
7911
+ if (state === undefined)
7912
+ return;
7913
+ const identities = [];
7914
+ const steps = diffSchemas({ from: state.from.schema, to: state.to.schema, dialect: params.dialect });
7915
+ for (const step of steps) {
7916
+ if (step.kind === "createNamespace")
7917
+ identities.push({ kind: "namespace", name: step.namespace });
7918
+ else if ("table" in step && typeof step.table === "string")
7919
+ identities.push({ kind: "table", name: { namespace: step.namespace ?? params.namespace, name: step.table } });
7920
+ else if ("enumName" in step)
7921
+ identities.push({ kind: "enum", name: { namespace: step.namespace ?? params.namespace, name: step.enumName } });
7922
+ }
7923
+ assertExternalOwnershipClaims({ external: params.external, identities });
7924
+ await refuseExternalDependents({ executor: params.executor, dialect: params.dialect, external: params.external, steps, namespace: params.namespace });
7925
+ }
7292
7926
  async function migrationStatus(params) {
7293
7927
  const { executor, migrations } = params;
7294
7928
  const dialect = params.dialect ?? "postgres";
@@ -7364,7 +7998,7 @@ async function repairMigration(params) {
7364
7998
  const { executor, name, dialect } = params;
7365
7999
  const moduleRecord = (await readModuleJournal({ executor, dialect })).get(name);
7366
8000
  if (moduleRecord?.entry !== undefined)
7367
- throw new VibeError32({ 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 } });
8001
+ 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 } });
7368
8002
  await ensureMigrationsTable({ executor, dialect });
7369
8003
  if (params.as === "applied") {
7370
8004
  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})`;
@@ -7386,7 +8020,7 @@ async function rollbackMigration(params) {
7386
8020
  const stored = assertRollbackRecord({ migration, stored: recorded.get(migration.name), dialect });
7387
8021
  const newest = [...recorded.keys()].at(-1);
7388
8022
  if (newest !== migration.name)
7389
- throw new VibeError32({ 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 } });
8023
+ 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 } });
7390
8024
  await validateRollbackRecovery({ executor, migration, downSql, stored, dialect });
7391
8025
  await executeRollback({ ...params, executor, dialect, downSql, stored });
7392
8026
  } });
@@ -7404,7 +8038,7 @@ async function rollbackMigrations(params) {
7404
8038
  const target = journal.findIndex((row) => row.name === params.to);
7405
8039
  const row = journal[target];
7406
8040
  if (row?.state !== "applied")
7407
- throw new VibeError32({ 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 } });
8041
+ 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 } });
7408
8042
  assertMigrationMetadata({ migration: byName.get(row.name), dialect });
7409
8043
  start = target + 1;
7410
8044
  }
@@ -7412,7 +8046,7 @@ async function rollbackMigrations(params) {
7412
8046
  for (const row of journal.slice(start).reverse()) {
7413
8047
  const migration = byName.get(row.name);
7414
8048
  if (migration === undefined)
7415
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
8049
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
7416
8050
  const stored = assertRollbackRecord({ migration, stored: recorded.get(row.name), dialect });
7417
8051
  const downSql = validateRollbackFile({ ...params, migration, dialect });
7418
8052
  plan.push({ migration, downSql, stored });
@@ -7429,20 +8063,23 @@ async function rollbackMigrations(params) {
7429
8063
  }
7430
8064
  function validateRollbackFile(params) {
7431
8065
  const { migration, dialect } = params;
8066
+ assertModuleAdoption({ migration });
8067
+ if (migration.moduleAdoption !== undefined)
8068
+ 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 } });
7432
8069
  if (migration.desiredStatePending === true)
7433
- throw new VibeError32({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
8070
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
7434
8071
  if (migration.moduleOrigin !== undefined) {
7435
8072
  assertMaterializedModuleMigration({ migration });
7436
8073
  assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
7437
8074
  }
7438
8075
  const downSql = migration.downSql;
7439
8076
  if (downSql === undefined)
7440
- throw new VibeError32({ 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 } });
8077
+ 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 } });
7441
8078
  assertProbeMode({ dialect, probe: params.probe });
7442
8079
  if (migration.desiredState !== undefined) {
7443
8080
  assertDesiredState({ value: migration.desiredState, migration: migration.name });
7444
8081
  if (migration.desiredState.dialect !== dialect)
7445
- throw new VibeError32({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
8082
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
7446
8083
  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 } });
7447
8084
  }
7448
8085
  assertNativeMigrationMetadata({ migration, sql: downSql, dialect });
@@ -7455,10 +8092,10 @@ function validateRollbackFile(params) {
7455
8092
  function assertRollbackRecord(params) {
7456
8093
  const { migration, stored, dialect } = params;
7457
8094
  if (stored === undefined)
7458
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
8095
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
7459
8096
  assertChecksumMatches({ migration, stored: stored.checksum });
7460
8097
  if (stored.statementIndex >= 0 && (!isCompleteRecord({ dialect, migration, stored }) || stored.error !== undefined))
7461
- throw new VibeError32({ 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 } });
8098
+ 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 } });
7462
8099
  return stored;
7463
8100
  }
7464
8101
  async function validateRollbackRecovery(params) {
@@ -7467,7 +8104,7 @@ async function validateRollbackRecovery(params) {
7467
8104
  return;
7468
8105
  const start = -stored.statementIndex - 1;
7469
8106
  if (start > downSql.length)
7470
- throw new VibeError32({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
8107
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
7471
8108
  if (dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql }))
7472
8109
  assertSelfManagedResumeSafe({ sql: downSql, start, migration: migration.name, direction: "down" });
7473
8110
  if (!marksInflight({ dialect, execution: migration.downExecution }) || start >= downSql.length)
@@ -7502,9 +8139,9 @@ async function runTransactionalDown(params) {
7502
8139
  const limits = migration.downExecution;
7503
8140
  if (wrap && dialect === "postgres" && limits !== undefined) {
7504
8141
  if ((limits.lockTimeoutMs ?? 0) > 0)
7505
- await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
8142
+ await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect9({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
7506
8143
  if ((limits.statementTimeoutMs ?? 0) > 0)
7507
- await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
8144
+ await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect9({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
7508
8145
  }
7509
8146
  if (migration.desiredState !== undefined)
7510
8147
  assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
@@ -7533,7 +8170,7 @@ async function runTransactionalDown(params) {
7533
8170
  } else {
7534
8171
  await cleanupSelfManagedFailure({ executor });
7535
8172
  }
7536
- throw new VibeError32({
8173
+ throw new VibeError35({
7537
8174
  code: "VIBE_MIGRATION",
7538
8175
  message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
7539
8176
  meta: { migration: migration.name, ...timeoutDiagnostic({ error }) },
@@ -7616,7 +8253,7 @@ async function runNonTransactionalDown(params) {
7616
8253
  values: [marks ? `${INFLIGHT_PREFIX}${index}` : error instanceof Error ? error.message : String(error), migration.name]
7617
8254
  });
7618
8255
  } catch {}
7619
- throw new VibeError32({
8256
+ throw new VibeError35({
7620
8257
  code: "VIBE_MIGRATION",
7621
8258
  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."}`,
7622
8259
  meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeoutDiagnostic({ error }) },
@@ -7643,11 +8280,11 @@ async function runTransactional(params) {
7643
8280
  const limits = migration.execution;
7644
8281
  if (wrap && dialect === "postgres" && limits !== undefined) {
7645
8282
  if ((limits.lockTimeoutMs ?? 0) > 0)
7646
- await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
8283
+ await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect9({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
7647
8284
  if ((limits.statementTimeoutMs ?? 0) > 0)
7648
- await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
8285
+ await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect9({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
7649
8286
  }
7650
- await assertForwardStart({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
8287
+ await assertForwardStart({ executor, dialect, migration, external: params.external, ...params.probe === undefined ? {} : { probe: params.probe } });
7651
8288
  await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
7652
8289
  if (migration.rls !== undefined)
7653
8290
  await introspectPostgres({ executor, rlsSchema: migration.rls.from, inTransaction: true, requireInstalledRls: true });
@@ -7657,7 +8294,7 @@ async function runTransactional(params) {
7657
8294
  statementIndex += 1;
7658
8295
  }
7659
8296
  if (migration.desiredState !== undefined)
7660
- assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
8297
+ assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, external: params.external, removals: comparisonRemovals({ migration, external: params.external }), dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
7661
8298
  if (migration.rls !== undefined)
7662
8299
  await introspectPostgres({ executor, rlsSchema: migration.rls.to, inTransaction: true, requireInstalledRls: true });
7663
8300
  await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
@@ -7678,7 +8315,7 @@ async function runTransactional(params) {
7678
8315
  } else {
7679
8316
  await cleanupSelfManagedFailure({ executor });
7680
8317
  }
7681
- throw new VibeError32({
8318
+ throw new VibeError35({
7682
8319
  code: "VIBE_MIGRATION",
7683
8320
  message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
7684
8321
  meta: { migration: migration.name },
@@ -7701,9 +8338,9 @@ async function runNonTransactional(params) {
7701
8338
  await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
7702
8339
  if (insertRecord) {
7703
8340
  if (migration.desiredState !== undefined) {
7704
- await assertForwardStart({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
8341
+ await assertForwardStart({ executor, dialect, migration, external: params.external, ...params.probe === undefined ? {} : { probe: params.probe } });
7705
8342
  if (migration.sql.length === 0)
7706
- assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
8343
+ assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, external: params.external, removals: comparisonRemovals({ migration, external: params.external }), dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
7707
8344
  }
7708
8345
  if (migration.moduleOrigin !== undefined)
7709
8346
  await insertModuleJournal({ executor, dialect, migration, index: 0, complete: false });
@@ -7726,7 +8363,7 @@ async function runNonTransactional(params) {
7726
8363
  const state = await establishOperationState({ executor, dialect, operation });
7727
8364
  if (state === "satisfied") {
7728
8365
  if (start + 1 === migration.sql.length && migration.desiredState !== undefined)
7729
- assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
8366
+ assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, external: params.external, removals: comparisonRemovals({ migration, external: params.external }), ...params.probe === undefined ? {} : { probe: params.probe } }) });
7730
8367
  if (start + 1 === migration.sql.length)
7731
8368
  await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
7732
8369
  await checkpoint(start + 1);
@@ -7756,7 +8393,7 @@ async function runNonTransactional(params) {
7756
8393
  }
7757
8394
  await executeStatement({ executor, statement: migration.sql[index], dialect });
7758
8395
  if (index === migration.sql.length - 1 && migration.desiredState !== undefined)
7759
- assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
8396
+ assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, external: params.external, removals: comparisonRemovals({ migration, external: params.external }), dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
7760
8397
  if (index === migration.sql.length - 1)
7761
8398
  await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
7762
8399
  if (atomic) {
@@ -7774,7 +8411,7 @@ async function runNonTransactional(params) {
7774
8411
  } catch {}
7775
8412
  const sqlState = sqlStateOf(error);
7776
8413
  const timeout = sqlState === "55P03" ? "lock-timeout" : sqlState === "57014" ? "statement-timeout" : undefined;
7777
- throw new VibeError32({
8414
+ throw new VibeError35({
7778
8415
  code: "VIBE_MIGRATION",
7779
8416
  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."}`,
7780
8417
  meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeout === undefined ? {} : { reason: timeout, sqlState } },
@@ -7785,7 +8422,7 @@ async function runNonTransactional(params) {
7785
8422
  await checkpoint(index + 1);
7786
8423
  }
7787
8424
  if (migration.moduleOrigin !== undefined) {
7788
- assertMigrationState({ report: await verifyMigrationState({ executor, dialect, expected: migration.desiredState.to }) });
8425
+ assertMigrationState({ report: await verifyMigrationState({ executor, dialect, expected: migration.desiredState.to, external: params.external }) });
7789
8426
  await completeModuleJournal({ executor, dialect, migration });
7790
8427
  }
7791
8428
  }
@@ -7802,7 +8439,7 @@ async function acquireLock(params) {
7802
8439
  });
7803
8440
  const acquired = Number(rows[0]?.vibe_lock) === 1;
7804
8441
  if (!acquired) {
7805
- throw new VibeError32({
8442
+ throw new VibeError35({
7806
8443
  code: "VIBE_MIGRATION",
7807
8444
  message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
7808
8445
  meta: { lock: MYSQL_LOCK_NAME }
@@ -7837,7 +8474,7 @@ function uncertainStatement(params) {
7837
8474
  const subject = direction === "down" ? `rollback of "${migration}"` : `migration "${migration}"`;
7838
8475
  const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${statements.length}`;
7839
8476
  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.";
7840
- return new VibeError32({
8477
+ return new VibeError35({
7841
8478
  code: "VIBE_MIGRATION",
7842
8479
  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)}`,
7843
8480
  meta: {
@@ -7855,7 +8492,7 @@ function assertExecutable(params) {
7855
8492
  if (execution === undefined || isDefaultExecution({ execution }))
7856
8493
  return;
7857
8494
  if (dialect !== "postgres") {
7858
- throw new VibeError32({
8495
+ throw new VibeError35({
7859
8496
  code: "VIBE_UNSUPPORTED_CAPABILITY",
7860
8497
  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`,
7861
8498
  meta: { reason: "nontransactional-unsupported", dialect, migration: migration.name, feature: "nontransactionalMigrations" }
@@ -7878,7 +8515,7 @@ async function withSessionLimits(params) {
7878
8515
  const statement = execution?.statementTimeoutMs ?? 0;
7879
8516
  if (dialect !== "postgres" || lock === 0 && statement === 0)
7880
8517
  return params.run();
7881
- const sqlDialect = getSqlDialect7({ dialect });
8518
+ const sqlDialect = getSqlDialect9({ dialect });
7882
8519
  const previous = (await executor({ text: renderMigrationSessionRead({ dialect: sqlDialect }).text, values: [] }))[0];
7883
8520
  if (lock > 0)
7884
8521
  await executor({ text: renderMigrationSessionSet({ dialect: sqlDialect, setting: "lock_timeout", milliseconds: lock }).text });
@@ -7931,7 +8568,7 @@ function assertChecksumMatches(params) {
7931
8568
  const actual = checksumOf({ migration });
7932
8569
  if (stored === actual)
7933
8570
  return;
7934
- throw new VibeError32({
8571
+ throw new VibeError35({
7935
8572
  code: "VIBE_MIGRATION",
7936
8573
  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)`,
7937
8574
  meta: { migration: migration.name, recorded: stored, current: actual }
@@ -7942,7 +8579,7 @@ function assertResumable(params) {
7942
8579
  const actual = checksumOf({ migration });
7943
8580
  if (stored.checksum === actual)
7944
8581
  return;
7945
- throw new VibeError32({
8582
+ throw new VibeError35({
7946
8583
  code: "VIBE_MIGRATION",
7947
8584
  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`,
7948
8585
  meta: {
@@ -7962,7 +8599,7 @@ function assertDistinctMigrationNames(params) {
7962
8599
  const names = new Set;
7963
8600
  for (const migration of params.migrations) {
7964
8601
  if (names.has(migration.name))
7965
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
8602
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
7966
8603
  names.add(migration.name);
7967
8604
  }
7968
8605
  }
@@ -7970,15 +8607,15 @@ function assertCompleteHistory(params) {
7970
8607
  const byName = new Map(params.migrations.map((migration) => [migration.name, migration]));
7971
8608
  for (const name of params.recorded.keys()) {
7972
8609
  if (!byName.has(name))
7973
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
8610
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
7974
8611
  }
7975
8612
  let index = 0;
7976
8613
  for (const [name, stored] of params.recorded) {
7977
8614
  const migration = params.migrations[index];
7978
8615
  if (migration?.name !== name)
7979
- throw new VibeError32({ 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 } });
8616
+ 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 } });
7980
8617
  if (stored.statementIndex >= 0 && !isCompleteRecord({ dialect: params.dialect, migration, stored }) && index !== params.recorded.size - 1)
7981
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
8618
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
7982
8619
  index++;
7983
8620
  }
7984
8621
  }
@@ -7987,16 +8624,16 @@ async function assertApplyHistory(params) {
7987
8624
  const byName = new Map(migrations.map((migration) => [migration.name, migration]));
7988
8625
  for (const [name, stored] of recorded) {
7989
8626
  if (stored.statementIndex < 0) {
7990
- throw new VibeError32({ 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 } });
8627
+ 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 } });
7991
8628
  }
7992
8629
  const migration = byName.get(name);
7993
8630
  if (migration === undefined) {
7994
8631
  if (stored.error !== undefined)
7995
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error but no supplied file`, meta: { reason: "history-unknown-dirty", migration: name } });
8632
+ 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 } });
7996
8633
  continue;
7997
8634
  }
7998
8635
  if (isCompleteRecord({ dialect, migration, stored }) && stored.error !== undefined)
7999
- throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
8636
+ throw new VibeError35({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
8000
8637
  if (isCompleteRecord({ dialect, migration, stored }))
8001
8638
  assertChecksumMatches({ migration, stored: stored.checksum });
8002
8639
  else
@@ -8021,10 +8658,10 @@ async function assertApplyHistory(params) {
8021
8658
  }
8022
8659
  }
8023
8660
  function q5(dialect, name) {
8024
- return getSqlDialect7({ dialect }).quoteIdent({ name });
8661
+ return getSqlDialect9({ dialect }).quoteIdent({ name });
8025
8662
  }
8026
8663
  function ph(dialect, index) {
8027
- return getSqlDialect7({ dialect }).placeholder({ index });
8664
+ return getSqlDialect9({ dialect }).placeholder({ index });
8028
8665
  }
8029
8666
 
8030
8667
  // packages/migrate/src/fs.ts
@@ -8032,7 +8669,7 @@ function absent(error) {
8032
8669
  return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
8033
8670
  }
8034
8671
  function failure(params) {
8035
- return new VibeError33({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
8672
+ return new VibeError36({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
8036
8673
  }
8037
8674
  async function optionalFile(params) {
8038
8675
  return readFile(params.path, "utf-8").catch((cause) => {
@@ -8046,13 +8683,13 @@ function rlsMetadata(params) {
8046
8683
  const metadata = JSON.parse(params.text);
8047
8684
  const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
8048
8685
  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 ?? [] })) {
8049
- throw new VibeError33({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
8686
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
8050
8687
  }
8051
8688
  assertValidSchemaIR4({ schema: metadata.rls.from });
8052
8689
  assertValidSchemaIR4({ schema: metadata.rls.to });
8053
8690
  return { ...params.migration, rls: metadata.rls, destructive: metadata.destructive, securityChanges: metadata.securityChanges, downSecurityChanges: metadata.downSecurityChanges };
8054
8691
  }).catch((cause) => {
8055
- throw new VibeError33({ code: "VIBE_MIGRATION", message: `Invalid native RLS metadata for migration "${params.migration.name}"`, meta: { reason: "rls-migration-metadata-invalid", migration: params.migration.name }, cause });
8692
+ 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 });
8056
8693
  });
8057
8694
  }
8058
8695
  async function loadMigrationFiles(params) {
@@ -8085,13 +8722,17 @@ async function loadMigrationFiles(params) {
8085
8722
  const desired = await optionalFile({ path: join(directory, DESIRED_STATE_METADATA.filename) });
8086
8723
  if (desired !== undefined) {
8087
8724
  const pending = await Promise.resolve().then(() => isUnfinalizedDesiredState({ text: desired })).catch((cause) => {
8088
- throw new VibeError33({ code: "VIBE_MIGRATION", message: `Malformed desired state for ${name}`, meta: { reason: "desired-state-invalid", migration: name }, cause });
8725
+ throw new VibeError36({ code: "VIBE_MIGRATION", message: `Malformed desired state for ${name}`, meta: { reason: "desired-state-invalid", migration: name }, cause });
8089
8726
  });
8090
8727
  migration = pending ? { ...migration, desiredStatePending: true } : { ...migration, desiredState: parseDesiredStateSidecar({ text: desired, migration: name }) };
8091
8728
  }
8092
8729
  const origin = await optionalFile({ path: join(directory, MODULE_ORIGIN_METADATA.filename) });
8093
8730
  if (origin !== undefined)
8094
8731
  migration = restoreModuleMigration({ migration, origin: parseModuleOrigin({ text: origin }) });
8732
+ const adoption = await optionalFile({ path: join(directory, MODULE_ADOPTION_METADATA.filename) });
8733
+ if (adoption !== undefined)
8734
+ migration = { ...migration, moduleAdoption: parseModuleAdoption({ text: adoption }) };
8735
+ assertModuleAdoption({ migration });
8095
8736
  migrations.push(migration);
8096
8737
  }
8097
8738
  return migrations;
@@ -8102,4 +8743,4 @@ export {
8102
8743
  splitSqlStatements
8103
8744
  };
8104
8745
 
8105
- //# debugId=B0F3289A0E0F6E5B64756E2164756E21
8746
+ //# debugId=000F8CFDEDF957F264756E2164756E21