@vibeorm/migrate 2.7.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 +395 -257
- package/dist/fs.js.map +14 -13
- package/dist/index.js +766 -455
- package/dist/index.js.map +16 -14
- 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/object-ownership.d.ts +8 -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/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/migrate/src/external-objects.ts
|
|
3
|
-
import { IDENTIFIER_LIMITS, VibeError as
|
|
3
|
+
import { IDENTIFIER_LIMITS, VibeError as VibeError18, identifierLength, sqlNamespaceIssue, sqlObjectKey as sqlObjectKey6, sqlObjectNamespace as sqlObjectNamespace2, qualifiedSqlName as qualifiedSqlName8, enumStorageOf as enumStorageOf3, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
4
4
|
import { getSqlDialect as getSqlDialect3, renderExternalOwnershipCatalog, renderReplayCoverageGaps } from "@vibeorm/sql";
|
|
5
5
|
|
|
6
6
|
// packages/migrate/src/inventory.ts
|
|
7
|
-
import { VibeError as
|
|
7
|
+
import { VibeError as VibeError17, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey5 } from "@vibeorm/schema";
|
|
8
8
|
import { getSqlDialect as getSqlDialect2, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
|
|
9
9
|
|
|
10
10
|
// packages/migrate/src/exclusions.ts
|
|
@@ -85,16 +85,12 @@ async function verifyExclusions(params) {
|
|
|
85
85
|
|
|
86
86
|
// packages/migrate/src/inspect-objects.ts
|
|
87
87
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
88
|
-
import { VibeError as
|
|
88
|
+
import { VibeError as VibeError16, sqlObjectKey as sqlObjectKey4, sqlObjectNamespace, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2, qualifiedSqlName as qualifiedSqlName7 } from "@vibeorm/schema";
|
|
89
89
|
import { getSqlDialect, renderMigrationObjectBatch, renderMigrationObjectQuery as renderMigrationObjectQuery2, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
|
|
90
90
|
|
|
91
91
|
// packages/migrate/src/desired-state.ts
|
|
92
92
|
import { VibeError as VibeError2, assertValidSchemaIR, isSqlObjectDefinition, isSqlObjectIdentity, providerToDialect, sqlObjectKey as sqlObjectKey2 } from "@vibeorm/schema";
|
|
93
93
|
var UNFINALIZED_DESIRED_STATE = Object.freeze({ version: 1, status: "unfinalized" });
|
|
94
|
-
function isUnfinalizedDesiredState(params) {
|
|
95
|
-
const value = JSON.parse(params.text);
|
|
96
|
-
return record(value) && value.version === 1 && value.status === "unfinalized" && Object.keys(value).length === 2;
|
|
97
|
-
}
|
|
98
94
|
var DESIRED_STATE_METADATA = Object.freeze({ filename: "desired-state.json", version: 1 });
|
|
99
95
|
function createDesiredState(params) {
|
|
100
96
|
assertValidSchemaIR({ schema: params.schema });
|
|
@@ -177,6 +173,15 @@ function parseDesiredStateSidecar(params) {
|
|
|
177
173
|
return checked.value;
|
|
178
174
|
}, failure: (cause) => invalid({ migration: params.migration, detail: "malformed metadata", cause }) });
|
|
179
175
|
}
|
|
176
|
+
function loadDesiredStateSidecar(params) {
|
|
177
|
+
return parseMigrationMetadata({ text: params.text, parse: (value) => {
|
|
178
|
+
if (record(value) && value.version === 1 && value.status === "unfinalized" && Object.keys(value).length === 2)
|
|
179
|
+
return { desiredStatePending: true };
|
|
180
|
+
const checked = { value, migration: params.migration };
|
|
181
|
+
assertDesiredState(checked);
|
|
182
|
+
return { desiredState: checked.value };
|
|
183
|
+
}, failure: (cause) => invalid({ migration: params.migration, detail: "malformed metadata", cause }) });
|
|
184
|
+
}
|
|
180
185
|
function parseMigrationMetadata(params) {
|
|
181
186
|
try {
|
|
182
187
|
return params.parse(JSON.parse(params.text));
|
|
@@ -1046,6 +1051,32 @@ function parseMysqlEnumColumnType(params) {
|
|
|
1046
1051
|
}
|
|
1047
1052
|
|
|
1048
1053
|
// packages/migrate/src/normalize.ts
|
|
1054
|
+
function firstByName(params) {
|
|
1055
|
+
const result = new Map;
|
|
1056
|
+
for (const entry of params.entries)
|
|
1057
|
+
if (!result.has(entry.name))
|
|
1058
|
+
result.set(entry.name, entry);
|
|
1059
|
+
return result;
|
|
1060
|
+
}
|
|
1061
|
+
function normalizationLookup(params) {
|
|
1062
|
+
const enums = new Map;
|
|
1063
|
+
const scopedEnums = new Map;
|
|
1064
|
+
for (const entry of params.schema.enums) {
|
|
1065
|
+
const namespace = qualifiedSqlName3({ name: entry.name, namespace: entry.namespace }).namespace;
|
|
1066
|
+
for (const name of new Set([entry.name, entry.dbName ?? entry.name])) {
|
|
1067
|
+
if (!enums.has(name))
|
|
1068
|
+
enums.set(name, entry);
|
|
1069
|
+
const key = JSON.stringify([namespace, name]);
|
|
1070
|
+
if (!scopedEnums.has(key))
|
|
1071
|
+
scopedEnums.set(key, entry);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return { models: firstByName({ entries: params.schema.models }), fields: new Map(params.schema.models.map((model) => [model, firstByName({ entries: model.fields })])), enums, scopedEnums };
|
|
1075
|
+
}
|
|
1076
|
+
function lookupFieldName(params) {
|
|
1077
|
+
const field = params.lookup.fields.get(params.model)?.get(params.name);
|
|
1078
|
+
return field?.dbName ?? field?.name ?? params.name;
|
|
1079
|
+
}
|
|
1049
1080
|
function normalizeSchema(params) {
|
|
1050
1081
|
const { schema } = params;
|
|
1051
1082
|
const dialect = params.dialect ?? "postgres";
|
|
@@ -1058,6 +1089,7 @@ function normalizeSchema(params) {
|
|
|
1058
1089
|
continue;
|
|
1059
1090
|
enums.set(physicalNameKey({ name: enumDef.dbName ?? enumDef.name, namespace: enumDef.namespace, dialect }), enumDef);
|
|
1060
1091
|
}
|
|
1092
|
+
const lookup = normalizationLookup({ schema });
|
|
1061
1093
|
const tables = new Map;
|
|
1062
1094
|
const views = new Map;
|
|
1063
1095
|
for (const model of schema.models) {
|
|
@@ -1067,25 +1099,25 @@ function normalizeSchema(params) {
|
|
|
1067
1099
|
views.set(physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect }), model);
|
|
1068
1100
|
continue;
|
|
1069
1101
|
}
|
|
1070
|
-
const table = normalizeModel({ model,
|
|
1102
|
+
const table = normalizeModel({ model, lookup, dialect, enums });
|
|
1071
1103
|
tables.set(physicalNameKey({ name: table.table, namespace: table.namespace, dialect }), table);
|
|
1072
1104
|
}
|
|
1073
1105
|
const joinTables = new Map;
|
|
1074
1106
|
for (const pair of detectImplicitM2MPairs({ schema })) {
|
|
1075
|
-
const join = normalizeJoinTable({ pair,
|
|
1107
|
+
const join = normalizeJoinTable({ pair, lookup, dialect });
|
|
1076
1108
|
joinTables.set(physicalNameKey({ name: join.table, namespace: join.namespace, dialect }), join);
|
|
1077
1109
|
}
|
|
1078
1110
|
return { tables, joinTables, enums, views };
|
|
1079
1111
|
}
|
|
1080
1112
|
function physicalEnumField(params) {
|
|
1081
|
-
const { field,
|
|
1113
|
+
const { field, lookup, dialect, table } = params;
|
|
1082
1114
|
if (!isEnumType5(field.type))
|
|
1083
1115
|
return field;
|
|
1084
1116
|
const type = field.type;
|
|
1085
1117
|
if (type.namespace !== undefined)
|
|
1086
1118
|
assertSqlNamespace({ namespace: type.namespace, dialect });
|
|
1087
1119
|
const name = type.enum;
|
|
1088
|
-
const enumDef =
|
|
1120
|
+
const enumDef = type.namespace === undefined ? lookup.enums.get(name) : lookup.scopedEnums.get(JSON.stringify([type.namespace, name]));
|
|
1089
1121
|
if (enumDef === undefined)
|
|
1090
1122
|
return field;
|
|
1091
1123
|
if (enumStorageOf({ enumDef }) !== "string") {
|
|
@@ -1150,48 +1182,49 @@ function defaultFingerprintFor(params) {
|
|
|
1150
1182
|
}
|
|
1151
1183
|
}
|
|
1152
1184
|
function normalizeModel(params) {
|
|
1153
|
-
const { model,
|
|
1185
|
+
const { model, lookup, dialect, enums } = params;
|
|
1154
1186
|
const table = model.dbName ?? model.name;
|
|
1155
1187
|
const columns = new Map;
|
|
1156
1188
|
for (const logicalField of model.fields) {
|
|
1157
|
-
const field = physicalEnumField({ field: logicalField,
|
|
1189
|
+
const field = physicalEnumField({ field: logicalField, lookup, dialect, table });
|
|
1158
1190
|
const name = field.dbName ?? field.name;
|
|
1191
|
+
const typeKey = columnTypeKeyFor({ field, dialect, enums });
|
|
1159
1192
|
columns.set(name, {
|
|
1160
1193
|
name,
|
|
1161
1194
|
field,
|
|
1162
|
-
typeKey
|
|
1163
|
-
exactTypeKey: columnTypeKeyFor({ field, dialect, enums, exact: true }),
|
|
1195
|
+
typeKey,
|
|
1196
|
+
exactTypeKey: dialect === "postgres" ? columnTypeKeyFor({ field, dialect, enums, exact: true }) : typeKey,
|
|
1164
1197
|
pinsExactType: dialect === "postgres" && declaresExactColumnType({ field }),
|
|
1165
1198
|
nullable: field.isOptional,
|
|
1166
1199
|
defaultKey: defaultFingerprintFor({ field, dialect })
|
|
1167
1200
|
});
|
|
1168
1201
|
}
|
|
1169
|
-
const pk = normalizePk({ model });
|
|
1170
|
-
const indexes = normalizeIndexes({ model, table, dialect });
|
|
1171
|
-
const fks = normalizeFks({ model, table,
|
|
1202
|
+
const pk = normalizePk({ model, lookup });
|
|
1203
|
+
const indexes = normalizeIndexes({ model, table, dialect, lookup });
|
|
1204
|
+
const fks = normalizeFks({ model, table, lookup, dialect });
|
|
1172
1205
|
const checks = new Map;
|
|
1173
1206
|
for (const check of model.checks ?? [])
|
|
1174
1207
|
checks.set(check.name, check.expression);
|
|
1175
1208
|
return { model, table, ...model.namespace === undefined ? {} : { namespace: model.namespace }, columns, pk, indexes, fks, checks };
|
|
1176
1209
|
}
|
|
1177
1210
|
function normalizePk(params) {
|
|
1178
|
-
const { model } = params;
|
|
1211
|
+
const { model, lookup } = params;
|
|
1179
1212
|
if (model.primaryKey !== undefined && model.primaryKey.fields.length > 0) {
|
|
1180
|
-
return model.primaryKey.fields.map((name) =>
|
|
1213
|
+
return model.primaryKey.fields.map((name) => lookupFieldName({ lookup, model, name }));
|
|
1181
1214
|
}
|
|
1182
1215
|
return model.fields.filter((field) => field.isId).map((field) => field.dbName ?? field.name);
|
|
1183
1216
|
}
|
|
1184
1217
|
function normalizeIndexes(params) {
|
|
1185
|
-
const { model, table, dialect } = params;
|
|
1218
|
+
const { model, table, dialect, lookup } = params;
|
|
1186
1219
|
const result = new Map;
|
|
1187
1220
|
const add = (index) => {
|
|
1188
1221
|
const unique = index.kind === "unique";
|
|
1189
1222
|
const kind = unique ? "btree" : index.kind;
|
|
1190
1223
|
const columns = index.fields.map((field) => {
|
|
1191
|
-
const fieldIR =
|
|
1224
|
+
const fieldIR = lookup.fields.get(model)?.get(field.name);
|
|
1192
1225
|
const decimal = fieldIR !== undefined && fieldIR.type === "Decimal" && fieldIR.isList !== true;
|
|
1193
1226
|
return {
|
|
1194
|
-
name:
|
|
1227
|
+
name: fieldIR?.dbName ?? fieldIR?.name ?? field.name,
|
|
1195
1228
|
desc: field.sort === "desc",
|
|
1196
1229
|
...decimal ? { decimal: true } : {},
|
|
1197
1230
|
...field.operatorClass === undefined ? {} : { operatorClass: field.operatorClass }
|
|
@@ -1225,8 +1258,7 @@ function normalizeIndexes(params) {
|
|
|
1225
1258
|
}
|
|
1226
1259
|
function indexComparisonKey(params) {
|
|
1227
1260
|
const { unique, kind, columns, where, nullsNotDistinct } = params;
|
|
1228
|
-
|
|
1229
|
-
return `${unique ? "u" : "i"}${nullsNotDistinct === true ? "n" : ""}:${kind}:${cols}:${normalizeWhere({ where })}`;
|
|
1261
|
+
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 })]);
|
|
1230
1262
|
}
|
|
1231
1263
|
function normalizeWhere(params) {
|
|
1232
1264
|
const { where } = params;
|
|
@@ -1388,22 +1420,22 @@ function parsePrimaryExpression(params) {
|
|
|
1388
1420
|
return { text: parts.join(" "), at, kind: "atom" };
|
|
1389
1421
|
}
|
|
1390
1422
|
function normalizeFks(params) {
|
|
1391
|
-
const { model, table,
|
|
1423
|
+
const { model, table, lookup } = params;
|
|
1392
1424
|
const result = new Map;
|
|
1393
1425
|
for (const relation of model.relations) {
|
|
1394
1426
|
if (relation.fields === undefined || relation.fields.length === 0)
|
|
1395
1427
|
continue;
|
|
1396
|
-
const target =
|
|
1428
|
+
const target = lookup.models.get(relation.targetModel);
|
|
1397
1429
|
if (target !== undefined && target.isView)
|
|
1398
1430
|
continue;
|
|
1399
1431
|
const refTable = target === undefined ? relation.targetModel : target.dbName ?? target.name;
|
|
1400
|
-
const columns = relation.fields.map((name) =>
|
|
1401
|
-
const refColumns = (relation.references ?? []).map((name) => target === undefined ? name :
|
|
1432
|
+
const columns = relation.fields.map((name) => lookupFieldName({ lookup, model, name }));
|
|
1433
|
+
const refColumns = (relation.references ?? []).map((name) => target === undefined ? name : lookupFieldName({ lookup, model: target, name }));
|
|
1402
1434
|
const required = relation.fields.every((name) => {
|
|
1403
|
-
const field =
|
|
1435
|
+
const field = lookup.fields.get(model)?.get(name);
|
|
1404
1436
|
return field === undefined ? true : !field.isOptional;
|
|
1405
1437
|
});
|
|
1406
|
-
const key =
|
|
1438
|
+
const key = JSON.stringify([columns, physicalNameKey({ name: refTable, namespace: target?.namespace, dialect: params.dialect }), refColumns]);
|
|
1407
1439
|
if (result.has(key))
|
|
1408
1440
|
continue;
|
|
1409
1441
|
result.set(key, {
|
|
@@ -1420,17 +1452,17 @@ function normalizeFks(params) {
|
|
|
1420
1452
|
return result;
|
|
1421
1453
|
}
|
|
1422
1454
|
function normalizeJoinTable(params) {
|
|
1423
|
-
const { pair,
|
|
1455
|
+
const { pair, lookup, dialect } = params;
|
|
1424
1456
|
return {
|
|
1425
1457
|
table: pair.tableName,
|
|
1426
1458
|
...pair.namespace === undefined ? {} : { namespace: pair.namespace },
|
|
1427
|
-
a: joinTableEnd({ modelName: pair.modelA,
|
|
1428
|
-
b: joinTableEnd({ modelName: pair.modelB,
|
|
1459
|
+
a: joinTableEnd({ modelName: pair.modelA, lookup, dialect, joinTable: pair.tableName }),
|
|
1460
|
+
b: joinTableEnd({ modelName: pair.modelB, lookup, dialect, joinTable: pair.tableName })
|
|
1429
1461
|
};
|
|
1430
1462
|
}
|
|
1431
1463
|
function joinTableEnd(params) {
|
|
1432
|
-
const { modelName,
|
|
1433
|
-
const model =
|
|
1464
|
+
const { modelName, lookup, joinTable } = params;
|
|
1465
|
+
const model = lookup.models.get(modelName);
|
|
1434
1466
|
if (model === undefined) {
|
|
1435
1467
|
throw new VibeError7({
|
|
1436
1468
|
code: "VIBE_MIGRATION",
|
|
@@ -1438,7 +1470,7 @@ function joinTableEnd(params) {
|
|
|
1438
1470
|
meta: { joinTable, model: modelName }
|
|
1439
1471
|
});
|
|
1440
1472
|
}
|
|
1441
|
-
const pkFields = model.primaryKey !== undefined && model.primaryKey.fields.length > 0 ? model.primaryKey.fields.map((name) =>
|
|
1473
|
+
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);
|
|
1442
1474
|
const pkField = pkFields[0];
|
|
1443
1475
|
if (pkFields.length !== 1 || pkField === undefined) {
|
|
1444
1476
|
throw new VibeError7({
|
|
@@ -1451,7 +1483,7 @@ function joinTableEnd(params) {
|
|
|
1451
1483
|
table: model.dbName ?? model.name,
|
|
1452
1484
|
...model.namespace === undefined ? {} : { namespace: model.namespace },
|
|
1453
1485
|
column: pkField.dbName ?? pkField.name,
|
|
1454
|
-
field: physicalEnumField({ field: pkField,
|
|
1486
|
+
field: physicalEnumField({ field: pkField, lookup, dialect: params.dialect, table: model.dbName ?? model.name })
|
|
1455
1487
|
};
|
|
1456
1488
|
}
|
|
1457
1489
|
function physicalNameKey(params) {
|
|
@@ -1467,10 +1499,19 @@ function schemaNamespaces(params) {
|
|
|
1467
1499
|
...(params.schema.migrationObjects ?? []).flatMap((object) => object.identity.kind === "namespace" ? [object.identity.name] : [])
|
|
1468
1500
|
])];
|
|
1469
1501
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1502
|
+
|
|
1503
|
+
// packages/migrate/src/introspect/group.ts
|
|
1504
|
+
function groupCatalogRows(params) {
|
|
1505
|
+
const groups = new Map;
|
|
1506
|
+
for (const row of params.rows) {
|
|
1507
|
+
const key = params.key(row);
|
|
1508
|
+
const group = groups.get(key);
|
|
1509
|
+
if (group === undefined)
|
|
1510
|
+
groups.set(key, [row]);
|
|
1511
|
+
else
|
|
1512
|
+
group.push(row);
|
|
1513
|
+
}
|
|
1514
|
+
return groups;
|
|
1474
1515
|
}
|
|
1475
1516
|
|
|
1476
1517
|
// packages/migrate/src/postgres-type-identity.ts
|
|
@@ -1874,10 +1915,15 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1874
1915
|
read("checks"),
|
|
1875
1916
|
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())
|
|
1876
1917
|
]);
|
|
1877
|
-
const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6 });
|
|
1918
|
+
const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6, namespaces });
|
|
1878
1919
|
const indexKeys = await executor({ text: keysQuery.text, values: [...keysQuery.values] });
|
|
1879
1920
|
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))
|
|
1880
1921
|
throw new VibeError11({ code: "VIBE_MIGRATION", message: "PostgreSQL index-key identity is incomplete", meta: { reason: "index-inspection-unavailable" } });
|
|
1922
|
+
const keysByIndex = groupCatalogRows({ rows: indexKeys, key: (row) => JSON.stringify([physicalNameKey({ namespace: String(row.table_namespace), name: String(row.table_name) }), row.index_name]) });
|
|
1923
|
+
const catalogColumns = new Map(columnRows.map((row) => [JSON.stringify([row.table_name, row.column_name]), row]));
|
|
1924
|
+
const foreignKeysByTable = groupCatalogRows({ rows: fkRows, key: (row) => String(row.table_name) });
|
|
1925
|
+
const catalogIndexesByTable = groupCatalogRows({ rows: indexRows, key: (row) => String(row.table_name) });
|
|
1926
|
+
const exclusionsByTable = groupCatalogRows({ rows: exclusionRows, key: (row) => String(row.table_name) });
|
|
1881
1927
|
const tables = tableRows.map((row) => row.table_name);
|
|
1882
1928
|
const views = viewRows.map((row) => row.table_name);
|
|
1883
1929
|
const columns = columnRows.map((row) => ({
|
|
@@ -1895,6 +1941,8 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1895
1941
|
numericScale: toNumberOrNull(row.numeric_scale),
|
|
1896
1942
|
fullType: row.full_type ?? null
|
|
1897
1943
|
}));
|
|
1944
|
+
const columnsByTable = groupCatalogRows({ rows: columns, key: (column) => column.table });
|
|
1945
|
+
const columnsByIdentity = new Map(columns.map((column) => [JSON.stringify([column.table, column.name]), column]));
|
|
1898
1946
|
const enums = [];
|
|
1899
1947
|
{
|
|
1900
1948
|
const valuesByName = new Map;
|
|
@@ -1935,6 +1983,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1935
1983
|
entry.columns.push(columnName);
|
|
1936
1984
|
}
|
|
1937
1985
|
}
|
|
1986
|
+
const uniquesByTable = groupCatalogRows({ rows: uniquesByConstraint.values(), key: (unique) => unique.table });
|
|
1938
1987
|
const checksByTable = new Map;
|
|
1939
1988
|
for (const row of checkRows) {
|
|
1940
1989
|
const table = row.table_name;
|
|
@@ -1969,7 +2018,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1969
2018
|
const fkGroups = [...fkGroupsByName.values()].map((group) => ({
|
|
1970
2019
|
...group,
|
|
1971
2020
|
required: group.columns.every((columnName) => {
|
|
1972
|
-
const column =
|
|
2021
|
+
const column = columnsByIdentity.get(JSON.stringify([group.table, columnName]));
|
|
1973
2022
|
return column === undefined ? true : !column.nullable;
|
|
1974
2023
|
})
|
|
1975
2024
|
}));
|
|
@@ -1977,7 +2026,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1977
2026
|
for (const row of indexRows) {
|
|
1978
2027
|
if (row.backing_constraint_type === "x")
|
|
1979
2028
|
continue;
|
|
1980
|
-
const operands =
|
|
2029
|
+
const operands = keysByIndex.get(JSON.stringify([row.table_name, row.index_name])) ?? [];
|
|
1981
2030
|
if (row.plain_columns === true && operands.length !== Number(row.key_count))
|
|
1982
2031
|
throw new VibeError11({ 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 } });
|
|
1983
2032
|
if (operands.length > 0) {
|
|
@@ -2002,22 +2051,24 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2002
2051
|
indexes.push(parsed);
|
|
2003
2052
|
}
|
|
2004
2053
|
}
|
|
2054
|
+
const indexesByTable = groupCatalogRows({ rows: indexes, key: (index) => index.table });
|
|
2055
|
+
const fkGroupsByTable = groupCatalogRows({ rows: fkGroups, key: (group) => group.table });
|
|
2005
2056
|
const joinTables = new Map;
|
|
2006
2057
|
for (const table of tables) {
|
|
2007
2058
|
const physicalTable = physicalNames.get(table);
|
|
2008
2059
|
if (!physicalTable.name.startsWith("_"))
|
|
2009
2060
|
continue;
|
|
2010
|
-
const tableFks =
|
|
2011
|
-
const tableIndexes =
|
|
2012
|
-
if (pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0 ||
|
|
2061
|
+
const tableFks = foreignKeysByTable.get(table) ?? [];
|
|
2062
|
+
const tableIndexes = catalogIndexesByTable.get(table) ?? [];
|
|
2063
|
+
if (pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0 || exclusionsByTable.has(table))
|
|
2013
2064
|
continue;
|
|
2014
2065
|
if (!tableFks.every((row) => row.validated === true && row.deferrable === false && row.triggers_enabled === true))
|
|
2015
2066
|
continue;
|
|
2016
2067
|
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))
|
|
2017
2068
|
continue;
|
|
2018
2069
|
if (tableFks.some((row) => {
|
|
2019
|
-
const source =
|
|
2020
|
-
const target =
|
|
2070
|
+
const source = catalogColumns.get(JSON.stringify([table, row.column_name]));
|
|
2071
|
+
const target = catalogColumns.get(JSON.stringify([row.foreign_table_name, row.foreign_column_name]));
|
|
2021
2072
|
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;
|
|
2022
2073
|
}))
|
|
2023
2074
|
continue;
|
|
@@ -2025,9 +2076,9 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2025
2076
|
continue;
|
|
2026
2077
|
const detected = detectJoinTable({
|
|
2027
2078
|
table: physicalTable.name,
|
|
2028
|
-
columnNames:
|
|
2029
|
-
fkGroups:
|
|
2030
|
-
indexes:
|
|
2079
|
+
columnNames: (columnsByTable.get(table) ?? []).map((column) => column.name),
|
|
2080
|
+
fkGroups: (fkGroupsByTable.get(table) ?? []).map((group) => ({ ...group, table: physicalTable.name, refTable: physicalNames.get(group.refTable)?.name ?? group.refTable })),
|
|
2081
|
+
indexes: (indexesByTable.get(table) ?? []).map((index) => ({ ...index, table: physicalTable.name }))
|
|
2031
2082
|
});
|
|
2032
2083
|
if (detected !== null)
|
|
2033
2084
|
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 }) });
|
|
@@ -2040,14 +2091,14 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2040
2091
|
table,
|
|
2041
2092
|
physicalTable: physicalNames.get(table).name,
|
|
2042
2093
|
isView: false,
|
|
2043
|
-
columns,
|
|
2094
|
+
columns: columnsByTable.get(table) ?? [],
|
|
2044
2095
|
pkByTable,
|
|
2045
2096
|
pkNames,
|
|
2046
|
-
|
|
2097
|
+
uniques: uniquesByTable.get(table) ?? [],
|
|
2047
2098
|
constraintNames,
|
|
2048
|
-
indexes,
|
|
2099
|
+
indexes: indexesByTable.get(table) ?? [],
|
|
2049
2100
|
enumNames,
|
|
2050
|
-
exclusions:
|
|
2101
|
+
exclusions: (exclusionsByTable.get(table) ?? []).map((row) => ({ name: String(row.constraint_name), definition: String(row.definition), requiredExtensions: [], catalogDefinition: true })),
|
|
2051
2102
|
checks: checksByTable.get(table) ?? []
|
|
2052
2103
|
}));
|
|
2053
2104
|
}
|
|
@@ -2056,10 +2107,10 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2056
2107
|
table: view,
|
|
2057
2108
|
physicalTable: physicalNames.get(view).name,
|
|
2058
2109
|
isView: true,
|
|
2059
|
-
columns,
|
|
2110
|
+
columns: columnsByTable.get(view) ?? [],
|
|
2060
2111
|
pkByTable,
|
|
2061
2112
|
pkNames,
|
|
2062
|
-
|
|
2113
|
+
uniques: uniquesByTable.get(view) ?? [],
|
|
2063
2114
|
constraintNames,
|
|
2064
2115
|
indexes: [],
|
|
2065
2116
|
enumNames,
|
|
@@ -2079,14 +2130,16 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2079
2130
|
logicalNames.add(name);
|
|
2080
2131
|
return name;
|
|
2081
2132
|
};
|
|
2133
|
+
const modelBasenames = groupCatalogRows({ rows: physicalNames.values(), key: (entry) => entry.name });
|
|
2134
|
+
const enumBasenames = groupCatalogRows({ rows: enumIdentities.values(), key: (entry) => entry.name });
|
|
2082
2135
|
const modelNames = new Map(models.map((model) => {
|
|
2083
2136
|
const identity = physicalNames.get(model.name);
|
|
2084
|
-
const duplicates =
|
|
2137
|
+
const duplicates = (modelBasenames.get(identity.name)?.length ?? 0) > 1;
|
|
2085
2138
|
return [model.name, logicalName({ key: model.name, name: identity.name, duplicates })];
|
|
2086
2139
|
}));
|
|
2087
2140
|
const enumLogicalNames = new Map(enums.map((entry) => {
|
|
2088
2141
|
const identity = enumIdentities.get(entry.name);
|
|
2089
|
-
const duplicates =
|
|
2142
|
+
const duplicates = (enumBasenames.get(identity.name)?.length ?? 0) > 1;
|
|
2090
2143
|
return [entry.name, logicalName({ key: entry.name, name: identity.name, duplicates })];
|
|
2091
2144
|
}));
|
|
2092
2145
|
const qualifiedModels = models.map((model) => {
|
|
@@ -2124,20 +2177,18 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2124
2177
|
return resolved.schema;
|
|
2125
2178
|
}
|
|
2126
2179
|
function buildModel(params) {
|
|
2127
|
-
const { table, isView, columns, pkByTable,
|
|
2128
|
-
const tableColumns = columns
|
|
2180
|
+
const { table, isView, columns, pkByTable, uniques, constraintNames, indexes, enumNames } = params;
|
|
2181
|
+
const tableColumns = columns;
|
|
2129
2182
|
const pk = pkByTable.get(table) ?? [];
|
|
2130
2183
|
const uniqueSingles = new Set;
|
|
2131
2184
|
const multiUniques = [];
|
|
2132
|
-
for (const unique of
|
|
2185
|
+
for (const unique of uniques) {
|
|
2133
2186
|
const name = unique.name;
|
|
2134
|
-
if (unique.table !== table)
|
|
2135
|
-
continue;
|
|
2136
2187
|
if (unique.columns.length === 1)
|
|
2137
2188
|
uniqueSingles.add(unique.columns[0]);
|
|
2138
2189
|
multiUniques.push({ name, columns: unique.columns });
|
|
2139
2190
|
}
|
|
2140
|
-
const tableIndexes = indexes.filter((index) =>
|
|
2191
|
+
const tableIndexes = indexes.filter((index) => !constraintNames.has(JSON.stringify([table, index.name])));
|
|
2141
2192
|
for (const index of tableIndexes) {
|
|
2142
2193
|
if (!index.unique)
|
|
2143
2194
|
continue;
|
|
@@ -2422,7 +2473,7 @@ function toNumberOrNull(value) {
|
|
|
2422
2473
|
}
|
|
2423
2474
|
|
|
2424
2475
|
// packages/migrate/src/introspect/mysql.ts
|
|
2425
|
-
import { exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
|
|
2476
|
+
import { VibeError as VibeError12, exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
|
|
2426
2477
|
import { mysqlDialect as mysqlDialect2 } from "@vibeorm/sql";
|
|
2427
2478
|
|
|
2428
2479
|
// packages/migrate/src/journal-table.ts
|
|
@@ -2551,6 +2602,12 @@ async function introspectMysql(params) {
|
|
|
2551
2602
|
collation: catalogField({ row, name: "collation_name" }) ?? null,
|
|
2552
2603
|
extra: (catalogField({ row, name: "extra" }) ?? "").toLowerCase()
|
|
2553
2604
|
}));
|
|
2605
|
+
const columnsByTable = groupCatalogRows({ rows: columns, key: (column) => column.table });
|
|
2606
|
+
const columnsByIdentity = new Map(columns.map((column) => [JSON.stringify([column.table, column.name]), column]));
|
|
2607
|
+
const catalogColumns = new Map(columnRows.map((row) => [JSON.stringify([catalogField({ row, name: "table_name" }), catalogField({ row, name: "column_name" })]), row]));
|
|
2608
|
+
const statisticsByTable = groupCatalogRows({ rows: statisticRows, key: (row) => String(catalogField({ row, name: "table_name" })) });
|
|
2609
|
+
const foreignKeysByTable = groupCatalogRows({ rows: fkRows, key: (row) => String(catalogField({ row, name: "table_name" })) });
|
|
2610
|
+
const enginesByTable = new Map(tableRows.map((row) => [catalogField({ row, name: "table_name" }), catalogField({ row, name: "engine" })]));
|
|
2554
2611
|
const rulesByConstraint = new Map;
|
|
2555
2612
|
for (const row of refRows) {
|
|
2556
2613
|
rulesByConstraint.set(catalogField({ row, name: "constraint_name" }), {
|
|
@@ -2562,6 +2619,15 @@ async function introspectMysql(params) {
|
|
|
2562
2619
|
for (const row of fkRows) {
|
|
2563
2620
|
const constraintName = catalogField({ row, name: "constraint_name" });
|
|
2564
2621
|
const table = catalogField({ row, name: "table_name" });
|
|
2622
|
+
const tableSchema = catalogField({ row, name: "table_schema" });
|
|
2623
|
+
const referencedSchema = catalogField({ row, name: "referenced_table_schema" });
|
|
2624
|
+
if (typeof tableSchema === "string" && typeof referencedSchema === "string" && tableSchema !== referencedSchema) {
|
|
2625
|
+
throw new VibeError12({
|
|
2626
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
2627
|
+
message: `MySQL cross-database foreign key "${constraintName}" from "${tableSchema}.${table}" to "${referencedSchema}" cannot be introspected without losing its database identity`,
|
|
2628
|
+
meta: { dialect: "mysql", feature: "cross-database foreign keys", constraint: constraintName, table, tableSchema, referencedSchema }
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2565
2631
|
const key = `${table}\x00${constraintName}`;
|
|
2566
2632
|
const existing = groupsByKey.get(key);
|
|
2567
2633
|
if (existing === undefined) {
|
|
@@ -2584,17 +2650,11 @@ async function introspectMysql(params) {
|
|
|
2584
2650
|
onDelete: rules?.onDelete ?? "NoAction",
|
|
2585
2651
|
onUpdate: rules?.onUpdate ?? "NoAction",
|
|
2586
2652
|
required: group.columns.every((columnName) => {
|
|
2587
|
-
const column =
|
|
2653
|
+
const column = columnsByIdentity.get(JSON.stringify([group.table, columnName]));
|
|
2588
2654
|
return column === undefined ? true : !column.nullable;
|
|
2589
2655
|
})
|
|
2590
2656
|
};
|
|
2591
2657
|
});
|
|
2592
|
-
const fkNamesByTable = new Map;
|
|
2593
|
-
for (const group of fkGroups) {
|
|
2594
|
-
const set = fkNamesByTable.get(group.table) ?? new Set;
|
|
2595
|
-
set.add(group.constraintName);
|
|
2596
|
-
fkNamesByTable.set(group.table, set);
|
|
2597
|
-
}
|
|
2598
2658
|
const pkByTable = new Map;
|
|
2599
2659
|
const indexes = [];
|
|
2600
2660
|
{
|
|
@@ -2603,6 +2663,12 @@ async function introspectMysql(params) {
|
|
|
2603
2663
|
const table = catalogField({ row, name: "table_name" });
|
|
2604
2664
|
const name = catalogField({ row, name: "index_name" });
|
|
2605
2665
|
const columnName = catalogField({ row, name: "column_name" });
|
|
2666
|
+
const type = catalogField({ row, name: "index_type" });
|
|
2667
|
+
const unique = Number(catalogField({ row, name: "non_unique" })) === 0;
|
|
2668
|
+
const kind = typeof type === "string" ? type.toLowerCase() : "btree";
|
|
2669
|
+
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)) {
|
|
2670
|
+
throw new VibeError12({ 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 } });
|
|
2671
|
+
}
|
|
2606
2672
|
if (name === "PRIMARY") {
|
|
2607
2673
|
const list = pkByTable.get(table);
|
|
2608
2674
|
if (list === undefined)
|
|
@@ -2611,8 +2677,6 @@ async function introspectMysql(params) {
|
|
|
2611
2677
|
list.push(columnName);
|
|
2612
2678
|
continue;
|
|
2613
2679
|
}
|
|
2614
|
-
if (fkNamesByTable.get(table)?.has(name) === true)
|
|
2615
|
-
continue;
|
|
2616
2680
|
const key = `${table}\x00${name}`;
|
|
2617
2681
|
const entry = byKey.get(key);
|
|
2618
2682
|
const column = {
|
|
@@ -2623,7 +2687,8 @@ async function introspectMysql(params) {
|
|
|
2623
2687
|
byKey.set(key, {
|
|
2624
2688
|
table,
|
|
2625
2689
|
name,
|
|
2626
|
-
unique
|
|
2690
|
+
unique,
|
|
2691
|
+
kind,
|
|
2627
2692
|
columns: [column]
|
|
2628
2693
|
});
|
|
2629
2694
|
} else {
|
|
@@ -2631,34 +2696,39 @@ async function introspectMysql(params) {
|
|
|
2631
2696
|
}
|
|
2632
2697
|
}
|
|
2633
2698
|
for (const entry of byKey.values()) {
|
|
2634
|
-
|
|
2699
|
+
const support = groupsByKey.get(`${entry.table}\x00${entry.name}`);
|
|
2700
|
+
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))
|
|
2701
|
+
continue;
|
|
2702
|
+
indexes.push(entry);
|
|
2635
2703
|
}
|
|
2636
2704
|
}
|
|
2705
|
+
const indexesByTable = groupCatalogRows({ rows: indexes, key: (index) => index.table });
|
|
2706
|
+
const fkGroupsByTable = groupCatalogRows({ rows: fkGroups, key: (group) => group.table });
|
|
2637
2707
|
const enums = [];
|
|
2638
2708
|
const joinTables = new Map;
|
|
2639
2709
|
let foreignKeysEnabled;
|
|
2640
2710
|
for (const table of tables) {
|
|
2641
2711
|
if (!table.startsWith("_") || pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0)
|
|
2642
2712
|
continue;
|
|
2643
|
-
const tableIndexes =
|
|
2713
|
+
const tableIndexes = statisticsByTable.get(table) ?? [];
|
|
2644
2714
|
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))
|
|
2645
2715
|
continue;
|
|
2646
|
-
const tableFks =
|
|
2716
|
+
const tableFks = foreignKeysByTable.get(table) ?? [];
|
|
2647
2717
|
if (tableFks.some((row) => {
|
|
2648
2718
|
const targetTable = catalogField({ row, name: "referenced_table_name" });
|
|
2649
|
-
const source =
|
|
2650
|
-
const target =
|
|
2719
|
+
const source = catalogColumns.get(JSON.stringify([table, catalogField({ row, name: "column_name" })]));
|
|
2720
|
+
const target = catalogColumns.get(JSON.stringify([targetTable, catalogField({ row, name: "referenced_column_name" })]));
|
|
2651
2721
|
if (source === undefined || target === undefined)
|
|
2652
2722
|
return true;
|
|
2653
2723
|
const type = catalogField({ row: source, name: "column_type" });
|
|
2654
|
-
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) =>
|
|
2724
|
+
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");
|
|
2655
2725
|
}))
|
|
2656
2726
|
continue;
|
|
2657
2727
|
const detected = detectJoinTable({
|
|
2658
2728
|
table,
|
|
2659
|
-
columnNames:
|
|
2660
|
-
fkGroups,
|
|
2661
|
-
indexes
|
|
2729
|
+
columnNames: (columnsByTable.get(table) ?? []).map((column) => column.name),
|
|
2730
|
+
fkGroups: fkGroupsByTable.get(table) ?? [],
|
|
2731
|
+
indexes: indexesByTable.get(table) ?? []
|
|
2662
2732
|
});
|
|
2663
2733
|
if (detected === null)
|
|
2664
2734
|
continue;
|
|
@@ -2670,7 +2740,7 @@ async function introspectMysql(params) {
|
|
|
2670
2740
|
continue;
|
|
2671
2741
|
const q = (name) => mysqlDialect2.quoteIdent({ name });
|
|
2672
2742
|
let valid = true;
|
|
2673
|
-
for (const fk of
|
|
2743
|
+
for (const fk of fkGroupsByTable.get(table) ?? []) {
|
|
2674
2744
|
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: [] });
|
|
2675
2745
|
if (rows.length > 0) {
|
|
2676
2746
|
valid = false;
|
|
@@ -2687,9 +2757,9 @@ async function introspectMysql(params) {
|
|
|
2687
2757
|
models.push(buildModel2({
|
|
2688
2758
|
table,
|
|
2689
2759
|
isView: false,
|
|
2690
|
-
columns,
|
|
2760
|
+
columns: columnsByTable.get(table) ?? [],
|
|
2691
2761
|
pkByTable,
|
|
2692
|
-
indexes,
|
|
2762
|
+
indexes: indexesByTable.get(table) ?? [],
|
|
2693
2763
|
enums,
|
|
2694
2764
|
checks: checksByTable.get(table) ?? []
|
|
2695
2765
|
}));
|
|
@@ -2698,7 +2768,7 @@ async function introspectMysql(params) {
|
|
|
2698
2768
|
models.push(buildModel2({
|
|
2699
2769
|
table: view,
|
|
2700
2770
|
isView: true,
|
|
2701
|
-
columns,
|
|
2771
|
+
columns: columnsByTable.get(view) ?? [],
|
|
2702
2772
|
pkByTable: new Map,
|
|
2703
2773
|
indexes: [],
|
|
2704
2774
|
enums,
|
|
@@ -2716,9 +2786,9 @@ async function introspectMysql(params) {
|
|
|
2716
2786
|
}
|
|
2717
2787
|
function buildModel2(params) {
|
|
2718
2788
|
const { table, isView, columns, pkByTable, indexes, enums } = params;
|
|
2719
|
-
const tableColumns = columns
|
|
2789
|
+
const tableColumns = columns;
|
|
2720
2790
|
const pk = pkByTable.get(table) ?? [];
|
|
2721
|
-
const tableIndexes = indexes
|
|
2791
|
+
const tableIndexes = indexes;
|
|
2722
2792
|
const uniqueSingles = new Set;
|
|
2723
2793
|
for (const index of tableIndexes) {
|
|
2724
2794
|
if (index.unique && index.columns.length === 1) {
|
|
@@ -2749,7 +2819,7 @@ function buildModel2(params) {
|
|
|
2749
2819
|
continue;
|
|
2750
2820
|
modelIndexes.push({
|
|
2751
2821
|
name: index.name,
|
|
2752
|
-
kind: index.unique ? "unique" :
|
|
2822
|
+
kind: index.unique ? "unique" : index.kind,
|
|
2753
2823
|
fields: index.columns.map((column) => ({
|
|
2754
2824
|
name: column.name,
|
|
2755
2825
|
...column.desc ? { sort: "desc" } : {}
|
|
@@ -3281,7 +3351,7 @@ function q(name) {
|
|
|
3281
3351
|
}
|
|
3282
3352
|
|
|
3283
3353
|
// packages/migrate/src/execute.ts
|
|
3284
|
-
import { VibeError as
|
|
3354
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
3285
3355
|
var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
|
|
3286
3356
|
var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
|
|
3287
3357
|
function usesSelfManagedTransactions(params) {
|
|
@@ -3322,7 +3392,7 @@ function assertSelfManagedResumeSafe(params) {
|
|
|
3322
3392
|
const completedIndex = params.direction === "up" ? params.start + 1 : -(params.start + 2);
|
|
3323
3393
|
const rewind = selfManagedResumeStart({ sql: params.sql, start: params.start });
|
|
3324
3394
|
const retryIndex = params.direction === "up" ? rewind : -(rewind + 1);
|
|
3325
|
-
throw new
|
|
3395
|
+
throw new VibeError13({
|
|
3326
3396
|
code: "VIBE_MIGRATION",
|
|
3327
3397
|
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.`,
|
|
3328
3398
|
meta: { reason: "uncertain-statement", migration: params.migration, statementIndex: params.start, direction: params.direction, dialect: "sqlite" }
|
|
@@ -3351,7 +3421,7 @@ function unsupportedScript(params) {
|
|
|
3351
3421
|
const { migration, direction, index, problem, found } = params;
|
|
3352
3422
|
const subject = direction === "down" ? `the down script of migration "${migration}"` : `migration "${migration}"`;
|
|
3353
3423
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${params.statements}`;
|
|
3354
|
-
return new
|
|
3424
|
+
return new VibeError13({
|
|
3355
3425
|
code: "VIBE_MIGRATION",
|
|
3356
3426
|
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.',
|
|
3357
3427
|
meta: { reason: "unsupported-self-managed-script", problem, migration, statementIndex: index, direction }
|
|
@@ -3407,7 +3477,7 @@ async function executeStatement(params) {
|
|
|
3407
3477
|
const rows = await executor({ text: statement, values: [] });
|
|
3408
3478
|
if (rows.length > 0) {
|
|
3409
3479
|
const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
|
|
3410
|
-
throw new
|
|
3480
|
+
throw new VibeError13({
|
|
3411
3481
|
code: "VIBE_MIGRATION",
|
|
3412
3482
|
message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
|
|
3413
3483
|
meta: { violations: rows }
|
|
@@ -3462,7 +3532,7 @@ async function prepareOperatorClassDiff(params) {
|
|
|
3462
3532
|
}
|
|
3463
3533
|
|
|
3464
3534
|
// packages/migrate/src/postgres-default-equivalence.ts
|
|
3465
|
-
import { VibeError as
|
|
3535
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
3466
3536
|
import { postgresDialect as postgresDialect8, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
|
|
3467
3537
|
var IDENTIFIER = '(?:"(?:[^"\\u0000]|"")+"|[A-Za-z_][A-Za-z_0-9$]*)';
|
|
3468
3538
|
var CALL = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*\\(\\s*\\)\\s*$`);
|
|
@@ -3481,7 +3551,7 @@ async function equivalentPostgresDefaultFunction(params) {
|
|
|
3481
3551
|
return false;
|
|
3482
3552
|
const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect8, table: params.table, column: params.column });
|
|
3483
3553
|
const rows = await params.executor({ text: query.text, values: [...query.values] }).catch((cause) => {
|
|
3484
|
-
throw new
|
|
3554
|
+
throw new VibeError14({ code: "VIBE_MIGRATION", message: "Cannot inspect the stored default function identity", meta: { reason: "default-inspection-unavailable", table: params.table, column: params.column }, cause });
|
|
3485
3555
|
});
|
|
3486
3556
|
if (rows.length !== 1)
|
|
3487
3557
|
return false;
|
|
@@ -3495,7 +3565,7 @@ async function equivalentPostgresDefaultFunction(params) {
|
|
|
3495
3565
|
}
|
|
3496
3566
|
|
|
3497
3567
|
// packages/migrate/src/object-ownership.ts
|
|
3498
|
-
import { VibeError as
|
|
3568
|
+
import { VibeError as VibeError15, defaultPrimaryKeyName, enumStorageOf as enumStorageOf2, implicitJoinTableObjectNames, postgresGeneratedObjectName, qualifiedSqlName as qualifiedSqlName6, sqlObjectKey as sqlObjectKey3 } from "@vibeorm/schema";
|
|
3499
3569
|
function legacyMigrationObjects(params) {
|
|
3500
3570
|
return params.artifacts.map((artifact) => {
|
|
3501
3571
|
const owner = artifact.owns;
|
|
@@ -3597,7 +3667,10 @@ function removalIdentity(params) {
|
|
|
3597
3667
|
}
|
|
3598
3668
|
}
|
|
3599
3669
|
function schemaObjectIdentities(params) {
|
|
3600
|
-
|
|
3670
|
+
return schemaObjectIdentitiesFromNormalized({ ...params, normalized: normalizeSchema({ schema: params.schema, dialect: params.dialect }) });
|
|
3671
|
+
}
|
|
3672
|
+
function schemaObjectIdentitiesFromNormalized(params) {
|
|
3673
|
+
const { normalized } = params;
|
|
3601
3674
|
const result = [];
|
|
3602
3675
|
if (params.dialect === "postgres") {
|
|
3603
3676
|
for (const namespace of schemaNamespaces({ schema: params.schema }))
|
|
@@ -3647,7 +3720,7 @@ function assertRetainedMigrationObjects(params) {
|
|
|
3647
3720
|
const keys = new Set(params.to.map((object) => sqlObjectKey3({ identity: object.identity })));
|
|
3648
3721
|
const removed = params.from.filter((object) => !keys.has(sqlObjectKey3({ identity: object.identity })));
|
|
3649
3722
|
if (removed.length > 0)
|
|
3650
|
-
throw new
|
|
3723
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Removing migration ownership requires a reviewed hand-authored migration and new desired state", meta: { reason: "migration-object-removal-review-required", shadowReusable: true, objects: removed.map((object) => object.identity) } });
|
|
3651
3724
|
}
|
|
3652
3725
|
function orderedMigrationObjects(params) {
|
|
3653
3726
|
const byKey = new Map(params.objects.map((object) => [sqlObjectKey3({ identity: object.identity }), object]));
|
|
@@ -3659,7 +3732,7 @@ function orderedMigrationObjects(params) {
|
|
|
3659
3732
|
if (visited.has(key))
|
|
3660
3733
|
return;
|
|
3661
3734
|
if (visiting.has(key))
|
|
3662
|
-
throw new
|
|
3735
|
+
throw new VibeError15({ code: "VIBE_SCHEMA_INVALID", message: "Migration object dependency cycle", meta: { id: object.id } });
|
|
3663
3736
|
visiting.add(key);
|
|
3664
3737
|
for (const dependency of object.dependsOn) {
|
|
3665
3738
|
const target = byKey.get(sqlObjectKey3({ identity: dependency }));
|
|
@@ -3678,10 +3751,10 @@ function assertNamedRemovals(params) {
|
|
|
3678
3751
|
const approved = new Set((params.approved ?? []).map((identity) => sqlObjectKey3({ identity })));
|
|
3679
3752
|
const missing = params.removals.filter((identity) => !approved.has(sqlObjectKey3({ identity })));
|
|
3680
3753
|
if (missing.length > 0)
|
|
3681
|
-
throw new
|
|
3754
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Unmanaged removals require individually named approvals", meta: { reason: "unmanaged-removal-required", shadowReusable: true, objects: missing } });
|
|
3682
3755
|
const integrity = params.removals.filter((identity) => identity.kind === "constraint" || identity.kind === "index");
|
|
3683
3756
|
if (integrity.length > 0 && params.acceptIntegrityLoss !== true)
|
|
3684
|
-
throw new
|
|
3757
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Named constraint or index removals also require acceptIntegrityLoss", meta: { reason: "integrity-acknowledgement-required", shadowReusable: true, objects: integrity } });
|
|
3685
3758
|
}
|
|
3686
3759
|
function assertRemovalAuthorization(params) {
|
|
3687
3760
|
const declared = params.previous === undefined ? [] : schemaObjectIdentities({ schema: params.previous.schema, dialect: params.dialect, namespace: params.namespace });
|
|
@@ -3706,9 +3779,9 @@ function assertRemovalAuthorization(params) {
|
|
|
3706
3779
|
}
|
|
3707
3780
|
}
|
|
3708
3781
|
if (unknown.length > 0)
|
|
3709
|
-
throw new
|
|
3782
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Unknown object removals require individually named removeUnmanaged decisions", meta: { reason: "unmanaged-removal-required", shadowReusable: true, objects: unknown } });
|
|
3710
3783
|
if (integrity.length > 0 && params.acceptIntegrityLoss !== true)
|
|
3711
|
-
throw new
|
|
3784
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Constraint or index removal requires acceptIntegrityLoss; data-loss acknowledgement does not authorize it", meta: { reason: "integrity-acknowledgement-required", shadowReusable: true, objects: integrity } });
|
|
3712
3785
|
}
|
|
3713
3786
|
|
|
3714
3787
|
// packages/migrate/src/inspect-objects.ts
|
|
@@ -3793,7 +3866,7 @@ async function inspectSqlObject(params) {
|
|
|
3793
3866
|
}
|
|
3794
3867
|
}
|
|
3795
3868
|
return { state: "present", definition: { identity: params.identity, facts, dependencies: [], format: "catalog-v2" } };
|
|
3796
|
-
}).catch((error) => ({ state: "unavailable", reason: error instanceof
|
|
3869
|
+
}).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError16 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
|
|
3797
3870
|
}
|
|
3798
3871
|
function postgresInspection(params) {
|
|
3799
3872
|
if (!params.addresses.every((address) => typeof address === "string"))
|
|
@@ -3808,7 +3881,7 @@ function postgresInspection(params) {
|
|
|
3808
3881
|
return { state: "present", definition: { identity: params.identity, facts, dependencies, format: "catalog-v2" } };
|
|
3809
3882
|
}
|
|
3810
3883
|
function unavailableReason(params) {
|
|
3811
|
-
return params.error instanceof
|
|
3884
|
+
return params.error instanceof VibeError16 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
|
|
3812
3885
|
}
|
|
3813
3886
|
async function readBatch(params) {
|
|
3814
3887
|
const positions = new Set(params.batch.positions);
|
|
@@ -3816,7 +3889,7 @@ async function readBatch(params) {
|
|
|
3816
3889
|
const rows = new Map;
|
|
3817
3890
|
for (const row of await params.executor({ text: params.batch.text, values: [...params.batch.values] })) {
|
|
3818
3891
|
if (typeof row.position !== "number" || !Number.isInteger(row.position) || !positions.has(row.position))
|
|
3819
|
-
throw new
|
|
3892
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
|
|
3820
3893
|
const matches = rows.get(row.position) ?? [];
|
|
3821
3894
|
matches.push(row);
|
|
3822
3895
|
rows.set(row.position, matches);
|
|
@@ -3861,7 +3934,7 @@ async function assertCatalogVisibility(params) {
|
|
|
3861
3934
|
const grants = new Set(rows.map((row) => row.privilege));
|
|
3862
3935
|
const missing = ["SELECT", "TRIGGER", "SHOW VIEW", "EXECUTE"].filter((privilege) => !grants.has(privilege));
|
|
3863
3936
|
if (missing.length > 0)
|
|
3864
|
-
throw new
|
|
3937
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL catalog visibility requires direct database-wide grants; role-only or partial grants cannot prove object absence", meta: { reason: "inventory-unavailable", missing } });
|
|
3865
3938
|
}
|
|
3866
3939
|
var CATALOG_V1_OMITTED_FACTS = Object.freeze({
|
|
3867
3940
|
function: ["definerOwner"],
|
|
@@ -3885,9 +3958,20 @@ function compare(params) {
|
|
|
3885
3958
|
differences.push({ attribute: "dependencies", expected, actual });
|
|
3886
3959
|
return differences;
|
|
3887
3960
|
}
|
|
3961
|
+
function compareInspectedSqlObject(params) {
|
|
3962
|
+
const { identity, ownership, expected, live } = params;
|
|
3963
|
+
if (live.state !== "present")
|
|
3964
|
+
return { identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} };
|
|
3965
|
+
if (expected === undefined)
|
|
3966
|
+
return identity.kind === "namespace" ? { identity, ownership, state: "matching", differences: [] } : { identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] };
|
|
3967
|
+
if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined))
|
|
3968
|
+
return { identity, ownership, state: "unavailable", reason: "table-physical-reference-required", differences: compare({ expected, live: live.definition }) };
|
|
3969
|
+
const differences = compare({ expected, live: live.definition });
|
|
3970
|
+
return { identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences };
|
|
3971
|
+
}
|
|
3888
3972
|
function assertProbeMode(params) {
|
|
3889
3973
|
if (params.probe !== undefined && params.probe !== "none" && DIALECT_CAPABILITIES2[params.dialect].checkNormalization !== params.probe)
|
|
3890
|
-
throw new
|
|
3974
|
+
throw new VibeError16({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
|
|
3891
3975
|
}
|
|
3892
3976
|
async function verifyMigrationState(params) {
|
|
3893
3977
|
return verifyState(params);
|
|
@@ -3910,18 +3994,7 @@ async function verifyState(params) {
|
|
|
3910
3994
|
const expected = definitions.get(key);
|
|
3911
3995
|
const live = inspections === undefined ? await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity }) : inspections[position];
|
|
3912
3996
|
position++;
|
|
3913
|
-
|
|
3914
|
-
objects.push({ identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
|
|
3915
|
-
} else if (expected === undefined && identity.kind === "namespace") {
|
|
3916
|
-
objects.push({ identity, ownership, state: "matching", differences: [] });
|
|
3917
|
-
} else if (expected === undefined) {
|
|
3918
|
-
objects.push({ identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] });
|
|
3919
|
-
} else if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined)) {
|
|
3920
|
-
objects.push({ identity, ownership, state: "unavailable", reason: "table-physical-reference-required", differences: compare({ expected, live: live.definition }) });
|
|
3921
|
-
} else {
|
|
3922
|
-
const differences = compare({ expected, live: live.definition });
|
|
3923
|
-
objects.push({ identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences });
|
|
3924
|
-
}
|
|
3997
|
+
objects.push(compareInspectedSqlObject({ identity, ownership, expected, live, dialect: params.dialect }));
|
|
3925
3998
|
}
|
|
3926
3999
|
if (params.dialect === "postgres" && schemaNamespaces({ schema: params.expected.schema }).length > 0 || params.expected.schema.models.length > 0 || params.expected.schema.enums.length > 0)
|
|
3927
4000
|
objects.push(...await verifySchemaObjects(params));
|
|
@@ -4090,7 +4163,7 @@ async function verifyMigrationTransition(params) {
|
|
|
4090
4163
|
}
|
|
4091
4164
|
async function verifySqliteTransitionBeforeRestore(params) {
|
|
4092
4165
|
if (params.dialect !== "sqlite")
|
|
4093
|
-
throw new
|
|
4166
|
+
throw new VibeError16({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
|
|
4094
4167
|
await executeStatement({ executor: params.executor, dialect: "sqlite", statement: "PRAGMA foreign_key_check;" });
|
|
4095
4168
|
return verifyTransition({ ...params, sqliteRestorePending: true });
|
|
4096
4169
|
}
|
|
@@ -4100,7 +4173,7 @@ async function verifyTransition(params) {
|
|
|
4100
4173
|
for (const identity of params.removals ?? []) {
|
|
4101
4174
|
const owner = externalObjectOwner({ identity, external });
|
|
4102
4175
|
if (owner !== undefined)
|
|
4103
|
-
throw new
|
|
4176
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Transition removals cannot target externally owned objects", meta: { reason: "external-owned-removal", owner, identity } });
|
|
4104
4177
|
}
|
|
4105
4178
|
params = { ...params, from: projectExternalState({ state: params.from, external }), to: projectExternalState({ state: params.to, external }) };
|
|
4106
4179
|
}
|
|
@@ -4125,12 +4198,12 @@ async function migrationNamespace(params) {
|
|
|
4125
4198
|
const rows = await params.executor({ text: query.text, values: [] });
|
|
4126
4199
|
const namespace = rows[0]?.namespace;
|
|
4127
4200
|
if (typeof namespace !== "string" || namespace.length === 0)
|
|
4128
|
-
throw new
|
|
4201
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
|
|
4129
4202
|
return namespace;
|
|
4130
4203
|
}
|
|
4131
4204
|
function assertMigrationState(params) {
|
|
4132
4205
|
if (!params.report.inSync)
|
|
4133
|
-
throw new
|
|
4206
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Migration state could not be verified", meta: { reason: "migration-state-unverified", shadowReusable: true, objects: params.report.objects.filter((object) => object.state !== "matching") } });
|
|
4134
4207
|
}
|
|
4135
4208
|
async function deparsePostgresCheck(params) {
|
|
4136
4209
|
const definition = await deparsePostgresCheckDefinition({ ...params, constraint: "vibeorm_check_probe" });
|
|
@@ -4144,7 +4217,7 @@ async function deparsePostgresCheckDefinition(params) {
|
|
|
4144
4217
|
await params.executor({ text: query.text });
|
|
4145
4218
|
const result = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: { kind: "constraint", constraintType: "check", table: params.table, name } });
|
|
4146
4219
|
if (result.state !== "present" || typeof result.definition.facts.definition !== "string")
|
|
4147
|
-
throw new
|
|
4220
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
4148
4221
|
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 } };
|
|
4149
4222
|
} finally {
|
|
4150
4223
|
await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_check_probe" });
|
|
@@ -4162,7 +4235,7 @@ async function deparseMysqlCheck(params) {
|
|
|
4162
4235
|
const source = await params.executor({ text: read.text, values: [] });
|
|
4163
4236
|
const definition = source[0]?.["Create Table"];
|
|
4164
4237
|
if (typeof definition !== "string")
|
|
4165
|
-
throw new
|
|
4238
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
|
|
4166
4239
|
const drop = renderMysqlCheckProbe({ dialect, probe, operation: "drop" });
|
|
4167
4240
|
await params.executor({ text: drop.text });
|
|
4168
4241
|
created = false;
|
|
@@ -4173,7 +4246,7 @@ async function deparseMysqlCheck(params) {
|
|
|
4173
4246
|
const normalizedDefinition = rows[0]?.["Create Table"];
|
|
4174
4247
|
const check = typeof normalizedDefinition === "string" ? parseSqliteNamedChecks({ tableSql: normalizedDefinition }).find((entry) => entry.name === probe) : undefined;
|
|
4175
4248
|
if (check === undefined)
|
|
4176
|
-
throw new
|
|
4249
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
4177
4250
|
return check.expression;
|
|
4178
4251
|
} finally {
|
|
4179
4252
|
if (created) {
|
|
@@ -4200,7 +4273,7 @@ async function prepareCheckDiff(params) {
|
|
|
4200
4273
|
const desired = await deparsePostgresCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "rollback" });
|
|
4201
4274
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
4202
4275
|
if (live.state !== "present")
|
|
4203
|
-
throw new
|
|
4276
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
4204
4277
|
if (live.definition.facts.definition === desired) {
|
|
4205
4278
|
checks.push(next);
|
|
4206
4279
|
continue;
|
|
@@ -4212,7 +4285,7 @@ async function prepareCheckDiff(params) {
|
|
|
4212
4285
|
const desired = await deparseMysqlCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "temporary" });
|
|
4213
4286
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
4214
4287
|
if (live.state !== "present")
|
|
4215
|
-
throw new
|
|
4288
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
4216
4289
|
checks.push(desired === live.definition.facts.definition ? next : { name: check.name, expression: check.expression });
|
|
4217
4290
|
continue;
|
|
4218
4291
|
}
|
|
@@ -4225,7 +4298,7 @@ async function prepareCheckDiff(params) {
|
|
|
4225
4298
|
checks.push({ name: check.name, expression: check.expression });
|
|
4226
4299
|
continue;
|
|
4227
4300
|
}
|
|
4228
|
-
throw new
|
|
4301
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: `Cannot establish CHECK equivalence for ${table}.${check.name}; request a rollback probe or an explicit replacement`, meta: { reason: "explicit-replacement-required", identity } });
|
|
4229
4302
|
}
|
|
4230
4303
|
models.push({ ...model, checks });
|
|
4231
4304
|
}
|
|
@@ -4252,7 +4325,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4252
4325
|
for (const row of rows) {
|
|
4253
4326
|
const name = row.name;
|
|
4254
4327
|
if (typeof name !== "string" || typeof row.kind !== "string")
|
|
4255
|
-
throw new
|
|
4328
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
|
|
4256
4329
|
const qualified = { namespace: params.namespace, name };
|
|
4257
4330
|
const table = { namespace: params.namespace, name: String(row.parent ?? "") };
|
|
4258
4331
|
let identity;
|
|
@@ -4278,7 +4351,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4278
4351
|
case "constraint": {
|
|
4279
4352
|
const constraintType = kinds[String(row.subtype)];
|
|
4280
4353
|
if (constraintType === undefined)
|
|
4281
|
-
throw new
|
|
4354
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
|
|
4282
4355
|
identity = { kind: "constraint", table, name, constraintType };
|
|
4283
4356
|
break;
|
|
4284
4357
|
}
|
|
@@ -4288,17 +4361,17 @@ async function inspectMigrationInventory(params) {
|
|
|
4288
4361
|
const query = renderMysqlObjectMembers2({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
|
|
4289
4362
|
const members = await params.executor({ text: query.text, values: [...query.values] });
|
|
4290
4363
|
if (members.length === 0)
|
|
4291
|
-
throw new
|
|
4364
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
|
|
4292
4365
|
argumentTypes = members.filter((member) => member.mode !== null).map((member) => ({ namespace: "mysql", name: String(member.type) }));
|
|
4293
4366
|
}
|
|
4294
4367
|
const candidate = { kind: "function", name: qualified, argumentTypes };
|
|
4295
4368
|
if (!isSqlObjectIdentity2(candidate))
|
|
4296
|
-
throw new
|
|
4369
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
|
|
4297
4370
|
identity = candidate;
|
|
4298
4371
|
break;
|
|
4299
4372
|
}
|
|
4300
4373
|
default:
|
|
4301
|
-
throw new
|
|
4374
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
|
|
4302
4375
|
}
|
|
4303
4376
|
objects.push({ identity, ...typeof row.backing === "string" ? { backingConstraint: row.backing } : {} });
|
|
4304
4377
|
if (params.dialect === "sqlite" && row.kind === "table" && typeof row.definition === "string" && !/^\s*CREATE\s+VIRTUAL\s+TABLE\b/i.test(row.definition)) {
|
|
@@ -4343,7 +4416,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4343
4416
|
// packages/migrate/src/external-objects.ts
|
|
4344
4417
|
var partitionPatterns = new WeakMap;
|
|
4345
4418
|
function invalid2(params) {
|
|
4346
|
-
return new
|
|
4419
|
+
return new VibeError18({ code: "VIBE_CONFIG", message: `Invalid external ownership declaration: ${params.detail}`, meta: { reason: "external-declaration-invalid", ...params.owner === undefined ? {} : { owner: params.owner } }, ...params.cause === undefined ? {} : { cause: params.cause } });
|
|
4347
4420
|
}
|
|
4348
4421
|
function record2(value) {
|
|
4349
4422
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -4431,7 +4504,7 @@ async function parseExternalObjects(params) {
|
|
|
4431
4504
|
}
|
|
4432
4505
|
return Object.freeze(result);
|
|
4433
4506
|
}).catch((cause) => {
|
|
4434
|
-
if (cause instanceof
|
|
4507
|
+
if (cause instanceof VibeError18)
|
|
4435
4508
|
throw cause;
|
|
4436
4509
|
throw invalid2({ detail: "regular-expression pattern cannot be compiled", cause });
|
|
4437
4510
|
});
|
|
@@ -4455,7 +4528,7 @@ function declaredTableOwner(params) {
|
|
|
4455
4528
|
return patterns?.some(({ pattern }) => pattern.exec(params.name.name)?.[0] === params.name.name) === true;
|
|
4456
4529
|
});
|
|
4457
4530
|
if (matches.length > 1)
|
|
4458
|
-
throw new
|
|
4531
|
+
throw new VibeError18({ code: "VIBE_CONFIG", message: "A table is claimed by more than one external owner", meta: { reason: "external-owner-collision", identity: { kind: "table", name: params.name }, owners: matches.map((entry) => entry.owner) } });
|
|
4459
4532
|
return matches[0]?.owner;
|
|
4460
4533
|
}
|
|
4461
4534
|
function externalObjectOwner(params) {
|
|
@@ -4477,13 +4550,13 @@ function resolveExternalObjectOwnership(params) {
|
|
|
4477
4550
|
if (params.external.length > 0 && params.dialect !== "postgres")
|
|
4478
4551
|
throw unsupportedCapability7({ dialect: params.dialect, feature: "externalObjects" });
|
|
4479
4552
|
if (params.catalog.inventory.some((object) => object.inspectionUnavailable !== undefined))
|
|
4480
|
-
throw new
|
|
4553
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "External ownership requires complete catalog visibility", meta: { reason: "external-inventory-unavailable", objects: params.catalog.inventory.filter((object) => object.inspectionUnavailable !== undefined) } });
|
|
4481
4554
|
const tables = new Map;
|
|
4482
4555
|
const assign = (entry) => {
|
|
4483
4556
|
const key = nameKey(entry.name);
|
|
4484
4557
|
const prior = tables.get(key)?.owner ?? declaredTableOwner({ external: params.external, name: entry.name });
|
|
4485
4558
|
if (prior !== undefined && prior !== entry.owner)
|
|
4486
|
-
throw new
|
|
4559
|
+
throw new VibeError18({ code: "VIBE_CONFIG", message: "An inherited table has conflicting external owners", meta: { reason: "external-owner-collision", identity: { kind: "table", name: entry.name }, owners: [prior, entry.owner] } });
|
|
4487
4560
|
if (tables.has(key))
|
|
4488
4561
|
return false;
|
|
4489
4562
|
tables.set(key, entry);
|
|
@@ -4514,7 +4587,7 @@ function resolveExternalObjectOwnership(params) {
|
|
|
4514
4587
|
return [];
|
|
4515
4588
|
const whole = schemaOwner({ external: params.external, namespace: edge.sequence.namespace });
|
|
4516
4589
|
if (whole !== undefined && whole !== owner)
|
|
4517
|
-
throw new
|
|
4590
|
+
throw new VibeError18({ code: "VIBE_CONFIG", message: "An owned sequence has conflicting external owners", meta: { reason: "external-owner-collision", identity: { kind: "sequence", name: edge.sequence }, owners: [whole, owner] } });
|
|
4518
4591
|
return [{ owner, name: edge.sequence }];
|
|
4519
4592
|
});
|
|
4520
4593
|
const closure = { declarations: params.external, catalog: params.catalog, inventory: params.catalog.inventory, tables: [...tables.values()], sequences, owners: [], identities: [] };
|
|
@@ -4522,7 +4595,7 @@ function resolveExternalObjectOwnership(params) {
|
|
|
4522
4595
|
const owner = externalObjectOwner({ external: closure, identity: { kind: "table", name: edge.table } });
|
|
4523
4596
|
const targetOwner = externalObjectOwner({ external: closure, identity: { kind: "table", name: edge.target } });
|
|
4524
4597
|
if (owner !== targetOwner && (owner !== undefined || targetOwner !== undefined))
|
|
4525
|
-
throw new
|
|
4598
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "Foreign keys cannot cross external ownership boundaries", meta: { reason: "external-cross-owner-foreign-key", name: edge.name, table: edge.table, target: edge.target, owner: owner ?? "application", targetOwner: targetOwner ?? "application" } });
|
|
4526
4599
|
}
|
|
4527
4600
|
const observed = new Map;
|
|
4528
4601
|
for (const namespace of params.catalog.namespaces) {
|
|
@@ -4550,7 +4623,7 @@ function catalogName(params) {
|
|
|
4550
4623
|
const namespace = params.row[params.namespace];
|
|
4551
4624
|
const name = params.row[params.name];
|
|
4552
4625
|
if (!identifier(namespace) || !identifier(name))
|
|
4553
|
-
throw new
|
|
4626
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "Malformed external ownership catalog endpoint", meta: { reason: "external-inventory-unavailable" } });
|
|
4554
4627
|
return { namespace, name };
|
|
4555
4628
|
}
|
|
4556
4629
|
async function inspectExternalObjects(params) {
|
|
@@ -4563,7 +4636,7 @@ async function inspectExternalObjects(params) {
|
|
|
4563
4636
|
};
|
|
4564
4637
|
const namespaceRows = await read("namespaces");
|
|
4565
4638
|
if (namespaceRows.some((row) => !identifier(row.namespace) || row.visible !== true))
|
|
4566
|
-
throw new
|
|
4639
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "External ownership cannot prove visibility of every inspected namespace", meta: { reason: "external-inventory-unavailable" } });
|
|
4567
4640
|
const namespaces = namespaceRows.map((row) => String(row.namespace));
|
|
4568
4641
|
const [inherited, owned, foreignKeys] = await Promise.all([read("inheritance"), read("ownedSequences"), read("foreignKeys")]);
|
|
4569
4642
|
const inventory = params.inventory ?? (await Promise.all(namespaces.map((namespace) => inspectMigrationInventory({ executor: params.executor, dialect: params.dialect, namespace })))).flat();
|
|
@@ -4584,7 +4657,7 @@ function assertExternalOwnershipClaims(params) {
|
|
|
4584
4657
|
for (const identity of params.identities) {
|
|
4585
4658
|
const owner = externalObjectOwner({ identity, external: params.external }) ?? (identity.kind === "namespace" ? [...params.external.tables, ...params.external.sequences].find((entry) => entry.name.namespace === identity.name)?.owner : undefined);
|
|
4586
4659
|
if (owner !== undefined)
|
|
4587
|
-
throw new
|
|
4660
|
+
throw new VibeError18({ code: "VIBE_CONFIG", message: "Application or module declarations collide with external ownership", meta: { reason: "external-owned-object-collision", owner, identity } });
|
|
4588
4661
|
}
|
|
4589
4662
|
}
|
|
4590
4663
|
function projectExternalSchema(params) {
|
|
@@ -4594,7 +4667,7 @@ function projectExternalSchema(params) {
|
|
|
4594
4667
|
for (const model of models)
|
|
4595
4668
|
for (const relation of model.relations)
|
|
4596
4669
|
if (removed.has(relation.targetModel))
|
|
4597
|
-
throw new
|
|
4670
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "Application relations cannot depend on externally owned tables", meta: { reason: "external-cross-owner-foreign-key", model: model.name, relation: relation.name } });
|
|
4598
4671
|
return {
|
|
4599
4672
|
...schema,
|
|
4600
4673
|
models,
|
|
@@ -4627,7 +4700,7 @@ function migrationCoverageGaps(params) {
|
|
|
4627
4700
|
return [...new Map(kept.map((gap) => [JSON.stringify(gap), gap])).values()];
|
|
4628
4701
|
}
|
|
4629
4702
|
// packages/migrate/src/module-adoption.ts
|
|
4630
|
-
import { VibeError as
|
|
4703
|
+
import { VibeError as VibeError22 } from "@vibeorm/schema";
|
|
4631
4704
|
|
|
4632
4705
|
// packages/migrate/src/mysql-catalog-visibility.ts
|
|
4633
4706
|
function hasMysqlGlobalCatalogVisibility(params) {
|
|
@@ -4645,10 +4718,10 @@ function hasMysqlGlobalCatalogVisibility(params) {
|
|
|
4645
4718
|
}
|
|
4646
4719
|
|
|
4647
4720
|
// packages/migrate/src/module-ownership.ts
|
|
4648
|
-
import { VibeError as
|
|
4721
|
+
import { VibeError as VibeError19, sqlObjectKey as sqlObjectKey7, sqlObjectNamespace as sqlObjectNamespace3 } from "@vibeorm/schema";
|
|
4649
4722
|
import { getSqlDialect as getSqlDialect4, renderModuleDependencyQuery } from "@vibeorm/sql";
|
|
4650
4723
|
function refusal(params) {
|
|
4651
|
-
return new
|
|
4724
|
+
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 });
|
|
4652
4725
|
}
|
|
4653
4726
|
function identities(params) {
|
|
4654
4727
|
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)];
|
|
@@ -4709,7 +4782,7 @@ function assertModuleOwnership(params) {
|
|
|
4709
4782
|
throw refusal({ reason: "module-mutates-application-state", moduleId: origin.moduleId });
|
|
4710
4783
|
}
|
|
4711
4784
|
function dependencyUnavailable(params) {
|
|
4712
|
-
return new
|
|
4785
|
+
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 } });
|
|
4713
4786
|
}
|
|
4714
4787
|
async function assertCrossNamespaceModuleDependencies(params) {
|
|
4715
4788
|
const { executor, origin } = params;
|
|
@@ -4771,12 +4844,12 @@ async function assertApplicationModuleState(params) {
|
|
|
4771
4844
|
const options = { executor: params.executor, dialect: origin.dialect, from: state.from, to: state.to, removals: state.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } };
|
|
4772
4845
|
const report = params.sqliteRestorePending === true ? await verifySqliteTransitionBeforeRestore(options) : await verifyMigrationTransition(options);
|
|
4773
4846
|
if (!report.inSync)
|
|
4774
|
-
throw new
|
|
4847
|
+
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 } });
|
|
4775
4848
|
}
|
|
4776
4849
|
}
|
|
4777
4850
|
|
|
4778
4851
|
// packages/migrate/src/sql-statements.ts
|
|
4779
|
-
import { VibeError as
|
|
4852
|
+
import { VibeError as VibeError20 } from "@vibeorm/schema";
|
|
4780
4853
|
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;
|
|
4781
4854
|
var BLOCK_OPENERS = new Set(["BEGIN", "CASE", "IF", "LOOP", "WHILE", "REPEAT"]);
|
|
4782
4855
|
function splitSqlStatements(params) {
|
|
@@ -4852,7 +4925,7 @@ function splitSqlStatements(params) {
|
|
|
4852
4925
|
continue;
|
|
4853
4926
|
}
|
|
4854
4927
|
if (dialect === "mysql" || dialect === undefined) {
|
|
4855
|
-
throw new
|
|
4928
|
+
throw new VibeError20({ code: "VIBE_MIGRATION", message: "SQL string backslash escaping depends on the session SQL mode; use mode-independent quoting and pass the migration dialect", meta: { reason: "sql-mode-ambiguous", dialect } });
|
|
4856
4929
|
}
|
|
4857
4930
|
}
|
|
4858
4931
|
if (value === endQuote) {
|
|
@@ -4926,10 +4999,10 @@ function joinSqlStatements(params) {
|
|
|
4926
4999
|
|
|
4927
5000
|
// packages/migrate/src/module-origin.ts
|
|
4928
5001
|
import { createHash } from "crypto";
|
|
4929
|
-
import { VibeError as
|
|
5002
|
+
import { VibeError as VibeError21, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
|
|
4930
5003
|
var MODULE_ORIGIN_METADATA = Object.freeze({ filename: "module-origin.json", version: 1 });
|
|
4931
5004
|
function invalid3() {
|
|
4932
|
-
return new
|
|
5005
|
+
return new VibeError21({ code: "VIBE_MIGRATION", message: "Module migration origin is malformed or differs from its immutable package artifact; restore the reviewed artifact", meta: { reason: "module-origin-invalid" } });
|
|
4933
5006
|
}
|
|
4934
5007
|
function record3(value) {
|
|
4935
5008
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -4993,7 +5066,7 @@ function assertMaterializedModuleMigration(params) {
|
|
|
4993
5066
|
return;
|
|
4994
5067
|
const restored = restoreModuleMigration({ migration, origin: migration.moduleOrigin });
|
|
4995
5068
|
if (migration.desiredState === undefined || canonicalMigrationJson({ value: restored }) !== canonicalMigrationJson({ value: migration }))
|
|
4996
|
-
throw new
|
|
5069
|
+
throw new VibeError21({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
|
|
4997
5070
|
}
|
|
4998
5071
|
function restoreModuleMigration(params) {
|
|
4999
5072
|
const { migration, origin } = params;
|
|
@@ -5018,7 +5091,7 @@ function restoreModuleMigration(params) {
|
|
|
5018
5091
|
// packages/migrate/src/module-adoption.ts
|
|
5019
5092
|
var MODULE_ADOPTION_METADATA = Object.freeze({ filename: "module-adoption.json", version: 1 });
|
|
5020
5093
|
function invalid4() {
|
|
5021
|
-
return new
|
|
5094
|
+
return new VibeError22({ 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" } });
|
|
5022
5095
|
}
|
|
5023
5096
|
function assertMarker(params) {
|
|
5024
5097
|
const value = params.value;
|
|
@@ -5053,22 +5126,22 @@ function serializeModuleAdoption(params) {
|
|
|
5053
5126
|
`;
|
|
5054
5127
|
}
|
|
5055
5128
|
// packages/migrate/src/module-adoption-record.ts
|
|
5056
|
-
import { VibeError as
|
|
5129
|
+
import { VibeError as VibeError43 } from "@vibeorm/schema";
|
|
5057
5130
|
|
|
5058
5131
|
// packages/migrate/src/module-adoption-reference.ts
|
|
5059
|
-
import { VibeError as
|
|
5132
|
+
import { VibeError as VibeError42, isEnumType as isEnumType9, sqlObjectKey as sqlObjectKey14 } from "@vibeorm/schema";
|
|
5060
5133
|
|
|
5061
5134
|
// packages/migrate/src/module-pending.ts
|
|
5062
|
-
import { VibeError as
|
|
5135
|
+
import { VibeError as VibeError38, sqlObjectKey as sqlObjectKey11, qualifiedSqlName as qualifiedSqlName11 } from "@vibeorm/schema";
|
|
5063
5136
|
|
|
5064
5137
|
// packages/migrate/src/module-bundle.ts
|
|
5065
|
-
import { VibeError as
|
|
5138
|
+
import { VibeError as VibeError37 } from "@vibeorm/schema";
|
|
5066
5139
|
|
|
5067
5140
|
// packages/migrate/src/migration-checksum.ts
|
|
5068
5141
|
import { createHash as createHash2 } from "crypto";
|
|
5069
5142
|
|
|
5070
5143
|
// packages/migrate/src/execution.ts
|
|
5071
|
-
import { VibeError as
|
|
5144
|
+
import { VibeError as VibeError23 } from "@vibeorm/schema";
|
|
5072
5145
|
var EXECUTION_SIDECAR_FILENAME = "execution.json";
|
|
5073
5146
|
var EXECUTION_SIDECAR_VERSION = 1;
|
|
5074
5147
|
function isDefaultExecution(params) {
|
|
@@ -5107,7 +5180,7 @@ ${lines.join(`
|
|
|
5107
5180
|
`)}`;
|
|
5108
5181
|
}
|
|
5109
5182
|
function invalid5(params) {
|
|
5110
|
-
return new
|
|
5183
|
+
return new VibeError23({
|
|
5111
5184
|
code: "VIBE_MIGRATION",
|
|
5112
5185
|
message: `invalid execution metadata for migration "${params.migration}": ${params.detail}`,
|
|
5113
5186
|
meta: { reason: "execution-metadata-invalid", migration: params.migration }
|
|
@@ -5158,7 +5231,7 @@ function parseExecutionSidecar(params) {
|
|
|
5158
5231
|
try {
|
|
5159
5232
|
parsed = JSON.parse(params.text);
|
|
5160
5233
|
} catch (error) {
|
|
5161
|
-
throw new
|
|
5234
|
+
throw new VibeError23({
|
|
5162
5235
|
code: "VIBE_MIGRATION",
|
|
5163
5236
|
message: `invalid execution metadata for migration "${migration}": not valid JSON`,
|
|
5164
5237
|
meta: { reason: "execution-metadata-invalid", migration },
|
|
@@ -5257,7 +5330,7 @@ function migrationChecksum(params) {
|
|
|
5257
5330
|
}
|
|
5258
5331
|
|
|
5259
5332
|
// packages/migrate/src/shadow-guard.ts
|
|
5260
|
-
import { VibeError as
|
|
5333
|
+
import { VibeError as VibeError24 } from "@vibeorm/schema";
|
|
5261
5334
|
import { getSqlDialect as getSqlDialect5, renderShadowOperation } from "@vibeorm/sql";
|
|
5262
5335
|
var replayExecutors = new WeakSet;
|
|
5263
5336
|
function isShadowReplayExecutor(params) {
|
|
@@ -5269,7 +5342,7 @@ async function assertDatabaseNotShadow(params) {
|
|
|
5269
5342
|
const [query] = renderShadowOperation({ dialect: getSqlDialect5({ dialect: params.dialect }), operation: { kind: "markerExists" } });
|
|
5270
5343
|
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
5271
5344
|
if (rows.length !== 0)
|
|
5272
|
-
throw new
|
|
5345
|
+
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" } });
|
|
5273
5346
|
}
|
|
5274
5347
|
function runShadowReplay(params) {
|
|
5275
5348
|
replayExecutors.add(params.executor);
|
|
@@ -5279,7 +5352,7 @@ function runShadowReplay(params) {
|
|
|
5279
5352
|
}
|
|
5280
5353
|
|
|
5281
5354
|
// packages/migrate/src/external-dependencies.ts
|
|
5282
|
-
import { VibeError as
|
|
5355
|
+
import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
5283
5356
|
import { getSqlDialect as getSqlDialect6, renderExternalDependents } from "@vibeorm/sql";
|
|
5284
5357
|
function externalDependencyTargets(params) {
|
|
5285
5358
|
return params.steps.flatMap((step) => {
|
|
@@ -5302,7 +5375,7 @@ function externalDependencyTargets(params) {
|
|
|
5302
5375
|
});
|
|
5303
5376
|
}
|
|
5304
5377
|
function unavailable() {
|
|
5305
|
-
return new
|
|
5378
|
+
return new VibeError25({ code: "VIBE_MIGRATION", message: "Malformed catalog dependency row", meta: { reason: "external-inventory-unavailable" } });
|
|
5306
5379
|
}
|
|
5307
5380
|
function text(params) {
|
|
5308
5381
|
const value = params.row[params.key];
|
|
@@ -5343,7 +5416,7 @@ function assertNoExternalDependents(params) {
|
|
|
5343
5416
|
const dependent = dependentOwner({ row, external: params.external });
|
|
5344
5417
|
if (dependent.owner === undefined)
|
|
5345
5418
|
continue;
|
|
5346
|
-
throw new
|
|
5419
|
+
throw new VibeError25({
|
|
5347
5420
|
code: "VIBE_MIGRATION",
|
|
5348
5421
|
message: "An externally owned object depends on an application object this change drops or retypes; automatic DDL is refused",
|
|
5349
5422
|
meta: { reason: "external-owned-dependency", owner: dependent.owner, ...dependent.identity === undefined ? {} : { identity: dependent.identity }, dependent: { type, identity }, dependency: target.dependency, referenced }
|
|
@@ -5364,9 +5437,9 @@ async function refuseExternalDependents(params) {
|
|
|
5364
5437
|
}
|
|
5365
5438
|
|
|
5366
5439
|
// packages/migrate/src/module-history.ts
|
|
5367
|
-
import { VibeError as
|
|
5440
|
+
import { VibeError as VibeError26, sqlObjectKey as sqlObjectKey9, providerToDialect as providerToDialect3 } from "@vibeorm/schema";
|
|
5368
5441
|
function refusal2(params) {
|
|
5369
|
-
return new
|
|
5442
|
+
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 });
|
|
5370
5443
|
}
|
|
5371
5444
|
function moduleHistoryHeads(params) {
|
|
5372
5445
|
const heads = new Map;
|
|
@@ -5473,11 +5546,11 @@ function assertModuleUpgradeHistory(params) {
|
|
|
5473
5546
|
}
|
|
5474
5547
|
|
|
5475
5548
|
// packages/migrate/src/module-journal.ts
|
|
5476
|
-
import { VibeError as
|
|
5549
|
+
import { VibeError as VibeError28 } from "@vibeorm/schema";
|
|
5477
5550
|
import { getSqlDialect as getSqlDialect7, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
|
|
5478
5551
|
|
|
5479
5552
|
// packages/migrate/src/journal-read.ts
|
|
5480
|
-
import { VibeError as
|
|
5553
|
+
import { VibeError as VibeError27 } from "@vibeorm/schema";
|
|
5481
5554
|
var MISSING_TABLE_IDENTIFIERS = new Set(["42P01", "42S02", "ER_NO_SUCH_TABLE"]);
|
|
5482
5555
|
function isMissingTable(params) {
|
|
5483
5556
|
let current = params.cause;
|
|
@@ -5501,7 +5574,7 @@ async function readJournalRows(params) {
|
|
|
5501
5574
|
if (presence.state === "missing")
|
|
5502
5575
|
return;
|
|
5503
5576
|
}
|
|
5504
|
-
throw new
|
|
5577
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
|
|
5505
5578
|
});
|
|
5506
5579
|
if (rows === undefined)
|
|
5507
5580
|
return;
|
|
@@ -5509,7 +5582,7 @@ async function readJournalRows(params) {
|
|
|
5509
5582
|
for (const row of rows) {
|
|
5510
5583
|
const index = row.statement_index;
|
|
5511
5584
|
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)) {
|
|
5512
|
-
throw new
|
|
5585
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
|
|
5513
5586
|
}
|
|
5514
5587
|
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 } : {} });
|
|
5515
5588
|
}
|
|
@@ -5518,7 +5591,7 @@ async function readJournalRows(params) {
|
|
|
5518
5591
|
|
|
5519
5592
|
// packages/migrate/src/module-journal.ts
|
|
5520
5593
|
function invalid6() {
|
|
5521
|
-
return new
|
|
5594
|
+
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" } });
|
|
5522
5595
|
}
|
|
5523
5596
|
function parseModuleJournalEntry(params) {
|
|
5524
5597
|
return parseMigrationMetadata({ text: params.text, failure: invalid6, parse: (value) => {
|
|
@@ -5577,7 +5650,7 @@ function moduleRecordComplete(params) {
|
|
|
5577
5650
|
}
|
|
5578
5651
|
|
|
5579
5652
|
// packages/migrate/src/runner.ts
|
|
5580
|
-
import { VibeError as
|
|
5653
|
+
import { VibeError as VibeError36 } from "@vibeorm/schema";
|
|
5581
5654
|
import {
|
|
5582
5655
|
DIALECT_CAPABILITIES as DIALECT_CAPABILITIES3,
|
|
5583
5656
|
getSqlDialect as getSqlDialect9,
|
|
@@ -5587,7 +5660,7 @@ import {
|
|
|
5587
5660
|
} from "@vibeorm/sql";
|
|
5588
5661
|
|
|
5589
5662
|
// packages/migrate/src/recovery.ts
|
|
5590
|
-
import { VibeError as
|
|
5663
|
+
import { VibeError as VibeError29 } from "@vibeorm/schema";
|
|
5591
5664
|
import {
|
|
5592
5665
|
getSqlDialect as getSqlDialect8,
|
|
5593
5666
|
renderColumnStateQuery,
|
|
@@ -5603,7 +5676,7 @@ function constraintUnverifiable(params) {
|
|
|
5603
5676
|
});
|
|
5604
5677
|
}
|
|
5605
5678
|
function conflict2(params) {
|
|
5606
|
-
return new
|
|
5679
|
+
return new VibeError29({
|
|
5607
5680
|
code: "VIBE_MIGRATION",
|
|
5608
5681
|
message: params.message,
|
|
5609
5682
|
meta: { reason: params.reason, ...params.meta }
|
|
@@ -5708,10 +5781,10 @@ async function establishOperationState(params) {
|
|
|
5708
5781
|
|
|
5709
5782
|
// packages/migrate/src/differ.ts
|
|
5710
5783
|
import { assertIndexOperatorClasses as assertIndexOperatorClasses2, isEnumType as isEnumType6, qualifiedSqlName as qualifiedSqlName9, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
5711
|
-
import { VibeError as
|
|
5784
|
+
import { VibeError as VibeError31 } from "@vibeorm/schema";
|
|
5712
5785
|
|
|
5713
5786
|
// packages/migrate/src/renames.ts
|
|
5714
|
-
import { VibeError as
|
|
5787
|
+
import { VibeError as VibeError30, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
5715
5788
|
function tableOf(model) {
|
|
5716
5789
|
return model.dbName ?? model.name;
|
|
5717
5790
|
}
|
|
@@ -5722,7 +5795,7 @@ function hasColumn(params) {
|
|
|
5722
5795
|
return params.model.fields.some((field) => (field.dbName ?? field.name) === params.column);
|
|
5723
5796
|
}
|
|
5724
5797
|
function ambiguous(params) {
|
|
5725
|
-
return new
|
|
5798
|
+
return new VibeError30({
|
|
5726
5799
|
code: "VIBE_MIGRATION",
|
|
5727
5800
|
message: `rename refused: ${params.detail}`,
|
|
5728
5801
|
meta: { reason: "rename-ambiguous", ...params.meta }
|
|
@@ -5824,17 +5897,34 @@ function planRenames(params) {
|
|
|
5824
5897
|
|
|
5825
5898
|
// packages/migrate/src/differ.ts
|
|
5826
5899
|
function diffSchemas(params) {
|
|
5900
|
+
return diffSchemasWithNormalization(params).steps;
|
|
5901
|
+
}
|
|
5902
|
+
function diffSchemasWithNormalization(params) {
|
|
5827
5903
|
const dialect = params.dialect ?? "postgres";
|
|
5828
5904
|
assertIndexOperatorClasses2({ schema: params.to, dialect });
|
|
5829
|
-
for (const kind of ["models", "enums"])
|
|
5905
|
+
for (const kind of ["models", "enums"]) {
|
|
5906
|
+
const sourceByName = new Map;
|
|
5907
|
+
const sourcePhysical = new Set;
|
|
5908
|
+
const targetPhysical = new Set(params.to[kind].map((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect })));
|
|
5909
|
+
const removedByBasename = new Map;
|
|
5910
|
+
for (const entry of params.from[kind]) {
|
|
5911
|
+
if (!sourceByName.has(entry.name))
|
|
5912
|
+
sourceByName.set(entry.name, entry);
|
|
5913
|
+
const basename = entry.dbName ?? entry.name;
|
|
5914
|
+
const key = physicalNameKey({ name: basename, namespace: entry.namespace, dialect });
|
|
5915
|
+
sourcePhysical.add(key);
|
|
5916
|
+
if (!targetPhysical.has(key) && !removedByBasename.has(basename))
|
|
5917
|
+
removedByBasename.set(basename, entry);
|
|
5918
|
+
}
|
|
5830
5919
|
for (const entry of params.to[kind]) {
|
|
5831
5920
|
const key = physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect });
|
|
5832
|
-
const before =
|
|
5833
|
-
if (before !== undefined && qualifiedSqlName9({ name: entry.name, namespace: before.namespace }).namespace !== qualifiedSqlName9({ name: entry.name, namespace: entry.namespace }).namespace && !
|
|
5921
|
+
const before = sourceByName.get(entry.name) ?? removedByBasename.get(entry.dbName ?? entry.name);
|
|
5922
|
+
if (before !== undefined && qualifiedSqlName9({ name: entry.name, namespace: before.namespace }).namespace !== qualifiedSqlName9({ name: entry.name, namespace: entry.namespace }).namespace && !sourcePhysical.has(key))
|
|
5834
5923
|
throw unsupportedCapability10({ dialect, feature: "automaticNamespaceMove", hint: "Moving populated objects to a fixed namespace requires an explicit reviewed migration" });
|
|
5835
5924
|
}
|
|
5836
|
-
|
|
5837
|
-
|
|
5925
|
+
}
|
|
5926
|
+
let from = normalizeSchema({ schema: params.from, dialect });
|
|
5927
|
+
const to = normalizeSchema({ schema: params.to, dialect });
|
|
5838
5928
|
for (const schema of [params.from, params.to])
|
|
5839
5929
|
if (dialect !== "postgres" && schema.models.some((model) => (model.exclusions?.length ?? 0) > 0))
|
|
5840
5930
|
throw unsupportedCapability10({ dialect, feature: "exclusionConstraints" });
|
|
@@ -5843,11 +5933,12 @@ function diffSchemas(params) {
|
|
|
5843
5933
|
if (dialect !== "postgres")
|
|
5844
5934
|
refuseScalarListColumns({ schema: params.to, dialect });
|
|
5845
5935
|
const renamed = planRenames({ from: params.from, to: params.to, renames: params.renames ?? [], dialect });
|
|
5936
|
+
if (renamed.from !== params.from)
|
|
5937
|
+
from = normalizeSchema({ schema: renamed.from, dialect });
|
|
5846
5938
|
params = { ...params, from: renamed.from };
|
|
5847
|
-
const
|
|
5848
|
-
const to = normalizeSchema({ schema: params.to, dialect });
|
|
5939
|
+
const sourceNamespaces = new Set(dialect === "postgres" ? schemaNamespaces({ schema: params.from }) : []);
|
|
5849
5940
|
const buckets = {
|
|
5850
|
-
createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !
|
|
5941
|
+
createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !sourceNamespaces.has(namespace)).map((namespace) => ({ kind: "createNamespace", namespace, destructive: false })) : [],
|
|
5851
5942
|
dropExclusion: [],
|
|
5852
5943
|
addExclusion: [],
|
|
5853
5944
|
dropView: [],
|
|
@@ -5875,16 +5966,22 @@ function diffSchemas(params) {
|
|
|
5875
5966
|
diffViews({ from, to, buckets });
|
|
5876
5967
|
diffJoinTables({ from, to, buckets });
|
|
5877
5968
|
diffTables({ from, to, buckets, dialect });
|
|
5969
|
+
const sourceModels = new Map;
|
|
5970
|
+
for (const model of params.from.models) {
|
|
5971
|
+
const key = physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect });
|
|
5972
|
+
if (!sourceModels.has(key))
|
|
5973
|
+
sourceModels.set(key, model);
|
|
5974
|
+
}
|
|
5878
5975
|
for (const model of params.to.models) {
|
|
5879
5976
|
const table = model.dbName ?? model.name;
|
|
5880
5977
|
const placement = model.namespace === undefined ? {} : { namespace: model.namespace };
|
|
5881
|
-
const previous =
|
|
5978
|
+
const previous = sourceModels.get(physicalNameKey({ name: table, namespace: model.namespace, dialect }));
|
|
5882
5979
|
for (const exclusion of model.exclusions ?? []) {
|
|
5883
5980
|
const before = previous?.exclusions?.find((entry) => entry.name === exclusion.name);
|
|
5884
5981
|
if (before?.definition === exclusion.definition)
|
|
5885
5982
|
continue;
|
|
5886
5983
|
if (before?.catalogDefinition)
|
|
5887
|
-
throw new
|
|
5984
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
|
|
5888
5985
|
if (before !== undefined)
|
|
5889
5986
|
buckets.dropExclusion.push({ kind: "dropExclusion", table, ...placement, exclusion: before, destructive: false });
|
|
5890
5987
|
buckets.addExclusion.push({ kind: "addExclusion", table, ...placement, exclusion, destructive: false });
|
|
@@ -5894,7 +5991,8 @@ function diffSchemas(params) {
|
|
|
5894
5991
|
buckets.dropExclusion.push({ kind: "dropExclusion", table, exclusion, destructive: false });
|
|
5895
5992
|
}
|
|
5896
5993
|
refuseDependentEnumStorageChanges({ from, to, buckets, dialect });
|
|
5897
|
-
|
|
5994
|
+
refuseMysqlForeignKeyColumnRewrites({ from, to, buckets, dialect });
|
|
5995
|
+
return { from, to, steps: withRlsSteps({ from: params.from, to: params.to, steps: [
|
|
5898
5996
|
...buckets.createNamespace,
|
|
5899
5997
|
...renamed.steps,
|
|
5900
5998
|
...buckets.dropView,
|
|
@@ -5918,7 +6016,7 @@ function diffSchemas(params) {
|
|
|
5918
6016
|
...buckets.addForeignKey,
|
|
5919
6017
|
...buckets.createJoinTable,
|
|
5920
6018
|
...buckets.dropEnum
|
|
5921
|
-
] });
|
|
6019
|
+
] }) };
|
|
5922
6020
|
}
|
|
5923
6021
|
function refuseScalarListColumns(params) {
|
|
5924
6022
|
const { schema, dialect } = params;
|
|
@@ -5985,6 +6083,40 @@ function refuseDependentEnumStorageChanges(params) {
|
|
|
5985
6083
|
}
|
|
5986
6084
|
}
|
|
5987
6085
|
}
|
|
6086
|
+
function refuseMysqlForeignKeyColumnRewrites(params) {
|
|
6087
|
+
const { from, to, buckets, dialect } = params;
|
|
6088
|
+
if (dialect !== "mysql")
|
|
6089
|
+
return;
|
|
6090
|
+
const droppedFks = new Set(buckets.dropForeignKey.map((step) => step.kind === "dropForeignKey" ? `${step.table}\x00${step.constraintName}` : ""));
|
|
6091
|
+
for (const step of buckets.alterColumn) {
|
|
6092
|
+
if (step.kind !== "alterColumn")
|
|
6093
|
+
continue;
|
|
6094
|
+
const collation = step.changes.includes("collation");
|
|
6095
|
+
const type = step.changes.includes("type") && !isMysqlVarcharWidening({ from: step.from, to: step.to });
|
|
6096
|
+
if (!collation && !type)
|
|
6097
|
+
continue;
|
|
6098
|
+
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));
|
|
6099
|
+
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))));
|
|
6100
|
+
if (!joined && !foreign)
|
|
6101
|
+
continue;
|
|
6102
|
+
throw unsupportedCapability10({
|
|
6103
|
+
dialect,
|
|
6104
|
+
feature: `${collation ? "collation" : "type"} change of "${step.table}"."${step.column}" ${joined ? "referenced by an implicit many-to-many join table" : "in a foreign key"}`,
|
|
6105
|
+
hint: `drop the foreign key, change both columns' ${collation ? "collation" : "type"} and re-add it in a reviewed manual migration`
|
|
6106
|
+
});
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
function isMysqlVarcharWidening(params) {
|
|
6110
|
+
const length = (field) => {
|
|
6111
|
+
if (isEnumType6(field.type) || field.isList || field.isUnsupported === true)
|
|
6112
|
+
return null;
|
|
6113
|
+
const match = /^varchar\((\d+)\)$/.exec(normalizeMysqlType({ raw: mysqlColumnTypeSql({ field, enums: new Map }) }));
|
|
6114
|
+
return match === null ? null : Number(match[1]);
|
|
6115
|
+
};
|
|
6116
|
+
const before = length(params.from);
|
|
6117
|
+
const after = length(params.to);
|
|
6118
|
+
return before !== null && after !== null && after >= before;
|
|
6119
|
+
}
|
|
5988
6120
|
function diffViews(params) {
|
|
5989
6121
|
const { from, to, buckets } = params;
|
|
5990
6122
|
for (const [name, view] of from.views) {
|
|
@@ -6140,7 +6272,7 @@ function diffOneTable(params) {
|
|
|
6140
6272
|
from: before.field,
|
|
6141
6273
|
to: column.field,
|
|
6142
6274
|
changes,
|
|
6143
|
-
destructive: narrowingType || collationChanged
|
|
6275
|
+
destructive: narrowingType || collationChanged || dialect === "mysql" && before.nullable && !column.nullable
|
|
6144
6276
|
});
|
|
6145
6277
|
}
|
|
6146
6278
|
for (const [name] of previous.columns) {
|
|
@@ -6148,9 +6280,7 @@ function diffOneTable(params) {
|
|
|
6148
6280
|
buckets.dropColumn.push({ kind: "dropColumn", table, ...placement, column: name, destructive: true });
|
|
6149
6281
|
}
|
|
6150
6282
|
}
|
|
6151
|
-
|
|
6152
|
-
const pkAfter = current.pk.join(",");
|
|
6153
|
-
if (pkBefore !== pkAfter) {
|
|
6283
|
+
if (previous.pk.length !== current.pk.length || previous.pk.some((column, index) => column !== current.pk[index])) {
|
|
6154
6284
|
if (previous.pk.length > 0) {
|
|
6155
6285
|
buckets.dropPrimaryKey.push({ kind: "dropPrimaryKey", table, ...placement, destructive: false, ...previous.model.primaryKeyConstraintName === undefined ? {} : { constraintName: previous.model.primaryKeyConstraintName } });
|
|
6156
6286
|
}
|
|
@@ -6170,7 +6300,7 @@ function diffOneTable(params) {
|
|
|
6170
6300
|
if (before === expression)
|
|
6171
6301
|
continue;
|
|
6172
6302
|
if (before !== undefined && previous.model.checks?.some((check) => check.name === name && check.catalogExpression === true))
|
|
6173
|
-
throw new
|
|
6303
|
+
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 } });
|
|
6174
6304
|
if (before !== undefined)
|
|
6175
6305
|
buckets.dropCheck.push({ kind: "dropCheck", table, ...placement, name, destructive: false });
|
|
6176
6306
|
buckets.addCheck.push({ kind: "addCheck", table, ...placement, name, expression, destructive: false });
|
|
@@ -6256,11 +6386,11 @@ function diffOneTable(params) {
|
|
|
6256
6386
|
}
|
|
6257
6387
|
|
|
6258
6388
|
// packages/migrate/src/ddl/render.ts
|
|
6259
|
-
import { VibeError as
|
|
6389
|
+
import { VibeError as VibeError35, sqlObjectKey as sqlObjectKey10 } from "@vibeorm/schema";
|
|
6260
6390
|
|
|
6261
6391
|
// packages/migrate/src/ddl/postgres.ts
|
|
6262
6392
|
import { renderExclusionSetup } from "@vibeorm/sql";
|
|
6263
|
-
import { VibeError as
|
|
6393
|
+
import { VibeError as VibeError32, assertSqlNamespace as assertSqlNamespace3, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
|
|
6264
6394
|
import { postgresDialect as postgresDialect9, renderRlsPolicyPath, renderIndexColumns } from "@vibeorm/sql";
|
|
6265
6395
|
function renderPostgresSteps(params) {
|
|
6266
6396
|
refuseSameTransactionEnumUse({ steps: params.steps });
|
|
@@ -6297,7 +6427,7 @@ function refuseSameTransactionEnumUse(params) {
|
|
|
6297
6427
|
const found = use(field);
|
|
6298
6428
|
if (found === undefined)
|
|
6299
6429
|
continue;
|
|
6300
|
-
throw new
|
|
6430
|
+
throw new VibeError32({
|
|
6301
6431
|
code: "VIBE_MIGRATION",
|
|
6302
6432
|
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`,
|
|
6303
6433
|
meta: { enum: found.enumName, value: found.value, table: step.table, column: field.dbName ?? field.name, rule: "enum-value-used-in-adding-transaction" }
|
|
@@ -6371,7 +6501,7 @@ ${lines.join(`,
|
|
|
6371
6501
|
case "dropIndex":
|
|
6372
6502
|
if (step.constraintName !== undefined) {
|
|
6373
6503
|
if (step.table === undefined)
|
|
6374
|
-
throw new
|
|
6504
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
|
|
6375
6505
|
return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.constraintName)};`];
|
|
6376
6506
|
}
|
|
6377
6507
|
return [`DROP INDEX ${qualifiedName(step.name, step.namespace)};`];
|
|
@@ -6436,7 +6566,7 @@ function columnDefSql(params) {
|
|
|
6436
6566
|
const { field } = params;
|
|
6437
6567
|
const name = q2(field.dbName ?? field.name);
|
|
6438
6568
|
if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
|
|
6439
|
-
throw new
|
|
6569
|
+
throw new VibeError32({
|
|
6440
6570
|
code: "VIBE_MIGRATION",
|
|
6441
6571
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
6442
6572
|
meta: { field: field.name }
|
|
@@ -6472,7 +6602,7 @@ function renderAlterColumn(params) {
|
|
|
6472
6602
|
}
|
|
6473
6603
|
if (step.changes.includes("default") || droppedDefault) {
|
|
6474
6604
|
if (step.to.default?.kind === "autoincrement") {
|
|
6475
|
-
throw new
|
|
6605
|
+
throw new VibeError32({
|
|
6476
6606
|
code: "VIBE_MIGRATION",
|
|
6477
6607
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
6478
6608
|
meta: { table: step.table, column: step.column }
|
|
@@ -6492,7 +6622,7 @@ function q2(name) {
|
|
|
6492
6622
|
}
|
|
6493
6623
|
|
|
6494
6624
|
// packages/migrate/src/ddl/sqlite.ts
|
|
6495
|
-
import { VibeError as
|
|
6625
|
+
import { VibeError as VibeError33, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
6496
6626
|
import { sqliteDialect as sqliteDialect3 } from "@vibeorm/sql";
|
|
6497
6627
|
|
|
6498
6628
|
// packages/migrate/src/ddl/drop-order.ts
|
|
@@ -6640,7 +6770,7 @@ function renderSqliteSteps(params) {
|
|
|
6640
6770
|
continue;
|
|
6641
6771
|
const awaitsNewParent = [...toNorm?.tables.get(table)?.fks.values() ?? []].some((fk) => createdTables.has(fk.refTable));
|
|
6642
6772
|
if (awaitsNewParent) {
|
|
6643
|
-
throw new
|
|
6773
|
+
throw new VibeError33({
|
|
6644
6774
|
code: "VIBE_MIGRATION",
|
|
6645
6775
|
message: `cannot order this sqlite migration safely: rebuilding "${table}" must happen BEFORE the dropped table(s) it references (${[
|
|
6646
6776
|
...droppedTables
|
|
@@ -6669,7 +6799,7 @@ function renderSqliteSteps(params) {
|
|
|
6669
6799
|
switch (step.kind) {
|
|
6670
6800
|
case "addExclusion":
|
|
6671
6801
|
case "dropExclusion":
|
|
6672
|
-
throw new
|
|
6802
|
+
throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
6673
6803
|
case "createEnum":
|
|
6674
6804
|
case "dropEnum":
|
|
6675
6805
|
case "addEnumValue":
|
|
@@ -6755,7 +6885,7 @@ function renderSqliteSteps(params) {
|
|
|
6755
6885
|
case "renameColumn": {
|
|
6756
6886
|
const renamed = step.kind === "renameTable" ? step.from : step.table;
|
|
6757
6887
|
if (rebuildSet.has(renamed) || step.kind === "renameTable" && rebuildSet.has(step.to)) {
|
|
6758
|
-
throw new
|
|
6888
|
+
throw new VibeError33({
|
|
6759
6889
|
code: "VIBE_MIGRATION",
|
|
6760
6890
|
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`,
|
|
6761
6891
|
meta: { reason: "rename-ambiguous", table: renamed, dialect: "sqlite" }
|
|
@@ -6809,7 +6939,7 @@ function classifyRebuilds(params) {
|
|
|
6809
6939
|
switch (step.kind) {
|
|
6810
6940
|
case "addExclusion":
|
|
6811
6941
|
case "dropExclusion":
|
|
6812
|
-
throw new
|
|
6942
|
+
throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
6813
6943
|
case "alterColumn":
|
|
6814
6944
|
case "dropForeignKey":
|
|
6815
6945
|
case "addPrimaryKey":
|
|
@@ -6870,7 +7000,7 @@ function renderTableRebuild(params) {
|
|
|
6870
7000
|
const source = from.tables.get(table);
|
|
6871
7001
|
const target = to.tables.get(table);
|
|
6872
7002
|
if (source === undefined || target === undefined) {
|
|
6873
|
-
throw new
|
|
7003
|
+
throw new VibeError33({
|
|
6874
7004
|
code: "VIBE_MIGRATION",
|
|
6875
7005
|
message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
|
|
6876
7006
|
meta: { table }
|
|
@@ -7005,7 +7135,7 @@ function columnDefSql2(params) {
|
|
|
7005
7135
|
const { field, constraintTable, enums, inlinePkAutoincrement } = params;
|
|
7006
7136
|
const name = field.dbName ?? field.name;
|
|
7007
7137
|
if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
|
|
7008
|
-
throw new
|
|
7138
|
+
throw new VibeError33({
|
|
7009
7139
|
code: "VIBE_MIGRATION",
|
|
7010
7140
|
message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
|
|
7011
7141
|
meta: { field: field.name }
|
|
@@ -7024,7 +7154,7 @@ function columnDefSql2(params) {
|
|
|
7024
7154
|
const enumName = field.type.enum;
|
|
7025
7155
|
const values = resolveEnumValues({ name: enumName, enums });
|
|
7026
7156
|
if (values === undefined) {
|
|
7027
|
-
throw new
|
|
7157
|
+
throw new VibeError33({
|
|
7028
7158
|
code: "VIBE_MIGRATION",
|
|
7029
7159
|
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`,
|
|
7030
7160
|
meta: { field: field.name, enum: enumName }
|
|
@@ -7059,7 +7189,7 @@ function requireSchemas(params) {
|
|
|
7059
7189
|
const { step, fromNorm, toNorm } = params;
|
|
7060
7190
|
if (fromNorm !== undefined && toNorm !== undefined)
|
|
7061
7191
|
return;
|
|
7062
|
-
throw new
|
|
7192
|
+
throw new VibeError33({
|
|
7063
7193
|
code: "VIBE_MIGRATION",
|
|
7064
7194
|
message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
|
|
7065
7195
|
meta: { step: step.kind }
|
|
@@ -7070,7 +7200,7 @@ function q3(name) {
|
|
|
7070
7200
|
}
|
|
7071
7201
|
|
|
7072
7202
|
// packages/migrate/src/ddl/mysql.ts
|
|
7073
|
-
import { VibeError as
|
|
7203
|
+
import { VibeError as VibeError34, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
7074
7204
|
import { mysqlDialect as mysqlDialect3 } from "@vibeorm/sql";
|
|
7075
7205
|
function renderMysqlSteps(params) {
|
|
7076
7206
|
for (const step of params.steps) {
|
|
@@ -7218,12 +7348,12 @@ ${lines.join(`,
|
|
|
7218
7348
|
];
|
|
7219
7349
|
case "addExclusion":
|
|
7220
7350
|
case "dropExclusion":
|
|
7221
|
-
throw new
|
|
7351
|
+
throw new VibeError34({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
|
|
7222
7352
|
case "dropCheck":
|
|
7223
7353
|
return [`ALTER TABLE ${q4(step.table)} DROP CHECK ${q4(step.name)};`];
|
|
7224
7354
|
case "dropIndex": {
|
|
7225
7355
|
if (step.table === undefined) {
|
|
7226
|
-
throw new
|
|
7356
|
+
throw new VibeError34({
|
|
7227
7357
|
code: "VIBE_MIGRATION",
|
|
7228
7358
|
message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
|
|
7229
7359
|
meta: { index: step.name }
|
|
@@ -7253,8 +7383,8 @@ ${lines.join(`,
|
|
|
7253
7383
|
case "dropForeignKey":
|
|
7254
7384
|
return [`ALTER TABLE ${q4(step.table)} DROP FOREIGN KEY ${q4(step.constraintName)};`];
|
|
7255
7385
|
case "createJoinTable": {
|
|
7256
|
-
const aType =
|
|
7257
|
-
const bType =
|
|
7386
|
+
const aType = joinColumnTypeSql({ field: step.a.field, enums });
|
|
7387
|
+
const bType = joinColumnTypeSql({ field: step.b.field, enums });
|
|
7258
7388
|
const names = implicitJoinTableObjectNames4({ table: step.table });
|
|
7259
7389
|
return [
|
|
7260
7390
|
`CREATE TABLE ${q4(step.table)} (
|
|
@@ -7275,12 +7405,17 @@ ${lines.join(`,
|
|
|
7275
7405
|
return [`ALTER TABLE ${q4(step.table)} DROP PRIMARY KEY;`];
|
|
7276
7406
|
}
|
|
7277
7407
|
}
|
|
7408
|
+
function joinColumnTypeSql(params) {
|
|
7409
|
+
const { field, enums } = params;
|
|
7410
|
+
const type = mysqlColumnTypeSql({ field: { ...field, isList: false }, enums });
|
|
7411
|
+
return field.collation === undefined ? type : `${type} COLLATE ${q4(field.collation)}`;
|
|
7412
|
+
}
|
|
7278
7413
|
function columnDefSql3(params) {
|
|
7279
7414
|
const { field, enums } = params;
|
|
7280
7415
|
const name = q4(field.dbName ?? field.name);
|
|
7281
7416
|
const autoincrement = field.default?.kind === "autoincrement";
|
|
7282
7417
|
if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
|
|
7283
|
-
throw new
|
|
7418
|
+
throw new VibeError34({
|
|
7284
7419
|
code: "VIBE_MIGRATION",
|
|
7285
7420
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
7286
7421
|
meta: { field: field.name }
|
|
@@ -7302,7 +7437,7 @@ function renderAlterColumn2(params) {
|
|
|
7302
7437
|
const { step, enums } = params;
|
|
7303
7438
|
assertEnumStringTransition({ from: step.from, to: step.to, dialect: "mysql", table: step.table, column: step.column });
|
|
7304
7439
|
if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
|
|
7305
|
-
throw new
|
|
7440
|
+
throw new VibeError34({
|
|
7306
7441
|
code: "VIBE_MIGRATION",
|
|
7307
7442
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
7308
7443
|
meta: { table: step.table, column: step.column }
|
|
@@ -7342,24 +7477,24 @@ function declaredSqlitePreservation(params) {
|
|
|
7342
7477
|
continue;
|
|
7343
7478
|
const facts = object.expected?.facts;
|
|
7344
7479
|
if (facts === undefined)
|
|
7345
|
-
throw new
|
|
7480
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7346
7481
|
if (identity.kind === "trigger" || identity.kind === "index") {
|
|
7347
7482
|
if (identity.kind === "index" && to.models.some((model) => (model.dbName ?? model.name) === identity.table.name && model.indexes.some((index) => index.name === identity.name)))
|
|
7348
7483
|
continue;
|
|
7349
7484
|
if (typeof facts.definition !== "string")
|
|
7350
|
-
throw new
|
|
7485
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7351
7486
|
const statements = restore.get(identity.table.name) ?? [];
|
|
7352
7487
|
statements.push(facts.definition);
|
|
7353
7488
|
restore.set(identity.table.name, statements);
|
|
7354
7489
|
} else if (identity.kind === "column" || identity.kind === "generatedColumn") {
|
|
7355
7490
|
if (typeof facts.columnDefinition !== "string")
|
|
7356
|
-
throw new
|
|
7491
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7357
7492
|
const fields = columns.get(identity.table.name) ?? new Map;
|
|
7358
7493
|
fields.set(identity.column, { sql: facts.columnDefinition, generated: Number(facts.generated) > 1 });
|
|
7359
7494
|
columns.set(identity.table.name, fields);
|
|
7360
7495
|
} else if (identity.kind === "columnDefault") {
|
|
7361
7496
|
if (facts.definition !== null && typeof facts.definition !== "string")
|
|
7362
|
-
throw new
|
|
7497
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7363
7498
|
const expression = facts.definition;
|
|
7364
7499
|
to = { ...to, models: to.models.map((model) => (model.dbName ?? model.name) !== identity.table.name ? model : { ...model, fields: model.fields.map((field) => {
|
|
7365
7500
|
if ((field.dbName ?? field.name) !== identity.column)
|
|
@@ -7396,7 +7531,7 @@ function renderSteps(params) {
|
|
|
7396
7531
|
}
|
|
7397
7532
|
|
|
7398
7533
|
// packages/migrate/src/extensions.ts
|
|
7399
|
-
import { extensionDeclarations } from "@vibeorm/schema";
|
|
7534
|
+
import { extensionDeclarations, providerToDialect as providerToDialect4, qualifiedSqlName as qualifiedSqlName10, SQL_NAMESPACES } from "@vibeorm/schema";
|
|
7400
7535
|
function collectExtensionArtifacts(params) {
|
|
7401
7536
|
const { schema, dialect, extensions } = params;
|
|
7402
7537
|
const setup = [];
|
|
@@ -7463,6 +7598,10 @@ function stripExtensionArtifacts(params) {
|
|
|
7463
7598
|
const models = [];
|
|
7464
7599
|
for (const model of schema.models) {
|
|
7465
7600
|
const tableName = model.dbName ?? model.name;
|
|
7601
|
+
if (providerToDialect4({ provider: schema.datasource.provider }) === "postgres" && qualifiedSqlName10({ name: tableName, namespace: model.namespace }).namespace !== SQL_NAMESPACES.postgres) {
|
|
7602
|
+
models.push(model);
|
|
7603
|
+
continue;
|
|
7604
|
+
}
|
|
7466
7605
|
if (tables.exact.has(tableName) || tables.prefixes.some((prefix) => tableName.startsWith(prefix))) {
|
|
7467
7606
|
continue;
|
|
7468
7607
|
}
|
|
@@ -7486,6 +7625,8 @@ function planMissingArtifacts(params) {
|
|
|
7486
7625
|
return [];
|
|
7487
7626
|
const modelByTable = new Map;
|
|
7488
7627
|
for (const model of introspected.models) {
|
|
7628
|
+
if (providerToDialect4({ provider: introspected.datasource.provider }) === "postgres" && qualifiedSqlName10({ name: model.dbName ?? model.name, namespace: model.namespace }).namespace !== SQL_NAMESPACES.postgres)
|
|
7629
|
+
continue;
|
|
7489
7630
|
modelByTable.set(model.dbName ?? model.name, model);
|
|
7490
7631
|
}
|
|
7491
7632
|
return artifacts.filter((artifact) => {
|
|
@@ -7644,10 +7785,10 @@ function assertNativeMigrationMetadata(params) {
|
|
|
7644
7785
|
if (!native)
|
|
7645
7786
|
return;
|
|
7646
7787
|
if (!DIALECT_CAPABILITIES3[params.dialect].nativeRowLevelSecurity) {
|
|
7647
|
-
throw new
|
|
7788
|
+
throw new VibeError36({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
|
|
7648
7789
|
}
|
|
7649
7790
|
if (params.migration.rls === undefined) {
|
|
7650
|
-
throw new
|
|
7791
|
+
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 } });
|
|
7651
7792
|
}
|
|
7652
7793
|
}
|
|
7653
7794
|
var PG_ADVISORY_LOCK_KEY = 1447645253;
|
|
@@ -7728,12 +7869,12 @@ function generateMigration(params) {
|
|
|
7728
7869
|
}),
|
|
7729
7870
|
...orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).flatMap((object) => {
|
|
7730
7871
|
if (object.dialect !== dialect)
|
|
7731
|
-
throw new
|
|
7872
|
+
throw new VibeError36({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
|
|
7732
7873
|
const previous = (params.desiredState?.from.schema.migrationObjects ?? from.migrationObjects)?.find((entry) => entry.id === object.id);
|
|
7733
7874
|
if (previous === undefined)
|
|
7734
7875
|
return [...object.create];
|
|
7735
7876
|
if (JSON.stringify(previous) !== JSON.stringify(object))
|
|
7736
|
-
throw new
|
|
7877
|
+
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 } });
|
|
7737
7878
|
return [];
|
|
7738
7879
|
})
|
|
7739
7880
|
];
|
|
@@ -7759,8 +7900,8 @@ async function withMigrationLock(params) {
|
|
|
7759
7900
|
}, (cause) => releaseLock({ executor, dialect: params.dialect }).then(() => {
|
|
7760
7901
|
throw cause;
|
|
7761
7902
|
}, (cleanupCause) => {
|
|
7762
|
-
if (cause instanceof
|
|
7763
|
-
throw new
|
|
7903
|
+
if (cause instanceof VibeError36)
|
|
7904
|
+
throw new VibeError36({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
|
|
7764
7905
|
throw cause;
|
|
7765
7906
|
}));
|
|
7766
7907
|
};
|
|
@@ -7790,11 +7931,11 @@ function assertMigrationMetadata(params) {
|
|
|
7790
7931
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
7791
7932
|
}
|
|
7792
7933
|
if (migration.desiredStatePending === true)
|
|
7793
|
-
throw new
|
|
7934
|
+
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 } });
|
|
7794
7935
|
if (migration.desiredState !== undefined) {
|
|
7795
7936
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
7796
7937
|
if (migration.desiredState.dialect !== dialect)
|
|
7797
|
-
throw new
|
|
7938
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
|
|
7798
7939
|
}
|
|
7799
7940
|
assertNativeMigrationMetadata({ migration, sql: migration.sql, dialect });
|
|
7800
7941
|
assertExecutable({ migration, execution: migration.execution, sql: migration.sql, dialect });
|
|
@@ -7804,7 +7945,7 @@ async function applyMigrations(params) {
|
|
|
7804
7945
|
const dialect = params.dialect ?? "postgres";
|
|
7805
7946
|
const trustedReplay = isShadowReplayExecutor({ executor });
|
|
7806
7947
|
if (params.history !== undefined && params.history !== "complete" && params.history !== "subset")
|
|
7807
|
-
throw new
|
|
7948
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
|
|
7808
7949
|
if (params.history === "complete")
|
|
7809
7950
|
assertDistinctMigrationNames({ migrations });
|
|
7810
7951
|
for (const migration of migrations)
|
|
@@ -7820,7 +7961,7 @@ async function applyMigrations(params) {
|
|
|
7820
7961
|
assertModuleUpgradeHistory({ migrations, records: moduleRecords });
|
|
7821
7962
|
const namespace = await migrationNamespace({ executor, dialect });
|
|
7822
7963
|
if (dialect !== "postgres" && migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
|
|
7823
|
-
throw new
|
|
7964
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
|
|
7824
7965
|
}
|
|
7825
7966
|
if (params.history === "complete")
|
|
7826
7967
|
assertCompleteHistory({ migrations, recorded, dialect });
|
|
@@ -7831,7 +7972,7 @@ async function applyMigrations(params) {
|
|
|
7831
7972
|
});
|
|
7832
7973
|
const adopted = pending.find((migration) => migration.moduleAdoption !== undefined);
|
|
7833
7974
|
if (adopted !== undefined && !trustedReplay)
|
|
7834
|
-
throw new
|
|
7975
|
+
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 } });
|
|
7835
7976
|
if (external !== undefined && !trustedReplay)
|
|
7836
7977
|
for (const migration of pending)
|
|
7837
7978
|
await assertExternalMigrationMutation({ executor, migration, dialect, namespace: await migrationNamespace({ executor, dialect }), external });
|
|
@@ -7847,7 +7988,7 @@ async function applyMigrations(params) {
|
|
|
7847
7988
|
await assertLiveModuleOwnership({ executor, origin: migration.moduleOrigin, resume: stored !== undefined });
|
|
7848
7989
|
if (stored !== undefined) {
|
|
7849
7990
|
if (stored.statementIndex < 0) {
|
|
7850
|
-
throw new
|
|
7991
|
+
throw new VibeError36({
|
|
7851
7992
|
code: "VIBE_MIGRATION",
|
|
7852
7993
|
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`,
|
|
7853
7994
|
meta: { migration: migration.name, statementIndex: stored.statementIndex }
|
|
@@ -8005,7 +8146,7 @@ async function repairMigration(params) {
|
|
|
8005
8146
|
const { executor, name, dialect } = params;
|
|
8006
8147
|
const moduleRecord = (await readModuleJournal({ executor, dialect })).get(name);
|
|
8007
8148
|
if (moduleRecord?.entry !== undefined)
|
|
8008
|
-
throw new
|
|
8149
|
+
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 } });
|
|
8009
8150
|
await ensureMigrationsTable({ executor, dialect });
|
|
8010
8151
|
if (params.as === "applied") {
|
|
8011
8152
|
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})`;
|
|
@@ -8027,7 +8168,7 @@ async function rollbackMigration(params) {
|
|
|
8027
8168
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(migration.name), dialect });
|
|
8028
8169
|
const newest = [...recorded.keys()].at(-1);
|
|
8029
8170
|
if (newest !== migration.name)
|
|
8030
|
-
throw new
|
|
8171
|
+
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 } });
|
|
8031
8172
|
await validateRollbackRecovery({ executor, migration, downSql, stored, dialect });
|
|
8032
8173
|
await executeRollback({ ...params, executor, dialect, downSql, stored });
|
|
8033
8174
|
} });
|
|
@@ -8045,7 +8186,7 @@ async function rollbackMigrations(params) {
|
|
|
8045
8186
|
const target = journal.findIndex((row) => row.name === params.to);
|
|
8046
8187
|
const row = journal[target];
|
|
8047
8188
|
if (row?.state !== "applied")
|
|
8048
|
-
throw new
|
|
8189
|
+
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 } });
|
|
8049
8190
|
assertMigrationMetadata({ migration: byName.get(row.name), dialect });
|
|
8050
8191
|
start = target + 1;
|
|
8051
8192
|
}
|
|
@@ -8053,7 +8194,7 @@ async function rollbackMigrations(params) {
|
|
|
8053
8194
|
for (const row of journal.slice(start).reverse()) {
|
|
8054
8195
|
const migration = byName.get(row.name);
|
|
8055
8196
|
if (migration === undefined)
|
|
8056
|
-
throw new
|
|
8197
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
|
|
8057
8198
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(row.name), dialect });
|
|
8058
8199
|
const downSql = validateRollbackFile({ ...params, migration, dialect });
|
|
8059
8200
|
plan.push({ migration, downSql, stored });
|
|
@@ -8072,21 +8213,21 @@ function validateRollbackFile(params) {
|
|
|
8072
8213
|
const { migration, dialect } = params;
|
|
8073
8214
|
assertModuleAdoption({ migration });
|
|
8074
8215
|
if (migration.moduleAdoption !== undefined)
|
|
8075
|
-
throw new
|
|
8216
|
+
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 } });
|
|
8076
8217
|
if (migration.desiredStatePending === true)
|
|
8077
|
-
throw new
|
|
8218
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
|
|
8078
8219
|
if (migration.moduleOrigin !== undefined) {
|
|
8079
8220
|
assertMaterializedModuleMigration({ migration });
|
|
8080
8221
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
8081
8222
|
}
|
|
8082
8223
|
const downSql = migration.downSql;
|
|
8083
8224
|
if (downSql === undefined)
|
|
8084
|
-
throw new
|
|
8225
|
+
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 } });
|
|
8085
8226
|
assertProbeMode({ dialect, probe: params.probe });
|
|
8086
8227
|
if (migration.desiredState !== undefined) {
|
|
8087
8228
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
8088
8229
|
if (migration.desiredState.dialect !== dialect)
|
|
8089
|
-
throw new
|
|
8230
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
|
|
8090
8231
|
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 } });
|
|
8091
8232
|
}
|
|
8092
8233
|
assertNativeMigrationMetadata({ migration, sql: downSql, dialect });
|
|
@@ -8099,10 +8240,10 @@ function validateRollbackFile(params) {
|
|
|
8099
8240
|
function assertRollbackRecord(params) {
|
|
8100
8241
|
const { migration, stored, dialect } = params;
|
|
8101
8242
|
if (stored === undefined)
|
|
8102
|
-
throw new
|
|
8243
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
|
|
8103
8244
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
8104
8245
|
if (stored.statementIndex >= 0 && (!isCompleteRecord({ dialect, migration, stored }) || stored.error !== undefined))
|
|
8105
|
-
throw new
|
|
8246
|
+
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 } });
|
|
8106
8247
|
return stored;
|
|
8107
8248
|
}
|
|
8108
8249
|
async function validateRollbackRecovery(params) {
|
|
@@ -8111,7 +8252,7 @@ async function validateRollbackRecovery(params) {
|
|
|
8111
8252
|
return;
|
|
8112
8253
|
const start = -stored.statementIndex - 1;
|
|
8113
8254
|
if (start > downSql.length)
|
|
8114
|
-
throw new
|
|
8255
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
|
|
8115
8256
|
if (dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql }))
|
|
8116
8257
|
assertSelfManagedResumeSafe({ sql: downSql, start, migration: migration.name, direction: "down" });
|
|
8117
8258
|
if (!marksInflight({ dialect, execution: migration.downExecution }) || start >= downSql.length)
|
|
@@ -8177,7 +8318,7 @@ async function runTransactionalDown(params) {
|
|
|
8177
8318
|
} else {
|
|
8178
8319
|
await cleanupSelfManagedFailure({ executor });
|
|
8179
8320
|
}
|
|
8180
|
-
throw new
|
|
8321
|
+
throw new VibeError36({
|
|
8181
8322
|
code: "VIBE_MIGRATION",
|
|
8182
8323
|
message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
8183
8324
|
meta: { migration: migration.name, ...timeoutDiagnostic({ error }) },
|
|
@@ -8260,7 +8401,7 @@ async function runNonTransactionalDown(params) {
|
|
|
8260
8401
|
values: [marks ? `${INFLIGHT_PREFIX}${index}` : error instanceof Error ? error.message : String(error), migration.name]
|
|
8261
8402
|
});
|
|
8262
8403
|
} catch {}
|
|
8263
|
-
throw new
|
|
8404
|
+
throw new VibeError36({
|
|
8264
8405
|
code: "VIBE_MIGRATION",
|
|
8265
8406
|
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."}`,
|
|
8266
8407
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeoutDiagnostic({ error }) },
|
|
@@ -8322,7 +8463,7 @@ async function runTransactional(params) {
|
|
|
8322
8463
|
} else {
|
|
8323
8464
|
await cleanupSelfManagedFailure({ executor });
|
|
8324
8465
|
}
|
|
8325
|
-
throw new
|
|
8466
|
+
throw new VibeError36({
|
|
8326
8467
|
code: "VIBE_MIGRATION",
|
|
8327
8468
|
message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
8328
8469
|
meta: { migration: migration.name },
|
|
@@ -8418,7 +8559,7 @@ async function runNonTransactional(params) {
|
|
|
8418
8559
|
} catch {}
|
|
8419
8560
|
const sqlState = sqlStateOf(error);
|
|
8420
8561
|
const timeout = sqlState === "55P03" ? "lock-timeout" : sqlState === "57014" ? "statement-timeout" : undefined;
|
|
8421
|
-
throw new
|
|
8562
|
+
throw new VibeError36({
|
|
8422
8563
|
code: "VIBE_MIGRATION",
|
|
8423
8564
|
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."}`,
|
|
8424
8565
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeout === undefined ? {} : { reason: timeout, sqlState } },
|
|
@@ -8446,7 +8587,7 @@ async function acquireLock(params) {
|
|
|
8446
8587
|
});
|
|
8447
8588
|
const acquired = Number(rows[0]?.vibe_lock) === 1;
|
|
8448
8589
|
if (!acquired) {
|
|
8449
|
-
throw new
|
|
8590
|
+
throw new VibeError36({
|
|
8450
8591
|
code: "VIBE_MIGRATION",
|
|
8451
8592
|
message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
|
|
8452
8593
|
meta: { lock: MYSQL_LOCK_NAME }
|
|
@@ -8481,7 +8622,7 @@ function uncertainStatement(params) {
|
|
|
8481
8622
|
const subject = direction === "down" ? `rollback of "${migration}"` : `migration "${migration}"`;
|
|
8482
8623
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${statements.length}`;
|
|
8483
8624
|
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.";
|
|
8484
|
-
return new
|
|
8625
|
+
return new VibeError36({
|
|
8485
8626
|
code: "VIBE_MIGRATION",
|
|
8486
8627
|
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)}`,
|
|
8487
8628
|
meta: {
|
|
@@ -8499,7 +8640,7 @@ function assertExecutable(params) {
|
|
|
8499
8640
|
if (execution === undefined || isDefaultExecution({ execution }))
|
|
8500
8641
|
return;
|
|
8501
8642
|
if (dialect !== "postgres") {
|
|
8502
|
-
throw new
|
|
8643
|
+
throw new VibeError36({
|
|
8503
8644
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8504
8645
|
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`,
|
|
8505
8646
|
meta: { reason: "nontransactional-unsupported", dialect, migration: migration.name, feature: "nontransactionalMigrations" }
|
|
@@ -8575,7 +8716,7 @@ function assertChecksumMatches(params) {
|
|
|
8575
8716
|
const actual = checksumOf({ migration });
|
|
8576
8717
|
if (stored === actual)
|
|
8577
8718
|
return;
|
|
8578
|
-
throw new
|
|
8719
|
+
throw new VibeError36({
|
|
8579
8720
|
code: "VIBE_MIGRATION",
|
|
8580
8721
|
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)`,
|
|
8581
8722
|
meta: { migration: migration.name, recorded: stored, current: actual }
|
|
@@ -8586,7 +8727,7 @@ function assertResumable(params) {
|
|
|
8586
8727
|
const actual = checksumOf({ migration });
|
|
8587
8728
|
if (stored.checksum === actual)
|
|
8588
8729
|
return;
|
|
8589
|
-
throw new
|
|
8730
|
+
throw new VibeError36({
|
|
8590
8731
|
code: "VIBE_MIGRATION",
|
|
8591
8732
|
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`,
|
|
8592
8733
|
meta: {
|
|
@@ -8606,7 +8747,7 @@ function assertDistinctMigrationNames(params) {
|
|
|
8606
8747
|
const names = new Set;
|
|
8607
8748
|
for (const migration of params.migrations) {
|
|
8608
8749
|
if (names.has(migration.name))
|
|
8609
|
-
throw new
|
|
8750
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
|
|
8610
8751
|
names.add(migration.name);
|
|
8611
8752
|
}
|
|
8612
8753
|
}
|
|
@@ -8614,15 +8755,15 @@ function assertCompleteHistory(params) {
|
|
|
8614
8755
|
const byName = new Map(params.migrations.map((migration) => [migration.name, migration]));
|
|
8615
8756
|
for (const name of params.recorded.keys()) {
|
|
8616
8757
|
if (!byName.has(name))
|
|
8617
|
-
throw new
|
|
8758
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
|
|
8618
8759
|
}
|
|
8619
8760
|
let index = 0;
|
|
8620
8761
|
for (const [name, stored] of params.recorded) {
|
|
8621
8762
|
const migration = params.migrations[index];
|
|
8622
8763
|
if (migration?.name !== name)
|
|
8623
|
-
throw new
|
|
8764
|
+
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 } });
|
|
8624
8765
|
if (stored.statementIndex >= 0 && !isCompleteRecord({ dialect: params.dialect, migration, stored }) && index !== params.recorded.size - 1)
|
|
8625
|
-
throw new
|
|
8766
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
|
|
8626
8767
|
index++;
|
|
8627
8768
|
}
|
|
8628
8769
|
}
|
|
@@ -8631,16 +8772,16 @@ async function assertApplyHistory(params) {
|
|
|
8631
8772
|
const byName = new Map(migrations.map((migration) => [migration.name, migration]));
|
|
8632
8773
|
for (const [name, stored] of recorded) {
|
|
8633
8774
|
if (stored.statementIndex < 0) {
|
|
8634
|
-
throw new
|
|
8775
|
+
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 } });
|
|
8635
8776
|
}
|
|
8636
8777
|
const migration = byName.get(name);
|
|
8637
8778
|
if (migration === undefined) {
|
|
8638
8779
|
if (stored.error !== undefined)
|
|
8639
|
-
throw new
|
|
8780
|
+
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 } });
|
|
8640
8781
|
continue;
|
|
8641
8782
|
}
|
|
8642
8783
|
if (isCompleteRecord({ dialect, migration, stored }) && stored.error !== undefined)
|
|
8643
|
-
throw new
|
|
8784
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
|
|
8644
8785
|
if (isCompleteRecord({ dialect, migration, stored }))
|
|
8645
8786
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
8646
8787
|
else
|
|
@@ -8674,17 +8815,17 @@ function ph(dialect, index) {
|
|
|
8674
8815
|
// packages/migrate/src/module-bundle.ts
|
|
8675
8816
|
function defineModuleMigrations(params) {
|
|
8676
8817
|
if (!Array.isArray(params.steps) || params.steps.length === 0)
|
|
8677
|
-
throw new
|
|
8818
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "A module upgrade bundle must contain its initial revision", meta: { reason: "module-chain-invalid", moduleId: params.moduleId } });
|
|
8678
8819
|
const migrations = [];
|
|
8679
8820
|
const revisions = new Set;
|
|
8680
8821
|
let previous = null;
|
|
8681
8822
|
for (const step of params.steps) {
|
|
8682
8823
|
if (step.previousRevision !== previous || revisions.has(step.revision))
|
|
8683
|
-
throw new
|
|
8824
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} has a duplicate revision or broken predecessor chain`, meta: { reason: "module-chain-invalid", moduleId: params.moduleId, revision: step.revision } });
|
|
8684
8825
|
const origin = createModuleOrigin({ moduleId: params.moduleId, dialect: params.dialect, revision: step.revision, previousRevision: step.previousRevision, contracts: step.contracts, owned: step.owned, source: step.migration });
|
|
8685
8826
|
const predecessor = migrations.at(-1)?.moduleOrigin;
|
|
8686
8827
|
if (predecessor !== undefined && canonicalMigrationJson({ value: predecessor.source.desiredState.to }) !== canonicalMigrationJson({ value: origin.source.desiredState.from }))
|
|
8687
|
-
throw new
|
|
8828
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Module source state differs from its predecessor", meta: { reason: "module-predecessor-state", moduleId: params.moduleId } });
|
|
8688
8829
|
validateMigrationReplay({ migrations: [origin.source], dialect: params.dialect, acceptSecurityChanges: true, acceptIntegrityLoss: true, removeUnmanaged: origin.source.desiredState?.unmanagedRemovals ?? [] });
|
|
8689
8830
|
migrations.push(Object.freeze({ ...origin.source, moduleOrigin: origin }));
|
|
8690
8831
|
revisions.add(step.revision);
|
|
@@ -8694,21 +8835,21 @@ function defineModuleMigrations(params) {
|
|
|
8694
8835
|
}
|
|
8695
8836
|
function moduleMigrationFiles(params) {
|
|
8696
8837
|
if (params.bundle === null || typeof params.bundle !== "object" || !Array.isArray(params.bundle.migrations))
|
|
8697
|
-
throw new
|
|
8838
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Module bundle must contain an immutable migration chain", meta: { reason: "module-chain-invalid" } });
|
|
8698
8839
|
if (!/^[A-Za-z0-9_-]+$/.test(params.prefix))
|
|
8699
|
-
throw new
|
|
8840
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Module migration prefix must be a safe local directory name", meta: { reason: "module-prefix-invalid" } });
|
|
8700
8841
|
const bundle = defineModuleMigrations({ moduleId: params.bundle.moduleId, dialect: params.bundle.dialect, steps: params.bundle.migrations.map((migration) => {
|
|
8701
8842
|
const origin = migration?.moduleOrigin;
|
|
8702
8843
|
if (migration.moduleAdoption !== undefined)
|
|
8703
|
-
throw new
|
|
8844
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Local adoption provenance cannot be exported as immutable package source", meta: { reason: "module-adoption-author-source" } });
|
|
8704
8845
|
if (origin === undefined || origin.moduleId !== params.bundle.moduleId || origin.dialect !== params.bundle.dialect)
|
|
8705
|
-
throw new
|
|
8846
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Module bundle lacks matching immutable origin metadata", meta: { reason: "module-origin-invalid" } });
|
|
8706
8847
|
assertModuleOrigin({ value: origin });
|
|
8707
8848
|
restoreModuleMigration({ migration, origin });
|
|
8708
8849
|
const { name: localName, sql: localSql, downSql: localDown, moduleOrigin: localOrigin, ...actual } = migration;
|
|
8709
8850
|
const { name: sourceName, sql: sourceSql, downSql: sourceDown, ...expected } = origin.source;
|
|
8710
8851
|
if (localSql === undefined || localDown === undefined !== (sourceDown === undefined) || canonicalMigrationJson({ value: actual }) !== canonicalMigrationJson({ value: expected }))
|
|
8711
|
-
throw new
|
|
8852
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Module bundle metadata differs from its sealed author source", meta: { reason: "module-origin-invalid", moduleId: origin.moduleId } });
|
|
8712
8853
|
return { revision: origin.revision, previousRevision: origin.previousRevision, contracts: origin.contracts, owned: origin.owned, migration: origin.source };
|
|
8713
8854
|
}) });
|
|
8714
8855
|
return Object.freeze(bundle.migrations.map((migration, index) => Object.freeze({ ...migration, name: `${params.prefix}_${String(index + 1).padStart(4, "0")}` })));
|
|
@@ -8716,10 +8857,10 @@ function moduleMigrationFiles(params) {
|
|
|
8716
8857
|
|
|
8717
8858
|
// packages/migrate/src/module-pending.ts
|
|
8718
8859
|
function refusal3(params) {
|
|
8719
|
-
return new
|
|
8860
|
+
return new VibeError38({ code: "VIBE_MIGRATION", message: "Module registration or immutable upgrade history conflicts with the application's declarations; review the registration and deployment sequence", meta: params });
|
|
8720
8861
|
}
|
|
8721
8862
|
function physicalKey(params) {
|
|
8722
|
-
return sqlObjectKey11({ identity: { kind: "table", name:
|
|
8863
|
+
return sqlObjectKey11({ identity: { kind: "table", name: qualifiedSqlName11({ name: params.entry.dbName ?? params.entry.name, namespace: params.entry.namespace }) } });
|
|
8723
8864
|
}
|
|
8724
8865
|
function composeModuleState(params) {
|
|
8725
8866
|
const { current, from, to } = params;
|
|
@@ -8830,7 +8971,7 @@ function pendingModuleMigrations(params) {
|
|
|
8830
8971
|
|
|
8831
8972
|
// packages/migrate/src/replay-reference.ts
|
|
8832
8973
|
import { createHash as createHash3 } from "crypto";
|
|
8833
|
-
import { VibeError as
|
|
8974
|
+
import { VibeError as VibeError39, sqlObjectKey as sqlObjectKey12, sqlObjectNamespace as sqlObjectNamespace4 } from "@vibeorm/schema";
|
|
8834
8975
|
import { getSqlDialect as getSqlDialect10, renderMigrationServerVersion, renderReplayCoverageGaps as renderReplayCoverageGaps2, renderShadowOperation as renderShadowOperation2, SHADOW_DATABASE } from "@vibeorm/sql";
|
|
8835
8976
|
function migrationReferenceNamespaces(params) {
|
|
8836
8977
|
return [...new Set([params.namespace, ...params.migrations.flatMap((migration) => [
|
|
@@ -8846,7 +8987,7 @@ async function captureReplayReference(params) {
|
|
|
8846
8987
|
for (const origin of params.namespaceOrigins ?? []) {
|
|
8847
8988
|
assertModuleOrigin({ value: origin });
|
|
8848
8989
|
if (origin.dialect !== dialect)
|
|
8849
|
-
throw new
|
|
8990
|
+
throw new VibeError39({ code: "VIBE_MIGRATION", message: "Namespace context origin uses a different dialect", meta: { reason: "module-dialect-mismatch", moduleId: origin.moduleId } });
|
|
8850
8991
|
}
|
|
8851
8992
|
const namespace = await migrationNamespace({ executor, dialect });
|
|
8852
8993
|
await assertCatalogVisibility({ executor, dialect, namespace });
|
|
@@ -8855,17 +8996,17 @@ async function captureReplayReference(params) {
|
|
|
8855
8996
|
const [query] = renderShadowOperation2({ dialect: sqlDialect, operation: { kind: "markerColumns" } });
|
|
8856
8997
|
const columns = await executor({ text: query.text, values: [...query.values] });
|
|
8857
8998
|
if (columns.length !== 1 || columns[0]?.name !== "payload" || columns[0]?.type !== "longtext" || columns[0]?.nullable !== "NO")
|
|
8858
|
-
throw new
|
|
8999
|
+
throw new VibeError39({ code: "VIBE_MIGRATION", message: "Replay changed the private shadow marker; its objects cannot be excluded from reference inspection", meta: { reason: "shadow-ownership-changed" } });
|
|
8859
9000
|
}
|
|
8860
9001
|
if (params.privateNamespace !== undefined) {
|
|
8861
9002
|
if (dialect !== "postgres" || params.privateNamespace !== SHADOW_DATABASE.schema)
|
|
8862
|
-
throw new
|
|
9003
|
+
throw new VibeError39({ code: "VIBE_MIGRATION", message: "Unknown private shadow namespace", meta: { reason: "shadow-ownership-invalid" } });
|
|
8863
9004
|
const [inventoryQuery] = renderShadowOperation2({ dialect: sqlDialect, operation: { kind: "inventory" } });
|
|
8864
9005
|
const privateObjects = (await executor({ text: inventoryQuery.text, values: [...inventoryQuery.values] })).filter((row) => row.namespace === params.privateNamespace);
|
|
8865
9006
|
const [columnsQuery] = renderShadowOperation2({ dialect: sqlDialect, operation: { kind: "markerColumns" } });
|
|
8866
9007
|
const columns = await executor({ text: columnsQuery.text, values: [...columnsQuery.values] });
|
|
8867
9008
|
if (columns.length !== 1 || columns[0]?.name !== "payload" || columns[0]?.type !== "text" || columns[0]?.nullable !== "NO" || privateObjects.some((row) => !(row.kind === "namespace" && row.name === SHADOW_DATABASE.schema || row.kind === "relation" && row.name === SHADOW_DATABASE.table)))
|
|
8868
|
-
throw new
|
|
9009
|
+
throw new VibeError39({ code: "VIBE_MIGRATION", message: "Replay changed the private shadow namespace; its objects cannot be excluded from reference inspection", meta: { reason: "shadow-ownership-changed", objects: privateObjects } });
|
|
8869
9010
|
}
|
|
8870
9011
|
const namespaces = dialect === "postgres" ? [...new Set([...migrationReferenceNamespaces({ migrations: params.migrations, namespace }), ...(params.namespaceOrigins ?? []).map((origin) => moduleNamespace({ origin }))])] : [namespace];
|
|
8871
9012
|
const external = (params.external?.length ?? 0) === 0 ? undefined : await inspectExternalObjects({ executor, dialect, external: params.external });
|
|
@@ -8876,7 +9017,7 @@ async function captureReplayReference(params) {
|
|
|
8876
9017
|
}))).flat();
|
|
8877
9018
|
const gaps = migrationCoverageGaps({ gaps: coverageRows, namespace, namespaces, external });
|
|
8878
9019
|
if (gaps.length > 0)
|
|
8879
|
-
throw new
|
|
9020
|
+
throw new VibeError39({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Replayed history contains objects whose full definitions are not represented by migration inspection. Use reviewed SQL for changes; automatic generation and verification require supported reference coverage.", meta: { feature: "replayInventoryCoverage", dialect, namespace, objects: gaps } });
|
|
8880
9021
|
const rls = [...params.migrations].reverse().find((migration) => migration.rls !== undefined)?.rls?.to;
|
|
8881
9022
|
const physical = dialect === "postgres" ? await introspectPostgres({ executor, namespaces: inspectedNamespaces, ...rls === undefined ? {} : { rlsSchema: rls, requireInstalledRls: true } }) : dialect === "mysql" ? await introspectMysql({ executor }) : await introspectSqlite({ executor });
|
|
8882
9023
|
const privateTable = params.privateTable !== undefined && physical.models.some((model) => !model.isView && (model.dbName ?? model.name) === params.privateTable) ? params.privateTable : undefined;
|
|
@@ -8924,7 +9065,7 @@ async function captureReplayReference(params) {
|
|
|
8924
9065
|
}
|
|
8925
9066
|
|
|
8926
9067
|
// packages/migrate/src/verify-history.ts
|
|
8927
|
-
import { VibeError as
|
|
9068
|
+
import { VibeError as VibeError41, sqlObjectKey as sqlObjectKey13 } from "@vibeorm/schema";
|
|
8928
9069
|
import { getSqlDialect as getSqlDialect11, renderReplayCoverageGaps as renderReplayCoverageGaps3 } from "@vibeorm/sql";
|
|
8929
9070
|
|
|
8930
9071
|
// packages/migrate/src/compare-sqlite-primary-key.ts
|
|
@@ -8995,7 +9136,7 @@ function compareSqlitePrimaryKeyRepresentations(params) {
|
|
|
8995
9136
|
}
|
|
8996
9137
|
|
|
8997
9138
|
// packages/migrate/src/compare-reference-rls.ts
|
|
8998
|
-
import { VibeError as
|
|
9139
|
+
import { VibeError as VibeError40 } from "@vibeorm/schema";
|
|
8999
9140
|
function securityFacts(params) {
|
|
9000
9141
|
return new Map(params.tables.filter((table) => table.enabled || table.force || table.policies.length > 0).map((table) => [table.table, {
|
|
9001
9142
|
table: table.table,
|
|
@@ -9006,7 +9147,7 @@ function securityFacts(params) {
|
|
|
9006
9147
|
}
|
|
9007
9148
|
async function comparePostgresReferenceRls(params) {
|
|
9008
9149
|
if (params.schema.rls !== undefined && params.referenceExecutor === undefined)
|
|
9009
|
-
throw new
|
|
9150
|
+
throw new VibeError40({ code: "VIBE_MIGRATION", message: "Native RLS verification requires the independently replayed reference connection; target policy probes are not permitted.", meta: { reason: "verification-rls-reference-required" } });
|
|
9010
9151
|
const expected = securityFacts({ tables: params.schema.rls === undefined ? [] : await readPostgresRls({ executor: params.referenceExecutor }) });
|
|
9011
9152
|
const actual = securityFacts({ tables: await readPostgresRls({ executor: params.executor }) });
|
|
9012
9153
|
const objects = [...new Set([...expected.keys(), ...actual.keys()])].sort().map((table) => {
|
|
@@ -9030,14 +9171,14 @@ function selectMigrationVerificationPrefix(params) {
|
|
|
9030
9171
|
for (const [index, file] of params.migrations.entries()) {
|
|
9031
9172
|
const previous = params.migrations[index - 1];
|
|
9032
9173
|
if (seen.has(file.name) || previous !== undefined && previous.name.localeCompare(file.name) > 0) {
|
|
9033
|
-
throw new
|
|
9174
|
+
throw new VibeError41({ code: "VIBE_MIGRATION", message: "Verification requires a complete local migration chain in unique name order", meta: { reason: "verification-history-order", migration: file.name } });
|
|
9034
9175
|
}
|
|
9035
9176
|
seen.add(file.name);
|
|
9036
9177
|
}
|
|
9037
9178
|
for (const [index, row] of params.recorded.entries()) {
|
|
9038
9179
|
const file = params.migrations[index];
|
|
9039
9180
|
if (row.state !== "applied" || row.statementIndex < 0 || row.checksum === "" || file?.name !== row.name || file.desiredStatePending === true || migrationChecksum(file) !== row.checksum) {
|
|
9040
|
-
throw new
|
|
9181
|
+
throw new VibeError41({ code: "VIBE_MIGRATION", message: "Verification requires an exact, complete, checksum-verified applied prefix; inspect migrate status and resolve the history discrepancy first", meta: { reason: "verification-history-unverified", migration: row.name, state: row.state } });
|
|
9041
9182
|
}
|
|
9042
9183
|
}
|
|
9043
9184
|
return params.migrations.slice(0, params.recorded.length);
|
|
@@ -9068,7 +9209,7 @@ async function verifyMigrationReference(params) {
|
|
|
9068
9209
|
}))).flat();
|
|
9069
9210
|
const gaps = migrationCoverageGaps({ gaps: coverageRows, namespace, namespaces, external });
|
|
9070
9211
|
if (gaps.length > 0)
|
|
9071
|
-
throw new
|
|
9212
|
+
throw new VibeError41({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Target contains objects whose full definitions are outside migration inspection coverage; verification is unavailable. No target changes were made.", meta: { feature: "targetInventoryCoverage", dialect, namespace, objects: gaps } });
|
|
9072
9213
|
const security = dialect === "postgres" ? await comparePostgresReferenceRls({ executor, referenceExecutor: params.referenceExecutor, schema: reference.state.schema }) : { inSync: true, objects: [] };
|
|
9073
9214
|
const inspected = await verifyMigrationState({ executor, dialect, expected: reference.state, external, probe: "none", batch: true });
|
|
9074
9215
|
const structural = dialect === "sqlite" ? compareSqlitePrimaryKeyRepresentations({ report: inspected }) : inspected;
|
|
@@ -9221,12 +9362,12 @@ async function mysqlComparisonFacts(params) {
|
|
|
9221
9362
|
return { ...facts, members };
|
|
9222
9363
|
}
|
|
9223
9364
|
function referenceContextError() {
|
|
9224
|
-
return new
|
|
9365
|
+
return new VibeError41({ code: "VIBE_MIGRATION", message: "Replay and target dialect/namespace differ; safe reference translation is unavailable. Use a supported same-namespace reference setup", meta: { reason: "verification-reference-context" } });
|
|
9225
9366
|
}
|
|
9226
9367
|
|
|
9227
9368
|
// packages/migrate/src/module-adoption-reference.ts
|
|
9228
9369
|
function refusal4(params) {
|
|
9229
|
-
return new
|
|
9370
|
+
return new VibeError42({ code: "VIBE_MIGRATION", message: "Module adoption needs complete independent source and application references with compatible ownership; no target SQL or metadata was authorized", meta: params });
|
|
9230
9371
|
}
|
|
9231
9372
|
function moduleAdoptionSource(params) {
|
|
9232
9373
|
if (params.migrations.length === 0)
|
|
@@ -9336,7 +9477,7 @@ async function verifyModuleAdoptionTarget(params) {
|
|
|
9336
9477
|
await assertLiveModuleOwnership({ executor: params.executor, origin, resume: true });
|
|
9337
9478
|
const report = await verifyMigrationReference({ executor: params.executor, dialect: origin.dialect, reference: params.reference, referenceExecutor: params.referenceExecutor });
|
|
9338
9479
|
if (!report.inSync)
|
|
9339
|
-
throw new
|
|
9480
|
+
throw new VibeError42({ code: "VIBE_MIGRATION", message: "Populated target differs from independently replayed package/application structure; no package SQL or metadata was applied", meta: { reason: "module-adoption-target-mismatch", report } });
|
|
9340
9481
|
}
|
|
9341
9482
|
|
|
9342
9483
|
// packages/migrate/src/history.ts
|
|
@@ -9352,10 +9493,10 @@ async function readMigrationHistory(params) {
|
|
|
9352
9493
|
|
|
9353
9494
|
// packages/migrate/src/module-adoption-record.ts
|
|
9354
9495
|
function refusal5(params) {
|
|
9355
|
-
return new
|
|
9496
|
+
return new VibeError43({ code: "VIBE_MIGRATION", message: "Module adoption cannot record an incomplete, concurrent or altered history; no package SQL was executed", meta: params });
|
|
9356
9497
|
}
|
|
9357
9498
|
function transactionFailure(params) {
|
|
9358
|
-
return params.cause instanceof
|
|
9499
|
+
return params.cause instanceof VibeError43 ? params.cause : new VibeError43({ code: "VIBE_MIGRATION", message: "Module adoption recording failed; inspect the local provenance and journal before retrying", meta: { reason: "module-adoption-transaction-failed" }, cause: params.cause });
|
|
9359
9500
|
}
|
|
9360
9501
|
async function recordModuleAdoption(params) {
|
|
9361
9502
|
moduleAdoptionSource(params);
|
|
@@ -9418,9 +9559,9 @@ async function recordModuleAdoption(params) {
|
|
|
9418
9559
|
await record();
|
|
9419
9560
|
}).then(undefined, async (cause) => {
|
|
9420
9561
|
if (committed)
|
|
9421
|
-
throw new
|
|
9562
|
+
throw new VibeError43({ code: "VIBE_MIGRATION", message: "All adopted revisions committed, but filesystem lease cleanup failed; inspect the remaining publication lease before retrying", meta: { reason: "module-adoption-publication-cleanup-failed", recorded: true }, cause });
|
|
9422
9563
|
await executor({ text: "ROLLBACK" }).then(undefined, (rollback) => {
|
|
9423
|
-
throw new
|
|
9564
|
+
throw new VibeError43({ code: "VIBE_MIGRATION", message: "Adoption recording failed and rollback also failed; the journal outcome is uncertain and must be inspected before recovery", meta: { reason: "module-adoption-rollback-failed" }, cause: new AggregateError([cause, rollback], "Adoption recording and rollback failed") });
|
|
9424
9565
|
});
|
|
9425
9566
|
throw transactionFailure({ cause });
|
|
9426
9567
|
});
|
|
@@ -9431,14 +9572,14 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
9431
9572
|
import { mkdtemp, rm } from "fs/promises";
|
|
9432
9573
|
import { tmpdir } from "os";
|
|
9433
9574
|
import { join } from "path";
|
|
9434
|
-
import { VibeError as
|
|
9575
|
+
import { VibeError as VibeError45, providerToDialect as providerToDialect6, sqlObjectKey as sqlObjectKey15, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
|
|
9435
9576
|
import { getSqlDialect as getSqlDialect14, renderShadowOperation as renderShadowOperation4, SHADOW_DATABASE as SHADOW_DATABASE3 } from "@vibeorm/sql";
|
|
9436
9577
|
|
|
9437
9578
|
// packages/migrate/src/shadow-dedicated.ts
|
|
9438
|
-
import { SQL_NAMESPACES, VibeError as
|
|
9579
|
+
import { SQL_NAMESPACES as SQL_NAMESPACES2, VibeError as VibeError44, providerToDialect as providerToDialect5, sqlNamespaceIssue as sqlNamespaceIssue2 } from "@vibeorm/schema";
|
|
9439
9580
|
import { getSqlDialect as getSqlDialect13, renderShadowOperation as renderShadowOperation3, SHADOW_DATABASE as SHADOW_DATABASE2 } from "@vibeorm/sql";
|
|
9440
9581
|
function refuse(params) {
|
|
9441
|
-
throw new
|
|
9582
|
+
throw new VibeError44({ code: "VIBE_MIGRATION", message: params.message, meta: { reason: params.reason, ...params.objects === undefined ? {} : { objects: params.objects } } });
|
|
9442
9583
|
}
|
|
9443
9584
|
function readIdentity(params) {
|
|
9444
9585
|
const row = params.rows[0];
|
|
@@ -9506,7 +9647,7 @@ async function readMarker(params) {
|
|
|
9506
9647
|
if (params.rows.length !== 1 || typeof payload !== "string")
|
|
9507
9648
|
return refuse({ reason: "shadow-ownership-invalid", message: "Shadow ownership marker is missing or malformed; automatic claim/reset is refused." });
|
|
9508
9649
|
const value = await Promise.resolve().then(() => JSON.parse(payload)).then((result) => result, (cause) => {
|
|
9509
|
-
throw new
|
|
9650
|
+
throw new VibeError44({ code: "VIBE_MIGRATION", message: "Shadow marker is not valid JSON", meta: { reason: "shadow-ownership-invalid" }, cause });
|
|
9510
9651
|
});
|
|
9511
9652
|
if (value === null || typeof value !== "object" || !("version" in value) || value.version !== 1 || !("ownerKey" in value) || typeof value.ownerKey !== "string" || !("identity" in value) || typeof value.identity !== "string" || !("status" in value) || value.status !== "READY" && value.status !== "BUILDING" || !("journal" in value) || !Array.isArray(value.journal) || !value.journal.every((row) => row !== null && typeof row === "object" && ("name" in row) && typeof row.name === "string" && ("checksum" in row) && typeof row.checksum === "string"))
|
|
9512
9653
|
return refuse({ reason: "shadow-ownership-invalid", message: "Shadow marker has an unsupported ownership or replay-journal shape." });
|
|
@@ -9520,8 +9661,8 @@ async function withDedicatedShadow(params) {
|
|
|
9520
9661
|
const intendedJournal = completedJournal({ rows: params.migrations.map((migration) => ({ name: migration.name, checksum: migrationChecksum(migration), statement_index: migration.sql.length, error: null })), reason: "shadow-replay-journal-changed" });
|
|
9521
9662
|
if (shadow.ownerKey.length === 0 || shadow.targetUrl.length === 0)
|
|
9522
9663
|
return refuse({ reason: "shadow-identity-unavailable", message: "Reusable shadow mode requires a project owner and a resolvable deployment target for an independent identity check." });
|
|
9523
|
-
const dialect =
|
|
9524
|
-
const declaredNamespaces = dialect === "postgres" ? migrationReferenceNamespaces({ migrations: params.migrations, namespace:
|
|
9664
|
+
const dialect = providerToDialect5({ provider: shadow.provider });
|
|
9665
|
+
const declaredNamespaces = dialect === "postgres" ? migrationReferenceNamespaces({ migrations: params.migrations, namespace: SQL_NAMESPACES2.postgres }).filter((namespace) => namespace !== SQL_NAMESPACES2.postgres) : [];
|
|
9525
9666
|
const sqlDialect = getSqlDialect13({ dialect });
|
|
9526
9667
|
const execute = async (executor, operation) => {
|
|
9527
9668
|
let rows = [];
|
|
@@ -9550,13 +9691,13 @@ async function withDedicatedShadow(params) {
|
|
|
9550
9691
|
return refuse({ reason: "shadow-identity-changed", message: "Shadow identity changed after lease acquisition; reset is refused." });
|
|
9551
9692
|
if (dialect === "mysql") {
|
|
9552
9693
|
const grants = await execute(executor, { kind: "grants" }).then(undefined, (cause) => {
|
|
9553
|
-
throw new
|
|
9694
|
+
throw new VibeError44({ code: "VIBE_MIGRATION", message: "Cannot establish dedicated MySQL catalog visibility from direct grants", meta: { reason: "shadow-inventory-unavailable" }, cause });
|
|
9554
9695
|
});
|
|
9555
9696
|
assertMysqlVisibility({ rows: grants, database: identity.database });
|
|
9556
9697
|
}
|
|
9557
9698
|
const existing = (await execute(executor, { kind: "markerExists" })).length > 0;
|
|
9558
9699
|
const inventory = await execute(executor, { kind: "inventory" }).then(undefined, (cause) => {
|
|
9559
|
-
throw new
|
|
9700
|
+
throw new VibeError44({ code: "VIBE_MIGRATION", message: "Cannot inspect the complete dedicated shadow inventory; no reset was attempted", meta: { reason: "shadow-inventory-unavailable" }, cause });
|
|
9560
9701
|
});
|
|
9561
9702
|
const externalReferences = inventory.filter((row) => row.kind === "EXTERNAL FOREIGN KEY");
|
|
9562
9703
|
if (externalReferences.length !== 0)
|
|
@@ -9609,7 +9750,7 @@ async function withDedicatedShadow(params) {
|
|
|
9609
9750
|
if (remaining.length !== 0)
|
|
9610
9751
|
return refuse({ reason: "shadow-reset-incomplete", message: "Shadow reset left objects behind. Replay was not started.", objects: remaining });
|
|
9611
9752
|
const result = await params.runReference(executor, dialect === "mysql" ? SHADOW_DATABASE2.table : undefined).then((value) => ({ ok: true, value }), (error) => {
|
|
9612
|
-
if (!(error instanceof
|
|
9753
|
+
if (!(error instanceof VibeError44) || error.meta.shadowInvalid === true || error.meta.shadowReusable !== true)
|
|
9613
9754
|
throw error;
|
|
9614
9755
|
return { ok: false, error };
|
|
9615
9756
|
});
|
|
@@ -9633,13 +9774,13 @@ async function withDedicatedShadow(params) {
|
|
|
9633
9774
|
function finishShadowWork(params) {
|
|
9634
9775
|
return Promise.resolve().then(params.run).then(async (value) => {
|
|
9635
9776
|
await Promise.resolve().then(params.cleanup).then(undefined, (cause) => {
|
|
9636
|
-
throw new
|
|
9777
|
+
throw new VibeError45({ code: "VIBE_MIGRATION", message: "Migration reference completed but its owned resource could not be cleaned. Inspect the named artifact before explicit cleanup.", meta: { cleanupReason: "shadow-cleanup-failed", artifact: params.artifact, cleanupFailure: cause instanceof VibeError45 ? cause.code : "unavailable" }, cause });
|
|
9637
9778
|
});
|
|
9638
9779
|
return value;
|
|
9639
9780
|
}, async (primary) => {
|
|
9640
9781
|
const [cleanup] = await Promise.allSettled([Promise.resolve().then(params.cleanup)]);
|
|
9641
9782
|
if (cleanup.status === "rejected")
|
|
9642
|
-
throw new
|
|
9783
|
+
throw new VibeError45({ code: primary instanceof VibeError45 ? primary.code : "VIBE_MIGRATION", message: "Migration reference failed and its owned resource could not be cleaned. Inspect the named artifact before explicit cleanup.", meta: { cleanupReason: "shadow-cleanup-failed", artifact: params.artifact, cleanupFailure: cleanup.reason instanceof VibeError45 ? cleanup.reason.code : "unavailable" }, cause: { primary, cleanup: cleanup.reason } });
|
|
9643
9784
|
throw primary;
|
|
9644
9785
|
});
|
|
9645
9786
|
}
|
|
@@ -9651,21 +9792,21 @@ function withShadowConnection(params) {
|
|
|
9651
9792
|
return finishShadowWork({ artifact: params.artifact, cleanup: () => params.connection.close(), run: () => params.connection.executor.withSession === undefined ? runPinned(params.connection.executor) : params.connection.executor.withSession({ run: runPinned }) });
|
|
9652
9793
|
}
|
|
9653
9794
|
async function withMigrationReference(params) {
|
|
9654
|
-
const dialect =
|
|
9795
|
+
const dialect = providerToDialect6({ provider: params.shadow.provider });
|
|
9655
9796
|
const external = params.external ?? params.replay?.external;
|
|
9656
9797
|
const replay = { ...params.replay, external, migrations: params.migrations, dialect };
|
|
9657
9798
|
validateMigrationReplay(replay);
|
|
9658
9799
|
for (const origin of params.namespaceOrigins ?? []) {
|
|
9659
9800
|
assertModuleOrigin({ value: origin });
|
|
9660
9801
|
if (origin.dialect !== dialect)
|
|
9661
|
-
throw new
|
|
9802
|
+
throw new VibeError45({ code: "VIBE_MIGRATION", message: "Namespace context origin uses a different dialect", meta: { reason: "module-dialect-mismatch", moduleId: origin.moduleId } });
|
|
9662
9803
|
}
|
|
9663
9804
|
const hasAdoption = params.migrations.some((migration) => migration.moduleAdoption !== undefined);
|
|
9664
9805
|
if (hasAdoption && params.shadow.provider === "mysql")
|
|
9665
9806
|
throw unsupportedCapability13({ dialect: "mysql", feature: "moduleAdoptionIndependentMysqlNamespace", hint: "Independent source and application databases require a proven metadata rebase before grouped adoption replay is supported" });
|
|
9666
9807
|
const adoptionShadow = params.shadow.mode === "automatic" ? params.shadow : undefined;
|
|
9667
9808
|
if (hasAdoption && adoptionShadow === undefined)
|
|
9668
|
-
throw new
|
|
9809
|
+
throw new VibeError45({ code: "VIBE_CONFIG", message: "Adopted history requires an independent automatic package source reference; a single dedicated shadow cannot prove both histories. Configure shadowAdminUrl.", meta: { reason: "module-adoption-automatic-shadow-required" } });
|
|
9669
9810
|
const inspect = async (executor, privateTable) => {
|
|
9670
9811
|
if ((external?.length ?? 0) > 0) {
|
|
9671
9812
|
const ownership = await inspectExternalObjects({ executor, dialect, external });
|
|
@@ -9692,7 +9833,7 @@ async function withMigrationReference(params) {
|
|
|
9692
9833
|
await withMigrationReference({ shadow: adoptionShadow, openConnection: params.openConnection, migrations: moduleAdoptionSource({ migrations: group }), replay: sourceReplay, run: async ({ executor: sourceExecutor, reference: source }) => {
|
|
9693
9834
|
const fingerprint = moduleAdoptionReferenceFingerprint({ migrations: group, reference: source });
|
|
9694
9835
|
if (group.some((migration) => migration.moduleAdoption?.referenceFingerprint !== fingerprint))
|
|
9695
|
-
throw new
|
|
9836
|
+
throw new VibeError45({ code: "VIBE_MIGRATION", message: "Local adoption proof does not match independently replayed immutable package source", meta: { reason: "module-adoption-reference-unverified", moduleId: first.moduleOrigin?.moduleId } });
|
|
9696
9837
|
const application = await captureReplayReference({
|
|
9697
9838
|
executor,
|
|
9698
9839
|
dialect,
|
|
@@ -9739,14 +9880,14 @@ async function withMigrationReference(params) {
|
|
|
9739
9880
|
} });
|
|
9740
9881
|
}
|
|
9741
9882
|
if (!("adminUrl" in params.shadow))
|
|
9742
|
-
throw new
|
|
9883
|
+
throw new VibeError45({ code: "VIBE_CONFIG", message: "Automatic server shadows require an explicit admin URL" });
|
|
9743
9884
|
const { provider, adminUrl } = params.shadow;
|
|
9744
9885
|
const url = await Promise.resolve().then(() => new URL(adminUrl)).then((value) => value, (cause) => {
|
|
9745
|
-
throw new
|
|
9886
|
+
throw new VibeError45({ code: "VIBE_CONFIG", message: "shadowAdminUrl must be an explicit server database URL", cause });
|
|
9746
9887
|
});
|
|
9747
9888
|
const expected = provider === "postgresql" ? ["postgres:", "postgresql:"] : ["mysql:"];
|
|
9748
9889
|
if (!expected.includes(url.protocol) || url.hostname.length === 0 || url.pathname.length <= 1 || [...url.searchParams.keys()].some((key) => ["database", "dbname", "host", "hostaddr", "port", "user", "socketpath", "connectionstring", "uri", "url", "service"].includes(key.toLowerCase())))
|
|
9749
|
-
throw new
|
|
9890
|
+
throw new VibeError45({ code: "VIBE_CONFIG", message: "Shadow admin URL has an unsupported protocol or conflicting connection selector", meta: { reason: "shadow-url-ambiguous" } });
|
|
9750
9891
|
const prefix = /^\/vibeorm_test(?:_|$)/.test(url.pathname) ? "vibeorm_test_shadow" : "vibeorm_shadow";
|
|
9751
9892
|
const name = `${prefix}_${randomUUID3().replaceAll("-", "")}`;
|
|
9752
9893
|
const connection = await params.openConnection({ provider, url: adminUrl });
|
|
@@ -9754,7 +9895,7 @@ async function withMigrationReference(params) {
|
|
|
9754
9895
|
const sqlDialect = getSqlDialect14({ dialect });
|
|
9755
9896
|
for (const query of renderShadowOperation4({ dialect: sqlDialect, operation: { kind: "createDatabase", name } })) {
|
|
9756
9897
|
await admin({ text: query.text, values: [...query.values] }).then(undefined, (cause) => {
|
|
9757
|
-
throw new
|
|
9898
|
+
throw new VibeError45({ code: "VIBE_MIGRATION", message: "Shadow CREATE did not report success. No deletion was attempted; inspect the named artifact on the configured admin server before explicit cleanup.", meta: { reason: "shadow-create-outcome-unknown", artifact: name }, cause });
|
|
9758
9899
|
});
|
|
9759
9900
|
}
|
|
9760
9901
|
return finishShadowWork({ artifact: name, cleanup: async () => {
|
|
@@ -9769,10 +9910,10 @@ async function withMigrationReference(params) {
|
|
|
9769
9910
|
}
|
|
9770
9911
|
// packages/migrate/src/replay-objects.ts
|
|
9771
9912
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
9772
|
-
import { VibeError as
|
|
9913
|
+
import { VibeError as VibeError46, sqlObjectKey as sqlObjectKey16 } from "@vibeorm/schema";
|
|
9773
9914
|
import { getSqlDialect as getSqlDialect15 } from "@vibeorm/sql";
|
|
9774
9915
|
function comparisonFailure(params) {
|
|
9775
|
-
return params.cause instanceof
|
|
9916
|
+
return params.cause instanceof VibeError46 ? params.cause : new VibeError46({ code: "VIBE_MIGRATION", message: "Authored custom SQL could not be safely compiled on the independent scratch reference. Review dependencies and provide an explicit SQL migration when replacement is not supported.", meta: { reason: "migration-object-comparison-unavailable" }, cause: params.cause });
|
|
9776
9917
|
}
|
|
9777
9918
|
function leadingCommand(params) {
|
|
9778
9919
|
const { sql } = params;
|
|
@@ -9814,11 +9955,11 @@ async function compareReplayMigrationObjects(params) {
|
|
|
9814
9955
|
if (objects.length === 0)
|
|
9815
9956
|
return params.schema;
|
|
9816
9957
|
if (params.reference.dialect !== "postgres")
|
|
9817
|
-
throw new
|
|
9958
|
+
throw new VibeError46({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Independent authored custom-object comparison requires transactional scratch DDL. Use reviewed SQL for this dialect's custom objects.", meta: { feature: "authoredCustomObjectComparison", dialect: params.reference.dialect, objects: objects.map((object) => object.identity) } });
|
|
9818
9959
|
const before = new Map(params.reference.state.definitions.map((definition) => [sqlObjectKey16({ identity: definition.identity }), definition]));
|
|
9819
9960
|
for (const object of objects) {
|
|
9820
9961
|
if (object.dialect !== params.reference.dialect || object.create.length === 0 || before.has(sqlObjectKey16({ identity: object.identity })) && object.drop.length === 0 || [...object.create, ...object.drop].some((sql) => /^(?:BEGIN|START|COMMIT|END|ABORT|ROLLBACK|PREPARE|VACUUM|CREATE\s+DATABASE|DROP\s+DATABASE)\b/i.test(leadingCommand({ sql }))))
|
|
9821
|
-
throw new
|
|
9962
|
+
throw new VibeError46({ code: "VIBE_MIGRATION", message: "Custom object cannot be safely dropped/recreated in a rollback-only shadow transaction. Supply a reviewed SQL migration.", meta: { reason: "migration-object-comparison-unavailable", identity: object.identity } });
|
|
9822
9963
|
}
|
|
9823
9964
|
const definitions = new Map;
|
|
9824
9965
|
const sentinel = getSqlDialect15({ dialect: "postgres" }).quoteIdent({ name: `vibe_probe_${randomUUID4().replaceAll("-", "")}` });
|
|
@@ -9826,7 +9967,7 @@ async function compareReplayMigrationObjects(params) {
|
|
|
9826
9967
|
await Promise.resolve().then(() => params.executor({ text: `RELEASE SAVEPOINT ${sentinel}` })).then(() => {
|
|
9827
9968
|
return;
|
|
9828
9969
|
}, (cause) => {
|
|
9829
|
-
throw new
|
|
9970
|
+
throw new VibeError46({ code: "VIBE_MIGRATION", message: "Authored SQL destroyed the scratch transaction or its savepoint. Discard this scratch reference; no comparison result can be trusted.", meta: { reason: "migration-object-transaction-lost", shadowInvalid: true }, cause });
|
|
9830
9971
|
});
|
|
9831
9972
|
await params.executor({ text: `SAVEPOINT ${sentinel}` });
|
|
9832
9973
|
};
|
|
@@ -9854,7 +9995,7 @@ async function compareReplayMigrationObjects(params) {
|
|
|
9854
9995
|
for (const object of objects) {
|
|
9855
9996
|
const inspected = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: object.identity });
|
|
9856
9997
|
if (inspected.state !== "present")
|
|
9857
|
-
throw new
|
|
9998
|
+
throw new VibeError46({ code: "VIBE_MIGRATION", message: "Authored custom SQL did not produce an inspectable object in the independent scratch probe.", meta: { reason: "migration-object-comparison-unavailable", identity: object.identity, state: inspected.state } });
|
|
9858
9999
|
definitions.set(sqlObjectKey16({ identity: object.identity }), inspected.definition);
|
|
9859
10000
|
}
|
|
9860
10001
|
for (const [key, expected] of before) {
|
|
@@ -9862,32 +10003,173 @@ async function compareReplayMigrationObjects(params) {
|
|
|
9862
10003
|
continue;
|
|
9863
10004
|
const inspected = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: expected.identity });
|
|
9864
10005
|
if (inspected.state !== "present" || canonicalMigrationJson({ value: inspected.definition }) !== canonicalMigrationJson({ value: expected }))
|
|
9865
|
-
throw new
|
|
10006
|
+
throw new VibeError46({ code: "VIBE_MIGRATION", message: "Custom scratch replacement changed a dependent object outside the declared replacement set. Supply reviewed SQL.", meta: { reason: "migration-object-dependency-unavailable", identity: expected.identity } });
|
|
9866
10007
|
}
|
|
9867
10008
|
await checkSentinel();
|
|
9868
10009
|
}).then(() => ({ ok: true }), (cause) => ({ ok: false, error: comparisonFailure({ cause }) }));
|
|
9869
|
-
const rollback = await Promise.resolve().then(() => params.executor({ text: "ROLLBACK" })).then(() => ({ ok: true }), (cause) => ({ ok: false, error: cause instanceof
|
|
10010
|
+
const rollback = await Promise.resolve().then(() => params.executor({ text: "ROLLBACK" })).then(() => ({ ok: true }), (cause) => ({ ok: false, error: cause instanceof VibeError46 ? cause : new VibeError46({ code: "VIBE_MIGRATION", message: "Scratch rollback failed", meta: { reason: "migration-object-rollback-failed" }, cause }) }));
|
|
9870
10011
|
if (!rollback.ok)
|
|
9871
|
-
throw new
|
|
10012
|
+
throw new VibeError46({ code: "VIBE_MIGRATION", message: "Custom comparison scratch could not be rolled back. Discard the scratch reference and inspect the preserved execution and cleanup errors.", meta: { reason: "migration-object-rollback-failed", shadowInvalid: true, cleanupFailure: rollback.error }, cause: outcome.ok ? rollback.error : outcome.error });
|
|
9872
10013
|
if (!outcome.ok)
|
|
9873
10014
|
throw outcome.error;
|
|
9874
10015
|
for (const object of objects) {
|
|
9875
10016
|
const key = sqlObjectKey16({ identity: object.identity });
|
|
9876
10017
|
const previous = before.get(key);
|
|
9877
10018
|
if (previous !== undefined && canonicalMigrationJson({ value: previous }) !== canonicalMigrationJson({ value: definitions.get(key) }))
|
|
9878
|
-
throw new
|
|
10019
|
+
throw new VibeError46({ code: "VIBE_MIGRATION", message: "Authored custom object differs from replayed history. Supply a reviewed forward SQL migration; generation will not silently replace its body.", meta: { reason: "migration-object-replacement-required", shadowReusable: true, identity: object.identity, id: object.id } });
|
|
9879
10020
|
}
|
|
9880
10021
|
return { ...params.schema, migrationObjects: objects.map((object) => ({ ...object, expected: definitions.get(sqlObjectKey16({ identity: object.identity })) })) };
|
|
9881
10022
|
}
|
|
9882
10023
|
// packages/migrate/src/replay-plan.ts
|
|
9883
|
-
import { VibeError as
|
|
10024
|
+
import { VibeError as VibeError51, sqlObjectKey as sqlObjectKey19 } from "@vibeorm/schema";
|
|
10025
|
+
|
|
10026
|
+
// packages/migrate/src/sqlite-fts-upgrade.ts
|
|
10027
|
+
import { VibeError as VibeError47, sqlObjectKey as sqlObjectKey17 } from "@vibeorm/schema";
|
|
10028
|
+
import { sqliteFtsUpdateUpgrade } from "@vibeorm/sql";
|
|
10029
|
+
var actionReceipts = new WeakMap;
|
|
10030
|
+
function specs(params) {
|
|
10031
|
+
const result = [];
|
|
10032
|
+
for (const group of params.collected.groups)
|
|
10033
|
+
for (const artifact of group.objects) {
|
|
10034
|
+
const owner = artifact.owns;
|
|
10035
|
+
const expected = artifact.expected;
|
|
10036
|
+
if (owner.kind !== "trigger" || artifact.create.length !== 0 || artifact.drop.length !== 0 || expected?.identity.kind !== "trigger" || expected.identity.table.namespace !== "main" || expected.format !== "catalog-v2" || expected.facts.type !== "trigger" || typeof expected.facts.definition !== "string" || expected.dependencies.length !== 0)
|
|
10037
|
+
continue;
|
|
10038
|
+
const identity = { kind: "trigger", table: { namespace: "main", name: owner.table }, name: owner.name };
|
|
10039
|
+
if (sqlObjectKey17({ identity }) !== sqlObjectKey17({ identity: expected.identity }))
|
|
10040
|
+
continue;
|
|
10041
|
+
const matches = [];
|
|
10042
|
+
for (const bundle of group.objects) {
|
|
10043
|
+
if (bundle.owns.kind !== "trigger" || bundle.owns.table !== owner.table || bundle.create.length !== 4 || bundle.drop.length !== 3)
|
|
10044
|
+
continue;
|
|
10045
|
+
const create = bundle.create[2];
|
|
10046
|
+
const parsed = sqliteFtsUpdateUpgrade({ create, installed: expected.facts.definition, table: owner.table, name: owner.name });
|
|
10047
|
+
if (parsed?.kind !== "tableRequired" || bundle.owns.name !== `${parsed.ftsTable}_ai`)
|
|
10048
|
+
continue;
|
|
10049
|
+
const tableArtifact = group.objects.find((entry) => entry.owns.kind === "table" && entry.owns.name === parsed.ftsTable && entry.create.length === 1);
|
|
10050
|
+
if (tableArtifact === undefined)
|
|
10051
|
+
continue;
|
|
10052
|
+
const declared = sqliteFtsUpdateUpgrade({ create, installed: expected.facts.definition, table: owner.table, name: owner.name, tableDefinition: tableArtifact.create[0] });
|
|
10053
|
+
if (declared?.kind !== "recognized" || declared.installed !== "current")
|
|
10054
|
+
continue;
|
|
10055
|
+
matches.push({ artifact, bundle, tableArtifact, identity, create, ftsTable: declared.ftsTable, currentDefinition: declared.currentDefinition });
|
|
10056
|
+
}
|
|
10057
|
+
if (matches.length === 1)
|
|
10058
|
+
result.push(matches[0]);
|
|
10059
|
+
}
|
|
10060
|
+
return result;
|
|
10061
|
+
}
|
|
10062
|
+
function tableIdentity(params) {
|
|
10063
|
+
return { kind: "table", name: { namespace: "main", name: params.spec.ftsTable } };
|
|
10064
|
+
}
|
|
10065
|
+
function unavailable2(params) {
|
|
10066
|
+
return { identity: params.identity, ownership: "migration", state: "unavailable", reason: params.reason, differences: [] };
|
|
10067
|
+
}
|
|
10068
|
+
async function planSqliteFtsUpdates(params) {
|
|
10069
|
+
const states = new Map;
|
|
10070
|
+
const forcedArtifacts = new Set;
|
|
10071
|
+
const before = [];
|
|
10072
|
+
const after = [];
|
|
10073
|
+
const checks = [];
|
|
10074
|
+
for (const spec of specs(params)) {
|
|
10075
|
+
const key = sqlObjectKey17({ identity: spec.identity });
|
|
10076
|
+
const live = await inspectSqlObject({ executor: params.executor, dialect: "sqlite", identity: spec.identity });
|
|
10077
|
+
const state = compareInspectedSqlObject({ identity: spec.identity, ownership: "migration", expected: spec.artifact.expected, live, dialect: "sqlite" });
|
|
10078
|
+
states.set(key, state);
|
|
10079
|
+
if (live.state === "unavailable")
|
|
10080
|
+
continue;
|
|
10081
|
+
const beforeTrigger = live.state === "present" && typeof live.definition.facts.definition === "string" ? live.definition.facts.definition : undefined;
|
|
10082
|
+
if (live.state === "present" && beforeTrigger === undefined)
|
|
10083
|
+
continue;
|
|
10084
|
+
const candidate = sqliteFtsUpdateUpgrade({ create: spec.create, installed: beforeTrigger ?? spec.currentDefinition, table: spec.identity.table.name, name: spec.identity.name });
|
|
10085
|
+
if (candidate?.kind !== "tableRequired")
|
|
10086
|
+
continue;
|
|
10087
|
+
const table = await inspectSqlObject({ executor: params.executor, dialect: "sqlite", identity: tableIdentity({ spec }) });
|
|
10088
|
+
if (table.state === "unavailable") {
|
|
10089
|
+
states.set(key, unavailable2({ identity: spec.identity, reason: table.reason }));
|
|
10090
|
+
continue;
|
|
10091
|
+
}
|
|
10092
|
+
const beforeTable = table.state === "present" && typeof table.definition.facts.definition === "string" ? table.definition.facts.definition : undefined;
|
|
10093
|
+
const recognized = sqliteFtsUpdateUpgrade({
|
|
10094
|
+
create: spec.create,
|
|
10095
|
+
installed: beforeTrigger ?? spec.currentDefinition,
|
|
10096
|
+
table: spec.identity.table.name,
|
|
10097
|
+
name: spec.identity.name,
|
|
10098
|
+
tableDefinition: table.state === "missing" ? spec.tableArtifact.create[0] : beforeTable ?? ""
|
|
10099
|
+
});
|
|
10100
|
+
if (recognized?.kind !== "recognized") {
|
|
10101
|
+
states.set(key, unavailable2({ identity: spec.identity, reason: "sqlite-fts-table-unverified" }));
|
|
10102
|
+
continue;
|
|
10103
|
+
}
|
|
10104
|
+
const installBundle = table.state === "missing" || !params.installed.triggers?.has(`${spec.ftsTable}_ai`) || !params.installed.triggers?.has(`${spec.ftsTable}_ad`);
|
|
10105
|
+
if (table.state === "missing")
|
|
10106
|
+
forcedArtifacts.add(spec.tableArtifact.id);
|
|
10107
|
+
if (installBundle)
|
|
10108
|
+
forcedArtifacts.add(spec.bundle.id);
|
|
10109
|
+
else if (live.state === "missing")
|
|
10110
|
+
after.push(spec.create, spec.bundle.create[3]);
|
|
10111
|
+
const legacy = live.state === "present" && recognized.installed === "legacy";
|
|
10112
|
+
if (legacy) {
|
|
10113
|
+
before.push(recognized.drop);
|
|
10114
|
+
if (!installBundle)
|
|
10115
|
+
after.push(spec.create);
|
|
10116
|
+
states.set(key, { ...state, state: "changed", reason: "sqlite-fts-update-upgrade" });
|
|
10117
|
+
}
|
|
10118
|
+
checks.push({ spec, beforeTrigger, beforeTable, changes: installBundle || live.state === "missing" || legacy });
|
|
10119
|
+
}
|
|
10120
|
+
return { states, forcedArtifacts, before, after, checks };
|
|
10121
|
+
}
|
|
10122
|
+
function retainSqliteFtsPlan(params) {
|
|
10123
|
+
if (params.fts.checks.length > 0)
|
|
10124
|
+
actionReceipts.set(params.plan, { fts: params.fts, ...params.transaction === undefined ? {} : { transaction: params.transaction } });
|
|
10125
|
+
}
|
|
10126
|
+
function sqliteFtsTransaction(params) {
|
|
10127
|
+
return actionReceipts.get(params.plan)?.transaction;
|
|
10128
|
+
}
|
|
10129
|
+
function stateChanged(params) {
|
|
10130
|
+
return new VibeError47({ code: "VIBE_MIGRATION", message: "SQLite FTS state changed after planning; re-plan before replacing synchronization", meta: { reason: "sqlite-fts-state-changed", identity: params.identity } });
|
|
10131
|
+
}
|
|
10132
|
+
async function assertSqliteFtsApplyState(params) {
|
|
10133
|
+
const receipt = actionReceipts.get(params.plan)?.fts;
|
|
10134
|
+
if (receipt === undefined)
|
|
10135
|
+
return;
|
|
10136
|
+
for (const check of receipt.checks) {
|
|
10137
|
+
if (!check.changes)
|
|
10138
|
+
continue;
|
|
10139
|
+
for (const observation of [{ identity: check.spec.identity, definition: check.beforeTrigger }, { identity: tableIdentity({ spec: check.spec }), definition: check.beforeTable }]) {
|
|
10140
|
+
const live = await inspectSqlObject({ executor: params.executor, dialect: "sqlite", identity: observation.identity });
|
|
10141
|
+
if (observation.definition === undefined ? live.state !== "missing" : live.state !== "present" || live.definition.facts.definition !== observation.definition)
|
|
10142
|
+
throw stateChanged({ identity: observation.identity });
|
|
10143
|
+
}
|
|
10144
|
+
}
|
|
10145
|
+
}
|
|
10146
|
+
async function verifySqliteFtsApplyState(params) {
|
|
10147
|
+
const receipt = actionReceipts.get(params.plan)?.fts;
|
|
10148
|
+
if (receipt === undefined)
|
|
10149
|
+
return;
|
|
10150
|
+
for (const { spec } of receipt.checks) {
|
|
10151
|
+
const live = await inspectSqlObject({ executor: params.executor, dialect: "sqlite", identity: spec.identity });
|
|
10152
|
+
const expected = { identity: spec.identity, format: "catalog-v2", facts: { definition: spec.currentDefinition, type: "trigger" }, dependencies: [] };
|
|
10153
|
+
const compared = compareInspectedSqlObject({ identity: spec.identity, ownership: "migration", expected, live, dialect: "sqlite" });
|
|
10154
|
+
if (compared.state !== "matching")
|
|
10155
|
+
throw new VibeError47({ code: "VIBE_MIGRATION", message: "SQLite FTS UPDATE synchronization did not match its independent definition after apply", meta: { reason: "migration-state-unverified", objects: [compared] } });
|
|
10156
|
+
const table = await inspectSqlObject({ executor: params.executor, dialect: "sqlite", identity: tableIdentity({ spec }) });
|
|
10157
|
+
if (table.state !== "present" || typeof table.definition.facts.definition !== "string" || sqliteFtsUpdateUpgrade({ create: spec.create, installed: spec.currentDefinition, table: spec.identity.table.name, name: spec.identity.name, tableDefinition: table.definition.facts.definition })?.kind !== "recognized")
|
|
10158
|
+
throw stateChanged({ identity: tableIdentity({ spec }) });
|
|
10159
|
+
for (const suffix of ["ai", "ad"]) {
|
|
10160
|
+
const identity = { kind: "trigger", table: spec.identity.table, name: `${spec.ftsTable}_${suffix}` };
|
|
10161
|
+
if ((await inspectSqlObject({ executor: params.executor, dialect: "sqlite", identity })).state !== "present")
|
|
10162
|
+
throw stateChanged({ identity });
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
}
|
|
9884
10166
|
|
|
9885
10167
|
// packages/migrate/src/planner.ts
|
|
9886
|
-
import { VibeError as
|
|
10168
|
+
import { VibeError as VibeError50, sqlObjectKey as sqlObjectKey18, qualifiedSqlName as qualifiedSqlName13 } from "@vibeorm/schema";
|
|
9887
10169
|
import { renderSqliteRebuildObjects, sqliteDialect as sqliteDialect4 } from "@vibeorm/sql";
|
|
9888
10170
|
|
|
9889
10171
|
// packages/migrate/src/artifact-probe.ts
|
|
9890
|
-
import { VibeError as
|
|
10172
|
+
import { VibeError as VibeError48 } from "@vibeorm/schema";
|
|
9891
10173
|
var SAVEPOINT = "vibeorm_artifact_probe";
|
|
9892
10174
|
async function probeChangedArtifacts(params) {
|
|
9893
10175
|
const { executor, namespace } = params;
|
|
@@ -9910,7 +10192,7 @@ async function probeChangedArtifacts(params) {
|
|
|
9910
10192
|
await executor({ text: `RELEASE SAVEPOINT ${SAVEPOINT}` });
|
|
9911
10193
|
}
|
|
9912
10194
|
if (after.state !== "present")
|
|
9913
|
-
throw new
|
|
10195
|
+
throw new VibeError48({ code: "VIBE_MIGRATION", message: `Extension artifact ${artifact.id} did not produce its object when probed; its create SQL cannot be verified against the replayed history`, meta: { reason: "artifact-probe-unavailable", id: artifact.id, identity } });
|
|
9914
10196
|
if (canonicalMigrationJson({ value: before.definition.facts }) === canonicalMigrationJson({ value: after.definition.facts }))
|
|
9915
10197
|
continue;
|
|
9916
10198
|
const definition = before.definition.facts.definition;
|
|
@@ -9920,8 +10202,8 @@ async function probeChangedArtifacts(params) {
|
|
|
9920
10202
|
}
|
|
9921
10203
|
|
|
9922
10204
|
// packages/migrate/src/index-predicates.ts
|
|
9923
|
-
import { VibeError as
|
|
9924
|
-
import { defaultIndexName as defaultIndexName3, qualifiedSqlName as
|
|
10205
|
+
import { VibeError as VibeError49 } from "@vibeorm/schema";
|
|
10206
|
+
import { defaultIndexName as defaultIndexName3, qualifiedSqlName as qualifiedSqlName12 } from "@vibeorm/schema";
|
|
9925
10207
|
function physicalIndexName(params) {
|
|
9926
10208
|
return params.index.name ?? defaultIndexName3({ table: params.table, columns: params.columns, unique: params.index.kind === "unique", dialect: "postgres" });
|
|
9927
10209
|
}
|
|
@@ -9948,7 +10230,7 @@ async function prepareIndexPredicateDiff(params) {
|
|
|
9948
10230
|
indexes.push(index);
|
|
9949
10231
|
continue;
|
|
9950
10232
|
}
|
|
9951
|
-
const deparsed = await deparseIndexPredicate({ executor: params.executor, table:
|
|
10233
|
+
const deparsed = await deparseIndexPredicate({ executor: params.executor, table: qualifiedSqlName12({ name: table, namespace: live.namespace, defaultNamespace: params.namespace }), where: next.where });
|
|
9952
10234
|
indexes.push(deparsed === index.where ? { ...index, where: next.where } : index);
|
|
9953
10235
|
}
|
|
9954
10236
|
models.push({ ...live, indexes });
|
|
@@ -9961,7 +10243,7 @@ async function deparseIndexPredicate(params) {
|
|
|
9961
10243
|
const match = /^CHECK \((.*)\)$/s.exec(definition);
|
|
9962
10244
|
return match === null ? undefined : match[1];
|
|
9963
10245
|
} catch (error) {
|
|
9964
|
-
if (error instanceof
|
|
10246
|
+
if (error instanceof VibeError49 && error.code === "VIBE_MIGRATION")
|
|
9965
10247
|
throw error;
|
|
9966
10248
|
return;
|
|
9967
10249
|
}
|
|
@@ -9985,19 +10267,23 @@ async function planSchemaMigration(params) {
|
|
|
9985
10267
|
assertRetainedMigrationObjects({ from: (previous?.schema.migrationObjects ?? []).filter((object) => object.identity.kind !== "namespace" || !schemaNamespaces({ schema }).includes(object.identity.name)), to: objects });
|
|
9986
10268
|
for (const object of objects)
|
|
9987
10269
|
if (object.dialect !== dialect)
|
|
9988
|
-
throw new
|
|
10270
|
+
throw new VibeError50({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}`, meta: { feature: "migrationObject", dialect } });
|
|
9989
10271
|
const physical = params.physical ?? (dialect === "postgres" ? await introspectPhysicalPostgres({ executor, namespaces }) : dialect === "mysql" ? await introspectMysql({ executor }) : await introspectSqlite({ executor }));
|
|
9990
10272
|
const comparablePhysical = external === undefined ? physical : projectExternalSchema({ schema: physical, external });
|
|
9991
10273
|
if (previous !== undefined)
|
|
9992
10274
|
assertMigrationState({ report: await verifyMigrationState({ executor, expected: previous, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
9993
10275
|
const inventory = params.physicalInventory ?? external?.inventory ?? (await Promise.all((dialect === "postgres" ? namespaces : [namespace]).map((scope) => inspectMigrationInventory({ executor, dialect, namespace: scope })))).flat();
|
|
9994
10276
|
const comparisonInventory = external === undefined ? inventory : projectExternalInventory({ inventory, external });
|
|
9995
|
-
const
|
|
10277
|
+
const visibleModels = new Set(physical.models.map((model) => physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect })));
|
|
10278
|
+
let visible;
|
|
9996
10279
|
for (const object of inventory) {
|
|
9997
10280
|
const identity = object.identity;
|
|
9998
|
-
if (identity.kind !== "table" ||
|
|
10281
|
+
if (identity.kind !== "table" || visibleModels.has(physicalNameKey({ ...identity.name, dialect })))
|
|
10282
|
+
continue;
|
|
10283
|
+
visible ??= normalizeSchema({ schema: physical, dialect });
|
|
10284
|
+
if (visible.joinTables.has(physicalNameKey({ ...identity.name, dialect })))
|
|
9999
10285
|
continue;
|
|
10000
|
-
throw new
|
|
10286
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Catalog table is not visible to schema introspection; automatic planning cannot prove the complete physical state", meta: { reason: "inventory-unavailable", identity: object.identity } });
|
|
10001
10287
|
}
|
|
10002
10288
|
const states = resolved.unavailable.map((identity) => {
|
|
10003
10289
|
const exists = identity.kind === "column" && [physical, previous?.schema].some((source) => source?.models.some((model) => physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect }) === physicalNameKey({ ...identity.table, dialect }) && model.fields.some((field) => (field.dbName ?? field.name) === identity.column)));
|
|
@@ -10008,13 +10294,13 @@ async function planSchemaMigration(params) {
|
|
|
10008
10294
|
const creates = [];
|
|
10009
10295
|
for (const object of objects) {
|
|
10010
10296
|
for (const dependency of object.dependsOn) {
|
|
10011
|
-
if ([...objects.map((entry) => entry.identity), ...schemaObjectIdentities({ schema, dialect, namespace })].some((identity) =>
|
|
10297
|
+
if ([...objects.map((entry) => entry.identity), ...schemaObjectIdentities({ schema, dialect, namespace })].some((identity) => sqlObjectKey18({ identity }) === sqlObjectKey18({ identity: dependency })))
|
|
10012
10298
|
continue;
|
|
10013
10299
|
const state = await inspectSqlObject({ executor, dialect, identity: dependency });
|
|
10014
10300
|
if (state.state !== "present")
|
|
10015
10301
|
states.push({ identity: dependency, ownership: "migration", state: "unavailable", reason: "dependency-unavailable", differences: [] });
|
|
10016
10302
|
}
|
|
10017
|
-
const expected = object.expected ?? previous?.definitions.find((entry) =>
|
|
10303
|
+
const expected = object.expected ?? previous?.definitions.find((entry) => sqlObjectKey18({ identity: entry.identity }) === sqlObjectKey18({ identity: object.identity }));
|
|
10018
10304
|
const live = await inspectSqlObject({ executor, dialect, identity: object.identity });
|
|
10019
10305
|
if (object.identity.kind === "namespace") {
|
|
10020
10306
|
states.push({ identity: object.identity, ownership: "schema", state: live.state === "present" ? "matching" : live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
|
|
@@ -10026,14 +10312,21 @@ async function planSchemaMigration(params) {
|
|
|
10026
10312
|
states.push({ identity: object.identity, ownership: "migration", state: "missing", differences: [] });
|
|
10027
10313
|
creates.push(...object.create);
|
|
10028
10314
|
} else {
|
|
10029
|
-
|
|
10030
|
-
states.push(...report.objects);
|
|
10315
|
+
states.push(compareInspectedSqlObject({ identity: object.identity, ownership: "migration", expected, live, dialect }));
|
|
10031
10316
|
}
|
|
10032
10317
|
}
|
|
10318
|
+
for (const object of legacyObjects)
|
|
10319
|
+
if (objects.some((entry) => sqlObjectKey18({ identity: entry.identity }) === sqlObjectKey18({ identity: object.identity })))
|
|
10320
|
+
throw new VibeError50({ code: "VIBE_SCHEMA_INVALID", message: "Duplicate combined and migration-only ownership", meta: { identity: object.identity } });
|
|
10321
|
+
const installed = await probeInstalledExtensionState({ executor, dialect, artifacts });
|
|
10322
|
+
const sqliteFts = dialect === "sqlite" && collected.objects.some((artifact) => artifact.owns.kind === "trigger" && artifact.create.length === 0 && artifact.drop.length === 0 && artifact.expected !== undefined) ? await planSqliteFtsUpdates({ executor, collected, installed }) : undefined;
|
|
10033
10323
|
for (const object of legacyObjects) {
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10324
|
+
const qualifiedFts = sqliteFts?.states.get(sqlObjectKey18({ identity: object.identity }));
|
|
10325
|
+
if (qualifiedFts !== undefined) {
|
|
10326
|
+
states.push(qualifiedFts);
|
|
10327
|
+
continue;
|
|
10328
|
+
}
|
|
10329
|
+
const expected = object.expected ?? previous?.definitions.find((entry) => sqlObjectKey18({ identity: entry.identity }) === sqlObjectKey18({ identity: object.identity }));
|
|
10037
10330
|
if (expected === undefined)
|
|
10038
10331
|
states.push({ identity: object.identity, ownership: "migration", state: "unavailable", reason: "legacy-definition-required", differences: [] });
|
|
10039
10332
|
else {
|
|
@@ -10041,7 +10334,7 @@ async function planSchemaMigration(params) {
|
|
|
10041
10334
|
if (live.state === "missing")
|
|
10042
10335
|
states.push({ identity: object.identity, ownership: "migration", state: "missing", differences: [] });
|
|
10043
10336
|
else
|
|
10044
|
-
states.push(
|
|
10337
|
+
states.push(compareInspectedSqlObject({ identity: object.identity, ownership: "migration", expected, live, dialect }));
|
|
10045
10338
|
}
|
|
10046
10339
|
}
|
|
10047
10340
|
const protectedObjects = [...legacyObjects, ...objects, ...previous?.schema.migrationObjects ?? []];
|
|
@@ -10049,16 +10342,17 @@ async function planSchemaMigration(params) {
|
|
|
10049
10342
|
const checked = await prepareCheckDiff({ executor, from: stripped, to: schema, dialect, ...params.probe === undefined ? {} : { probe: params.probe }, ...previous === undefined ? {} : { previous }, ...params.replaceChecks === undefined ? {} : { replaceChecks: params.replaceChecks } });
|
|
10050
10343
|
const exclusionsPrepared = await prepareExclusionDiff({ ...params, ...previous === undefined ? {} : { previous }, from: checked, to: schema });
|
|
10051
10344
|
const from = dialect === "postgres" ? await prepareOperatorClassDiff({ executor, from: await prepareIndexPredicateDiff({ executor, from: exclusionsPrepared, to: schema, namespace, ...params.probe === undefined ? {} : { probe: params.probe } }), to: schema }) : exclusionsPrepared;
|
|
10052
|
-
const
|
|
10053
|
-
const
|
|
10054
|
-
const declaredIndexes = normalizeSchema({ schema: previous
|
|
10345
|
+
const comparison = diffSchemasWithNormalization({ from, to: schema, dialect });
|
|
10346
|
+
const steps = comparison.steps;
|
|
10347
|
+
const declaredIndexes = previous === undefined ? comparison.to : normalizeSchema({ schema: previous.schema, dialect });
|
|
10348
|
+
const known = new Set(schemaObjectIdentitiesFromNormalized({ schema: previous?.schema ?? schema, dialect, namespace, normalized: declaredIndexes }).map((identity) => sqlObjectKey18({ identity })));
|
|
10055
10349
|
const physicalIndexes = normalizeSchema({ schema: physical, dialect });
|
|
10056
10350
|
for (const [table, model] of physicalIndexes.tables)
|
|
10057
10351
|
for (const [key, index] of model.indexes) {
|
|
10058
10352
|
if (index.constraintName !== undefined && declaredIndexes.tables.get(table)?.indexes.has(key))
|
|
10059
|
-
known.add(
|
|
10353
|
+
known.add(sqlObjectKey18({ identity: { kind: "constraint", constraintType: "unique", table: qualifiedSqlName13({ name: model.table, namespace: model.namespace, defaultNamespace: namespace }), name: index.constraintName } }));
|
|
10060
10354
|
}
|
|
10061
|
-
const migrationOwned = new Set(protectedObjects.map((object) =>
|
|
10355
|
+
const migrationOwned = new Set(protectedObjects.map((object) => sqlObjectKey18({ identity: object.identity })));
|
|
10062
10356
|
const removals = steps.flatMap((step) => {
|
|
10063
10357
|
const identity = removalIdentity({ step, namespace });
|
|
10064
10358
|
return identity === undefined ? [] : [identity];
|
|
@@ -10067,30 +10361,30 @@ async function planSchemaMigration(params) {
|
|
|
10067
10361
|
for (const identity of removals) {
|
|
10068
10362
|
const owner = externalObjectOwner({ external, identity });
|
|
10069
10363
|
if (owner !== undefined)
|
|
10070
|
-
throw new
|
|
10364
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Automatic DDL cannot remove externally owned objects", meta: { reason: "external-owned-removal", owner, identity } });
|
|
10071
10365
|
}
|
|
10072
10366
|
const enumRemovals = new Set(removals.flatMap((identity) => identity.kind === "enum" ? [physicalNameKey({ ...identity.name, dialect })] : []));
|
|
10073
10367
|
if (external !== undefined)
|
|
10074
10368
|
await refuseExternalDependents({ executor, dialect, external, steps, namespace });
|
|
10075
10369
|
const droppedTables = new Set(steps.flatMap((step) => step.kind === "dropTable" || step.kind === "dropJoinTable" ? [physicalNameKey({ name: step.table, namespace: step.namespace, dialect })] : []));
|
|
10076
10370
|
const unmanagedRemovals = [];
|
|
10077
|
-
const invalid = dialect === "sqlite" ? sqliteInvalidatedTables({ steps, from: physical }) : { rebuilt: new Set, dropped: new Set };
|
|
10371
|
+
const invalid = dialect === "sqlite" && steps.length > 0 ? sqliteInvalidatedTables({ steps, from: physical }) : { rebuilt: new Set, dropped: new Set };
|
|
10078
10372
|
if (dialect === "sqlite")
|
|
10079
10373
|
await assertSqliteRebuildTableOptions({ executor, tables: invalid.rebuilt });
|
|
10080
10374
|
for (const object of inventory) {
|
|
10081
|
-
const key =
|
|
10375
|
+
const key = sqlObjectKey18({ identity: object.identity });
|
|
10082
10376
|
const normalizedOwner = object.backingConstraint === undefined ? undefined : inventory.find((entry) => entry.identity.kind === "constraint" && entry.identity.name === object.backingConstraint && ("table" in object.identity) && entry.identity.table.name === object.identity.table.name && entry.identity.table.namespace === object.identity.table.namespace);
|
|
10083
|
-
const ownership = migrationOwned.has(key) ? "migration" : known.has(key) || normalizedOwner !== undefined && known.has(
|
|
10377
|
+
const ownership = migrationOwned.has(key) ? "migration" : known.has(key) || normalizedOwner !== undefined && known.has(sqlObjectKey18({ identity: normalizedOwner.identity })) ? "schema" : "unknown";
|
|
10084
10378
|
const externalOwner = external === undefined ? undefined : externalObjectOwner({ identity: object.identity, external });
|
|
10085
10379
|
if (object.inspectionUnavailable !== undefined)
|
|
10086
10380
|
states.push({ identity: object.identity, ownership, state: "unavailable", reason: object.inspectionUnavailable, differences: [] });
|
|
10087
10381
|
if (object.inspectionUnavailable !== undefined && invalid.rebuilt.has("table" in object.identity ? object.identity.table.name : typeof object.identity.name === "object" ? object.identity.name.name : object.identity.name))
|
|
10088
|
-
throw new
|
|
10382
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "SQLite rebuild cannot discard an unrepresented constraint; declare its independent definition or use a reviewed migration", meta: { reason: "sqlite-preservation-unavailable", identity: object.identity } });
|
|
10089
10383
|
if (ownership === "unknown" && comparisonInventory.includes(object))
|
|
10090
10384
|
states.push({ identity: object.identity, ownership, state: "unavailable", reason: "unmanaged", differences: [] });
|
|
10091
10385
|
let collateral = "table" in object.identity && droppedTables.has(physicalNameKey({ ...object.identity.table, dialect }));
|
|
10092
|
-
const declared = protectedObjects.find((entry) =>
|
|
10093
|
-
if (declared?.dependsOn.some((dependency) => removals.some((identity) =>
|
|
10386
|
+
const declared = protectedObjects.find((entry) => sqlObjectKey18({ identity: entry.identity }) === key);
|
|
10387
|
+
if (declared?.dependsOn.some((dependency) => removals.some((identity) => sqlObjectKey18({ identity }) === sqlObjectKey18({ identity: dependency })) || ("table" in dependency) && droppedTables.has(physicalNameKey({ ...dependency.table, dialect }))))
|
|
10094
10388
|
collateral = true;
|
|
10095
10389
|
if (ownership === "unknown" && object.identity.kind === "function" && steps.some((step) => step.kind === "dropTable" || step.kind === "dropColumn" || step.kind === "dropJoinTable" || step.kind === "dropEnum"))
|
|
10096
10390
|
states.push({ identity: object.identity, ownership, state: "unavailable", reason: "dependency-inspection-unavailable", differences: [] });
|
|
@@ -10104,7 +10398,7 @@ async function planSchemaMigration(params) {
|
|
|
10104
10398
|
for (const address of addresses) {
|
|
10105
10399
|
const value = JSON.parse(String(address));
|
|
10106
10400
|
if (!Array.isArray(value) || !Array.isArray(value[2]))
|
|
10107
|
-
throw new
|
|
10401
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Malformed catalog dependency address" });
|
|
10108
10402
|
const names = value[2];
|
|
10109
10403
|
if (typeof names[0] !== "string" || typeof names[1] !== "string")
|
|
10110
10404
|
continue;
|
|
@@ -10116,22 +10410,23 @@ async function planSchemaMigration(params) {
|
|
|
10116
10410
|
}
|
|
10117
10411
|
}
|
|
10118
10412
|
if (dialect === "sqlite" && ownership === "unknown" && object.identity.kind === "generatedColumn" && invalid.rebuilt.has(object.identity.table.name))
|
|
10119
|
-
throw new
|
|
10120
|
-
const removed = collateral || removals.some((identity) =>
|
|
10413
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "SQLite generated-column preservation requires an independent ownership declaration", meta: { reason: "sqlite-preservation-unavailable", identity: object.identity } });
|
|
10414
|
+
const removed = collateral || removals.some((identity) => sqlObjectKey18({ identity }) === key);
|
|
10121
10415
|
if (removed && externalOwner !== undefined)
|
|
10122
|
-
throw new
|
|
10416
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Automatic or collateral DDL cannot remove externally owned objects", meta: { reason: "external-owned-removal", owner: externalOwner, identity: object.identity } });
|
|
10123
10417
|
if (removed && ownership === "migration")
|
|
10124
|
-
throw new
|
|
10418
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Automatic DDL would remove a migration-owned object", meta: { reason: "migration-owned-removal", identity: object.identity } });
|
|
10125
10419
|
if (removed && ownership === "unknown" && comparisonInventory.includes(object) && object.backingConstraint === undefined)
|
|
10126
10420
|
unmanagedRemovals.push(object.identity);
|
|
10127
10421
|
}
|
|
10128
10422
|
const invalidated = new Set([...invalid.rebuilt, ...invalid.dropped]);
|
|
10129
|
-
const installed = await probeInstalledExtensionState({ executor, dialect, artifacts });
|
|
10130
10423
|
const triggers = installed.triggers === undefined ? undefined : new Set(installed.triggers);
|
|
10131
10424
|
for (const artifact of artifacts)
|
|
10132
10425
|
if (artifact.owns.kind === "trigger" && invalidated.has(artifact.owns.table))
|
|
10133
10426
|
triggers?.delete(artifact.owns.name);
|
|
10134
|
-
const
|
|
10427
|
+
const absentArtifacts = planMissingArtifacts({ introspected: physical, artifacts, installed: { ...installed, ...triggers === undefined ? {} : { triggers } } });
|
|
10428
|
+
const absentIds = new Set(absentArtifacts.map((artifact) => artifact.id));
|
|
10429
|
+
const missing = artifacts.filter((artifact) => absentIds.has(artifact.id) || sqliteFts?.forcedArtifacts.has(artifact.id));
|
|
10135
10430
|
const declaredPrevious = params.previousDeclarations ?? previous?.schema;
|
|
10136
10431
|
const previousDeclarations = declaredPrevious === undefined || external === undefined ? declaredPrevious : projectExternalSchema({ schema: declaredPrevious, external });
|
|
10137
10432
|
const previousArtifacts = previousDeclarations === undefined ? undefined : collectExtensionArtifacts({ schema: previousDeclarations, dialect, extensions: params.extensions ?? [] });
|
|
@@ -10169,11 +10464,11 @@ async function planSchemaMigration(params) {
|
|
|
10169
10464
|
continue;
|
|
10170
10465
|
const live = await inspectSqlObject({ executor, dialect, identity });
|
|
10171
10466
|
if (live.state !== "present" || identity.kind !== "columnDefault" && typeof live.definition.facts.columnDefinition !== "string")
|
|
10172
|
-
throw new
|
|
10467
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Cannot read the verified SQLite column definition for preservation", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
10173
10468
|
if (identity.kind === "columnDefault") {
|
|
10174
10469
|
const definition = live.definition.facts.definition;
|
|
10175
10470
|
if (definition !== null && typeof definition !== "string")
|
|
10176
|
-
throw new
|
|
10471
|
+
throw new VibeError50({ code: "VIBE_MIGRATION", message: "Cannot preserve SQLite default", meta: { identity } });
|
|
10177
10472
|
const defaults = preservedDefaults.get(identity.table.name) ?? new Map;
|
|
10178
10473
|
defaults.set(identity.column, definition);
|
|
10179
10474
|
preservedDefaults.set(identity.table.name, defaults);
|
|
@@ -10194,25 +10489,31 @@ async function planSchemaMigration(params) {
|
|
|
10194
10489
|
const expression = defaults.get(column);
|
|
10195
10490
|
return { ...rest, ...typeof expression === "string" ? { default: { kind: "dbgenerated", sql: expression } } : {} };
|
|
10196
10491
|
}) })) };
|
|
10197
|
-
const core = renderSteps({ steps, dialect, from: dialect === "sqlite" ? physical : from, to: dialect === "mysql" ? { ...target, migrationObjects: protectedObjects } : target, sqliteRestore: restore, sqliteColumns: preservedColumns });
|
|
10492
|
+
const core = steps.length === 0 ? [] : renderSteps({ steps, dialect, from: dialect === "sqlite" ? physical : from, to: dialect === "mysql" ? { ...target, migrationObjects: protectedObjects } : target, sqliteRestore: restore, sqliteColumns: preservedColumns });
|
|
10198
10493
|
const planned = new Set([...missing, ...rebuilt].map((artifact) => artifact.id));
|
|
10199
|
-
const
|
|
10200
|
-
...
|
|
10201
|
-
...core,
|
|
10494
|
+
const extensionSql = [
|
|
10495
|
+
...sqliteFts?.before ?? [],
|
|
10202
10496
|
...artifactDropSql({ artifacts: rebuilt }),
|
|
10203
|
-
...artifactCreateSql({ artifacts: collected.objects.filter((artifact) => planned.has(artifact.id) && !(artifact.owns.kind === "trigger" && invalid.rebuilt.has(artifact.owns.table))) }),
|
|
10497
|
+
...artifactCreateSql({ artifacts: collected.objects.filter((artifact) => planned.has(artifact.id) && (!(artifact.owns.kind === "trigger" && invalid.rebuilt.has(artifact.owns.table)) || sqliteFts?.forcedArtifacts.has(artifact.id))) }),
|
|
10498
|
+
...sqliteFts?.after ?? [],
|
|
10204
10499
|
...creates
|
|
10205
10500
|
];
|
|
10501
|
+
const atomicFts = dialect === "sqlite" && sqliteFts?.checks.some((check) => check.changes) === true && usesSelfManagedTransactions({ sql: core });
|
|
10502
|
+
const setupSql = artifactCreateSql({ artifacts: missing.filter((artifact) => artifact.owns.kind === "postgresExtension") });
|
|
10503
|
+
const sql = [...setupSql, ...core, ...atomicFts ? ["BEGIN;"] : [], ...extensionSql, ...atomicFts ? ["COMMIT;"] : []];
|
|
10206
10504
|
if (sql.length === 0) {
|
|
10207
10505
|
const definitions = [...previous?.definitions ?? []];
|
|
10208
10506
|
for (const object of objects)
|
|
10209
|
-
if (object.expected !== undefined && !definitions.some((definition) =>
|
|
10507
|
+
if (object.expected !== undefined && !definitions.some((definition) => sqlObjectKey18({ identity: definition.identity }) === sqlObjectKey18({ identity: object.identity })))
|
|
10210
10508
|
definitions.push(object.expected);
|
|
10211
10509
|
const report = await verifyMigrationState({ executor, dialect, expected: { schema, definitions }, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
10212
10510
|
states.push(...report.objects);
|
|
10213
10511
|
}
|
|
10214
|
-
const blocked = states.some((state) => state.reason === "dependency-inspection-unavailable" || state.ownership !== "unknown" && state.reason !== "legacy-definition-required" && (state.state === "unavailable" || state.state === "changed" || state.state === "missing" && sql.length === 0));
|
|
10215
|
-
|
|
10512
|
+
const blocked = states.some((state) => state.reason === "dependency-inspection-unavailable" || state.ownership !== "unknown" && state.reason !== "legacy-definition-required" && state.reason !== "sqlite-fts-update-upgrade" && (state.state === "unavailable" || state.state === "changed" || state.state === "missing" && sql.length === 0));
|
|
10513
|
+
const plan = { physical, from, to: schema, namespace, steps, objects: states, unmanagedRemovals, blocked, sql: blocked ? [] : sql, rebuiltArtifacts };
|
|
10514
|
+
if (sqliteFts !== undefined)
|
|
10515
|
+
retainSqliteFtsPlan({ plan, fts: sqliteFts, ...atomicFts ? { transaction: { begin: setupSql.length + core.length, commit: sql.length - 1 } } : {} });
|
|
10516
|
+
return plan;
|
|
10216
10517
|
}
|
|
10217
10518
|
|
|
10218
10519
|
// packages/migrate/src/replay-plan.ts
|
|
@@ -10238,13 +10539,13 @@ async function planReplayMigration(params) {
|
|
|
10238
10539
|
moduleHeads.set(migration.moduleOrigin.moduleId, migration);
|
|
10239
10540
|
const immutableObjects = new Map([...moduleHeads.values()].flatMap((migration) => {
|
|
10240
10541
|
const state = migration.moduleOrigin.source.desiredState.to;
|
|
10241
|
-
return (state.schema.migrationObjects ?? []).map((object) => [
|
|
10542
|
+
return (state.schema.migrationObjects ?? []).map((object) => [sqlObjectKey19({ identity: object.identity }), object.expected ?? state.definitions.find((definition) => sqlObjectKey19({ identity: definition.identity }) === sqlObjectKey19({ identity: object.identity }))]);
|
|
10242
10543
|
}));
|
|
10243
10544
|
const authored = reference.dialect === "postgres" ? (await resolvePostgresTypeIdentities({ executor, schema: params.schema, previous: reference.state.schema, mode: "verify" })).schema : params.schema;
|
|
10244
|
-
const compared = await compareReplayMigrationObjects({ executor, reference, schema: { ...authored, migrationObjects: (authored.migrationObjects ?? []).filter((object) => !immutableObjects.has(
|
|
10245
|
-
const comparedObjects = new Map((compared.migrationObjects ?? []).map((object) => [
|
|
10545
|
+
const compared = await compareReplayMigrationObjects({ executor, reference, schema: { ...authored, migrationObjects: (authored.migrationObjects ?? []).filter((object) => !immutableObjects.has(sqlObjectKey19({ identity: object.identity }))) } });
|
|
10546
|
+
const comparedObjects = new Map((compared.migrationObjects ?? []).map((object) => [sqlObjectKey19({ identity: object.identity }), object]));
|
|
10246
10547
|
const schema = { ...compared, migrationObjects: (params.schema.migrationObjects ?? []).map((object) => {
|
|
10247
|
-
const key =
|
|
10548
|
+
const key = sqlObjectKey19({ identity: object.identity });
|
|
10248
10549
|
if (!immutableObjects.has(key))
|
|
10249
10550
|
return comparedObjects.get(key);
|
|
10250
10551
|
const expected = immutableObjects.get(key);
|
|
@@ -10263,57 +10564,57 @@ async function planReplayMigration(params) {
|
|
|
10263
10564
|
const identity = removalIdentity({ step, namespace: plan.namespace });
|
|
10264
10565
|
return identity === undefined ? [] : [identity];
|
|
10265
10566
|
})])
|
|
10266
|
-
removals.set(
|
|
10267
|
-
const declared = new Set((schema.migrationObjects ?? []).map((object) =>
|
|
10567
|
+
removals.set(sqlObjectKey19({ identity }), identity);
|
|
10568
|
+
const declared = new Set((schema.migrationObjects ?? []).map((object) => sqlObjectKey19({ identity: object.identity })));
|
|
10268
10569
|
const historicalObjects = new Map;
|
|
10269
10570
|
for (const migration of params.migrations)
|
|
10270
10571
|
for (const object of migration.desiredState?.to.schema.migrationObjects ?? []) {
|
|
10271
10572
|
if (external === undefined || externalObjectOwner({ identity: object.identity, external }) === undefined)
|
|
10272
|
-
historicalObjects.set(
|
|
10573
|
+
historicalObjects.set(sqlObjectKey19({ identity: object.identity }), object.identity);
|
|
10273
10574
|
}
|
|
10274
|
-
const present = new Set(reference.inventory.map((object) =>
|
|
10275
|
-
const retainedObjects = reference.inventory.filter((object) => !declared.has(
|
|
10575
|
+
const present = new Set(reference.inventory.map((object) => sqlObjectKey19({ identity: object.identity })));
|
|
10576
|
+
const retainedObjects = reference.inventory.filter((object) => !declared.has(sqlObjectKey19({ identity: object.identity })) && !removals.has(sqlObjectKey19({ identity: object.identity })) && (object.identity.kind === "function" || object.identity.kind === "trigger" || historicalObjects.has(sqlObjectKey19({ identity: object.identity })))).map((object) => object.identity);
|
|
10276
10577
|
const removedRegistrations = [...historicalObjects].filter(([key]) => present.has(key) && !declared.has(key)).map(([, identity]) => identity);
|
|
10277
10578
|
if (removedRegistrations.length > 0)
|
|
10278
|
-
throw new
|
|
10579
|
+
throw new VibeError51({ code: "VIBE_MIGRATION", message: "Custom registrations removed from declarations still exist in replayed history. Restore the registration or supply reviewed SQL removing the exact named objects before generating again.", meta: { reason: "migration-object-removal-review-required", shadowReusable: true, objects: removedRegistrations } });
|
|
10279
10580
|
if (external !== undefined)
|
|
10280
10581
|
assertExternalOwnershipClaims({ external, identities: [...removals.values()] });
|
|
10281
10582
|
return { ...plan, requiredRemovals: [...removals.values()], retainedObjects, securityChanges: securityChangesOf({ steps: plan.steps }), replayRemovals: params.migrations.flatMap((migration) => migration.desiredState?.unmanagedRemovals ?? []).filter((identity) => external === undefined || externalObjectOwner({ identity, external }) === undefined) };
|
|
10282
10583
|
}
|
|
10283
10584
|
function assertReplayRemovals(params) {
|
|
10284
|
-
const wanted = new Set([...params.plan.requiredRemovals, ...params.plan.replayRemovals].map((identity) =>
|
|
10285
|
-
const keys = params.approved.map((identity) =>
|
|
10585
|
+
const wanted = new Set([...params.plan.requiredRemovals, ...params.plan.replayRemovals].map((identity) => sqlObjectKey19({ identity })));
|
|
10586
|
+
const keys = params.approved.map((identity) => sqlObjectKey19({ identity }));
|
|
10286
10587
|
if (new Set(keys).size !== keys.length)
|
|
10287
|
-
throw new
|
|
10288
|
-
const unused = params.approved.filter((identity) => !wanted.has(
|
|
10588
|
+
throw new VibeError51({ code: "VIBE_MIGRATION", message: "Removal decisions contain duplicate identities", meta: { reason: "removal-decision-invalid", shadowReusable: true } });
|
|
10589
|
+
const unused = params.approved.filter((identity) => !wanted.has(sqlObjectKey19({ identity })));
|
|
10289
10590
|
if (unused.length > 0)
|
|
10290
|
-
throw new
|
|
10591
|
+
throw new VibeError51({ code: "VIBE_MIGRATION", message: "Removal decisions do not identify proposed removals. Custom objects without a generated removal require a reviewed hand-authored SQL migration.", meta: { reason: "removal-decision-unused", shadowReusable: true, objects: unused } });
|
|
10291
10592
|
assertNamedRemovals({ removals: params.plan.requiredRemovals, approved: params.approved, acceptIntegrityLoss: params.acceptIntegrityLoss });
|
|
10292
10593
|
}
|
|
10293
10594
|
// packages/migrate/src/baseline.ts
|
|
10294
|
-
import { VibeError as
|
|
10595
|
+
import { VibeError as VibeError54, sqlObjectKey as sqlObjectKey21 } from "@vibeorm/schema";
|
|
10295
10596
|
|
|
10296
10597
|
// packages/migrate/src/reference-state.ts
|
|
10297
|
-
import { VibeError as
|
|
10598
|
+
import { VibeError as VibeError52, sqlObjectKey as sqlObjectKey20 } from "@vibeorm/schema";
|
|
10298
10599
|
async function captureMigrationReference(params) {
|
|
10299
10600
|
const namespace = await migrationNamespace({ executor: params.referenceExecutor, dialect: params.dialect });
|
|
10300
10601
|
const identities = [...schemaObjectIdentities({ schema: params.schema, dialect: params.dialect, namespace }), ...(params.schema.migrationObjects ?? []).map((object) => object.identity)];
|
|
10301
10602
|
const definitions = [];
|
|
10302
|
-
for (const identity of new Map(identities.map((identity) => [
|
|
10603
|
+
for (const identity of new Map(identities.map((identity) => [sqlObjectKey20({ identity }), identity])).values()) {
|
|
10303
10604
|
if (identity.kind === "namespace")
|
|
10304
10605
|
continue;
|
|
10305
|
-
if (!params.schema.migrationObjects?.some((object) =>
|
|
10606
|
+
if (!params.schema.migrationObjects?.some((object) => sqlObjectKey20({ identity: object.identity }) === sqlObjectKey20({ identity })) && !(identity.kind === "constraint" && (identity.constraintType === "check" || identity.constraintType === "exclusion")))
|
|
10306
10607
|
continue;
|
|
10307
10608
|
const result = await inspectSqlObject({ executor: params.referenceExecutor, dialect: params.dialect, identity });
|
|
10308
10609
|
if (result.state !== "present")
|
|
10309
|
-
throw new
|
|
10610
|
+
throw new VibeError52({ code: "VIBE_MIGRATION", message: "Independent reference object is missing or unavailable", meta: { reason: "reference-unavailable", identity, state: result.state } });
|
|
10310
10611
|
definitions.push(result.definition);
|
|
10311
10612
|
}
|
|
10312
10613
|
let schema = params.schema;
|
|
10313
10614
|
if (params.dialect === "postgres" && schema.models.some((model) => model.fields.some((field) => field.isUnsupported === true))) {
|
|
10314
10615
|
const resolved = await resolvePostgresTypeIdentities({ executor: params.referenceExecutor, schema, mode: "reference" });
|
|
10315
10616
|
if (resolved.unavailable.length > 0)
|
|
10316
|
-
throw new
|
|
10617
|
+
throw new VibeError52({ code: "VIBE_MIGRATION", message: "Resolve unsupported types in the independent reference before capture", meta: { reason: "type-reference-required", columns: resolved.unavailable } });
|
|
10317
10618
|
const physical = await introspectPhysicalPostgres({ executor: params.referenceExecutor, namespaces: ["public", ...schemaNamespaces({ schema })] });
|
|
10318
10619
|
for (const model of resolved.schema.models)
|
|
10319
10620
|
for (const field of model.fields) {
|
|
@@ -10321,7 +10622,7 @@ async function captureMigrationReference(params) {
|
|
|
10321
10622
|
continue;
|
|
10322
10623
|
const actual = physical.models.find((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace }) === physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace }))?.fields.find((entry) => entry.name === (field.dbName ?? field.name));
|
|
10323
10624
|
if (actual === undefined || columnTypeKey({ field }) !== columnTypeKey({ field: actual }))
|
|
10324
|
-
throw new
|
|
10625
|
+
throw new VibeError52({ code: "VIBE_MIGRATION", message: "Independent reference column does not match its declared PostgreSQL type", meta: { reason: "reference-type-mismatch", table: model.dbName ?? model.name, column: field.dbName ?? field.name } });
|
|
10325
10626
|
}
|
|
10326
10627
|
schema = resolved.schema;
|
|
10327
10628
|
}
|
|
@@ -10329,13 +10630,13 @@ async function captureMigrationReference(params) {
|
|
|
10329
10630
|
}
|
|
10330
10631
|
|
|
10331
10632
|
// packages/migrate/src/compile-desired-state.ts
|
|
10332
|
-
import { VibeError as
|
|
10633
|
+
import { VibeError as VibeError53, qualifiedSqlName as qualifiedSqlName14 } from "@vibeorm/schema";
|
|
10333
10634
|
import { renderExclusionSetup as renderExclusionSetup2, getSqlDialect as getSqlDialect16, renderMigrationServerVersion as renderMigrationServerVersion2 } from "@vibeorm/sql";
|
|
10334
10635
|
async function compileDesiredState(params) {
|
|
10335
10636
|
if (params.dialect !== "postgres")
|
|
10336
|
-
throw new
|
|
10637
|
+
throw new VibeError53({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Rollback declaration compilation requires PostgreSQL", meta: { feature: "desiredStateCompilation", dialect: params.dialect } });
|
|
10337
10638
|
if (params.steps.some((step) => step.kind === "alterColumn" || step.kind === "addColumn" || step.kind === "addEnumValue"))
|
|
10338
|
-
throw new
|
|
10639
|
+
throw new VibeError53({ code: "VIBE_MIGRATION", message: "CHECK compilation after column or enum changes requires an independently built reference with the new type context", meta: { reason: "reference-required" } });
|
|
10339
10640
|
const definitions = (params.schema.migrationObjects ?? []).flatMap((object) => object.expected === undefined ? [] : [object.expected]);
|
|
10340
10641
|
await params.executor({ text: "SAVEPOINT vibeorm_desired_state" });
|
|
10341
10642
|
try {
|
|
@@ -10344,7 +10645,7 @@ async function compileDesiredState(params) {
|
|
|
10344
10645
|
await params.executor({ text });
|
|
10345
10646
|
const models = [];
|
|
10346
10647
|
for (const model of params.schema.models) {
|
|
10347
|
-
const table =
|
|
10648
|
+
const table = qualifiedSqlName14({ name: model.dbName ?? model.name, namespace: model.namespace });
|
|
10348
10649
|
const exclusions = [];
|
|
10349
10650
|
for (const exclusion of model.exclusions ?? []) {
|
|
10350
10651
|
for (const text of renderExclusionSetup2({ dialect: getSqlDialect16({ dialect: "postgres" }), exclusion }))
|
|
@@ -10358,7 +10659,7 @@ async function compileDesiredState(params) {
|
|
|
10358
10659
|
const query = renderMigrationServerVersion2({ dialect: getSqlDialect16({ dialect: params.dialect }) });
|
|
10359
10660
|
const version = (await params.executor({ text: query.text, values: [] }))[0]?.version;
|
|
10360
10661
|
if (typeof version !== "string")
|
|
10361
|
-
throw new
|
|
10662
|
+
throw new VibeError53({ code: "VIBE_MIGRATION", message: "Cannot determine definition normalization version" });
|
|
10362
10663
|
return { ...createDesiredState({ schema: { ...params.schema, models }, dialect: params.dialect, definitions }), normalization: { format: "catalog-v1", dialect: params.dialect, serverVersion: version } };
|
|
10363
10664
|
} finally {
|
|
10364
10665
|
await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_desired_state" });
|
|
@@ -10371,19 +10672,19 @@ function generateBaselineMigration(params) {
|
|
|
10371
10672
|
const objects = orderedMigrationObjects({ objects: params.schema.migrationObjects ?? [] });
|
|
10372
10673
|
for (const object of objects)
|
|
10373
10674
|
if (object.dialect !== params.dialect)
|
|
10374
|
-
throw new
|
|
10675
|
+
throw new VibeError54({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Baseline object ${object.id} requires ${object.dialect}`, meta: { identity: object.identity, dialect: params.dialect } });
|
|
10375
10676
|
const prerequisites = objects.filter((object) => object.identity.kind === "postgresExtension" || object.identity.kind === "enum" || object.identity.kind === "sequence");
|
|
10376
|
-
const prerequisiteKeys = new Set(prerequisites.map((object) =>
|
|
10677
|
+
const prerequisiteKeys = new Set(prerequisites.map((object) => sqlObjectKey21({ identity: object.identity })));
|
|
10377
10678
|
for (const object of prerequisites) {
|
|
10378
|
-
const unresolved = object.dependsOn.filter((identity) => !prerequisiteKeys.has(
|
|
10679
|
+
const unresolved = object.dependsOn.filter((identity) => !prerequisiteKeys.has(sqlObjectKey21({ identity })));
|
|
10379
10680
|
if (unresolved.length > 0)
|
|
10380
|
-
throw new
|
|
10681
|
+
throw new VibeError54({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Baseline prerequisite depends on objects created later. Supply reviewed initial SQL with an explicit dependency order.", meta: { reason: "baseline-prerequisite-order", identity: object.identity, dependencies: unresolved } });
|
|
10381
10682
|
}
|
|
10382
|
-
const schema = { ...params.schema, migrationObjects: objects.filter((object) => !prerequisiteKeys.has(
|
|
10683
|
+
const schema = { ...params.schema, migrationObjects: objects.filter((object) => !prerequisiteKeys.has(sqlObjectKey21({ identity: object.identity }))) };
|
|
10383
10684
|
const generated = generateMigration({ from: { datasource: schema.datasource, models: [], enums: [], extensions: [] }, to: schema, name: params.name, dialect: params.dialect, extensions: params.extensions ?? [] });
|
|
10384
10685
|
const sql = splitSqlStatements({ sql: joinSqlStatements({ sql: [...prerequisites.flatMap((object) => object.create), ...generated.sql] }), dialect: params.dialect });
|
|
10385
10686
|
if (sql.length === 0)
|
|
10386
|
-
throw new
|
|
10687
|
+
throw new VibeError54({ code: "VIBE_MIGRATION", message: "A baseline must create declared database objects", meta: { reason: "baseline-empty" } });
|
|
10387
10688
|
return {
|
|
10388
10689
|
name: generated.name,
|
|
10389
10690
|
sql,
|
|
@@ -10395,8 +10696,8 @@ function generateBaselineMigration(params) {
|
|
|
10395
10696
|
async function verifyBaselineDeclarations(params) {
|
|
10396
10697
|
const captured = await captureMigrationReference({ referenceExecutor: params.executor, schema: params.schema, dialect: params.dialect });
|
|
10397
10698
|
const authored = (params.schema.migrationObjects ?? []).flatMap((object) => object.expected === undefined ? [] : [object.expected]);
|
|
10398
|
-
const customKeys = new Set((params.schema.migrationObjects ?? []).map((object) =>
|
|
10399
|
-
const customDefinitions = captured.definitions.filter((definition) => customKeys.has(
|
|
10699
|
+
const customKeys = new Set((params.schema.migrationObjects ?? []).map((object) => sqlObjectKey21({ identity: object.identity })));
|
|
10700
|
+
const customDefinitions = captured.definitions.filter((definition) => customKeys.has(sqlObjectKey21({ identity: definition.identity })) && !authored.some((entry) => sqlObjectKey21({ identity: entry.identity }) === sqlObjectKey21({ identity: definition.identity })));
|
|
10400
10701
|
const verify = async () => {
|
|
10401
10702
|
const compiled = params.dialect === "postgres" ? await compileDesiredState({ ...params, schema: captured.schema, steps: [], probe: "rollback" }) : { schema: captured.schema, definitions: authored };
|
|
10402
10703
|
const expected = { ...compiled, definitions: [...compiled.definitions, ...customDefinitions] };
|
|
@@ -10412,48 +10713,48 @@ async function verifyBaselineDeclarations(params) {
|
|
|
10412
10713
|
async function verifyBaselineTarget(params) {
|
|
10413
10714
|
const { reference } = params;
|
|
10414
10715
|
if (reference.unavailable.length > 0 || reference.inventory.some((object) => object.inspectionUnavailable !== undefined))
|
|
10415
|
-
throw new
|
|
10716
|
+
throw new VibeError54({ code: "VIBE_MIGRATION", message: "Baseline reference contains objects that cannot be inspected. Supply supported reviewed declarations before adoption.", meta: { reason: "baseline-reference-unavailable", objects: reference.unavailable, inventory: reference.inventory.filter((object) => object.inspectionUnavailable !== undefined) } });
|
|
10416
10717
|
const comparison = await verifyMigrationReference({ executor: params.executor, dialect: reference.dialect, reference, referenceExecutor: params.referenceExecutor }).catch((cause) => {
|
|
10417
|
-
if (cause instanceof
|
|
10418
|
-
throw new
|
|
10718
|
+
if (cause instanceof VibeError54 && cause.meta.reason === "verification-rls-reference-required")
|
|
10719
|
+
throw new VibeError54({ code: cause.code, message: cause.message, meta: { reason: "baseline-rls-reference-required" }, cause });
|
|
10419
10720
|
throw cause;
|
|
10420
10721
|
});
|
|
10421
10722
|
const security = comparison.objects.filter((object) => object.reason === "native-rls-mismatch");
|
|
10422
10723
|
if (security.length > 0)
|
|
10423
|
-
throw new
|
|
10724
|
+
throw new VibeError54({ code: "VIBE_MIGRATION", message: "Target native RLS policies or flags differ from the independent baseline. Restore the reviewed security declarations before recording; no target policies were changed.", meta: { reason: "baseline-rls-mismatch", shadowReusable: true, expected: security.flatMap((object) => object.differences.flatMap((difference) => difference.expected === undefined ? [] : [difference.expected])), actual: security.flatMap((object) => object.differences.flatMap((difference) => difference.actual === undefined ? [] : [difference.actual])) } });
|
|
10424
10725
|
if (comparison.unavailable.length > 0)
|
|
10425
|
-
throw new
|
|
10726
|
+
throw new VibeError54({ code: "VIBE_MIGRATION", message: "Baseline comparison contains objects whose definitions cannot be verified in the target context; use a supported reference before adoption.", meta: { reason: "baseline-target-unavailable", shadowReusable: true, objects: comparison.unavailable } });
|
|
10426
10727
|
const report = { inSync: comparison.objects.every((object) => object.state === "matching"), objects: comparison.objects };
|
|
10427
10728
|
assertMigrationState({ report });
|
|
10428
10729
|
const unmanaged = comparison.unmanaged.map((object) => object.identity);
|
|
10429
|
-
const extras = new Set(unmanaged.map((identity) =>
|
|
10730
|
+
const extras = new Set(unmanaged.map((identity) => sqlObjectKey21({ identity })));
|
|
10430
10731
|
const exclusions = params.excludeUnmanaged ?? [];
|
|
10431
|
-
const excluded = new Set(exclusions.map((identity) =>
|
|
10432
|
-
if (excluded.size !== exclusions.length || exclusions.some((identity) => !extras.has(
|
|
10433
|
-
throw new
|
|
10434
|
-
const unreviewed = unmanaged.filter((identity) => !excluded.has(
|
|
10732
|
+
const excluded = new Set(exclusions.map((identity) => sqlObjectKey21({ identity })));
|
|
10733
|
+
if (excluded.size !== exclusions.length || exclusions.some((identity) => !extras.has(sqlObjectKey21({ identity }))))
|
|
10734
|
+
throw new VibeError54({ code: "VIBE_CONFIG", message: "Baseline exclusions must uniquely name actual unmanaged target objects, never declared objects", meta: { reason: "baseline-exclusion-invalid", shadowReusable: true, exclusions } });
|
|
10735
|
+
const unreviewed = unmanaged.filter((identity) => !excluded.has(sqlObjectKey21({ identity })));
|
|
10435
10736
|
if (unreviewed.length > 0)
|
|
10436
|
-
throw new
|
|
10737
|
+
throw new VibeError54({ code: "VIBE_MIGRATION", message: "Target has unmanaged objects. Register their SQL or explicitly exclude each identity with --exclude-unmanaged; no target objects were removed.", meta: { reason: "baseline-unmanaged-objects", shadowReusable: true, objects: unreviewed } });
|
|
10437
10738
|
return { ...report, unmanaged };
|
|
10438
10739
|
}
|
|
10439
10740
|
// packages/migrate/src/baseline-record.ts
|
|
10440
|
-
import { VibeError as
|
|
10741
|
+
import { VibeError as VibeError55 } from "@vibeorm/schema";
|
|
10441
10742
|
import { getSqlDialect as getSqlDialect17, renderStatement } from "@vibeorm/sql";
|
|
10442
10743
|
function transactionFailure2(params) {
|
|
10443
|
-
if (params.cause instanceof
|
|
10744
|
+
if (params.cause instanceof VibeError55)
|
|
10444
10745
|
return params.cause;
|
|
10445
|
-
return new
|
|
10746
|
+
return new VibeError55({ code: "VIBE_MIGRATION", message: "SQLite baseline recording failed; inspect the journal and transaction outcome before retrying.", meta: { reason: "baseline-record-transaction-failed" }, cause: params.cause });
|
|
10446
10747
|
}
|
|
10447
10748
|
async function recordBaselineMigration(params) {
|
|
10448
10749
|
if (params.migration.moduleOrigin !== undefined)
|
|
10449
|
-
throw new
|
|
10750
|
+
throw new VibeError55({ code: "VIBE_MIGRATION", message: "Package module migrations must use the immutable module installation workflow, not baseline adoption", meta: { reason: "baseline-module-unsupported" } });
|
|
10450
10751
|
validateMigrationReplay({ dialect: params.dialect, migrations: [params.migration], acceptSecurityChanges: params.acceptSecurityChanges });
|
|
10451
10752
|
await withMigrationLock({ executor: params.executor, dialect: params.dialect, run: async (executor) => {
|
|
10452
10753
|
const record = async () => {
|
|
10453
10754
|
const assertEmpty = async () => {
|
|
10454
10755
|
const history = await readMigrationHistory({ executor, migrations: [params.migration], dialect: params.dialect });
|
|
10455
10756
|
if (history.recorded.length > 0)
|
|
10456
|
-
throw new
|
|
10757
|
+
throw new VibeError55({ code: "VIBE_MIGRATION", message: "Baseline recording requires an absent or empty migration journal. Use normal apply or explicit recovery for existing history.", meta: { reason: "baseline-journal-not-empty", shadowReusable: true, recorded: history.recorded.map((row) => ({ name: row.name, state: row.state })) } });
|
|
10457
10758
|
};
|
|
10458
10759
|
await assertEmpty();
|
|
10459
10760
|
await params.verify({ executor });
|
|
@@ -10478,14 +10779,14 @@ async function recordBaselineMigration(params) {
|
|
|
10478
10779
|
await executor({ text: "COMMIT" });
|
|
10479
10780
|
}).then(undefined, async (cause) => {
|
|
10480
10781
|
await executor({ text: "ROLLBACK" }).then(undefined, (rollback) => {
|
|
10481
|
-
throw new
|
|
10782
|
+
throw new VibeError55({ code: "VIBE_MIGRATION", message: "Baseline recording failed and its SQLite transaction could not be rolled back. Inspect the journal and connection state before retrying; the outcome is uncertain.", meta: { reason: "baseline-record-rollback-failed" }, cause: new AggregateError([cause, rollback], "Baseline recording and rollback failed") });
|
|
10482
10783
|
});
|
|
10483
10784
|
throw transactionFailure2({ cause });
|
|
10484
10785
|
});
|
|
10485
10786
|
} });
|
|
10486
10787
|
}
|
|
10487
10788
|
// packages/migrate/src/down.ts
|
|
10488
|
-
import { VibeError as
|
|
10789
|
+
import { VibeError as VibeError56, sqlObjectKey as sqlObjectKey22 } from "@vibeorm/schema";
|
|
10489
10790
|
function buildRollbackTarget(params) {
|
|
10490
10791
|
const { from, to, dialect } = params;
|
|
10491
10792
|
const skipped = [];
|
|
@@ -10589,10 +10890,10 @@ function generateDownMigration(params) {
|
|
|
10589
10890
|
}
|
|
10590
10891
|
restoreSql.push(...entry.restore);
|
|
10591
10892
|
}
|
|
10592
|
-
const previousKeys = new Set((from.migrationObjects ?? []).map((object) =>
|
|
10893
|
+
const previousKeys = new Set((from.migrationObjects ?? []).map((object) => sqlObjectKey22({ identity: object.identity })));
|
|
10593
10894
|
const ownedDropSql = [];
|
|
10594
10895
|
for (const object of orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).reverse()) {
|
|
10595
|
-
if (previousKeys.has(
|
|
10896
|
+
if (previousKeys.has(sqlObjectKey22({ identity: object.identity })))
|
|
10596
10897
|
continue;
|
|
10597
10898
|
if (object.identity.kind === "namespace") {
|
|
10598
10899
|
skipped.push(`namespace ${object.identity.name} remains installed`);
|
|
@@ -10603,7 +10904,7 @@ function generateDownMigration(params) {
|
|
|
10603
10904
|
continue;
|
|
10604
10905
|
}
|
|
10605
10906
|
if (object.drop.length === 0)
|
|
10606
|
-
throw new
|
|
10907
|
+
throw new VibeError56({ code: "VIBE_MIGRATION", message: `Migration object ${object.id} needs reviewed reverse SQL`, meta: { reason: "migration-object-reverse-required", id: object.id } });
|
|
10607
10908
|
ownedDropSql.push(...object.drop);
|
|
10608
10909
|
}
|
|
10609
10910
|
return {
|
|
@@ -10616,11 +10917,11 @@ function generateDownMigration(params) {
|
|
|
10616
10917
|
};
|
|
10617
10918
|
}
|
|
10618
10919
|
// packages/migrate/src/push.ts
|
|
10619
|
-
import { VibeError as
|
|
10920
|
+
import { VibeError as VibeError57, assertValidSchemaIR as assertValidSchemaIR4, providerToDialect as providerToDialect7, sqlObjectKey as sqlObjectKey23, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
|
|
10620
10921
|
import { DIALECT_CAPABILITIES as DIALECT_CAPABILITIES4 } from "@vibeorm/sql";
|
|
10621
10922
|
async function push(params) {
|
|
10622
10923
|
assertValidSchemaIR4({ schema: params.schema });
|
|
10623
|
-
const dialect =
|
|
10924
|
+
const dialect = providerToDialect7({ provider: params.schema.datasource.provider });
|
|
10624
10925
|
if (params.rebuildArtifacts === true && dialect !== "postgres")
|
|
10625
10926
|
throw unsupportedCapability14({ dialect, feature: "rebuilding extension artifacts on push (rebuildArtifacts)", hint: "drop the extension's objects by hand and push again; the transactional rebuild exists on PostgreSQL only" });
|
|
10626
10927
|
if (dialect !== "postgres")
|
|
@@ -10660,11 +10961,11 @@ async function pushChanges(params) {
|
|
|
10660
10961
|
if (params.dryRun === true)
|
|
10661
10962
|
return { steps, applied: [], planned: sql, securityChanges, objects: plan.objects, blocked: plan.blocked, unmanagedRemovals: plan.unmanagedRemovals };
|
|
10662
10963
|
if (plan.blocked)
|
|
10663
|
-
throw new
|
|
10964
|
+
throw new VibeError57({ code: "VIBE_MIGRATION", message: "Schema push is blocked by unverified object definitions", meta: { reason: "migration-state-unverified", objects: plan.objects } });
|
|
10664
10965
|
assertSecurityAcknowledgement({ securityChanges, ...params.acceptSecurityChanges === undefined ? {} : { acceptSecurityChanges: params.acceptSecurityChanges } });
|
|
10665
10966
|
const destructive = steps.filter((step) => step.destructive);
|
|
10666
10967
|
if (destructive.length > 0 && params.acceptDataLoss !== true)
|
|
10667
|
-
throw new
|
|
10968
|
+
throw new VibeError57({ code: "VIBE_MIGRATION", message: `push refused: ${destructive.length} destructive step(s) would lose data; pass acceptDataLoss: true: ${destructive.map((step) => describeStep({ step })).join("; ")}`, meta: { destructive: destructive.map((step) => describeStep({ step })) } });
|
|
10668
10969
|
assertRemovalAuthorization({
|
|
10669
10970
|
steps,
|
|
10670
10971
|
dialect,
|
|
@@ -10673,16 +10974,26 @@ async function pushChanges(params) {
|
|
|
10673
10974
|
...params.acceptIntegrityLoss === undefined ? {} : { acceptIntegrityLoss: params.acceptIntegrityLoss },
|
|
10674
10975
|
...params.removeUnmanaged === undefined ? {} : { removeUnmanaged: params.removeUnmanaged }
|
|
10675
10976
|
});
|
|
10676
|
-
const approved = new Set((params.removeUnmanaged ?? []).map((identity) =>
|
|
10677
|
-
const unapproved = plan.unmanagedRemovals.filter((identity) => !approved.has(
|
|
10977
|
+
const approved = new Set((params.removeUnmanaged ?? []).map((identity) => sqlObjectKey23({ identity })));
|
|
10978
|
+
const unapproved = plan.unmanagedRemovals.filter((identity) => !approved.has(sqlObjectKey23({ identity })));
|
|
10678
10979
|
if (unapproved.length > 0)
|
|
10679
|
-
throw new
|
|
10980
|
+
throw new VibeError57({ code: "VIBE_MIGRATION", message: "DDL would remove unmanaged dependent objects; approve their exact identities", meta: { reason: "unmanaged-removal-required", objects: unapproved } });
|
|
10680
10981
|
const wrap = dialect !== "postgres" && DIALECT_CAPABILITIES4[dialect].transactionalDdl && !usesSelfManagedTransactions({ sql });
|
|
10982
|
+
const ftsTransaction = dialect === "sqlite" ? sqliteFtsTransaction({ plan }) : undefined;
|
|
10681
10983
|
if (wrap)
|
|
10682
10984
|
await executor({ text: "BEGIN" });
|
|
10683
10985
|
try {
|
|
10684
|
-
|
|
10986
|
+
if (dialect === "sqlite" && ftsTransaction === undefined)
|
|
10987
|
+
await assertSqliteFtsApplyState({ plan, executor });
|
|
10988
|
+
for (const [index, statement] of sql.entries()) {
|
|
10989
|
+
if (ftsTransaction?.commit === index)
|
|
10990
|
+
await verifySqliteFtsApplyState({ plan, executor });
|
|
10685
10991
|
await executeStatement({ executor, statement, dialect });
|
|
10992
|
+
if (ftsTransaction?.begin === index)
|
|
10993
|
+
await assertSqliteFtsApplyState({ plan, executor });
|
|
10994
|
+
}
|
|
10995
|
+
if (dialect === "sqlite" && ftsTransaction === undefined)
|
|
10996
|
+
await verifySqliteFtsApplyState({ plan, executor });
|
|
10686
10997
|
if ((schema.migrationObjects?.length ?? 0) > 0)
|
|
10687
10998
|
assertMigrationState({ report: await verifyMigrationState({ executor, dialect, expected: createDesiredState({ schema: { ...schema, models: [] }, dialect }), ...params.external === undefined ? {} : { external: params.external } }) });
|
|
10688
10999
|
if (wrap)
|
|
@@ -10694,7 +11005,7 @@ async function pushChanges(params) {
|
|
|
10694
11005
|
} catch {}
|
|
10695
11006
|
} else if (dialect === "sqlite")
|
|
10696
11007
|
await cleanupSelfManagedFailure({ executor });
|
|
10697
|
-
throw new
|
|
11008
|
+
throw new VibeError57({ code: "VIBE_MIGRATION", message: `push failed while applying schema changes: ${error instanceof Error ? error.message : String(error)}`, meta: { statements: sql }, cause: error });
|
|
10698
11009
|
}
|
|
10699
11010
|
return { steps, applied: sql, securityChanges, objects: plan.objects, desiredState: createDesiredState({ schema: plan.to, dialect }) };
|
|
10700
11011
|
}
|
|
@@ -10709,11 +11020,11 @@ async function pull(params) {
|
|
|
10709
11020
|
}
|
|
10710
11021
|
}
|
|
10711
11022
|
// packages/migrate/src/module-export.ts
|
|
10712
|
-
import { VibeError as
|
|
11023
|
+
import { VibeError as VibeError58, providerToDialect as providerToDialect8 } from "@vibeorm/schema";
|
|
10713
11024
|
function runtimeSchema(params) {
|
|
10714
11025
|
const byName = (left, right) => left.name.localeCompare(right.name);
|
|
10715
11026
|
return {
|
|
10716
|
-
datasource: { provider:
|
|
11027
|
+
datasource: { provider: providerToDialect8({ provider: params.schema.datasource.provider }) },
|
|
10717
11028
|
models: [...params.schema.models].sort(byName),
|
|
10718
11029
|
enums: [...params.schema.enums].sort(byName),
|
|
10719
11030
|
extensions: [...params.schema.extensions].sort(byName),
|
|
@@ -10724,13 +11035,13 @@ function emitModuleMigrationExport(params) {
|
|
|
10724
11035
|
moduleMigrationFiles({ bundle: params.bundle, prefix: "export" });
|
|
10725
11036
|
const origin = params.bundle.migrations.at(-1).moduleOrigin;
|
|
10726
11037
|
if (!origin.contracts.includes(params.contract))
|
|
10727
|
-
throw new
|
|
11038
|
+
throw new VibeError58({ code: "VIBE_CONFIG", message: "The package runtime contract is not supported by its migration head", meta: { reason: "module-contract-mismatch", moduleId: origin.moduleId } });
|
|
10728
11039
|
if (params.client !== undefined) {
|
|
10729
11040
|
const { module, schema } = params.client;
|
|
10730
|
-
if (module.id !== origin.moduleId || module.revision !== origin.revision || module.contract !== params.contract ||
|
|
10731
|
-
throw new
|
|
11041
|
+
if (module.id !== origin.moduleId || module.revision !== origin.revision || module.contract !== params.contract || providerToDialect8({ provider: schema.datasource.provider }) !== origin.dialect)
|
|
11042
|
+
throw new VibeError58({ code: "VIBE_CONFIG", message: "The generated module identity, revision or contract differs from its migration head", meta: { reason: "module-client-head-mismatch", moduleId: origin.moduleId } });
|
|
10732
11043
|
if (canonicalMigrationJson({ value: runtimeSchema({ schema }) }) !== canonicalMigrationJson({ value: runtimeSchema({ schema: origin.source.desiredState.to.schema }) }))
|
|
10733
|
-
throw new
|
|
11044
|
+
throw new VibeError58({ code: "VIBE_CONFIG", message: "The generated module schema differs from its immutable migration head", meta: { reason: "module-client-schema-mismatch", moduleId: origin.moduleId } });
|
|
10734
11045
|
}
|
|
10735
11046
|
const expectation = { moduleId: origin.moduleId, revision: origin.revision, artifactChecksum: origin.artifactChecksum, contract: params.contract };
|
|
10736
11047
|
return [
|
|
@@ -10749,16 +11060,16 @@ export const moduleReadiness = Object.freeze(${JSON.stringify(expectation)});
|
|
|
10749
11060
|
];
|
|
10750
11061
|
}
|
|
10751
11062
|
// packages/migrate/src/module-readiness.ts
|
|
10752
|
-
import { VibeError as
|
|
11063
|
+
import { VibeError as VibeError59 } from "@vibeorm/schema";
|
|
10753
11064
|
async function inspectModuleReadiness(params) {
|
|
10754
11065
|
const ids = new Set;
|
|
10755
11066
|
for (const expected of params.modules) {
|
|
10756
11067
|
if (ids.has(expected.moduleId) || !expected.moduleId || !expected.revision || !expected.contract || !/^[a-f0-9]{64}$/.test(expected.artifactChecksum))
|
|
10757
|
-
throw new
|
|
11068
|
+
throw new VibeError59({ code: "VIBE_CONFIG", message: "Module readiness needs a unique module id, revision, contract and immutable artifact checksum", meta: { reason: "module-readiness-expectation", moduleId: expected.moduleId } });
|
|
10758
11069
|
ids.add(expected.moduleId);
|
|
10759
11070
|
}
|
|
10760
11071
|
if (params.compatibility !== undefined && params.compatibility !== "exact" && params.compatibility !== "contract")
|
|
10761
|
-
throw new
|
|
11072
|
+
throw new VibeError59({ code: "VIBE_CONFIG", message: "Unknown module compatibility mode", meta: { reason: "module-readiness-expectation" } });
|
|
10762
11073
|
const records = await readModuleJournal(params);
|
|
10763
11074
|
const heads = moduleHistoryHeads({ records });
|
|
10764
11075
|
const unresolved = [...records.values()].some(({ row, entry }) => row.error !== undefined || row.statementIndex < 0 || entry !== undefined && (!entry.complete || row.statementIndex !== entry.migration.sql.length));
|
|
@@ -10793,7 +11104,7 @@ async function inspectModuleReadiness(params) {
|
|
|
10793
11104
|
return { ready: !unresolved && modules.every((module) => (module.revision === "matching" || module.revision === "compatible") && module.structural.status !== "drifted"), modules };
|
|
10794
11105
|
}
|
|
10795
11106
|
// packages/migrate/src/adoption.ts
|
|
10796
|
-
import { VibeError as
|
|
11107
|
+
import { VibeError as VibeError60 } from "@vibeorm/schema";
|
|
10797
11108
|
async function adoptMigration(params) {
|
|
10798
11109
|
const report = await verifyMigrationState({ executor: params.executor, expected: params.reference, dialect: params.dialect });
|
|
10799
11110
|
assertMigrationState({ report });
|
|
@@ -10809,11 +11120,11 @@ function deployedDesiredState(params) {
|
|
|
10809
11120
|
for (const [index, row] of params.recorded.entries()) {
|
|
10810
11121
|
const file = params.migrations[index];
|
|
10811
11122
|
if (row.state !== "applied" || row.checksum === "" || file?.name !== row.name || migrationChecksum(file) !== row.checksum)
|
|
10812
|
-
throw new
|
|
11123
|
+
throw new VibeError60({ code: "VIBE_MIGRATION", message: "Desired state requires complete, checksum-verified migration history", meta: { reason: "desired-state-history-unverified", migration: row.name, state: row.state } });
|
|
10813
11124
|
}
|
|
10814
11125
|
const applied = new Set(params.recorded.map((row) => row.name));
|
|
10815
11126
|
if (params.migrations.some((migration) => applied.has(migration.name) && migration.desiredStatePending === true))
|
|
10816
|
-
throw new
|
|
11127
|
+
throw new VibeError60({ code: "VIBE_MIGRATION", message: "Applied unfinalized metadata cannot establish desired state", meta: { reason: "desired-state-history-unverified" } });
|
|
10817
11128
|
const last = [...params.migrations].reverse().find((migration) => applied.has(migration.name));
|
|
10818
11129
|
return last?.desiredState?.to;
|
|
10819
11130
|
}
|
|
@@ -10928,4 +11239,4 @@ export {
|
|
|
10928
11239
|
withMigrationReference
|
|
10929
11240
|
};
|
|
10930
11241
|
|
|
10931
|
-
//# debugId=
|
|
11242
|
+
//# debugId=F91BB3120BEADA3064756E2164756E21
|