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