@vibeorm/migrate 2.2.0 → 2.3.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/dist/adoption.d.ts.map +1 -1
- package/dist/baseline-record.d.ts +14 -0
- package/dist/baseline-record.d.ts.map +1 -0
- package/dist/baseline.d.ts +31 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/compare-reference-rls.d.ts +10 -0
- package/dist/compare-reference-rls.d.ts.map +1 -0
- package/dist/compare-sqlite-primary-key.d.ts +6 -0
- package/dist/compare-sqlite-primary-key.d.ts.map +1 -0
- package/dist/desired-state.d.ts.map +1 -1
- package/dist/fs.js +636 -392
- package/dist/fs.js.map +14 -11
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6322 -5092
- package/dist/index.js.map +46 -33
- package/dist/inspect-objects.d.ts +8 -1
- package/dist/inspect-objects.d.ts.map +1 -1
- package/dist/introspect/postgres.d.ts.map +1 -1
- package/dist/module-history.d.ts +6 -0
- package/dist/module-history.d.ts.map +1 -1
- package/dist/module-ownership.d.ts +8 -0
- package/dist/module-ownership.d.ts.map +1 -1
- package/dist/module-pending.d.ts +2 -0
- package/dist/module-pending.d.ts.map +1 -1
- package/dist/mysql-catalog-visibility.d.ts +5 -0
- package/dist/mysql-catalog-visibility.d.ts.map +1 -0
- package/dist/planner.d.ts +5 -0
- package/dist/planner.d.ts.map +1 -1
- package/dist/postgres-type-identity.d.ts +15 -0
- package/dist/postgres-type-identity.d.ts.map +1 -0
- package/dist/postgres-types.d.ts +5 -0
- package/dist/postgres-types.d.ts.map +1 -1
- package/dist/reference-state.d.ts.map +1 -1
- package/dist/replay-objects.d.ts +10 -0
- package/dist/replay-objects.d.ts.map +1 -0
- package/dist/replay-plan.d.ts +28 -0
- package/dist/replay-plan.d.ts.map +1 -0
- package/dist/replay-reference.d.ts +18 -0
- package/dist/replay-reference.d.ts.map +1 -0
- package/dist/runner.d.ts +6 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/shadow-dedicated.d.ts +12 -0
- package/dist/shadow-dedicated.d.ts.map +1 -0
- package/dist/shadow-guard.d.ts +13 -0
- package/dist/shadow-guard.d.ts.map +1 -0
- package/dist/shadow-types.d.ts +52 -0
- package/dist/shadow-types.d.ts.map +1 -0
- package/dist/shadow.d.ts +17 -0
- package/dist/shadow.d.ts.map +1 -0
- package/dist/verify-history.d.ts +35 -0
- package/dist/verify-history.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/fs.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/migrate/src/mysql-catalog-visibility.ts
|
|
3
|
+
function hasMysqlGlobalCatalogVisibility(params) {
|
|
4
|
+
let visible = false;
|
|
5
|
+
for (const row of params.rows) {
|
|
6
|
+
const values = Object.values(row);
|
|
7
|
+
if (values.length !== 1 || typeof values[0] !== "string" || !values[0].startsWith("GRANT "))
|
|
8
|
+
return false;
|
|
9
|
+
const grant = /^GRANT ([A-Z_ ,]+) ON \*\.\* TO /.exec(values[0]);
|
|
10
|
+
const privileges = grant?.[1]?.split(", ") ?? [];
|
|
11
|
+
if (privileges.some((privilege) => privilege === "SHOW VIEW" || privilege === "SELECT" || privilege === "ALL PRIVILEGES"))
|
|
12
|
+
visible = true;
|
|
13
|
+
}
|
|
14
|
+
return visible;
|
|
15
|
+
}
|
|
16
|
+
|
|
2
17
|
// packages/migrate/src/module-ownership.ts
|
|
3
|
-
import { VibeError as
|
|
18
|
+
import { VibeError as VibeError17, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
|
|
19
|
+
import { getSqlDialect as getSqlDialect3, renderModuleDependencyQuery } from "@vibeorm/sql";
|
|
4
20
|
|
|
5
21
|
// packages/migrate/src/desired-state.ts
|
|
6
22
|
import { VibeError, assertValidSchemaIR, isSqlObjectDefinition, isSqlObjectIdentity, providerToDialect, sqlObjectKey } from "@vibeorm/schema";
|
|
@@ -38,7 +54,7 @@ function keysOnly(value, allowed) {
|
|
|
38
54
|
function modelShape(value) {
|
|
39
55
|
if (!record(value) || typeof value.name !== "string" || !Array.isArray(value.fields) || !Array.isArray(value.relations) || !Array.isArray(value.indexes))
|
|
40
56
|
return false;
|
|
41
|
-
return value.fields.every((field) => record(field) && typeof field.name === "string" && (typeof field.type === "string" || record(field.type) && typeof field.type.enum === "string") && ["isId", "isUnique", "isOptional", "isList"].every((key) => typeof field[key] === "boolean")) && value.relations.every(record) && value.indexes.every((index) => record(index) && Array.isArray(index.fields) && index.fields.every(record)) && (value.checks === undefined || Array.isArray(value.checks) && value.checks.every((check) => record(check) && typeof check.name === "string" && typeof check.expression === "string"));
|
|
57
|
+
return value.fields.every((field) => record(field) && typeof field.name === "string" && (field.nativeTypeIdentity === undefined || record(field.nativeTypeIdentity) && keysOnly(field.nativeTypeIdentity, ["source", "namespace"]) && field.isUnsupported === true && typeof field.nativeTypeIdentity.source === "string" && field.nativeTypeIdentity.source === field.nativeType && typeof field.nativeTypeIdentity.namespace === "string" && field.nativeTypeIdentity.namespace.length > 0) && (typeof field.type === "string" || record(field.type) && typeof field.type.enum === "string") && ["isId", "isUnique", "isOptional", "isList"].every((key) => typeof field[key] === "boolean")) && value.relations.every(record) && value.indexes.every((index) => record(index) && Array.isArray(index.fields) && index.fields.every(record)) && (value.checks === undefined || Array.isArray(value.checks) && value.checks.every((check) => record(check) && typeof check.name === "string" && typeof check.expression === "string"));
|
|
42
58
|
}
|
|
43
59
|
function state(value) {
|
|
44
60
|
if (!record(value) || !keysOnly(value, ["schema", "definitions", "normalization"]) || !record(value.schema) || !Array.isArray(value.definitions) || !value.definitions.every(isSqlObjectDefinition))
|
|
@@ -138,7 +154,7 @@ function detectImplicitM2MPairs(params) {
|
|
|
138
154
|
|
|
139
155
|
// packages/migrate/src/postgres-types.ts
|
|
140
156
|
import { VibeError as VibeError2, isEnumType, scalarListDefault, serializeJsonDefault } from "@vibeorm/schema";
|
|
141
|
-
import { POSTGRES_CODECS, postgresDialect, toPgArrayLiteral } from "@vibeorm/sql";
|
|
157
|
+
import { POSTGRES_CODECS, postgresDialect, toPgArrayLiteral, parsePostgresTypeName, renderPostgresTypeName } from "@vibeorm/sql";
|
|
142
158
|
var SCALAR_TO_PG = {
|
|
143
159
|
String: "text",
|
|
144
160
|
Boolean: "boolean",
|
|
@@ -182,11 +198,26 @@ function normalizeNativeType(params) {
|
|
|
182
198
|
const base = NATIVE_ALIASES[match[1]] ?? match[1];
|
|
183
199
|
return `${base}${match[2] ?? ""}${match[3] ?? ""}`;
|
|
184
200
|
}
|
|
201
|
+
function parseUnsupportedPostgresType(params) {
|
|
202
|
+
const temporal = /^\s*(time(?:stamp)?)\s*(\(\s*\d+\s*\))?\s+(with|without)\s+time\s+zone\s*((?:\[\s*\]\s*)*)$/i.exec(params.raw);
|
|
203
|
+
if (temporal !== null)
|
|
204
|
+
return parsePostgresTypeName({ raw: `${temporal[1].toLowerCase()}${temporal[3].toLowerCase() === "with" ? "tz" : ""}${temporal[2] ?? ""}${temporal[4] ?? ""}` });
|
|
205
|
+
const parsed = parsePostgresTypeName(params);
|
|
206
|
+
if (parsed !== undefined || /["']/.test(params.raw))
|
|
207
|
+
return parsed;
|
|
208
|
+
const base = params.raw.toLowerCase().replace(/\s+/g, "").replace(/\(.*\)|\[\]/g, "");
|
|
209
|
+
return NATIVE_ALIASES[base] === undefined ? undefined : parsePostgresTypeName({ raw: normalizeNativeType(params) });
|
|
210
|
+
}
|
|
185
211
|
function columnTypeKey(params) {
|
|
186
212
|
const { field } = params;
|
|
187
213
|
const listSuffix = field.isList ? "[]" : "";
|
|
188
214
|
if (field.isUnsupported === true) {
|
|
189
|
-
|
|
215
|
+
const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
|
|
216
|
+
if (parsed === undefined)
|
|
217
|
+
return `u:${field.nativeType ?? ""}`;
|
|
218
|
+
const evidence = field.nativeTypeIdentity;
|
|
219
|
+
const namespace = evidence !== undefined && evidence.source === field.nativeType ? evidence.namespace : parsed.namespace;
|
|
220
|
+
return `u:${JSON.stringify([namespace ?? null, parsed.name, parsed.modifier, parsed.array || field.isList])}`;
|
|
190
221
|
}
|
|
191
222
|
if (isEnumType(field.type)) {
|
|
192
223
|
return `e:${field.type.enum}${listSuffix}`;
|
|
@@ -218,6 +249,10 @@ function columnTypeSql(params) {
|
|
|
218
249
|
meta: { field: field.name }
|
|
219
250
|
});
|
|
220
251
|
}
|
|
252
|
+
const parsed = parseUnsupportedPostgresType({ raw: field.nativeType });
|
|
253
|
+
const evidence = field.nativeTypeIdentity;
|
|
254
|
+
if (parsed !== undefined && evidence?.source === field.nativeType)
|
|
255
|
+
return renderPostgresTypeName({ dialect: postgresDialect, type: { ...parsed, namespace: evidence.namespace, array: parsed.array || field.isList } });
|
|
221
256
|
return field.nativeType;
|
|
222
257
|
}
|
|
223
258
|
const listSuffix = field.isList ? "[]" : "";
|
|
@@ -1196,7 +1231,7 @@ function assertRetainedMigrationObjects(params) {
|
|
|
1196
1231
|
const keys = new Set(params.to.map((object) => sqlObjectKey2({ identity: object.identity })));
|
|
1197
1232
|
const removed = params.from.filter((object) => !keys.has(sqlObjectKey2({ identity: object.identity })));
|
|
1198
1233
|
if (removed.length > 0)
|
|
1199
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Removing migration ownership requires a reviewed hand-authored migration and new desired state", meta: { reason: "migration-object-removal-review-required", objects: removed.map((object) => object.identity) } });
|
|
1234
|
+
throw new VibeError6({ 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) } });
|
|
1200
1235
|
}
|
|
1201
1236
|
function orderedMigrationObjects(params) {
|
|
1202
1237
|
const byKey = new Map(params.objects.map((object) => [sqlObjectKey2({ identity: object.identity }), object]));
|
|
@@ -1227,10 +1262,10 @@ function assertNamedRemovals(params) {
|
|
|
1227
1262
|
const approved = new Set((params.approved ?? []).map((identity) => sqlObjectKey2({ identity })));
|
|
1228
1263
|
const missing = params.removals.filter((identity) => !approved.has(sqlObjectKey2({ identity })));
|
|
1229
1264
|
if (missing.length > 0)
|
|
1230
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unmanaged removals require individually named approvals", meta: { reason: "unmanaged-removal-required", objects: missing } });
|
|
1265
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unmanaged removals require individually named approvals", meta: { reason: "unmanaged-removal-required", shadowReusable: true, objects: missing } });
|
|
1231
1266
|
const integrity = params.removals.filter((identity) => identity.kind === "constraint" || identity.kind === "index");
|
|
1232
1267
|
if (integrity.length > 0 && params.acceptIntegrityLoss !== true)
|
|
1233
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Named constraint or index removals also require acceptIntegrityLoss", meta: { reason: "integrity-acknowledgement-required", objects: integrity } });
|
|
1268
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Named constraint or index removals also require acceptIntegrityLoss", meta: { reason: "integrity-acknowledgement-required", shadowReusable: true, objects: integrity } });
|
|
1234
1269
|
}
|
|
1235
1270
|
function assertRemovalAuthorization(params) {
|
|
1236
1271
|
const declared = params.previous === undefined ? [] : schemaObjectIdentities({ schema: params.previous.schema, dialect: params.dialect, namespace: params.namespace });
|
|
@@ -1255,14 +1290,14 @@ function assertRemovalAuthorization(params) {
|
|
|
1255
1290
|
}
|
|
1256
1291
|
}
|
|
1257
1292
|
if (unknown.length > 0)
|
|
1258
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unknown object removals require individually named removeUnmanaged decisions", meta: { reason: "unmanaged-removal-required", objects: unknown } });
|
|
1293
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unknown object removals require individually named removeUnmanaged decisions", meta: { reason: "unmanaged-removal-required", shadowReusable: true, objects: unknown } });
|
|
1259
1294
|
if (integrity.length > 0 && params.acceptIntegrityLoss !== true)
|
|
1260
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Constraint or index removal requires acceptIntegrityLoss; data-loss acknowledgement does not authorize it", meta: { reason: "integrity-acknowledgement-required", objects: integrity } });
|
|
1295
|
+
throw new VibeError6({ 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 } });
|
|
1261
1296
|
}
|
|
1262
1297
|
|
|
1263
1298
|
// packages/migrate/src/inventory.ts
|
|
1264
|
-
import { VibeError as
|
|
1265
|
-
import { getSqlDialect as getSqlDialect2, renderMigrationInventory, renderSqliteColumnInventory, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
|
|
1299
|
+
import { VibeError as VibeError16, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey5 } from "@vibeorm/schema";
|
|
1300
|
+
import { getSqlDialect as getSqlDialect2, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
|
|
1266
1301
|
|
|
1267
1302
|
// packages/migrate/src/exclusions.ts
|
|
1268
1303
|
import { randomUUID } from "crypto";
|
|
@@ -1342,16 +1377,16 @@ async function verifyExclusions(params) {
|
|
|
1342
1377
|
|
|
1343
1378
|
// packages/migrate/src/inspect-objects.ts
|
|
1344
1379
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1345
|
-
import { VibeError as
|
|
1346
|
-
import { getSqlDialect, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2 } from "@vibeorm/sql";
|
|
1380
|
+
import { VibeError as VibeError15, sqlObjectKey as sqlObjectKey4, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2 } from "@vibeorm/schema";
|
|
1381
|
+
import { getSqlDialect, renderMigrationObjectBatch, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
|
|
1347
1382
|
|
|
1348
1383
|
// packages/migrate/src/introspect/postgres.ts
|
|
1349
1384
|
import { renderExclusionCatalog as renderExclusionCatalog2 } from "@vibeorm/sql";
|
|
1350
1385
|
import { parsePgArrayLiteral } from "@vibeorm/sql";
|
|
1351
1386
|
import { scalarListDefault as scalarListDefault2 } from "@vibeorm/schema";
|
|
1352
1387
|
import { exactIntegerLiteral } from "@vibeorm/schema";
|
|
1353
|
-
import { postgresDialect as
|
|
1354
|
-
import { VibeError as
|
|
1388
|
+
import { postgresDialect as postgresDialect6, renderRlsReadinessQuery } from "@vibeorm/sql";
|
|
1389
|
+
import { VibeError as VibeError12 } from "@vibeorm/schema";
|
|
1355
1390
|
|
|
1356
1391
|
// packages/migrate/src/journal-table.ts
|
|
1357
1392
|
var MIGRATIONS_TABLE_NAME = "_vibe_migrations";
|
|
@@ -1543,12 +1578,58 @@ function lowerFirst(value) {
|
|
|
1543
1578
|
return value.charAt(0).toLowerCase() + value.slice(1);
|
|
1544
1579
|
}
|
|
1545
1580
|
|
|
1581
|
+
// packages/migrate/src/postgres-type-identity.ts
|
|
1582
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1583
|
+
import { postgresDialect as postgresDialect3, renderPostgresTypeIdentity } from "@vibeorm/sql";
|
|
1584
|
+
async function resolvePostgresTypeIdentities(params) {
|
|
1585
|
+
const unavailable = [];
|
|
1586
|
+
const models = [];
|
|
1587
|
+
for (const model of params.schema.models) {
|
|
1588
|
+
const table = { namespace: "public", name: model.dbName ?? model.name };
|
|
1589
|
+
const previous = params.previous?.models.find((entry) => (entry.dbName ?? entry.name) === table.name);
|
|
1590
|
+
const fields = [];
|
|
1591
|
+
for (const field of model.fields) {
|
|
1592
|
+
if (field.isUnsupported !== true) {
|
|
1593
|
+
fields.push(field);
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
const column = field.dbName ?? field.name;
|
|
1597
|
+
const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
|
|
1598
|
+
const evidence = field.nativeTypeIdentity;
|
|
1599
|
+
if (evidence !== undefined && (evidence.source !== field.nativeType || parsed?.namespace !== undefined && parsed.namespace !== evidence.namespace)) {
|
|
1600
|
+
throw new VibeError9({ code: "VIBE_MIGRATION", message: "PostgreSQL type evidence no longer matches its declaration; recapture the independent reference", meta: { reason: "type-reference-invalid", shadowReusable: true, table: table.name, column } });
|
|
1601
|
+
}
|
|
1602
|
+
const prior = previous?.fields.find((entry) => (entry.dbName ?? entry.name) === column && entry.isUnsupported === true);
|
|
1603
|
+
const priorType = parseUnsupportedPostgresType({ raw: prior?.nativeType ?? "" });
|
|
1604
|
+
const priorNamespace = prior?.nativeTypeIdentity?.source === prior?.nativeType ? prior?.nativeTypeIdentity?.namespace : priorType?.namespace;
|
|
1605
|
+
let namespace = parsed?.namespace ?? evidence?.namespace ?? (priorType?.name === parsed?.name ? priorNamespace : undefined);
|
|
1606
|
+
if (params.mode === "catalog" || params.mode === "reference" || namespace === undefined) {
|
|
1607
|
+
const source = params.mode === "catalog" ? { table, column } : { type: { name: parsed?.name ?? "", ...namespace === undefined && params.mode === "reference" ? {} : { namespace: namespace ?? "pg_catalog" } } };
|
|
1608
|
+
const query = renderPostgresTypeIdentity({ dialect: postgresDialect3, source });
|
|
1609
|
+
const rows = parsed === undefined && params.mode !== "catalog" ? [] : await params.executor({ text: query.text, values: [...query.values] }).then((rows) => rows, (cause) => {
|
|
1610
|
+
throw new VibeError9({ code: "VIBE_MIGRATION", message: "PostgreSQL type identity inspection failed", meta: { reason: "type-inspection-unavailable", table: table.name, column }, cause });
|
|
1611
|
+
});
|
|
1612
|
+
const resolved = rows.length === 1 ? rows[0] : undefined;
|
|
1613
|
+
namespace = typeof resolved?.namespace === "string" && (resolved.name === parsed?.name || parsed === undefined && params.mode === "catalog") ? resolved.namespace : undefined;
|
|
1614
|
+
}
|
|
1615
|
+
if (parsed === undefined && params.mode !== "catalog" || namespace === undefined || field.nativeType === undefined) {
|
|
1616
|
+
unavailable.push({ kind: "column", table, column });
|
|
1617
|
+
fields.push(field);
|
|
1618
|
+
} else {
|
|
1619
|
+
fields.push({ ...field, nativeTypeIdentity: { source: field.nativeType, namespace } });
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
models.push({ ...model, fields });
|
|
1623
|
+
}
|
|
1624
|
+
return { schema: { ...params.schema, models }, unavailable };
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1546
1627
|
// packages/migrate/src/introspect/postgres-rls.ts
|
|
1547
|
-
import { VibeError as
|
|
1548
|
-
import { NATIVE_RLS, postgresDialect as
|
|
1628
|
+
import { VibeError as VibeError11, assertValidSchemaIR as assertValidSchemaIR3 } from "@vibeorm/schema";
|
|
1629
|
+
import { NATIVE_RLS, postgresDialect as postgresDialect5, renderRlsPolicy as renderRlsPolicy2 } from "@vibeorm/sql";
|
|
1549
1630
|
|
|
1550
1631
|
// packages/migrate/src/ddl/rls.ts
|
|
1551
|
-
import { VibeError as
|
|
1632
|
+
import { VibeError as VibeError10, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
1552
1633
|
import * as sql from "@vibeorm/sql";
|
|
1553
1634
|
function isRlsProtected(params) {
|
|
1554
1635
|
return params.model.rls !== undefined && params.model.rls !== "unprotected";
|
|
@@ -1664,7 +1745,7 @@ function renderRlsStep(params) {
|
|
|
1664
1745
|
return [renderer(common).text];
|
|
1665
1746
|
}
|
|
1666
1747
|
default:
|
|
1667
|
-
throw new
|
|
1748
|
+
throw new VibeError10({ code: "VIBE_MIGRATION", message: "Expected a native RLS migration step" });
|
|
1668
1749
|
}
|
|
1669
1750
|
}
|
|
1670
1751
|
function securityChangesOf(params) {
|
|
@@ -1673,7 +1754,7 @@ function securityChangesOf(params) {
|
|
|
1673
1754
|
function assertSecurityAcknowledgement(params) {
|
|
1674
1755
|
if (params.securityChanges.length === 0 || params.acceptSecurityChanges === true)
|
|
1675
1756
|
return;
|
|
1676
|
-
throw new
|
|
1757
|
+
throw new VibeError10({
|
|
1677
1758
|
code: "VIBE_MIGRATION",
|
|
1678
1759
|
message: `Security changes require acceptSecurityChanges: true; acceptDataLoss does not authorize them:
|
|
1679
1760
|
${params.securityChanges.join(`
|
|
@@ -1700,19 +1781,19 @@ async function readPostgresRls(params) {
|
|
|
1700
1781
|
const tables = new Map;
|
|
1701
1782
|
for (const row of rows) {
|
|
1702
1783
|
const table = String(row.table_name);
|
|
1703
|
-
let
|
|
1704
|
-
if (
|
|
1705
|
-
|
|
1706
|
-
tables.set(table,
|
|
1784
|
+
let state = tables.get(table);
|
|
1785
|
+
if (state === undefined) {
|
|
1786
|
+
state = { table, enabled: row.enabled === true, force: row.force === true, policies: [] };
|
|
1787
|
+
tables.set(table, state);
|
|
1707
1788
|
}
|
|
1708
1789
|
if (row.policy_name !== null && row.policy_name !== undefined) {
|
|
1709
|
-
|
|
1790
|
+
state.policies.push({ name: String(row.policy_name), command: String(row.command), permissive: row.permissive === true, roles: String(row.roles), using: row.using_expression === null ? null : String(row.using_expression), withCheck: row.check_expression === null ? null : String(row.check_expression), comment: row.comment === null ? null : String(row.comment) });
|
|
1710
1791
|
}
|
|
1711
1792
|
}
|
|
1712
1793
|
return [...tables.values()];
|
|
1713
1794
|
}
|
|
1714
1795
|
function conflict(params) {
|
|
1715
|
-
throw new
|
|
1796
|
+
throw new VibeError11({
|
|
1716
1797
|
code: "VIBE_MIGRATION",
|
|
1717
1798
|
message: `Native RLS on public.${params.table} cannot be managed safely (${params.attributes.join(", ")}). Review the live policies and restore the deployed declaration, or remove conflicting policies explicitly with administrator credentials. Supply rlsSchema/previousSchema for the deployed policy; no policy is deleted or adopted automatically.`,
|
|
1718
1799
|
meta: params
|
|
@@ -1725,32 +1806,32 @@ async function verifyPostgresRls(params) {
|
|
|
1725
1806
|
await executor({ text: "SET LOCAL search_path = ''" });
|
|
1726
1807
|
const declared = new Map((schema?.models ?? []).map((model) => [model.dbName ?? model.name, model]));
|
|
1727
1808
|
let tables = await readPostgresRls({ executor });
|
|
1728
|
-
for (const
|
|
1729
|
-
const model = declared.get(
|
|
1730
|
-
if (!
|
|
1809
|
+
for (const state of tables) {
|
|
1810
|
+
const model = declared.get(state.table);
|
|
1811
|
+
if (!state.enabled && !state.force && state.policies.length === 0 && (model === undefined || !isRlsProtected({ model })))
|
|
1731
1812
|
continue;
|
|
1732
|
-
await executor({ text: `LOCK TABLE "public".${
|
|
1813
|
+
await executor({ text: `LOCK TABLE "public".${postgresDialect5.quoteIdent({ name: state.table })} IN ACCESS EXCLUSIVE MODE` });
|
|
1733
1814
|
}
|
|
1734
1815
|
tables = await readPostgresRls({ executor });
|
|
1735
1816
|
const matched = new Set;
|
|
1736
|
-
for (const
|
|
1737
|
-
const model = declared.get(
|
|
1738
|
-
const active =
|
|
1817
|
+
for (const state of tables) {
|
|
1818
|
+
const model = declared.get(state.table);
|
|
1819
|
+
const active = state.enabled || state.force || state.policies.length > 0;
|
|
1739
1820
|
if (schema === undefined && active)
|
|
1740
|
-
conflict({ table:
|
|
1821
|
+
conflict({ table: state.table, reason: "rls-unmanaged", attributes: ["security metadata cannot be exported losslessly without a trusted declaration"] });
|
|
1741
1822
|
if (model === undefined || !isRlsProtected({ model })) {
|
|
1742
1823
|
if (active)
|
|
1743
|
-
conflict({ table:
|
|
1824
|
+
conflict({ table: state.table, reason: "rls-conflict", attributes: ["unexpected native policies or RLS flags"] });
|
|
1744
1825
|
continue;
|
|
1745
1826
|
}
|
|
1746
1827
|
if (!active && params.requireInstalled !== true)
|
|
1747
1828
|
continue;
|
|
1748
|
-
const unknown =
|
|
1829
|
+
const unknown = state.policies.filter((policy) => policy.name !== NATIVE_RLS.policyName);
|
|
1749
1830
|
if (unknown.length > 0)
|
|
1750
|
-
conflict({ table:
|
|
1751
|
-
const policy =
|
|
1831
|
+
conflict({ table: state.table, reason: "rls-conflict", attributes: unknown.map((policy) => `unexpected policy ${policy.name}`) });
|
|
1832
|
+
const policy = state.policies.find((entry) => entry.name === NATIVE_RLS.policyName);
|
|
1752
1833
|
if (policy === undefined)
|
|
1753
|
-
conflict({ table:
|
|
1834
|
+
conflict({ table: state.table, reason: "rls-drift", attributes: ["missing policy"] });
|
|
1754
1835
|
const structure = [
|
|
1755
1836
|
...policy.command !== "*" ? ["command"] : [],
|
|
1756
1837
|
...!policy.permissive ? ["permissiveness"] : [],
|
|
@@ -1758,27 +1839,27 @@ async function verifyPostgresRls(params) {
|
|
|
1758
1839
|
...policy.withCheck === null ? ["missing WITH CHECK"] : []
|
|
1759
1840
|
];
|
|
1760
1841
|
if (structure.length > 0)
|
|
1761
|
-
conflict({ table:
|
|
1762
|
-
const attributes = [...!
|
|
1842
|
+
conflict({ table: state.table, reason: "rls-conflict", attributes: structure });
|
|
1843
|
+
const attributes = [...!state.enabled ? ["enabled"] : [], ...!state.force ? ["force"] : []];
|
|
1763
1844
|
await executor({ text: "SAVEPOINT vibeorm_rls_probe" });
|
|
1764
1845
|
let probe;
|
|
1765
1846
|
try {
|
|
1766
|
-
const rendered = renderRlsPolicy2({ dialect:
|
|
1847
|
+
const rendered = renderRlsPolicy2({ dialect: postgresDialect5, schema, model: model.name, policyName: PROBE });
|
|
1767
1848
|
await executor({ text: rendered.text });
|
|
1768
|
-
probe = (await readPostgresRls({ executor })).find((entry) => entry.table ===
|
|
1849
|
+
probe = (await readPostgresRls({ executor })).find((entry) => entry.table === state.table)?.policies.find((entry) => entry.name === PROBE);
|
|
1769
1850
|
} finally {
|
|
1770
1851
|
await executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_rls_probe" });
|
|
1771
1852
|
await executor({ text: "RELEASE SAVEPOINT vibeorm_rls_probe" });
|
|
1772
1853
|
}
|
|
1773
1854
|
if (probe === undefined)
|
|
1774
|
-
conflict({ table:
|
|
1855
|
+
conflict({ table: state.table, reason: "rls-drift", attributes: ["probe unavailable"] });
|
|
1775
1856
|
if (policy.using !== probe.using)
|
|
1776
1857
|
attributes.push("using");
|
|
1777
1858
|
if (policy.withCheck !== probe.withCheck)
|
|
1778
1859
|
attributes.push("withCheck");
|
|
1779
1860
|
if (attributes.length > 0)
|
|
1780
|
-
conflict({ table:
|
|
1781
|
-
matched.add(
|
|
1861
|
+
conflict({ table: state.table, reason: "rls-drift", attributes });
|
|
1862
|
+
matched.add(state.table);
|
|
1782
1863
|
}
|
|
1783
1864
|
if (params.requireInstalled === true) {
|
|
1784
1865
|
for (const [table, model] of declared) {
|
|
@@ -1961,18 +2042,18 @@ async function introspectPostgres(params) {
|
|
|
1961
2042
|
if (params.inTransaction !== true)
|
|
1962
2043
|
await executor({ text: "BEGIN" });
|
|
1963
2044
|
try {
|
|
1964
|
-
const savedPath = params.inTransaction === true ? await executor(renderRlsReadinessQuery({ dialect:
|
|
2045
|
+
const savedPath = params.inTransaction === true ? await executor(renderRlsReadinessQuery({ dialect: postgresDialect6, query: "readSearchPath" })) : undefined;
|
|
1965
2046
|
const searchPath = savedPath?.[0]?.searchPath;
|
|
1966
2047
|
if (params.inTransaction === true && typeof searchPath !== "string")
|
|
1967
|
-
throw new
|
|
2048
|
+
throw new VibeError12({ code: "VIBE_MIGRATION", message: "Cannot read the migration search_path" });
|
|
1968
2049
|
const matched = await verifyPostgresRls({ executor, ...params.rlsSchema === undefined ? {} : { schema: params.rlsSchema }, ...params.requireInstalledRls === undefined ? {} : { requireInstalled: params.requireInstalledRls } }).then((verified) => verified, (error) => {
|
|
1969
|
-
if (error instanceof
|
|
2050
|
+
if (error instanceof VibeError12)
|
|
1970
2051
|
throw error;
|
|
1971
|
-
throw new
|
|
2052
|
+
throw new VibeError12({ code: "VIBE_MIGRATION", message: "Native RLS verification could not complete. Check migration privileges and supply the deployed schema as previousSchema/rlsSchema before changing referenced columns.", meta: { reason: "rls-verification-unavailable" }, cause: error });
|
|
1972
2053
|
});
|
|
1973
2054
|
const physical = await introspectPhysicalPostgres({ executor });
|
|
1974
2055
|
if (typeof searchPath === "string")
|
|
1975
|
-
await executor(renderRlsReadinessQuery({ dialect:
|
|
2056
|
+
await executor(renderRlsReadinessQuery({ dialect: postgresDialect6, query: "setSearchPath", searchPath }));
|
|
1976
2057
|
return projectPostgresRls({ physical, ...params.rlsSchema === undefined ? {} : { declared: params.rlsSchema }, matched });
|
|
1977
2058
|
} finally {
|
|
1978
2059
|
if (params.inTransaction !== true)
|
|
@@ -1990,7 +2071,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1990
2071
|
executor({ text: ENUMS_QUERY }),
|
|
1991
2072
|
executor({ text: INDEXES_QUERY }),
|
|
1992
2073
|
executor({ text: CHECKS_QUERY }),
|
|
1993
|
-
executor({ text: renderExclusionCatalog2({ dialect:
|
|
2074
|
+
executor({ text: renderExclusionCatalog2({ dialect: postgresDialect6 }) })
|
|
1994
2075
|
]);
|
|
1995
2076
|
const tables = tableRows.map((row) => row.table_name);
|
|
1996
2077
|
const views = viewRows.map((row) => row.table_name);
|
|
@@ -2158,12 +2239,15 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2158
2239
|
}
|
|
2159
2240
|
addFkRelations({ models, fkGroups, joinTables });
|
|
2160
2241
|
addJoinTableRelations({ models, joinTables });
|
|
2161
|
-
|
|
2242
|
+
const resolved = await resolvePostgresTypeIdentities({ executor, mode: "catalog", schema: {
|
|
2162
2243
|
datasource: { provider: "postgresql" },
|
|
2163
2244
|
models,
|
|
2164
2245
|
enums,
|
|
2165
2246
|
extensions: []
|
|
2166
|
-
};
|
|
2247
|
+
} });
|
|
2248
|
+
if (resolved.unavailable.length > 0)
|
|
2249
|
+
throw new VibeError12({ code: "VIBE_MIGRATION", message: "PostgreSQL column type identity could not be read", meta: { reason: "type-inspection-unavailable", columns: resolved.unavailable } });
|
|
2250
|
+
return resolved.schema;
|
|
2167
2251
|
}
|
|
2168
2252
|
function buildModel(params) {
|
|
2169
2253
|
const { table, isView, columns, pkByTable, uniquesByConstraint, constraintNames, indexes, enumNames } = params;
|
|
@@ -3132,8 +3216,8 @@ async function readIndexes(params) {
|
|
|
3132
3216
|
}
|
|
3133
3217
|
let where;
|
|
3134
3218
|
if (partial) {
|
|
3135
|
-
const
|
|
3136
|
-
const match =
|
|
3219
|
+
const sql = indexSqlByName.get(name);
|
|
3220
|
+
const match = sql?.match(/\sWHERE\s+(.*)$/is);
|
|
3137
3221
|
where = match?.[1]?.trim();
|
|
3138
3222
|
}
|
|
3139
3223
|
tableIndexes.push({
|
|
@@ -3164,17 +3248,17 @@ async function readIndexes(params) {
|
|
|
3164
3248
|
var PLAIN_INDEX_COLUMN = /^"((?:[^"]|"")+)"(?:\s+(ASC|DESC))?$/i;
|
|
3165
3249
|
var CAST_REAL_INDEX_COLUMN = /^CAST\s*\(\s*"((?:[^"]|"")+)"\s+AS\s+REAL\s*\)(?:\s+(ASC|DESC))?$/i;
|
|
3166
3250
|
function parseIndexColumnsFromSql(params) {
|
|
3167
|
-
const { sql
|
|
3168
|
-
if (
|
|
3251
|
+
const { sql } = params;
|
|
3252
|
+
if (sql === null)
|
|
3169
3253
|
return null;
|
|
3170
|
-
const onMatch = /\sON\s+"(?:[^"]|"")+"\s*\(/i.exec(
|
|
3254
|
+
const onMatch = /\sON\s+"(?:[^"]|"")+"\s*\(/i.exec(sql);
|
|
3171
3255
|
if (onMatch === null)
|
|
3172
3256
|
return null;
|
|
3173
3257
|
const open = onMatch.index + onMatch[0].length - 1;
|
|
3174
3258
|
let listDepth = 0;
|
|
3175
3259
|
let close = -1;
|
|
3176
|
-
for (let index = open;index <
|
|
3177
|
-
const char =
|
|
3260
|
+
for (let index = open;index < sql.length; index++) {
|
|
3261
|
+
const char = sql[index];
|
|
3178
3262
|
if (char === "(")
|
|
3179
3263
|
listDepth += 1;
|
|
3180
3264
|
else if (char === ")") {
|
|
@@ -3187,7 +3271,7 @@ function parseIndexColumnsFromSql(params) {
|
|
|
3187
3271
|
}
|
|
3188
3272
|
if (close === -1)
|
|
3189
3273
|
return null;
|
|
3190
|
-
const list =
|
|
3274
|
+
const list = sql.slice(open + 1, close);
|
|
3191
3275
|
const terms = [];
|
|
3192
3276
|
let depth = 0;
|
|
3193
3277
|
let start = 0;
|
|
@@ -3328,7 +3412,7 @@ function q(name) {
|
|
|
3328
3412
|
}
|
|
3329
3413
|
|
|
3330
3414
|
// packages/migrate/src/execute.ts
|
|
3331
|
-
import { VibeError as
|
|
3415
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
3332
3416
|
var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
|
|
3333
3417
|
var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
|
|
3334
3418
|
function usesSelfManagedTransactions(params) {
|
|
@@ -3349,9 +3433,9 @@ function classifySelfManagedStatement(params) {
|
|
|
3349
3433
|
return "plain";
|
|
3350
3434
|
}
|
|
3351
3435
|
function selfManagedResumeStart(params) {
|
|
3352
|
-
const { sql
|
|
3436
|
+
const { sql, start } = params;
|
|
3353
3437
|
for (let index = start - 1;index >= 0; index -= 1) {
|
|
3354
|
-
const control = classifySelfManagedStatement({ statement:
|
|
3438
|
+
const control = classifySelfManagedStatement({ statement: sql[index] });
|
|
3355
3439
|
if (control === "endTransaction" || control === "abandon" || control === "fkOn")
|
|
3356
3440
|
return start;
|
|
3357
3441
|
if (control === "fkOff")
|
|
@@ -3369,7 +3453,7 @@ function assertSelfManagedResumeSafe(params) {
|
|
|
3369
3453
|
const completedIndex = params.direction === "up" ? params.start + 1 : -(params.start + 2);
|
|
3370
3454
|
const rewind = selfManagedResumeStart({ sql: params.sql, start: params.start });
|
|
3371
3455
|
const retryIndex = params.direction === "up" ? rewind : -(rewind + 1);
|
|
3372
|
-
throw new
|
|
3456
|
+
throw new VibeError13({
|
|
3373
3457
|
code: "VIBE_MIGRATION",
|
|
3374
3458
|
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.`,
|
|
3375
3459
|
meta: { reason: "uncertain-statement", migration: params.migration, statementIndex: params.start, direction: params.direction, dialect: "sqlite" }
|
|
@@ -3398,37 +3482,37 @@ function unsupportedScript(params) {
|
|
|
3398
3482
|
const { migration, direction, index, problem, found } = params;
|
|
3399
3483
|
const subject = direction === "down" ? `the down script of migration "${migration}"` : `migration "${migration}"`;
|
|
3400
3484
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${params.statements}`;
|
|
3401
|
-
return new
|
|
3485
|
+
return new VibeError13({
|
|
3402
3486
|
code: "VIBE_MIGRATION",
|
|
3403
3487
|
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.',
|
|
3404
3488
|
meta: { reason: "unsupported-self-managed-script", problem, migration, statementIndex: index, direction }
|
|
3405
3489
|
});
|
|
3406
3490
|
}
|
|
3407
3491
|
function assertSupportedSelfManagedScript(params) {
|
|
3408
|
-
const { sql
|
|
3409
|
-
const statements =
|
|
3492
|
+
const { sql, migration, direction } = params;
|
|
3493
|
+
const statements = sql.length;
|
|
3410
3494
|
const refuse = (index, problem, found) => unsupportedScript({ migration, direction, index, statements, problem, found });
|
|
3411
|
-
let
|
|
3495
|
+
let state = "outside";
|
|
3412
3496
|
let openedAt = -1;
|
|
3413
3497
|
for (let index = 0;index < statements; index += 1) {
|
|
3414
|
-
const control = classifySelfManagedStatement({ statement:
|
|
3415
|
-
if (
|
|
3498
|
+
const control = classifySelfManagedStatement({ statement: sql[index] });
|
|
3499
|
+
if (state === "fkOff") {
|
|
3416
3500
|
if (control !== "begin") {
|
|
3417
3501
|
throw refuse(index, "fk-off-without-transaction", "`PRAGMA foreign_keys=OFF` is not followed by `BEGIN;`");
|
|
3418
3502
|
}
|
|
3419
|
-
|
|
3503
|
+
state = "inBlock";
|
|
3420
3504
|
continue;
|
|
3421
3505
|
}
|
|
3422
|
-
if (
|
|
3506
|
+
if (state === "committed") {
|
|
3423
3507
|
if (control !== "fkOn") {
|
|
3424
3508
|
throw refuse(index, "commit-without-restore", "`COMMIT;` is not followed by `PRAGMA foreign_keys=ON;`");
|
|
3425
3509
|
}
|
|
3426
|
-
|
|
3510
|
+
state = "outside";
|
|
3427
3511
|
continue;
|
|
3428
3512
|
}
|
|
3429
|
-
if (
|
|
3513
|
+
if (state === "inBlock") {
|
|
3430
3514
|
if (control === "endTransaction") {
|
|
3431
|
-
|
|
3515
|
+
state = "committed";
|
|
3432
3516
|
continue;
|
|
3433
3517
|
}
|
|
3434
3518
|
if (control === "plain")
|
|
@@ -3436,7 +3520,7 @@ function assertSupportedSelfManagedScript(params) {
|
|
|
3436
3520
|
throw refuse(index, "unsupported-control-in-block", "transaction or pragma control appears inside an open rebuild block");
|
|
3437
3521
|
}
|
|
3438
3522
|
if (control === "fkOff") {
|
|
3439
|
-
|
|
3523
|
+
state = "fkOff";
|
|
3440
3524
|
openedAt = index;
|
|
3441
3525
|
continue;
|
|
3442
3526
|
}
|
|
@@ -3444,8 +3528,8 @@ function assertSupportedSelfManagedScript(params) {
|
|
|
3444
3528
|
throw refuse(index, "transaction-control-outside-block", "transaction control appears outside any rebuild block");
|
|
3445
3529
|
}
|
|
3446
3530
|
}
|
|
3447
|
-
if (
|
|
3448
|
-
throw refuse(openedAt,
|
|
3531
|
+
if (state !== "outside") {
|
|
3532
|
+
throw refuse(openedAt, state === "committed" ? "commit-without-restore" : "unclosed-block", state === "committed" ? "the block commits but never restores foreign keys with `PRAGMA foreign_keys=ON;`" : "`PRAGMA foreign_keys=OFF` is never closed by `COMMIT;` and `PRAGMA foreign_keys=ON;`");
|
|
3449
3533
|
}
|
|
3450
3534
|
}
|
|
3451
3535
|
async function executeStatement(params) {
|
|
@@ -3454,7 +3538,7 @@ async function executeStatement(params) {
|
|
|
3454
3538
|
const rows = await executor({ text: statement, values: [] });
|
|
3455
3539
|
if (rows.length > 0) {
|
|
3456
3540
|
const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
|
|
3457
|
-
throw new
|
|
3541
|
+
throw new VibeError13({
|
|
3458
3542
|
code: "VIBE_MIGRATION",
|
|
3459
3543
|
message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
|
|
3460
3544
|
meta: { violations: rows }
|
|
@@ -3474,8 +3558,8 @@ async function cleanupSelfManagedFailure(params) {
|
|
|
3474
3558
|
}
|
|
3475
3559
|
|
|
3476
3560
|
// packages/migrate/src/postgres-default-equivalence.ts
|
|
3477
|
-
import { VibeError as
|
|
3478
|
-
import { postgresDialect as
|
|
3561
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
3562
|
+
import { postgresDialect as postgresDialect7, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
|
|
3479
3563
|
var IDENTIFIER = '(?:"(?:[^"\\u0000]|"")+"|[A-Za-z_][A-Za-z_0-9$]*)';
|
|
3480
3564
|
var CALL = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*\\(\\s*\\)\\s*$`);
|
|
3481
3565
|
function parseCall(params) {
|
|
@@ -3491,9 +3575,9 @@ async function equivalentPostgresDefaultFunction(params) {
|
|
|
3491
3575
|
const observed = parseCall({ expression: params.actual });
|
|
3492
3576
|
if (expected?.namespace === undefined || observed === undefined)
|
|
3493
3577
|
return false;
|
|
3494
|
-
const query = renderPostgresDefaultFunctionIdentity({ dialect:
|
|
3578
|
+
const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect7, table: params.table, column: params.column });
|
|
3495
3579
|
const rows = await params.executor({ text: query.text, values: [...query.values] }).catch((cause) => {
|
|
3496
|
-
throw new
|
|
3580
|
+
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 });
|
|
3497
3581
|
});
|
|
3498
3582
|
if (rows.length !== 1)
|
|
3499
3583
|
return false;
|
|
@@ -3545,9 +3629,17 @@ async function inspectSqlObject(params) {
|
|
|
3545
3629
|
} else {
|
|
3546
3630
|
const constraintType = params.identity.constraintType;
|
|
3547
3631
|
const constraint = sqliteTableConstraints2({ sql: String(facts.definition ?? "") }).find((entry) => entry.name === name && entry.type === constraintType);
|
|
3548
|
-
if (constraint === undefined)
|
|
3549
|
-
|
|
3550
|
-
|
|
3632
|
+
if (constraint === undefined && constraintType === "primaryKey" && name === `${params.identity.table.name}_pkey`) {
|
|
3633
|
+
const query = renderSqliteColumnInventory({ dialect: getSqlDialect({ dialect: "sqlite" }), namespace: params.identity.table.namespace, table: params.identity.table.name });
|
|
3634
|
+
const columns = (await params.executor({ text: query.text, values: [...query.values] })).filter((column) => Number(column.pk) > 0).sort((a, b) => Number(a.pk) - Number(b.pk));
|
|
3635
|
+
if (columns.length === 0)
|
|
3636
|
+
return { state: "missing" };
|
|
3637
|
+
facts = { type: "primaryKey", columns: columns.map((column) => String(column.name)) };
|
|
3638
|
+
} else {
|
|
3639
|
+
if (constraint === undefined)
|
|
3640
|
+
return { state: "unavailable", reason: "constraint-definition-inspection-unavailable" };
|
|
3641
|
+
facts = { type: constraint.type, definition: constraint.sql };
|
|
3642
|
+
}
|
|
3551
3643
|
}
|
|
3552
3644
|
}
|
|
3553
3645
|
if (params.dialect === "sqlite" && (params.identity.kind === "column" || params.identity.kind === "columnDefault" || params.identity.kind === "generatedColumn")) {
|
|
@@ -3561,34 +3653,84 @@ async function inspectSqlObject(params) {
|
|
|
3561
3653
|
if (params.identity.kind === "columnDefault")
|
|
3562
3654
|
facts = { definition: facts.definition ?? null };
|
|
3563
3655
|
if (params.dialect === "postgres") {
|
|
3564
|
-
const
|
|
3565
|
-
const
|
|
3566
|
-
|
|
3567
|
-
return { state: "unavailable", reason: "dependency-inspection-unavailable" };
|
|
3568
|
-
facts = { ...facts, dependencyAddresses: rows2.map((row) => String(row.address)).sort() };
|
|
3656
|
+
const query = renderMigrationDependencies({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
|
|
3657
|
+
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
3658
|
+
return postgresInspection({ identity: params.identity, facts, addresses: rows.map((row) => row.address) });
|
|
3569
3659
|
}
|
|
3570
3660
|
if (params.dialect === "mysql") {
|
|
3571
|
-
const
|
|
3572
|
-
if (
|
|
3573
|
-
const
|
|
3574
|
-
if (params.identity.kind === "function" &&
|
|
3661
|
+
const query = renderMysqlObjectMembers({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
|
|
3662
|
+
if (query !== undefined) {
|
|
3663
|
+
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
3664
|
+
if (params.identity.kind === "function" && rows.length === 0)
|
|
3575
3665
|
return { state: "unavailable", reason: "function-signature-unavailable" };
|
|
3576
3666
|
if (params.identity.kind === "function") {
|
|
3577
|
-
const signature =
|
|
3667
|
+
const signature = rows.filter((row) => row.mode !== null).map((row) => ({ namespace: "mysql", name: String(row.type) }));
|
|
3578
3668
|
if (canonicalMigrationJson({ value: signature }) !== canonicalMigrationJson({ value: params.identity.argumentTypes }))
|
|
3579
3669
|
return { state: "missing" };
|
|
3580
3670
|
}
|
|
3581
|
-
facts = { ...facts, members:
|
|
3671
|
+
facts = { ...facts, members: rows.map((row) => canonicalMigrationJson({ value: row })) };
|
|
3582
3672
|
}
|
|
3583
3673
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3674
|
+
return { state: "present", definition: { identity: params.identity, facts, dependencies: [], format: "catalog-v2" } };
|
|
3675
|
+
}).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError15 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
|
|
3676
|
+
}
|
|
3677
|
+
function postgresInspection(params) {
|
|
3678
|
+
if (!params.addresses.every((address) => typeof address === "string"))
|
|
3679
|
+
return { state: "unavailable", reason: "dependency-inspection-unavailable" };
|
|
3680
|
+
const facts = { ...params.identity.kind === "columnDefault" ? { definition: params.facts.definition ?? null } : params.facts, dependencyAddresses: [...params.addresses].sort() };
|
|
3681
|
+
const dependencies = [];
|
|
3682
|
+
if (params.identity.kind === "trigger") {
|
|
3683
|
+
if (params.facts.functionArguments !== "")
|
|
3684
|
+
return { state: "unavailable", reason: "trigger-function-signature-unavailable" };
|
|
3685
|
+
dependencies.push({ kind: "function", name: { namespace: String(params.facts.functionNamespace), name: String(params.facts.functionName) }, argumentTypes: [] });
|
|
3686
|
+
}
|
|
3687
|
+
return { state: "present", definition: { identity: params.identity, facts, dependencies, format: "catalog-v2" } };
|
|
3688
|
+
}
|
|
3689
|
+
function unavailableReason(params) {
|
|
3690
|
+
return params.error instanceof VibeError15 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
|
|
3691
|
+
}
|
|
3692
|
+
async function readBatch(params) {
|
|
3693
|
+
const positions = new Set(params.batch.positions);
|
|
3694
|
+
return Promise.resolve().then(async () => {
|
|
3695
|
+
const rows = new Map;
|
|
3696
|
+
for (const row of await params.executor({ text: params.batch.text, values: [...params.batch.values] })) {
|
|
3697
|
+
if (typeof row.position !== "number" || !Number.isInteger(row.position) || !positions.has(row.position))
|
|
3698
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
|
|
3699
|
+
const matches = rows.get(row.position) ?? [];
|
|
3700
|
+
matches.push(row);
|
|
3701
|
+
rows.set(row.position, matches);
|
|
3702
|
+
}
|
|
3703
|
+
return { ok: true, rows };
|
|
3704
|
+
}).then((outcome) => outcome, (error) => ({ ok: false, reason: unavailableReason({ error }) }));
|
|
3705
|
+
}
|
|
3706
|
+
function batchedInspection(params) {
|
|
3707
|
+
if (params.matches.length === 0)
|
|
3708
|
+
return { state: "missing" };
|
|
3709
|
+
if (params.matches.length !== 1)
|
|
3710
|
+
return { state: "unavailable", reason: "ambiguous-catalog-identity" };
|
|
3711
|
+
const row = params.matches[0];
|
|
3712
|
+
const facts = row.facts !== null && typeof row.facts === "object" && !Array.isArray(row.facts) ? factsOf({ row: row.facts }) : undefined;
|
|
3713
|
+
if (facts === undefined || !Array.isArray(row.dependencyAddresses))
|
|
3714
|
+
return { state: "unavailable", reason: "unsupported-catalog-value" };
|
|
3715
|
+
return postgresInspection({ identity: params.identity, facts, addresses: row.dependencyAddresses });
|
|
3716
|
+
}
|
|
3717
|
+
async function inspectSqlObjects(params) {
|
|
3718
|
+
if (params.dialect !== "postgres") {
|
|
3719
|
+
const results = [];
|
|
3720
|
+
for (const identity of params.identities)
|
|
3721
|
+
results.push(await inspectSqlObject({ ...params, identity }));
|
|
3722
|
+
return results;
|
|
3723
|
+
}
|
|
3724
|
+
return Promise.resolve().then(async () => {
|
|
3725
|
+
const results = params.identities.map(() => ({ state: "unavailable", reason: "catalog-query-failed" }));
|
|
3726
|
+
for (const batch of renderMigrationObjectBatch({ dialect: getSqlDialect({ dialect: params.dialect }), identities: params.identities })) {
|
|
3727
|
+
const outcome = await readBatch({ executor: params.executor, batch });
|
|
3728
|
+
for (const position of batch.positions) {
|
|
3729
|
+
results[position] = outcome.ok ? batchedInspection({ identity: params.identities[position], matches: outcome.rows.get(position) ?? [] }) : { state: "unavailable", reason: outcome.reason };
|
|
3730
|
+
}
|
|
3589
3731
|
}
|
|
3590
|
-
return
|
|
3591
|
-
}).
|
|
3732
|
+
return results;
|
|
3733
|
+
}).then((result) => result, (error) => params.identities.map(() => ({ state: "unavailable", reason: unavailableReason({ error }) })));
|
|
3592
3734
|
}
|
|
3593
3735
|
async function assertCatalogVisibility(params) {
|
|
3594
3736
|
if (params.dialect !== "mysql")
|
|
@@ -3598,15 +3740,23 @@ async function assertCatalogVisibility(params) {
|
|
|
3598
3740
|
const grants = new Set(rows.map((row) => row.privilege));
|
|
3599
3741
|
const missing = ["SELECT", "TRIGGER", "SHOW VIEW", "EXECUTE"].filter((privilege) => !grants.has(privilege));
|
|
3600
3742
|
if (missing.length > 0)
|
|
3601
|
-
throw new
|
|
3743
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "MySQL catalog visibility requires direct database-wide grants; role-only or partial grants cannot prove object absence", meta: { reason: "inventory-unavailable", missing } });
|
|
3602
3744
|
}
|
|
3745
|
+
var CATALOG_V1_OMITTED_FACTS = Object.freeze({
|
|
3746
|
+
function: ["definerOwner"],
|
|
3747
|
+
column: ["collationNamespace", "collationName"],
|
|
3748
|
+
generatedColumn: ["collationNamespace", "collationName"]
|
|
3749
|
+
});
|
|
3603
3750
|
function compare(params) {
|
|
3604
3751
|
const differences = [];
|
|
3752
|
+
const omitted = params.expected.format === "catalog-v1" ? CATALOG_V1_OMITTED_FACTS[params.expected.identity.kind] ?? [] : [];
|
|
3605
3753
|
for (const key of new Set([...Object.keys(params.expected.facts), ...Object.keys(params.live.facts)])) {
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3754
|
+
if (omitted.includes(key) && !Object.hasOwn(params.expected.facts, key))
|
|
3755
|
+
continue;
|
|
3756
|
+
const expected = params.expected.facts[key];
|
|
3757
|
+
const actual = params.live.facts[key];
|
|
3758
|
+
if (expected === undefined !== (actual === undefined) || canonicalMigrationJson({ value: expected }) !== canonicalMigrationJson({ value: actual }))
|
|
3759
|
+
differences.push({ attribute: key, expected, actual });
|
|
3610
3760
|
}
|
|
3611
3761
|
const expected = params.expected.dependencies.map((identity) => sqlObjectKey4({ identity })).sort();
|
|
3612
3762
|
const actual = params.live.dependencies.map((identity) => sqlObjectKey4({ identity })).sort();
|
|
@@ -3616,7 +3766,7 @@ function compare(params) {
|
|
|
3616
3766
|
}
|
|
3617
3767
|
function assertProbeMode(params) {
|
|
3618
3768
|
if (params.probe !== undefined && params.probe !== "none" && DIALECT_CAPABILITIES2[params.dialect].checkNormalization !== params.probe)
|
|
3619
|
-
throw new
|
|
3769
|
+
throw new VibeError15({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
|
|
3620
3770
|
}
|
|
3621
3771
|
async function verifyMigrationState(params) {
|
|
3622
3772
|
return verifyState(params);
|
|
@@ -3630,10 +3780,13 @@ async function verifyState(params) {
|
|
|
3630
3780
|
for (const object of declarations)
|
|
3631
3781
|
identities.set(sqlObjectKey4({ identity: object.identity }), object.identity);
|
|
3632
3782
|
const objects = [];
|
|
3783
|
+
const inspections = params.batch === true ? await inspectSqlObjects({ ...params, identities: [...identities.values()] }) : undefined;
|
|
3784
|
+
let position = 0;
|
|
3633
3785
|
for (const [key, identity] of identities) {
|
|
3634
3786
|
const ownership = owned.has(key) ? "migration" : "schema";
|
|
3635
3787
|
const expected = definitions.get(key);
|
|
3636
|
-
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3788
|
+
const live = inspections === undefined ? await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity }) : inspections[position];
|
|
3789
|
+
position++;
|
|
3637
3790
|
if (live.state !== "present") {
|
|
3638
3791
|
objects.push({ identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
|
|
3639
3792
|
} else if (expected === undefined) {
|
|
@@ -3653,9 +3806,10 @@ async function verifyState(params) {
|
|
|
3653
3806
|
}
|
|
3654
3807
|
async function verifySchemaObjects(params) {
|
|
3655
3808
|
const physical = params.dialect === "postgres" ? await introspectPhysicalPostgres({ executor: params.executor }) : params.dialect === "mysql" ? await introspectMysql({ executor: params.executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor: params.executor }) : await introspectSqlite({ executor: params.executor });
|
|
3656
|
-
const
|
|
3809
|
+
const resolved = params.dialect === "postgres" ? await resolvePostgresTypeIdentities({ executor: params.executor, schema: params.expected.schema, mode: "verify" }) : { schema: params.expected.schema, unavailable: [] };
|
|
3810
|
+
const expected = normalizeSchema({ schema: resolved.schema, dialect: params.dialect });
|
|
3657
3811
|
const actual = normalizeSchema({ schema: physical, dialect: params.dialect });
|
|
3658
|
-
const objects = [];
|
|
3812
|
+
const objects = resolved.unavailable.map((identity) => ({ identity, ownership: "schema", state: "unavailable", reason: "type-reference-required", differences: [] }));
|
|
3659
3813
|
const namespace = await migrationNamespace({ executor: params.executor, dialect: params.dialect });
|
|
3660
3814
|
const sqliteTableOptions = params.dialect === "sqlite" && expected.tables.size > 0 ? await params.executor({ text: "PRAGMA main.table_list;", values: [] }) : undefined;
|
|
3661
3815
|
if (params.dialect === "postgres")
|
|
@@ -3690,9 +3844,9 @@ async function verifySchemaObjects(params) {
|
|
|
3690
3844
|
}
|
|
3691
3845
|
}
|
|
3692
3846
|
const differences = [];
|
|
3693
|
-
const pin = (attribute,
|
|
3694
|
-
if (canonicalMigrationJson({ value:
|
|
3695
|
-
differences.push({ attribute, expected
|
|
3847
|
+
const pin = (attribute, expected, actual) => {
|
|
3848
|
+
if (canonicalMigrationJson({ value: expected }) !== canonicalMigrationJson({ value: actual }))
|
|
3849
|
+
differences.push({ attribute, expected, actual });
|
|
3696
3850
|
};
|
|
3697
3851
|
pin("primaryKey", desired.pk, live.pk);
|
|
3698
3852
|
for (const [column, field] of desired.columns) {
|
|
@@ -3722,8 +3876,8 @@ async function verifySchemaObjects(params) {
|
|
|
3722
3876
|
if (inspection.state !== "present") {
|
|
3723
3877
|
objects.push({ identity, ownership: "schema", state: inspection.state, differences: [], ...inspection.state === "unavailable" ? { reason: inspection.reason } : {} });
|
|
3724
3878
|
} else {
|
|
3725
|
-
const
|
|
3726
|
-
objects.push({ identity, ownership: "schema", state:
|
|
3879
|
+
const differences = ["validated", "triggersEnabled"].flatMap((attribute) => inspection.definition.facts[attribute] === true ? [] : [{ attribute, expected: true, actual: inspection.definition.facts[attribute] }]);
|
|
3880
|
+
objects.push({ identity, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
|
|
3727
3881
|
}
|
|
3728
3882
|
}
|
|
3729
3883
|
}
|
|
@@ -3738,8 +3892,8 @@ async function verifySchemaObjects(params) {
|
|
|
3738
3892
|
if (inspection.state !== "present") {
|
|
3739
3893
|
objects.push({ identity, ownership: "schema", state: inspection.state, differences: [], ...inspection.state === "unavailable" ? { reason: inspection.reason } : {} });
|
|
3740
3894
|
} else {
|
|
3741
|
-
const
|
|
3742
|
-
objects.push({ identity, ownership: "schema", state:
|
|
3895
|
+
const differences = ["valid", "ready"].flatMap((attribute) => inspection.definition.facts[attribute] === true ? [] : [{ attribute, expected: true, actual: inspection.definition.facts[attribute] }]);
|
|
3896
|
+
objects.push({ identity, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
|
|
3743
3897
|
}
|
|
3744
3898
|
}
|
|
3745
3899
|
objects.push({ identity: { kind: "table", name: table }, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
|
|
@@ -3771,14 +3925,14 @@ async function verifySchemaObjects(params) {
|
|
|
3771
3925
|
if (params.dialect === "postgres" && params.probe === "rollback") {
|
|
3772
3926
|
const normalized = await deparsePostgresCheck({ executor: params.executor, table, expression, probe: "rollback" });
|
|
3773
3927
|
const read = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3774
|
-
const
|
|
3775
|
-
objects.push({ identity, ownership: "schema", state: normalized ===
|
|
3928
|
+
const actual = read.state === "present" ? read.definition.facts.definition : undefined;
|
|
3929
|
+
objects.push({ identity, ownership: "schema", state: normalized === actual ? "matching" : "changed", differences: normalized === actual ? [] : [{ attribute: "definition", expected: normalized, actual }] });
|
|
3776
3930
|
} else if (params.dialect === "mysql" && params.probe === "temporary") {
|
|
3777
3931
|
const normalized = await deparseMysqlCheck({ executor: params.executor, table, expression, probe: "temporary" });
|
|
3778
3932
|
const inspection = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3779
|
-
const
|
|
3780
|
-
const match = normalized ===
|
|
3781
|
-
objects.push({ identity, ownership: "schema", state: inspection.state === "unavailable" ? "unavailable" : match ? "matching" : "changed", differences: match ? [] : [{ attribute: "definition", expected: normalized, actual
|
|
3933
|
+
const actual = inspection.state === "present" ? inspection.definition.facts.definition : undefined;
|
|
3934
|
+
const match = normalized === actual;
|
|
3935
|
+
objects.push({ identity, ownership: "schema", state: inspection.state === "unavailable" ? "unavailable" : match ? "matching" : "changed", differences: match ? [] : [{ attribute: "definition", expected: normalized, actual }] });
|
|
3782
3936
|
} else
|
|
3783
3937
|
objects.push({ identity, ownership: "schema", state: "unavailable", reason: "explicit-replacement-required", differences: [{ attribute: "definition", expected: expression, actual: stored }] });
|
|
3784
3938
|
}
|
|
@@ -3800,14 +3954,14 @@ async function verifyMigrationTransition(params) {
|
|
|
3800
3954
|
}
|
|
3801
3955
|
async function verifySqliteTransitionBeforeRestore(params) {
|
|
3802
3956
|
if (params.dialect !== "sqlite")
|
|
3803
|
-
throw new
|
|
3957
|
+
throw new VibeError15({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
|
|
3804
3958
|
await executeStatement({ executor: params.executor, dialect: "sqlite", statement: "PRAGMA foreign_key_check;" });
|
|
3805
3959
|
return verifyTransition({ ...params, sqliteRestorePending: true });
|
|
3806
3960
|
}
|
|
3807
3961
|
async function verifyTransition(params) {
|
|
3808
3962
|
const report = await verifyState({ executor: params.executor, dialect: params.dialect, expected: params.to, ...params.sqliteRestorePending === true ? { sqliteRestorePending: true } : {}, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
3809
3963
|
const namespace = await migrationNamespace(params);
|
|
3810
|
-
const identities = (
|
|
3964
|
+
const identities = (state) => [...schemaObjectIdentities({ schema: state.schema, dialect: params.dialect, namespace }), ...(state.schema.migrationObjects ?? []).map((object) => object.identity), ...state.definitions.map((definition) => definition.identity)];
|
|
3811
3965
|
const retained = new Set(identities(params.to).map((identity) => sqlObjectKey4({ identity })));
|
|
3812
3966
|
const removed = new Map([...identities(params.from).filter((identity) => !retained.has(sqlObjectKey4({ identity }))), ...params.removals ?? []].map((identity) => [sqlObjectKey4({ identity }), identity]));
|
|
3813
3967
|
const objects = [...report.objects];
|
|
@@ -3826,12 +3980,12 @@ async function migrationNamespace(params) {
|
|
|
3826
3980
|
const rows = await params.executor({ text: query.text, values: [] });
|
|
3827
3981
|
const namespace = rows[0]?.namespace;
|
|
3828
3982
|
if (typeof namespace !== "string" || namespace.length === 0)
|
|
3829
|
-
throw new
|
|
3983
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
|
|
3830
3984
|
return namespace;
|
|
3831
3985
|
}
|
|
3832
3986
|
function assertMigrationState(params) {
|
|
3833
3987
|
if (!params.report.inSync)
|
|
3834
|
-
throw new
|
|
3988
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Migration state could not be verified", meta: { reason: "migration-state-unverified", shadowReusable: true, objects: params.report.objects.filter((object) => object.state !== "matching") } });
|
|
3835
3989
|
}
|
|
3836
3990
|
async function deparsePostgresCheck(params) {
|
|
3837
3991
|
const definition = await deparsePostgresCheckDefinition({ ...params, constraint: "vibeorm_check_probe" });
|
|
@@ -3845,7 +3999,7 @@ async function deparsePostgresCheckDefinition(params) {
|
|
|
3845
3999
|
await params.executor({ text: query.text });
|
|
3846
4000
|
const result = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: { kind: "constraint", constraintType: "check", table: params.table, name } });
|
|
3847
4001
|
if (result.state !== "present" || typeof result.definition.facts.definition !== "string")
|
|
3848
|
-
throw new
|
|
4002
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
3849
4003
|
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 } };
|
|
3850
4004
|
} finally {
|
|
3851
4005
|
await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_check_probe" });
|
|
@@ -3863,7 +4017,7 @@ async function deparseMysqlCheck(params) {
|
|
|
3863
4017
|
const source = await params.executor({ text: read.text, values: [] });
|
|
3864
4018
|
const definition = source[0]?.["Create Table"];
|
|
3865
4019
|
if (typeof definition !== "string")
|
|
3866
|
-
throw new
|
|
4020
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
|
|
3867
4021
|
const drop = renderMysqlCheckProbe({ dialect, probe, operation: "drop" });
|
|
3868
4022
|
await params.executor({ text: drop.text });
|
|
3869
4023
|
created = false;
|
|
@@ -3874,7 +4028,7 @@ async function deparseMysqlCheck(params) {
|
|
|
3874
4028
|
const normalizedDefinition = rows[0]?.["Create Table"];
|
|
3875
4029
|
const check = typeof normalizedDefinition === "string" ? parseSqliteNamedChecks({ tableSql: normalizedDefinition }).find((entry) => entry.name === probe) : undefined;
|
|
3876
4030
|
if (check === undefined)
|
|
3877
|
-
throw new
|
|
4031
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
3878
4032
|
return check.expression;
|
|
3879
4033
|
} finally {
|
|
3880
4034
|
if (created) {
|
|
@@ -3901,7 +4055,7 @@ async function prepareCheckDiff(params) {
|
|
|
3901
4055
|
const desired = await deparsePostgresCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "rollback" });
|
|
3902
4056
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3903
4057
|
if (live.state !== "present")
|
|
3904
|
-
throw new
|
|
4058
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
3905
4059
|
if (live.definition.facts.definition === desired) {
|
|
3906
4060
|
checks.push(next);
|
|
3907
4061
|
continue;
|
|
@@ -3913,7 +4067,7 @@ async function prepareCheckDiff(params) {
|
|
|
3913
4067
|
const desired = await deparseMysqlCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "temporary" });
|
|
3914
4068
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3915
4069
|
if (live.state !== "present")
|
|
3916
|
-
throw new
|
|
4070
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
3917
4071
|
checks.push(desired === live.definition.facts.definition ? next : { name: check.name, expression: check.expression });
|
|
3918
4072
|
continue;
|
|
3919
4073
|
}
|
|
@@ -3926,7 +4080,7 @@ async function prepareCheckDiff(params) {
|
|
|
3926
4080
|
checks.push({ name: check.name, expression: check.expression });
|
|
3927
4081
|
continue;
|
|
3928
4082
|
}
|
|
3929
|
-
throw new
|
|
4083
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: `Cannot establish CHECK equivalence for ${table}.${check.name}; request a rollback probe or an explicit replacement`, meta: { reason: "explicit-replacement-required", identity } });
|
|
3930
4084
|
}
|
|
3931
4085
|
models.push({ ...model, checks });
|
|
3932
4086
|
}
|
|
@@ -3953,7 +4107,7 @@ async function inspectMigrationInventory(params) {
|
|
|
3953
4107
|
for (const row of rows) {
|
|
3954
4108
|
const name = row.name;
|
|
3955
4109
|
if (typeof name !== "string" || typeof row.kind !== "string")
|
|
3956
|
-
throw new
|
|
4110
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
|
|
3957
4111
|
const qualified = { namespace: params.namespace, name };
|
|
3958
4112
|
const table = { namespace: params.namespace, name: String(row.parent ?? "") };
|
|
3959
4113
|
let identity;
|
|
@@ -3975,27 +4129,27 @@ async function inspectMigrationInventory(params) {
|
|
|
3975
4129
|
case "constraint": {
|
|
3976
4130
|
const constraintType = kinds[String(row.subtype)];
|
|
3977
4131
|
if (constraintType === undefined)
|
|
3978
|
-
throw new
|
|
4132
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
|
|
3979
4133
|
identity = { kind: "constraint", table, name, constraintType };
|
|
3980
4134
|
break;
|
|
3981
4135
|
}
|
|
3982
4136
|
case "function": {
|
|
3983
4137
|
let argumentTypes = JSON.parse(String(row.signature));
|
|
3984
4138
|
if (params.dialect === "mysql") {
|
|
3985
|
-
const
|
|
3986
|
-
const members = await params.executor({ text:
|
|
4139
|
+
const query = renderMysqlObjectMembers2({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
|
|
4140
|
+
const members = await params.executor({ text: query.text, values: [...query.values] });
|
|
3987
4141
|
if (members.length === 0)
|
|
3988
|
-
throw new
|
|
4142
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
|
|
3989
4143
|
argumentTypes = members.filter((member) => member.mode !== null).map((member) => ({ namespace: "mysql", name: String(member.type) }));
|
|
3990
4144
|
}
|
|
3991
4145
|
const candidate = { kind: "function", name: qualified, argumentTypes };
|
|
3992
4146
|
if (!isSqlObjectIdentity2(candidate))
|
|
3993
|
-
throw new
|
|
4147
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
|
|
3994
4148
|
identity = candidate;
|
|
3995
4149
|
break;
|
|
3996
4150
|
}
|
|
3997
4151
|
default:
|
|
3998
|
-
throw new
|
|
4152
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
|
|
3999
4153
|
}
|
|
4000
4154
|
objects.push({ identity, ...typeof row.backing === "string" ? { backingConstraint: row.backing } : {} });
|
|
4001
4155
|
if (params.dialect === "sqlite" && row.kind === "table" && typeof row.definition === "string" && !/^\s*CREATE\s+VIRTUAL\s+TABLE\b/i.test(row.definition)) {
|
|
@@ -4003,16 +4157,16 @@ async function inspectMigrationInventory(params) {
|
|
|
4003
4157
|
for (const constraint of clauses) {
|
|
4004
4158
|
if (constraint.type === "check")
|
|
4005
4159
|
continue;
|
|
4006
|
-
const
|
|
4007
|
-
objects.push({ identity: { kind: "constraint", constraintType: constraint.type, table: qualified, name
|
|
4160
|
+
const name = constraint.named ? constraint.name : constraint.type === "primaryKey" ? `${qualified.name}_pkey` : constraint.type === "foreignKey" ? `${qualified.name}_${constraint.columns.join("_")}_fkey` : constraint.name;
|
|
4161
|
+
objects.push({ identity: { kind: "constraint", constraintType: constraint.type, table: qualified, name }, ...constraint.type === "unique" && !constraint.named ? { inspectionUnavailable: "sqlite-anonymous-unique-constraint" } : {} });
|
|
4008
4162
|
}
|
|
4009
4163
|
const checks = parseSqliteNamedChecks({ tableSql: row.definition });
|
|
4010
4164
|
for (const check of checks)
|
|
4011
4165
|
objects.push({ identity: { kind: "constraint", constraintType: "check", table: qualified, name: check.name } });
|
|
4012
4166
|
if ([...row.definition.matchAll(/\bCHECK\s*\(/gi)].length > checks.length)
|
|
4013
4167
|
objects.push({ identity: { kind: "table", name: qualified }, inspectionUnavailable: "sqlite-unnamed-check-definition-unavailable" });
|
|
4014
|
-
const
|
|
4015
|
-
const columns = await params.executor({ text:
|
|
4168
|
+
const query = renderSqliteColumnInventory2({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace, table: name });
|
|
4169
|
+
const columns = await params.executor({ text: query.text, values: [...query.values] });
|
|
4016
4170
|
if (columns.some((column) => Number(column.pk) > 0) && !clauses.some((clause) => clause.type === "primaryKey"))
|
|
4017
4171
|
objects.push({ identity: { kind: "constraint", constraintType: "primaryKey", table: qualified, name: `${qualified.name}_pkey` } });
|
|
4018
4172
|
const fkQuery = renderSqliteForeignKeyInventory({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace, table: name });
|
|
@@ -4039,7 +4193,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4039
4193
|
|
|
4040
4194
|
// packages/migrate/src/module-ownership.ts
|
|
4041
4195
|
function refusal(params) {
|
|
4042
|
-
return new
|
|
4196
|
+
return new VibeError17({ code: "VIBE_MIGRATION", message: "Module ownership or dependencies cannot be reconciled with the declared and live schema; no package SQL was authorized", meta: params });
|
|
4043
4197
|
}
|
|
4044
4198
|
function identities(params) {
|
|
4045
4199
|
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)];
|
|
@@ -4055,14 +4209,14 @@ function moduleNamespace(params) {
|
|
|
4055
4209
|
function moduleOwnedObjects(params) {
|
|
4056
4210
|
const { desiredState } = params;
|
|
4057
4211
|
const namespace = params.namespace ?? (desiredState.dialect === "sqlite" ? "main" : "public");
|
|
4058
|
-
const all = [desiredState.from, desiredState.to, ...desiredState.down === undefined ? [] : [desiredState.down]].flatMap((
|
|
4212
|
+
const all = [desiredState.from, desiredState.to, ...desiredState.down === undefined ? [] : [desiredState.down]].flatMap((state) => identities({ state, dialect: desiredState.dialect, namespace }));
|
|
4059
4213
|
return [...new Map(all.map((identity) => [sqlObjectKey6({ identity }), identity])).values()].sort((a, b) => sqlObjectKey6({ identity: a }).localeCompare(sqlObjectKey6({ identity: b })));
|
|
4060
4214
|
}
|
|
4061
4215
|
function assertModuleOwnership(params) {
|
|
4062
4216
|
const { origin } = params;
|
|
4063
|
-
const
|
|
4217
|
+
const state = origin.source.desiredState;
|
|
4064
4218
|
const namespace = moduleNamespace({ origin });
|
|
4065
|
-
const expected = moduleOwnedObjects({ desiredState:
|
|
4219
|
+
const expected = moduleOwnedObjects({ desiredState: state, namespace });
|
|
4066
4220
|
const owned = new Set(origin.owned.map((identity) => sqlObjectKey6({ identity })));
|
|
4067
4221
|
if (expected.length !== owned.size || expected.some((identity) => !owned.has(sqlObjectKey6({ identity }))))
|
|
4068
4222
|
throw refusal({ reason: "module-ownership-invalid", moduleId: origin.moduleId });
|
|
@@ -4071,10 +4225,10 @@ function assertModuleOwnership(params) {
|
|
|
4071
4225
|
if (name?.namespace !== namespace || name.name === "_vibe_migrations")
|
|
4072
4226
|
throw refusal({ reason: "module-ownership-invalid", moduleId: origin.moduleId, identity });
|
|
4073
4227
|
}
|
|
4074
|
-
for (const target of [
|
|
4228
|
+
for (const target of [state.from, state.to, ...state.down === undefined ? [] : [state.down]]) {
|
|
4075
4229
|
if (target.schema.rls !== undefined || target.schema.extensions.length > 0)
|
|
4076
4230
|
throw refusal({ reason: "module-schema-unsupported", moduleId: origin.moduleId });
|
|
4077
|
-
for (const dependency of [...target.definitions.flatMap((definition) => definition.dependencies), ...(target.schema.migrationObjects ?? []).flatMap((object) => object.dependsOn), ...
|
|
4231
|
+
for (const dependency of [...target.definitions.flatMap((definition) => definition.dependencies), ...(target.schema.migrationObjects ?? []).flatMap((object) => object.dependsOn), ...state.unmanagedRemovals ?? []]) {
|
|
4078
4232
|
if (!owned.has(sqlObjectKey6({ identity: dependency })))
|
|
4079
4233
|
throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId, identity: dependency });
|
|
4080
4234
|
}
|
|
@@ -4095,10 +4249,39 @@ function assertModuleOwnership(params) {
|
|
|
4095
4249
|
throw refusal({ reason: "module-source-state-mismatch", moduleId: origin.moduleId });
|
|
4096
4250
|
return { ...actual, schema: { ...actual.schema, models: actual.schema.models.filter((model) => !tables.has(model.dbName ?? model.name)), enums: actual.schema.enums.filter((entry) => !enums.has(entry.dbName ?? entry.name)), migrationObjects: (actual.schema.migrationObjects ?? []).filter((object) => !owned.has(sqlObjectKey6({ identity: object.identity }))) }, definitions: actual.definitions.filter((definition) => !owned.has(sqlObjectKey6({ identity: definition.identity }))) };
|
|
4097
4251
|
};
|
|
4098
|
-
const before = canonicalMigrationJson({ value: rest(local.from,
|
|
4099
|
-
if (before !== canonicalMigrationJson({ value: rest(local.to,
|
|
4252
|
+
const before = canonicalMigrationJson({ value: rest(local.from, state.from) });
|
|
4253
|
+
if (before !== canonicalMigrationJson({ value: rest(local.to, state.to) }) || before !== canonicalMigrationJson({ value: rest(local.down ?? local.from, state.down ?? state.from) }))
|
|
4100
4254
|
throw refusal({ reason: "module-mutates-application-state", moduleId: origin.moduleId });
|
|
4101
4255
|
}
|
|
4256
|
+
function dependencyUnavailable(params) {
|
|
4257
|
+
return new VibeError17({ code: "VIBE_MIGRATION", message: "Cannot establish module foreign-key dependencies across namespaces. MySQL requires direct global SHOW VIEW (or SELECT) without partial revokes; SHOW VIEW exposes metadata without granting external row reads. Restore catalog visibility before retrying", meta: { reason: "module-dependency-unavailable", moduleId: params.origin.moduleId }, ...params.cause === undefined ? {} : { cause: params.cause } });
|
|
4258
|
+
}
|
|
4259
|
+
async function assertCrossNamespaceModuleDependencies(params) {
|
|
4260
|
+
const { executor, origin } = params;
|
|
4261
|
+
const dialect = getSqlDialect3({ dialect: origin.dialect });
|
|
4262
|
+
const query = renderModuleDependencyQuery({ dialect, operation: { kind: "foreignKeys", namespace: params.namespace, tables: [...params.tables] } });
|
|
4263
|
+
if (query === undefined)
|
|
4264
|
+
return;
|
|
4265
|
+
const visibility = renderModuleDependencyQuery({ dialect, operation: { kind: "visibility" } });
|
|
4266
|
+
if (visibility !== undefined) {
|
|
4267
|
+
const rows = await executor({ text: visibility.text, values: [...visibility.values] }).then(undefined, (cause) => {
|
|
4268
|
+
throw dependencyUnavailable({ origin, cause });
|
|
4269
|
+
});
|
|
4270
|
+
if (!hasMysqlGlobalCatalogVisibility({ rows }))
|
|
4271
|
+
throw dependencyUnavailable({ origin });
|
|
4272
|
+
}
|
|
4273
|
+
const rows = await executor({ text: query.text, values: [...query.values] }).then(undefined, (cause) => {
|
|
4274
|
+
throw dependencyUnavailable({ origin, cause });
|
|
4275
|
+
});
|
|
4276
|
+
const flags = [true, false, 0, 1, "0", "1"];
|
|
4277
|
+
for (const row of rows) {
|
|
4278
|
+
const { constraint_name: name, source_namespace: namespace, source_table: table, target_namespace: targetNamespace, target_table: targetTable, source_owned: source, target_owned: target } = row;
|
|
4279
|
+
if (typeof name !== "string" || name.length === 0 || typeof namespace !== "string" || namespace.length === 0 || typeof table !== "string" || table.length === 0 || typeof targetNamespace !== "string" || targetNamespace.length === 0 || typeof targetTable !== "string" || targetTable.length === 0 || !flags.includes(source) || !flags.includes(target) || Number(source) + Number(target) === 0)
|
|
4280
|
+
throw dependencyUnavailable({ origin });
|
|
4281
|
+
if (Number(source) !== Number(target))
|
|
4282
|
+
throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId, identity: { kind: "constraint", constraintType: "foreignKey", table: { namespace, name: table }, name } });
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4102
4285
|
async function assertLiveModuleOwnership(params) {
|
|
4103
4286
|
const { executor, origin } = params;
|
|
4104
4287
|
const namespace = moduleNamespace({ origin });
|
|
@@ -4108,6 +4291,7 @@ async function assertLiveModuleOwnership(params) {
|
|
|
4108
4291
|
const before = new Set(identities({ state: origin.source.desiredState.from, dialect: origin.dialect, namespace }).map((identity) => sqlObjectKey6({ identity })));
|
|
4109
4292
|
const owned = new Set(origin.owned.map((identity) => sqlObjectKey6({ identity })));
|
|
4110
4293
|
const tables = new Set(origin.owned.flatMap((identity) => identity.kind === "table" ? [identity.name.name] : []));
|
|
4294
|
+
await assertCrossNamespaceModuleDependencies({ executor, origin, namespace, tables });
|
|
4111
4295
|
for (const object of inventory) {
|
|
4112
4296
|
const key = sqlObjectKey6({ identity: object.identity });
|
|
4113
4297
|
if (params.resume !== true && owned.has(key) && !before.has(key))
|
|
@@ -4117,7 +4301,7 @@ async function assertLiveModuleOwnership(params) {
|
|
|
4117
4301
|
if (object.inspectionUnavailable !== undefined && (owned.has(key) || ("table" in object.identity) && tables.has(object.identity.table.name)))
|
|
4118
4302
|
throw refusal({ reason: "module-dependency-unavailable", moduleId: origin.moduleId, identity: object.identity });
|
|
4119
4303
|
}
|
|
4120
|
-
const physical = origin.dialect === "postgres" ? await introspectPhysicalPostgres({ executor }) : origin.dialect === "mysql" ? await introspectMysql({ executor }) : await introspectSqlite({ executor });
|
|
4304
|
+
const physical = origin.dialect === "postgres" ? await introspectPhysicalPostgres({ executor }) : origin.dialect === "mysql" ? await introspectMysql({ executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor }) : await introspectSqlite({ executor });
|
|
4121
4305
|
for (const model of physical.models)
|
|
4122
4306
|
for (const relation of model.relations) {
|
|
4123
4307
|
const target = physical.models.find((candidate) => candidate.name === relation.targetModel);
|
|
@@ -4125,13 +4309,23 @@ async function assertLiveModuleOwnership(params) {
|
|
|
4125
4309
|
throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId });
|
|
4126
4310
|
}
|
|
4127
4311
|
}
|
|
4312
|
+
async function assertApplicationModuleState(params) {
|
|
4313
|
+
for (const origin of params.origins) {
|
|
4314
|
+
await assertLiveModuleOwnership({ executor: params.executor, origin, resume: true, ...params.sqliteRestorePending === true ? { sqliteRestorePending: true } : {} });
|
|
4315
|
+
const state = origin.source.desiredState;
|
|
4316
|
+
const options = { executor: params.executor, dialect: origin.dialect, from: state.from, to: state.to, removals: state.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } };
|
|
4317
|
+
const report = params.sqliteRestorePending === true ? await verifySqliteTransitionBeforeRestore(options) : await verifyMigrationTransition(options);
|
|
4318
|
+
if (!report.inSync)
|
|
4319
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: `Application SQL cannot change objects owned by module ${origin.moduleId}; use its reviewed module upgrade`, meta: { reason: "module-application-mutation", moduleId: origin.moduleId, report } });
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4128
4322
|
|
|
4129
4323
|
// packages/migrate/src/sql-statements.ts
|
|
4130
|
-
import { VibeError as
|
|
4324
|
+
import { VibeError as VibeError18 } from "@vibeorm/schema";
|
|
4131
4325
|
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;
|
|
4132
4326
|
var BLOCK_OPENERS = new Set(["BEGIN", "CASE", "IF", "LOOP", "WHILE", "REPEAT"]);
|
|
4133
4327
|
function splitSqlStatements(params) {
|
|
4134
|
-
const { sql
|
|
4328
|
+
const { sql, dialect } = params;
|
|
4135
4329
|
const statements = [];
|
|
4136
4330
|
let current = "";
|
|
4137
4331
|
let hasContent = false;
|
|
@@ -4149,65 +4343,65 @@ function splitSqlStatements(params) {
|
|
|
4149
4343
|
compound = false;
|
|
4150
4344
|
afterEnd = false;
|
|
4151
4345
|
};
|
|
4152
|
-
while (i <
|
|
4153
|
-
const ch =
|
|
4154
|
-
const next =
|
|
4155
|
-
if (ch === "-" && next === "-" && (dialect !== "mysql" || /\s/.test(
|
|
4346
|
+
while (i < sql.length) {
|
|
4347
|
+
const ch = sql[i];
|
|
4348
|
+
const next = sql[i + 1];
|
|
4349
|
+
if (ch === "-" && next === "-" && (dialect !== "mysql" || /\s/.test(sql[i + 2] ?? `
|
|
4156
4350
|
`)) || ch === "#" && dialect === "mysql") {
|
|
4157
|
-
while (i <
|
|
4351
|
+
while (i < sql.length && sql[i] !== `
|
|
4158
4352
|
`) {
|
|
4159
|
-
current +=
|
|
4353
|
+
current += sql[i];
|
|
4160
4354
|
i++;
|
|
4161
4355
|
}
|
|
4162
4356
|
continue;
|
|
4163
4357
|
}
|
|
4164
4358
|
if (ch === "/" && next === "*") {
|
|
4165
|
-
current +=
|
|
4359
|
+
current += sql.slice(i, i + 2);
|
|
4166
4360
|
i += 2;
|
|
4167
4361
|
let depth = 1;
|
|
4168
|
-
while (i <
|
|
4169
|
-
if (
|
|
4362
|
+
while (i < sql.length && depth > 0) {
|
|
4363
|
+
if (sql[i] === "/" && sql[i + 1] === "*" && (dialect === undefined || dialect === "postgres")) {
|
|
4170
4364
|
depth++;
|
|
4171
4365
|
current += "/*";
|
|
4172
4366
|
i += 2;
|
|
4173
|
-
} else if (
|
|
4367
|
+
} else if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
4174
4368
|
depth--;
|
|
4175
4369
|
current += "*/";
|
|
4176
4370
|
i += 2;
|
|
4177
4371
|
} else {
|
|
4178
|
-
current +=
|
|
4372
|
+
current += sql[i];
|
|
4179
4373
|
i++;
|
|
4180
4374
|
}
|
|
4181
4375
|
}
|
|
4182
4376
|
continue;
|
|
4183
4377
|
}
|
|
4184
|
-
const escapeString = (dialect === undefined || dialect === "postgres") && (ch === "e" || ch === "E") && next === "'" && !/[A-Za-z0-9_$]/.test(
|
|
4378
|
+
const escapeString = (dialect === undefined || dialect === "postgres") && (ch === "e" || ch === "E") && next === "'" && !/[A-Za-z0-9_$]/.test(sql[i - 1] ?? "");
|
|
4185
4379
|
if (escapeString) {
|
|
4186
4380
|
current += ch;
|
|
4187
4381
|
i++;
|
|
4188
4382
|
}
|
|
4189
|
-
const quote =
|
|
4383
|
+
const quote = sql[i];
|
|
4190
4384
|
if (escapeString || quote === "'" || quote === '"' || quote === "`" && dialect !== "postgres" || quote === "[" && (dialect === undefined || dialect === "sqlite")) {
|
|
4191
4385
|
hasContent = true;
|
|
4192
4386
|
const endQuote = quote === "[" ? "]" : quote;
|
|
4193
4387
|
current += quote;
|
|
4194
4388
|
i++;
|
|
4195
|
-
while (i <
|
|
4196
|
-
const value =
|
|
4389
|
+
while (i < sql.length) {
|
|
4390
|
+
const value = sql[i];
|
|
4197
4391
|
current += value;
|
|
4198
4392
|
if (value === "\\" && (quote === "'" || quote === '"')) {
|
|
4199
4393
|
if (escapeString) {
|
|
4200
|
-
if (
|
|
4201
|
-
current +=
|
|
4394
|
+
if (sql[i + 1] !== undefined)
|
|
4395
|
+
current += sql[i + 1];
|
|
4202
4396
|
i += 2;
|
|
4203
4397
|
continue;
|
|
4204
4398
|
}
|
|
4205
4399
|
if (dialect === "mysql" || dialect === undefined) {
|
|
4206
|
-
throw new
|
|
4400
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "SQL string backslash escaping depends on the session SQL mode; use mode-independent quoting and pass the migration dialect", meta: { reason: "sql-mode-ambiguous", dialect } });
|
|
4207
4401
|
}
|
|
4208
4402
|
}
|
|
4209
4403
|
if (value === endQuote) {
|
|
4210
|
-
if (quote !== "[" &&
|
|
4404
|
+
if (quote !== "[" && sql[i + 1] === endQuote) {
|
|
4211
4405
|
current += endQuote;
|
|
4212
4406
|
i += 2;
|
|
4213
4407
|
continue;
|
|
@@ -4219,14 +4413,14 @@ function splitSqlStatements(params) {
|
|
|
4219
4413
|
}
|
|
4220
4414
|
continue;
|
|
4221
4415
|
}
|
|
4222
|
-
if (ch === "$" && (dialect === undefined || dialect === "postgres") && !/[A-Za-z0-9_$]/.test(
|
|
4223
|
-
const tagMatch = /^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/.exec(
|
|
4416
|
+
if (ch === "$" && (dialect === undefined || dialect === "postgres") && !/[A-Za-z0-9_$]/.test(sql[i - 1] ?? "")) {
|
|
4417
|
+
const tagMatch = /^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/.exec(sql.slice(i));
|
|
4224
4418
|
if (tagMatch !== null) {
|
|
4225
4419
|
hasContent = true;
|
|
4226
4420
|
const tag = tagMatch[0];
|
|
4227
|
-
const end =
|
|
4228
|
-
const stop = end === -1 ?
|
|
4229
|
-
current +=
|
|
4421
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
4422
|
+
const stop = end === -1 ? sql.length : end + tag.length;
|
|
4423
|
+
current += sql.slice(i, stop);
|
|
4230
4424
|
i = stop;
|
|
4231
4425
|
continue;
|
|
4232
4426
|
}
|
|
@@ -4241,14 +4435,14 @@ function splitSqlStatements(params) {
|
|
|
4241
4435
|
i++;
|
|
4242
4436
|
continue;
|
|
4243
4437
|
}
|
|
4244
|
-
const word = /^[A-Za-z_][A-Za-z_0-9]*/.exec(
|
|
4438
|
+
const word = /^[A-Za-z_][A-Za-z_0-9]*/.exec(sql.slice(i))?.[0];
|
|
4245
4439
|
if (word !== undefined) {
|
|
4246
4440
|
hasContent = true;
|
|
4247
4441
|
if (!compound && COMPOUND_BODY_HEADER.test(current + word))
|
|
4248
4442
|
compound = true;
|
|
4249
4443
|
if (compound) {
|
|
4250
4444
|
const upper = word.toUpperCase();
|
|
4251
|
-
const rest =
|
|
4445
|
+
const rest = sql.slice(i + word.length);
|
|
4252
4446
|
const opens = !afterEnd && (upper === "BEGIN" || blockDepth > 0 && BLOCK_OPENERS.has(upper) && !/^\s*\(/.test(rest) && !(upper === "IF" && /^\s+(?:NOT\s+)?EXISTS\b/i.test(rest)));
|
|
4253
4447
|
afterEnd = upper === "END";
|
|
4254
4448
|
if (opens)
|
|
@@ -4277,10 +4471,10 @@ function joinSqlStatements(params) {
|
|
|
4277
4471
|
|
|
4278
4472
|
// packages/migrate/src/module-origin.ts
|
|
4279
4473
|
import { createHash } from "crypto";
|
|
4280
|
-
import { VibeError as
|
|
4474
|
+
import { VibeError as VibeError19, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey7 } from "@vibeorm/schema";
|
|
4281
4475
|
var MODULE_ORIGIN_METADATA = Object.freeze({ filename: "module-origin.json", version: 1 });
|
|
4282
4476
|
function invalid2() {
|
|
4283
|
-
return new
|
|
4477
|
+
return new VibeError19({ code: "VIBE_MIGRATION", message: "Module migration origin is malformed or differs from its immutable package artifact; restore the reviewed artifact", meta: { reason: "module-origin-invalid" } });
|
|
4284
4478
|
}
|
|
4285
4479
|
function record2(value) {
|
|
4286
4480
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -4344,7 +4538,7 @@ function assertMaterializedModuleMigration(params) {
|
|
|
4344
4538
|
return;
|
|
4345
4539
|
const restored = restoreModuleMigration({ migration, origin: migration.moduleOrigin });
|
|
4346
4540
|
if (migration.desiredState === undefined || canonicalMigrationJson({ value: restored }) !== canonicalMigrationJson({ value: migration }))
|
|
4347
|
-
throw new
|
|
4541
|
+
throw new VibeError19({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
|
|
4348
4542
|
}
|
|
4349
4543
|
function restoreModuleMigration(params) {
|
|
4350
4544
|
const { migration, origin } = params;
|
|
@@ -4353,7 +4547,7 @@ function restoreModuleMigration(params) {
|
|
|
4353
4547
|
const actual = migration[key];
|
|
4354
4548
|
if (actual === undefined)
|
|
4355
4549
|
continue;
|
|
4356
|
-
const normalized = (
|
|
4550
|
+
const normalized = (sql) => sql.flatMap((text) => splitSqlStatements({ sql: text, dialect: origin.dialect }));
|
|
4357
4551
|
if (canonicalMigrationJson({ value: normalized(actual) }) !== canonicalMigrationJson({ value: normalized(origin.source[key] ?? []) }))
|
|
4358
4552
|
throw invalid2();
|
|
4359
4553
|
}
|
|
@@ -4367,13 +4561,13 @@ function restoreModuleMigration(params) {
|
|
|
4367
4561
|
// packages/migrate/src/fs.ts
|
|
4368
4562
|
import { readFile, readdir } from "fs/promises";
|
|
4369
4563
|
import { join } from "path";
|
|
4370
|
-
import { VibeError as
|
|
4564
|
+
import { VibeError as VibeError33, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
|
|
4371
4565
|
|
|
4372
4566
|
// packages/migrate/src/migration-checksum.ts
|
|
4373
4567
|
import { createHash as createHash2 } from "crypto";
|
|
4374
4568
|
|
|
4375
4569
|
// packages/migrate/src/execution.ts
|
|
4376
|
-
import { VibeError as
|
|
4570
|
+
import { VibeError as VibeError20 } from "@vibeorm/schema";
|
|
4377
4571
|
var EXECUTION_SIDECAR_FILENAME = "execution.json";
|
|
4378
4572
|
var EXECUTION_SIDECAR_VERSION = 1;
|
|
4379
4573
|
function isDefaultExecution(params) {
|
|
@@ -4412,7 +4606,7 @@ ${lines.join(`
|
|
|
4412
4606
|
`)}`;
|
|
4413
4607
|
}
|
|
4414
4608
|
function invalid3(params) {
|
|
4415
|
-
return new
|
|
4609
|
+
return new VibeError20({
|
|
4416
4610
|
code: "VIBE_MIGRATION",
|
|
4417
4611
|
message: `invalid execution metadata for migration "${params.migration}": ${params.detail}`,
|
|
4418
4612
|
meta: { reason: "execution-metadata-invalid", migration: params.migration }
|
|
@@ -4463,7 +4657,7 @@ function parseExecutionSidecar(params) {
|
|
|
4463
4657
|
try {
|
|
4464
4658
|
parsed = JSON.parse(params.text);
|
|
4465
4659
|
} catch (error) {
|
|
4466
|
-
throw new
|
|
4660
|
+
throw new VibeError20({
|
|
4467
4661
|
code: "VIBE_MIGRATION",
|
|
4468
4662
|
message: `invalid execution metadata for migration "${migration}": not valid JSON`,
|
|
4469
4663
|
meta: { reason: "execution-metadata-invalid", migration },
|
|
@@ -4561,16 +4755,35 @@ function migrationChecksum(params) {
|
|
|
4561
4755
|
-- vibeorm:down ${JSON.stringify(params.downSql ?? null)}${executionChecksumSuffix({ ...params.downExecution === undefined ? {} : { execution: params.downExecution } })}`)).digest("hex");
|
|
4562
4756
|
}
|
|
4563
4757
|
|
|
4758
|
+
// packages/migrate/src/shadow-guard.ts
|
|
4759
|
+
import { VibeError as VibeError21 } from "@vibeorm/schema";
|
|
4760
|
+
import { getSqlDialect as getSqlDialect4, renderShadowOperation } from "@vibeorm/sql";
|
|
4761
|
+
var replayExecutors = new WeakSet;
|
|
4762
|
+
async function assertDatabaseNotShadow(params) {
|
|
4763
|
+
if (params.dialect === "sqlite" || replayExecutors.has(params.executor))
|
|
4764
|
+
return;
|
|
4765
|
+
const [query] = renderShadowOperation({ dialect: getSqlDialect4({ dialect: params.dialect }), operation: { kind: "markerExists" } });
|
|
4766
|
+
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
4767
|
+
if (rows.length !== 0)
|
|
4768
|
+
throw new VibeError21({ code: "VIBE_MIGRATION", message: "This database is claimed as a disposable migration shadow. Application migrations must use the deployment target.", meta: { reason: "shadow-database-is-not-target" } });
|
|
4769
|
+
}
|
|
4770
|
+
function runShadowReplay(params) {
|
|
4771
|
+
replayExecutors.add(params.executor);
|
|
4772
|
+
return Promise.resolve().then(params.run).finally(() => {
|
|
4773
|
+
replayExecutors.delete(params.executor);
|
|
4774
|
+
});
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4564
4777
|
// packages/migrate/src/module-history.ts
|
|
4565
|
-
import { VibeError as
|
|
4778
|
+
import { VibeError as VibeError22, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
|
|
4566
4779
|
function refusal2(params) {
|
|
4567
|
-
return new
|
|
4780
|
+
return new VibeError22({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} history, ownership or predecessor state is inconsistent; restore or complete the reviewed upgrade first`, meta: params });
|
|
4568
4781
|
}
|
|
4569
4782
|
function moduleHistoryHeads(params) {
|
|
4570
4783
|
const heads = new Map;
|
|
4571
4784
|
const revisions = new Map;
|
|
4572
|
-
for (const
|
|
4573
|
-
const origin =
|
|
4785
|
+
for (const record of params.records.values()) {
|
|
4786
|
+
const origin = record.entry?.migration.moduleOrigin;
|
|
4574
4787
|
if (origin === undefined)
|
|
4575
4788
|
continue;
|
|
4576
4789
|
const previous = heads.get(origin.moduleId);
|
|
@@ -4583,10 +4796,21 @@ function moduleHistoryHeads(params) {
|
|
|
4583
4796
|
throw refusal2({ reason: "module-predecessor-state", moduleId: origin.moduleId });
|
|
4584
4797
|
seen.add(origin.revision);
|
|
4585
4798
|
revisions.set(origin.moduleId, seen);
|
|
4586
|
-
heads.set(origin.moduleId,
|
|
4799
|
+
heads.set(origin.moduleId, record);
|
|
4587
4800
|
}
|
|
4588
4801
|
return heads;
|
|
4589
4802
|
}
|
|
4803
|
+
function applicationModuleOrigins(params) {
|
|
4804
|
+
if (params.migration.moduleOrigin !== undefined || params.migration.desiredState !== undefined)
|
|
4805
|
+
return [];
|
|
4806
|
+
const origins = [];
|
|
4807
|
+
for (const [moduleId, head] of moduleHistoryHeads(params)) {
|
|
4808
|
+
if (!head.entry.complete || head.row.error !== undefined || head.row.statementIndex !== head.entry.migration.sql.length)
|
|
4809
|
+
throw refusal2({ reason: "module-chain-incomplete", moduleId });
|
|
4810
|
+
origins.push(head.entry.migration.moduleOrigin);
|
|
4811
|
+
}
|
|
4812
|
+
return origins;
|
|
4813
|
+
}
|
|
4590
4814
|
function preserves(params) {
|
|
4591
4815
|
const tables = new Set(params.expected.schema.models.map((model) => model.dbName ?? model.name));
|
|
4592
4816
|
const enums = new Set(params.expected.schema.enums.map((entry) => entry.dbName ?? entry.name));
|
|
@@ -4617,9 +4841,9 @@ function assertModuleUpgradeHistory(params) {
|
|
|
4617
4841
|
owners.set(key, origin.moduleId);
|
|
4618
4842
|
}
|
|
4619
4843
|
};
|
|
4620
|
-
for (const
|
|
4621
|
-
if (
|
|
4622
|
-
register(
|
|
4844
|
+
for (const record of params.records.values())
|
|
4845
|
+
if (record.entry !== undefined)
|
|
4846
|
+
register(record.entry.migration);
|
|
4623
4847
|
for (const migration of params.migrations) {
|
|
4624
4848
|
const stored = params.records.get(migration.name);
|
|
4625
4849
|
const origin = migration.moduleOrigin;
|
|
@@ -4631,7 +4855,7 @@ function assertModuleUpgradeHistory(params) {
|
|
|
4631
4855
|
if (origin === undefined) {
|
|
4632
4856
|
for (const [moduleId, head] of heads) {
|
|
4633
4857
|
if (migration.desiredState === undefined)
|
|
4634
|
-
|
|
4858
|
+
continue;
|
|
4635
4859
|
const expected = head.entry.migration.moduleOrigin.source.desiredState.to;
|
|
4636
4860
|
for (const actual of [migration.desiredState.from, migration.desiredState.to, migration.desiredState.down ?? migration.desiredState.from])
|
|
4637
4861
|
preserves({ actual, expected, moduleId });
|
|
@@ -4658,22 +4882,22 @@ function assertModuleUpgradeHistory(params) {
|
|
|
4658
4882
|
}
|
|
4659
4883
|
|
|
4660
4884
|
// packages/migrate/src/module-journal.ts
|
|
4661
|
-
import { VibeError as
|
|
4662
|
-
import { getSqlDialect as
|
|
4885
|
+
import { VibeError as VibeError24 } from "@vibeorm/schema";
|
|
4886
|
+
import { getSqlDialect as getSqlDialect5, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
|
|
4663
4887
|
|
|
4664
4888
|
// packages/migrate/src/journal-read.ts
|
|
4665
|
-
import { VibeError as
|
|
4889
|
+
import { VibeError as VibeError23 } from "@vibeorm/schema";
|
|
4666
4890
|
var MISSING_TABLE_IDENTIFIERS = new Set(["42P01", "42S02", "ER_NO_SUCH_TABLE"]);
|
|
4667
4891
|
function isMissingTable(params) {
|
|
4668
4892
|
let current = params.cause;
|
|
4669
4893
|
for (let depth = 0;depth < 6 && current !== null && typeof current === "object"; depth++) {
|
|
4670
|
-
const
|
|
4894
|
+
const record = current;
|
|
4671
4895
|
for (const key of ["code", "errno", "sqlState"]) {
|
|
4672
|
-
const value =
|
|
4896
|
+
const value = record[key];
|
|
4673
4897
|
if (typeof value === "string" && MISSING_TABLE_IDENTIFIERS.has(value))
|
|
4674
4898
|
return true;
|
|
4675
4899
|
}
|
|
4676
|
-
current =
|
|
4900
|
+
current = record.cause;
|
|
4677
4901
|
}
|
|
4678
4902
|
return false;
|
|
4679
4903
|
}
|
|
@@ -4686,7 +4910,7 @@ async function readJournalRows(params) {
|
|
|
4686
4910
|
if (presence.state === "missing")
|
|
4687
4911
|
return;
|
|
4688
4912
|
}
|
|
4689
|
-
throw new
|
|
4913
|
+
throw new VibeError23({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
|
|
4690
4914
|
});
|
|
4691
4915
|
if (rows === undefined)
|
|
4692
4916
|
return;
|
|
@@ -4694,7 +4918,7 @@ async function readJournalRows(params) {
|
|
|
4694
4918
|
for (const row of rows) {
|
|
4695
4919
|
const index = row.statement_index;
|
|
4696
4920
|
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)) {
|
|
4697
|
-
throw new
|
|
4921
|
+
throw new VibeError23({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
|
|
4698
4922
|
}
|
|
4699
4923
|
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 } : {} });
|
|
4700
4924
|
}
|
|
@@ -4703,7 +4927,7 @@ async function readJournalRows(params) {
|
|
|
4703
4927
|
|
|
4704
4928
|
// packages/migrate/src/module-journal.ts
|
|
4705
4929
|
function invalid4() {
|
|
4706
|
-
return new
|
|
4930
|
+
return new VibeError24({ code: "VIBE_MIGRATION", message: "Module migration history is missing, altered or inconsistent; restore the reviewed history before using this module", meta: { reason: "module-history-invalid" } });
|
|
4707
4931
|
}
|
|
4708
4932
|
function parseModuleJournalEntry(params) {
|
|
4709
4933
|
return parseMigrationMetadata({ text: params.text, failure: invalid4, parse: (value) => {
|
|
@@ -4719,7 +4943,7 @@ function parseModuleJournalEntry(params) {
|
|
|
4719
4943
|
} });
|
|
4720
4944
|
}
|
|
4721
4945
|
async function readModuleJournal(params) {
|
|
4722
|
-
const query = renderModuleJournal({ dialect:
|
|
4946
|
+
const query = renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: { kind: "read" } });
|
|
4723
4947
|
const rows = await readJournalRows({ ...params, query });
|
|
4724
4948
|
return new Map([...rows ?? []].map(([name, row]) => {
|
|
4725
4949
|
const entry = row.moduleEntry === undefined ? undefined : parseModuleJournalEntry({ text: row.moduleEntry, name, checksum: row.checksum });
|
|
@@ -4729,14 +4953,14 @@ async function readModuleJournal(params) {
|
|
|
4729
4953
|
}));
|
|
4730
4954
|
}
|
|
4731
4955
|
async function ensureModuleJournal(params) {
|
|
4732
|
-
const dialect =
|
|
4956
|
+
const dialect = getSqlDialect5({ dialect: params.dialect });
|
|
4733
4957
|
const columns = await params.executor(renderModuleJournal({ dialect, table: MIGRATIONS_TABLE_NAME, operation: { kind: "columns" } }));
|
|
4734
4958
|
if (columns.some((row) => row.name === MODULE_JOURNAL_COLUMN))
|
|
4735
4959
|
return;
|
|
4736
4960
|
await params.executor(renderModuleJournal({ dialect, table: MIGRATIONS_TABLE_NAME, operation: { kind: "addColumn" } }));
|
|
4737
4961
|
}
|
|
4738
4962
|
async function insertModuleJournal(params) {
|
|
4739
|
-
await params.executor(renderModuleJournal({ dialect:
|
|
4963
|
+
await params.executor(renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
|
|
4740
4964
|
kind: "insert",
|
|
4741
4965
|
name: params.migration.name,
|
|
4742
4966
|
checksum: migrationChecksum(params.migration),
|
|
@@ -4745,7 +4969,7 @@ async function insertModuleJournal(params) {
|
|
|
4745
4969
|
} }));
|
|
4746
4970
|
}
|
|
4747
4971
|
async function completeModuleJournal(params) {
|
|
4748
|
-
await params.executor(renderModuleJournal({ dialect:
|
|
4972
|
+
await params.executor(renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
|
|
4749
4973
|
kind: "complete",
|
|
4750
4974
|
name: params.migration.name,
|
|
4751
4975
|
entry: canonicalMigrationJson({ value: { version: 1, migration: params.migration, complete: true } })
|
|
@@ -4762,19 +4986,19 @@ function moduleRecordComplete(params) {
|
|
|
4762
4986
|
}
|
|
4763
4987
|
|
|
4764
4988
|
// packages/migrate/src/runner.ts
|
|
4765
|
-
import { VibeError as
|
|
4989
|
+
import { VibeError as VibeError32 } from "@vibeorm/schema";
|
|
4766
4990
|
import {
|
|
4767
4991
|
DIALECT_CAPABILITIES as DIALECT_CAPABILITIES3,
|
|
4768
|
-
getSqlDialect as
|
|
4992
|
+
getSqlDialect as getSqlDialect7,
|
|
4769
4993
|
renderMigrationSessionRead,
|
|
4770
4994
|
renderMigrationSessionRestore,
|
|
4771
4995
|
renderMigrationSessionSet
|
|
4772
4996
|
} from "@vibeorm/sql";
|
|
4773
4997
|
|
|
4774
4998
|
// packages/migrate/src/recovery.ts
|
|
4775
|
-
import { VibeError as
|
|
4999
|
+
import { VibeError as VibeError25 } from "@vibeorm/schema";
|
|
4776
5000
|
import {
|
|
4777
|
-
getSqlDialect as
|
|
5001
|
+
getSqlDialect as getSqlDialect6,
|
|
4778
5002
|
renderColumnStateQuery,
|
|
4779
5003
|
renderConstraintStateQuery,
|
|
4780
5004
|
renderIndexStateQuery,
|
|
@@ -4788,7 +5012,7 @@ function constraintUnverifiable(params) {
|
|
|
4788
5012
|
});
|
|
4789
5013
|
}
|
|
4790
5014
|
function conflict2(params) {
|
|
4791
|
-
return new
|
|
5015
|
+
return new VibeError25({
|
|
4792
5016
|
code: "VIBE_MIGRATION",
|
|
4793
5017
|
message: params.message,
|
|
4794
5018
|
meta: { reason: params.reason, ...params.meta }
|
|
@@ -4813,7 +5037,7 @@ function sameShape(params) {
|
|
|
4813
5037
|
}
|
|
4814
5038
|
async function establishOperationState(params) {
|
|
4815
5039
|
const { executor, operation } = params;
|
|
4816
|
-
const dialect =
|
|
5040
|
+
const dialect = getSqlDialect6({ dialect: params.dialect });
|
|
4817
5041
|
const run = async (query) => executor({ text: query.text, values: [...query.values] });
|
|
4818
5042
|
switch (operation.kind) {
|
|
4819
5043
|
case "createIndexConcurrently": {
|
|
@@ -4893,10 +5117,10 @@ async function establishOperationState(params) {
|
|
|
4893
5117
|
|
|
4894
5118
|
// packages/migrate/src/differ.ts
|
|
4895
5119
|
import { isEnumType as isEnumType6, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
4896
|
-
import { VibeError as
|
|
5120
|
+
import { VibeError as VibeError27 } from "@vibeorm/schema";
|
|
4897
5121
|
|
|
4898
5122
|
// packages/migrate/src/renames.ts
|
|
4899
|
-
import { VibeError as
|
|
5123
|
+
import { VibeError as VibeError26 } from "@vibeorm/schema";
|
|
4900
5124
|
function tableOf(model) {
|
|
4901
5125
|
return model.dbName ?? model.name;
|
|
4902
5126
|
}
|
|
@@ -4907,7 +5131,7 @@ function hasColumn(params) {
|
|
|
4907
5131
|
return params.model.fields.some((field) => (field.dbName ?? field.name) === params.column);
|
|
4908
5132
|
}
|
|
4909
5133
|
function ambiguous(params) {
|
|
4910
|
-
return new
|
|
5134
|
+
return new VibeError26({
|
|
4911
5135
|
code: "VIBE_MIGRATION",
|
|
4912
5136
|
message: `rename refused: ${params.detail}`,
|
|
4913
5137
|
meta: { reason: "rename-ambiguous", ...params.meta }
|
|
@@ -5055,7 +5279,7 @@ function diffSchemas(params) {
|
|
|
5055
5279
|
if (before?.definition === exclusion.definition)
|
|
5056
5280
|
continue;
|
|
5057
5281
|
if (before?.catalogDefinition)
|
|
5058
|
-
throw new
|
|
5282
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
|
|
5059
5283
|
if (before !== undefined)
|
|
5060
5284
|
buckets.dropExclusion.push({ kind: "dropExclusion", table, exclusion: before, destructive: false });
|
|
5061
5285
|
buckets.addExclusion.push({ kind: "addExclusion", table, exclusion, destructive: false });
|
|
@@ -5267,7 +5491,7 @@ function diffOneTable(params) {
|
|
|
5267
5491
|
changes.push("collation");
|
|
5268
5492
|
if (changes.length === 0)
|
|
5269
5493
|
continue;
|
|
5270
|
-
if (changes.includes("type") && [...previous.checks].some(([
|
|
5494
|
+
if (changes.includes("type") && [...previous.checks].some(([name, expression]) => current.checks.get(name) === expression)) {
|
|
5271
5495
|
throw unsupportedCapability6({ dialect, feature: "alterColumnWithRetainedCheck", hint: "CHECK dependencies and type compatibility are not proven; use a reviewed migration that replaces and revalidates the affected constraints" });
|
|
5272
5496
|
}
|
|
5273
5497
|
const storageChange = isEnumStringTransition({ from: before.field, to: column.field });
|
|
@@ -5331,7 +5555,7 @@ function diffOneTable(params) {
|
|
|
5331
5555
|
if (before === expression)
|
|
5332
5556
|
continue;
|
|
5333
5557
|
if (before !== undefined && previous.model.checks?.some((check) => check.name === name && check.catalogExpression === true))
|
|
5334
|
-
throw new
|
|
5558
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Unequal catalog CHECK text requires normalized evidence or explicit replacement through the live planner", meta: { reason: "check-inspection-unavailable", table, name } });
|
|
5335
5559
|
if (before !== undefined)
|
|
5336
5560
|
buckets.dropCheck.push({ kind: "dropCheck", table, name, destructive: false });
|
|
5337
5561
|
buckets.addCheck.push({ kind: "addCheck", table, name, expression, destructive: false });
|
|
@@ -5410,12 +5634,12 @@ function diffOneTable(params) {
|
|
|
5410
5634
|
}
|
|
5411
5635
|
|
|
5412
5636
|
// packages/migrate/src/ddl/render.ts
|
|
5413
|
-
import { VibeError as
|
|
5637
|
+
import { VibeError as VibeError31, sqlObjectKey as sqlObjectKey9 } from "@vibeorm/schema";
|
|
5414
5638
|
|
|
5415
5639
|
// packages/migrate/src/ddl/postgres.ts
|
|
5416
5640
|
import { renderExclusionSetup } from "@vibeorm/sql";
|
|
5417
|
-
import { VibeError as
|
|
5418
|
-
import { postgresDialect as
|
|
5641
|
+
import { VibeError as VibeError28, isEnumType as isEnumType7 } from "@vibeorm/schema";
|
|
5642
|
+
import { postgresDialect as postgresDialect8, renderRlsPolicyPath } from "@vibeorm/sql";
|
|
5419
5643
|
function renderPostgresSteps(params) {
|
|
5420
5644
|
refuseSameTransactionEnumUse({ steps: params.steps });
|
|
5421
5645
|
return params.steps.flatMap((step) => renderStep({ step }));
|
|
@@ -5449,7 +5673,7 @@ function refuseSameTransactionEnumUse(params) {
|
|
|
5449
5673
|
const found = use(field);
|
|
5450
5674
|
if (found === undefined)
|
|
5451
5675
|
continue;
|
|
5452
|
-
throw new
|
|
5676
|
+
throw new VibeError28({
|
|
5453
5677
|
code: "VIBE_MIGRATION",
|
|
5454
5678
|
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`,
|
|
5455
5679
|
meta: { enum: found.enumName, value: found.value, table: step.table, column: field.dbName ?? field.name, rule: "enum-value-used-in-adding-transaction" }
|
|
@@ -5461,7 +5685,7 @@ function renderStep(params) {
|
|
|
5461
5685
|
const { step } = params;
|
|
5462
5686
|
switch (step.kind) {
|
|
5463
5687
|
case "addExclusion":
|
|
5464
|
-
return [...renderExclusionSetup({ dialect:
|
|
5688
|
+
return [...renderExclusionSetup({ dialect: postgresDialect8, exclusion: step.exclusion }), `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.exclusion.name)} ${step.exclusion.definition};`];
|
|
5465
5689
|
case "dropExclusion":
|
|
5466
5690
|
return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.exclusion.name)};`];
|
|
5467
5691
|
case "enableRls":
|
|
@@ -5470,7 +5694,7 @@ function renderStep(params) {
|
|
|
5470
5694
|
case "noForceRls":
|
|
5471
5695
|
case "createPolicy":
|
|
5472
5696
|
case "dropPolicy":
|
|
5473
|
-
return step.kind === "createPolicy" ? [renderRlsPolicyPath({ dialect:
|
|
5697
|
+
return step.kind === "createPolicy" ? [renderRlsPolicyPath({ dialect: postgresDialect8, stage: "save" }).text, renderRlsPolicyPath({ dialect: postgresDialect8, stage: "empty" }).text, ...renderRlsStep({ step, dialect: "postgres" }), renderRlsPolicyPath({ dialect: postgresDialect8, stage: "restore" }).text] : renderRlsStep({ step, dialect: "postgres" });
|
|
5474
5698
|
case "createEnum": {
|
|
5475
5699
|
const name = step.enumDef.dbName ?? step.enumDef.name;
|
|
5476
5700
|
const values = step.enumDef.values.map((value) => quotePgString({ value })).join(", ");
|
|
@@ -5520,7 +5744,7 @@ ${lines.join(`,
|
|
|
5520
5744
|
case "dropIndex":
|
|
5521
5745
|
if (step.constraintName !== undefined) {
|
|
5522
5746
|
if (step.table === undefined)
|
|
5523
|
-
throw new
|
|
5747
|
+
throw new VibeError28({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
|
|
5524
5748
|
return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName)};`];
|
|
5525
5749
|
}
|
|
5526
5750
|
return [`DROP INDEX ${publicName(step.name)};`];
|
|
@@ -5584,7 +5808,7 @@ function columnDefSql(params) {
|
|
|
5584
5808
|
const { field } = params;
|
|
5585
5809
|
const name = q2(field.dbName ?? field.name);
|
|
5586
5810
|
if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
|
|
5587
|
-
throw new
|
|
5811
|
+
throw new VibeError28({
|
|
5588
5812
|
code: "VIBE_MIGRATION",
|
|
5589
5813
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
5590
5814
|
meta: { field: field.name }
|
|
@@ -5620,7 +5844,7 @@ function renderAlterColumn(params) {
|
|
|
5620
5844
|
}
|
|
5621
5845
|
if (step.changes.includes("default") || droppedDefault) {
|
|
5622
5846
|
if (step.to.default?.kind === "autoincrement") {
|
|
5623
|
-
throw new
|
|
5847
|
+
throw new VibeError28({
|
|
5624
5848
|
code: "VIBE_MIGRATION",
|
|
5625
5849
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
5626
5850
|
meta: { table: step.table, column: step.column }
|
|
@@ -5635,11 +5859,11 @@ function publicName(name) {
|
|
|
5635
5859
|
return `${q2("public")}.${q2(name)}`;
|
|
5636
5860
|
}
|
|
5637
5861
|
function q2(name) {
|
|
5638
|
-
return
|
|
5862
|
+
return postgresDialect8.quoteIdent({ name });
|
|
5639
5863
|
}
|
|
5640
5864
|
|
|
5641
5865
|
// packages/migrate/src/ddl/sqlite.ts
|
|
5642
|
-
import { VibeError as
|
|
5866
|
+
import { VibeError as VibeError29, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
5643
5867
|
import { sqliteDialect as sqliteDialect3 } from "@vibeorm/sql";
|
|
5644
5868
|
|
|
5645
5869
|
// packages/migrate/src/ddl/drop-order.ts
|
|
@@ -5779,7 +6003,7 @@ function renderSqliteSteps(params) {
|
|
|
5779
6003
|
continue;
|
|
5780
6004
|
const awaitsNewParent = [...toNorm?.tables.get(table)?.fks.values() ?? []].some((fk) => createdTables.has(fk.refTable));
|
|
5781
6005
|
if (awaitsNewParent) {
|
|
5782
|
-
throw new
|
|
6006
|
+
throw new VibeError29({
|
|
5783
6007
|
code: "VIBE_MIGRATION",
|
|
5784
6008
|
message: `cannot order this sqlite migration safely: rebuilding "${table}" must happen BEFORE the dropped table(s) it references (${[
|
|
5785
6009
|
...droppedTables
|
|
@@ -5808,7 +6032,7 @@ function renderSqliteSteps(params) {
|
|
|
5808
6032
|
switch (step.kind) {
|
|
5809
6033
|
case "addExclusion":
|
|
5810
6034
|
case "dropExclusion":
|
|
5811
|
-
throw new
|
|
6035
|
+
throw new VibeError29({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
5812
6036
|
case "createEnum":
|
|
5813
6037
|
case "dropEnum":
|
|
5814
6038
|
case "addEnumValue":
|
|
@@ -5893,7 +6117,7 @@ function renderSqliteSteps(params) {
|
|
|
5893
6117
|
case "renameColumn": {
|
|
5894
6118
|
const renamed = step.kind === "renameTable" ? step.from : step.table;
|
|
5895
6119
|
if (rebuildSet.has(renamed) || step.kind === "renameTable" && rebuildSet.has(step.to)) {
|
|
5896
|
-
throw new
|
|
6120
|
+
throw new VibeError29({
|
|
5897
6121
|
code: "VIBE_MIGRATION",
|
|
5898
6122
|
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`,
|
|
5899
6123
|
meta: { reason: "rename-ambiguous", table: renamed, dialect: "sqlite" }
|
|
@@ -5947,7 +6171,7 @@ function classifyRebuilds(params) {
|
|
|
5947
6171
|
switch (step.kind) {
|
|
5948
6172
|
case "addExclusion":
|
|
5949
6173
|
case "dropExclusion":
|
|
5950
|
-
throw new
|
|
6174
|
+
throw new VibeError29({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
5951
6175
|
case "alterColumn":
|
|
5952
6176
|
case "dropForeignKey":
|
|
5953
6177
|
case "addPrimaryKey":
|
|
@@ -6008,7 +6232,7 @@ function renderTableRebuild(params) {
|
|
|
6008
6232
|
const source = from.tables.get(table);
|
|
6009
6233
|
const target = to.tables.get(table);
|
|
6010
6234
|
if (source === undefined || target === undefined) {
|
|
6011
|
-
throw new
|
|
6235
|
+
throw new VibeError29({
|
|
6012
6236
|
code: "VIBE_MIGRATION",
|
|
6013
6237
|
message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
|
|
6014
6238
|
meta: { table }
|
|
@@ -6143,7 +6367,7 @@ function columnDefSql2(params) {
|
|
|
6143
6367
|
const { field, constraintTable, enums, inlinePkAutoincrement } = params;
|
|
6144
6368
|
const name = field.dbName ?? field.name;
|
|
6145
6369
|
if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
|
|
6146
|
-
throw new
|
|
6370
|
+
throw new VibeError29({
|
|
6147
6371
|
code: "VIBE_MIGRATION",
|
|
6148
6372
|
message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
|
|
6149
6373
|
meta: { field: field.name }
|
|
@@ -6162,7 +6386,7 @@ function columnDefSql2(params) {
|
|
|
6162
6386
|
const enumName = field.type.enum;
|
|
6163
6387
|
const values = resolveEnumValues({ name: enumName, enums });
|
|
6164
6388
|
if (values === undefined) {
|
|
6165
|
-
throw new
|
|
6389
|
+
throw new VibeError29({
|
|
6166
6390
|
code: "VIBE_MIGRATION",
|
|
6167
6391
|
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`,
|
|
6168
6392
|
meta: { field: field.name, enum: enumName }
|
|
@@ -6197,7 +6421,7 @@ function requireSchemas(params) {
|
|
|
6197
6421
|
const { step, fromNorm, toNorm } = params;
|
|
6198
6422
|
if (fromNorm !== undefined && toNorm !== undefined)
|
|
6199
6423
|
return;
|
|
6200
|
-
throw new
|
|
6424
|
+
throw new VibeError29({
|
|
6201
6425
|
code: "VIBE_MIGRATION",
|
|
6202
6426
|
message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
|
|
6203
6427
|
meta: { step: step.kind }
|
|
@@ -6208,7 +6432,7 @@ function q3(name) {
|
|
|
6208
6432
|
}
|
|
6209
6433
|
|
|
6210
6434
|
// packages/migrate/src/ddl/mysql.ts
|
|
6211
|
-
import { VibeError as
|
|
6435
|
+
import { VibeError as VibeError30, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
6212
6436
|
import { mysqlDialect as mysqlDialect3 } from "@vibeorm/sql";
|
|
6213
6437
|
function renderMysqlSteps(params) {
|
|
6214
6438
|
for (const step of params.steps) {
|
|
@@ -6350,12 +6574,12 @@ ${lines.join(`,
|
|
|
6350
6574
|
];
|
|
6351
6575
|
case "addExclusion":
|
|
6352
6576
|
case "dropExclusion":
|
|
6353
|
-
throw new
|
|
6577
|
+
throw new VibeError30({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
|
|
6354
6578
|
case "dropCheck":
|
|
6355
6579
|
return [`ALTER TABLE ${q4(step.table)} DROP CHECK ${q4(step.name)};`];
|
|
6356
6580
|
case "dropIndex": {
|
|
6357
6581
|
if (step.table === undefined) {
|
|
6358
|
-
throw new
|
|
6582
|
+
throw new VibeError30({
|
|
6359
6583
|
code: "VIBE_MIGRATION",
|
|
6360
6584
|
message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
|
|
6361
6585
|
meta: { index: step.name }
|
|
@@ -6411,7 +6635,7 @@ function columnDefSql3(params) {
|
|
|
6411
6635
|
const name = q4(field.dbName ?? field.name);
|
|
6412
6636
|
const autoincrement = field.default?.kind === "autoincrement";
|
|
6413
6637
|
if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
|
|
6414
|
-
throw new
|
|
6638
|
+
throw new VibeError30({
|
|
6415
6639
|
code: "VIBE_MIGRATION",
|
|
6416
6640
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
6417
6641
|
meta: { field: field.name }
|
|
@@ -6433,7 +6657,7 @@ function renderAlterColumn2(params) {
|
|
|
6433
6657
|
const { step, enums } = params;
|
|
6434
6658
|
assertEnumStringTransition({ from: step.from, to: step.to, dialect: "mysql", table: step.table, column: step.column });
|
|
6435
6659
|
if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
|
|
6436
|
-
throw new
|
|
6660
|
+
throw new VibeError30({
|
|
6437
6661
|
code: "VIBE_MIGRATION",
|
|
6438
6662
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
6439
6663
|
meta: { table: step.table, column: step.column }
|
|
@@ -6465,32 +6689,32 @@ function declaredSqlitePreservation(params) {
|
|
|
6465
6689
|
let to = params.to;
|
|
6466
6690
|
if (params.from === undefined || to === undefined || (params.from.migrationObjects?.length ?? 0) === 0)
|
|
6467
6691
|
return { to, restore, columns };
|
|
6468
|
-
const
|
|
6692
|
+
const invalid = sqliteInvalidatedTables({ steps: params.steps, from: params.from });
|
|
6469
6693
|
const retained = new Set((to.migrationObjects ?? []).map((object) => sqlObjectKey9({ identity: object.identity })));
|
|
6470
6694
|
for (const object of params.from.migrationObjects ?? []) {
|
|
6471
6695
|
const identity = object.identity;
|
|
6472
|
-
if (!("table" in identity) || identity.table.namespace !== "main" || !
|
|
6696
|
+
if (!("table" in identity) || identity.table.namespace !== "main" || !invalid.rebuilt.has(identity.table.name) || !retained.has(sqlObjectKey9({ identity })))
|
|
6473
6697
|
continue;
|
|
6474
6698
|
const facts = object.expected?.facts;
|
|
6475
6699
|
if (facts === undefined)
|
|
6476
|
-
throw new
|
|
6700
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6477
6701
|
if (identity.kind === "trigger" || identity.kind === "index") {
|
|
6478
6702
|
if (identity.kind === "index" && to.models.some((model) => (model.dbName ?? model.name) === identity.table.name && model.indexes.some((index) => index.name === identity.name)))
|
|
6479
6703
|
continue;
|
|
6480
6704
|
if (typeof facts.definition !== "string")
|
|
6481
|
-
throw new
|
|
6705
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6482
6706
|
const statements = restore.get(identity.table.name) ?? [];
|
|
6483
6707
|
statements.push(facts.definition);
|
|
6484
6708
|
restore.set(identity.table.name, statements);
|
|
6485
6709
|
} else if (identity.kind === "column" || identity.kind === "generatedColumn") {
|
|
6486
6710
|
if (typeof facts.columnDefinition !== "string")
|
|
6487
|
-
throw new
|
|
6711
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6488
6712
|
const fields = columns.get(identity.table.name) ?? new Map;
|
|
6489
6713
|
fields.set(identity.column, { sql: facts.columnDefinition, generated: Number(facts.generated) > 1 });
|
|
6490
6714
|
columns.set(identity.table.name, fields);
|
|
6491
6715
|
} else if (identity.kind === "columnDefault") {
|
|
6492
6716
|
if (facts.definition !== null && typeof facts.definition !== "string")
|
|
6493
|
-
throw new
|
|
6717
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6494
6718
|
const expression = facts.definition;
|
|
6495
6719
|
to = { ...to, models: to.models.map((model) => (model.dbName ?? model.name) !== identity.table.name ? model : { ...model, fields: model.fields.map((field) => {
|
|
6496
6720
|
if ((field.dbName ?? field.name) !== identity.column)
|
|
@@ -6634,19 +6858,19 @@ function planMissingArtifacts(params) {
|
|
|
6634
6858
|
}
|
|
6635
6859
|
async function probeInstalledExtensionState(params) {
|
|
6636
6860
|
const { executor, dialect, artifacts } = params;
|
|
6637
|
-
const
|
|
6861
|
+
const state = {};
|
|
6638
6862
|
if (dialect === "postgres" && artifacts.some((artifact) => artifact.owns.kind === "postgresExtension")) {
|
|
6639
6863
|
const rows = await executor({ text: "SELECT extname FROM pg_extension" });
|
|
6640
|
-
|
|
6864
|
+
state.pgExtensions = new Set(rows.map((row) => String(row["extname"])));
|
|
6641
6865
|
}
|
|
6642
6866
|
if (dialect === "sqlite" && artifacts.some((artifact) => artifact.owns.kind === "trigger")) {
|
|
6643
6867
|
const rows = await executor({
|
|
6644
6868
|
text: "SELECT name FROM sqlite_master WHERE type = 'trigger'",
|
|
6645
6869
|
values: []
|
|
6646
6870
|
});
|
|
6647
|
-
|
|
6871
|
+
state.triggers = new Set(rows.map((row) => String(row["name"])));
|
|
6648
6872
|
}
|
|
6649
|
-
return
|
|
6873
|
+
return state;
|
|
6650
6874
|
}
|
|
6651
6875
|
function artifactCreateSql(params) {
|
|
6652
6876
|
return params.artifacts.flatMap((artifact) => [...artifact.create]);
|
|
@@ -6659,29 +6883,29 @@ function migrationRlsSecurityChanges(params) {
|
|
|
6659
6883
|
return securityChangesOf({ steps: withRlsSteps({ from: params.down ? source.to : source.from, to: params.down ? source.from : source.to, steps: [] }) });
|
|
6660
6884
|
}
|
|
6661
6885
|
function containsNativeRlsSql(params) {
|
|
6662
|
-
const { sql
|
|
6886
|
+
const { sql, dialect } = params;
|
|
6663
6887
|
const tokens = [];
|
|
6664
6888
|
let i = 0;
|
|
6665
|
-
while (i <
|
|
6666
|
-
const ch =
|
|
6889
|
+
while (i < sql.length) {
|
|
6890
|
+
const ch = sql[i];
|
|
6667
6891
|
if (/\s/.test(ch)) {
|
|
6668
6892
|
i++;
|
|
6669
6893
|
continue;
|
|
6670
6894
|
}
|
|
6671
|
-
if (
|
|
6672
|
-
const end =
|
|
6895
|
+
if (sql.startsWith("--", i) || dialect === "mysql" && ch === "#") {
|
|
6896
|
+
const end = sql.indexOf(`
|
|
6673
6897
|
`, i);
|
|
6674
|
-
i = end === -1 ?
|
|
6898
|
+
i = end === -1 ? sql.length : end + 1;
|
|
6675
6899
|
continue;
|
|
6676
6900
|
}
|
|
6677
|
-
if (
|
|
6901
|
+
if (sql.startsWith("/*", i)) {
|
|
6678
6902
|
let depth = 1;
|
|
6679
6903
|
i += 2;
|
|
6680
|
-
while (i <
|
|
6681
|
-
if (
|
|
6904
|
+
while (i < sql.length && depth > 0) {
|
|
6905
|
+
if (sql.startsWith("/*", i)) {
|
|
6682
6906
|
depth++;
|
|
6683
6907
|
i += 2;
|
|
6684
|
-
} else if (
|
|
6908
|
+
} else if (sql.startsWith("*/", i)) {
|
|
6685
6909
|
depth--;
|
|
6686
6910
|
i += 2;
|
|
6687
6911
|
} else
|
|
@@ -6691,15 +6915,15 @@ function containsNativeRlsSql(params) {
|
|
|
6691
6915
|
}
|
|
6692
6916
|
if (ch === "'" || ch === '"' || ch === "`" || dialect === "sqlite" && ch === "[") {
|
|
6693
6917
|
const endQuote = ch === "[" ? "]" : ch;
|
|
6694
|
-
const backslash = dialect === "mysql" || ch === "'" && /(?:^|[^\w$])[eE]$/.test(
|
|
6918
|
+
const backslash = dialect === "mysql" || ch === "'" && /(?:^|[^\w$])[eE]$/.test(sql.slice(0, i));
|
|
6695
6919
|
i++;
|
|
6696
|
-
while (i <
|
|
6697
|
-
if (backslash &&
|
|
6920
|
+
while (i < sql.length) {
|
|
6921
|
+
if (backslash && sql[i] === "\\") {
|
|
6698
6922
|
i += 2;
|
|
6699
6923
|
continue;
|
|
6700
6924
|
}
|
|
6701
|
-
if (
|
|
6702
|
-
if (
|
|
6925
|
+
if (sql[i] === endQuote) {
|
|
6926
|
+
if (sql[i + 1] === endQuote) {
|
|
6703
6927
|
i += 2;
|
|
6704
6928
|
continue;
|
|
6705
6929
|
}
|
|
@@ -6712,15 +6936,15 @@ function containsNativeRlsSql(params) {
|
|
|
6712
6936
|
continue;
|
|
6713
6937
|
}
|
|
6714
6938
|
if (ch === "$") {
|
|
6715
|
-
const tag = /^(?:\$\$|\$[A-Za-z_][A-Za-z_0-9]*\$)/.exec(
|
|
6939
|
+
const tag = /^(?:\$\$|\$[A-Za-z_][A-Za-z_0-9]*\$)/.exec(sql.slice(i))?.[0];
|
|
6716
6940
|
if (tag !== undefined) {
|
|
6717
|
-
const end =
|
|
6718
|
-
i = end === -1 ?
|
|
6941
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
6942
|
+
i = end === -1 ? sql.length : end + tag.length;
|
|
6719
6943
|
tokens.push("@");
|
|
6720
6944
|
continue;
|
|
6721
6945
|
}
|
|
6722
6946
|
}
|
|
6723
|
-
const word = /^[A-Za-z_][A-Za-z_0-9$]*/.exec(
|
|
6947
|
+
const word = /^[A-Za-z_][A-Za-z_0-9$]*/.exec(sql.slice(i))?.[0];
|
|
6724
6948
|
tokens.push(word === undefined ? ch : word.toUpperCase());
|
|
6725
6949
|
i += word?.length ?? 1;
|
|
6726
6950
|
}
|
|
@@ -6730,14 +6954,14 @@ function containsNativeRlsSql(params) {
|
|
|
6730
6954
|
});
|
|
6731
6955
|
}
|
|
6732
6956
|
function assertNativeMigrationMetadata(params) {
|
|
6733
|
-
const native = params.migration.rls !== undefined || params.sql.some((
|
|
6957
|
+
const native = params.migration.rls !== undefined || params.sql.some((sql) => containsNativeRlsSql({ sql, dialect: params.dialect }));
|
|
6734
6958
|
if (!native)
|
|
6735
6959
|
return;
|
|
6736
6960
|
if (!DIALECT_CAPABILITIES3[params.dialect].nativeRowLevelSecurity) {
|
|
6737
|
-
throw new
|
|
6961
|
+
throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
|
|
6738
6962
|
}
|
|
6739
6963
|
if (params.migration.rls === undefined) {
|
|
6740
|
-
throw new
|
|
6964
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Native RLS migration files require trusted rls.from/rls.to schema metadata. SQL alone cannot prove policy ownership or drift; regenerate with preserved security metadata.", meta: { reason: "rls-migration-metadata-required", migration: params.migration.name } });
|
|
6741
6965
|
}
|
|
6742
6966
|
}
|
|
6743
6967
|
var PG_ADVISORY_LOCK_KEY = 1447645253;
|
|
@@ -6779,14 +7003,14 @@ function checksumOf(params) {
|
|
|
6779
7003
|
return migrationChecksum(migration);
|
|
6780
7004
|
}
|
|
6781
7005
|
async function assertForwardStart(params) {
|
|
6782
|
-
const
|
|
6783
|
-
if (
|
|
7006
|
+
const state = params.migration.desiredState;
|
|
7007
|
+
if (state === undefined)
|
|
6784
7008
|
return;
|
|
6785
|
-
const report = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected:
|
|
7009
|
+
const report = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.from, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
6786
7010
|
if (report.inSync)
|
|
6787
7011
|
return;
|
|
6788
|
-
if (
|
|
6789
|
-
const restored = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected:
|
|
7012
|
+
if (state.down !== undefined) {
|
|
7013
|
+
const restored = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.down, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
6790
7014
|
if (restored.inSync)
|
|
6791
7015
|
return;
|
|
6792
7016
|
}
|
|
@@ -6806,7 +7030,7 @@ function generateMigration(params) {
|
|
|
6806
7030
|
const steps = diffSchemas({ from: strippedFrom, to, dialect, ...params.renames === undefined ? {} : { renames: params.renames } });
|
|
6807
7031
|
const coreSql = renderSteps({ steps, dialect, from: dialect === "sqlite" ? from : strippedFrom, to: dialect === "sqlite" ? sqlitePreservedSchema({ from, to, objects: [...from.migrationObjects ?? [], ...to.migrationObjects ?? []] }) : to });
|
|
6808
7032
|
const missing = planMissingArtifacts({ introspected: from, artifacts: allArtifacts });
|
|
6809
|
-
const
|
|
7033
|
+
const sql = [
|
|
6810
7034
|
...artifactCreateSql({
|
|
6811
7035
|
artifacts: missing.filter((artifact) => artifact.owns.kind === "postgresExtension")
|
|
6812
7036
|
}),
|
|
@@ -6816,18 +7040,18 @@ function generateMigration(params) {
|
|
|
6816
7040
|
}),
|
|
6817
7041
|
...orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).flatMap((object) => {
|
|
6818
7042
|
if (object.dialect !== dialect)
|
|
6819
|
-
throw new
|
|
7043
|
+
throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
|
|
6820
7044
|
const previous = (params.desiredState?.from.schema.migrationObjects ?? from.migrationObjects)?.find((entry) => entry.id === object.id);
|
|
6821
7045
|
if (previous === undefined)
|
|
6822
7046
|
return [...object.create];
|
|
6823
7047
|
if (JSON.stringify(previous) !== JSON.stringify(object))
|
|
6824
|
-
throw new
|
|
7048
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration-owned object ${object.id} changed; supply a reviewed forward SQL migration`, meta: { reason: "migration-object-replacement-required", shadowReusable: true, id: object.id } });
|
|
6825
7049
|
return [];
|
|
6826
7050
|
})
|
|
6827
7051
|
];
|
|
6828
7052
|
return {
|
|
6829
7053
|
name,
|
|
6830
|
-
sql
|
|
7054
|
+
sql,
|
|
6831
7055
|
destructive: steps.some((step) => step.destructive),
|
|
6832
7056
|
...securityChangesOf({ steps }).length === 0 ? {} : { securityChanges: securityChangesOf({ steps }) },
|
|
6833
7057
|
...from.rls === undefined && to.rls === undefined ? {} : { rls: { from, to } },
|
|
@@ -6835,6 +7059,25 @@ function generateMigration(params) {
|
|
|
6835
7059
|
desiredState: params.desiredState ?? { version: 1, dialect, from: createDesiredState({ schema: from, dialect }), to: createDesiredState({ schema: to, dialect }) }
|
|
6836
7060
|
};
|
|
6837
7061
|
}
|
|
7062
|
+
async function withMigrationLock(params) {
|
|
7063
|
+
const runLocked = async (executor) => {
|
|
7064
|
+
await acquireLock({ executor, dialect: params.dialect });
|
|
7065
|
+
return Promise.resolve().then(async () => {
|
|
7066
|
+
await assertDatabaseNotShadow({ executor, dialect: params.dialect });
|
|
7067
|
+
return params.run(executor);
|
|
7068
|
+
}).then(async (value) => {
|
|
7069
|
+
await releaseLock({ executor, dialect: params.dialect });
|
|
7070
|
+
return value;
|
|
7071
|
+
}, (cause) => releaseLock({ executor, dialect: params.dialect }).then(() => {
|
|
7072
|
+
throw cause;
|
|
7073
|
+
}, (cleanupCause) => {
|
|
7074
|
+
if (cause instanceof VibeError32)
|
|
7075
|
+
throw new VibeError32({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
|
|
7076
|
+
throw cause;
|
|
7077
|
+
}));
|
|
7078
|
+
};
|
|
7079
|
+
return params.executor.withSession === undefined ? runLocked(params.executor) : params.executor.withSession({ run: runLocked });
|
|
7080
|
+
}
|
|
6838
7081
|
function validateMigrationReplay(params) {
|
|
6839
7082
|
const dialect = params.dialect ?? "postgres";
|
|
6840
7083
|
assertProbeMode({ dialect, probe: params.probe });
|
|
@@ -6845,8 +7088,8 @@ function validateMigrationReplay(params) {
|
|
|
6845
7088
|
assertSupportedSelfManagedScript({ sql: migration.sql, migration: migration.name, direction: "up" });
|
|
6846
7089
|
assertNamedRemovals({ removals: migration.desiredState?.unmanagedRemovals ?? [], approved: params.removeUnmanaged, acceptIntegrityLoss: params.acceptIntegrityLoss });
|
|
6847
7090
|
if (migration.desiredState !== undefined) {
|
|
6848
|
-
const
|
|
6849
|
-
assertRemovalAuthorization({ steps: diffSchemas({ from:
|
|
7091
|
+
const state = migration.desiredState;
|
|
7092
|
+
assertRemovalAuthorization({ steps: diffSchemas({ from: state.from.schema, to: state.to.schema, dialect }), previous: state.from, dialect, namespace: dialect === "sqlite" ? "main" : "public", ...params.acceptIntegrityLoss === undefined ? {} : { acceptIntegrityLoss: params.acceptIntegrityLoss } });
|
|
6850
7093
|
}
|
|
6851
7094
|
assertSecurityAcknowledgement({ securityChanges: [...migration.securityChanges ?? [], ...migrationRlsSecurityChanges({ migration, down: false })], ...params.acceptSecurityChanges === undefined ? {} : { acceptSecurityChanges: params.acceptSecurityChanges } });
|
|
6852
7095
|
}
|
|
@@ -6858,11 +7101,11 @@ function assertMigrationMetadata(params) {
|
|
|
6858
7101
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
6859
7102
|
}
|
|
6860
7103
|
if (migration.desiredStatePending === true)
|
|
6861
|
-
throw new
|
|
7104
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration ${migration.name} has unfinalized desired state; finalize its reviewed before/after declarations before applying`, meta: { reason: "desired-state-unfinalized", migration: migration.name } });
|
|
6862
7105
|
if (migration.desiredState !== undefined) {
|
|
6863
7106
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
6864
7107
|
if (migration.desiredState.dialect !== dialect)
|
|
6865
|
-
throw new
|
|
7108
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
|
|
6866
7109
|
}
|
|
6867
7110
|
assertNativeMigrationMetadata({ migration, sql: migration.sql, dialect });
|
|
6868
7111
|
assertExecutable({ migration, execution: migration.execution, sql: migration.sql, dialect });
|
|
@@ -6871,23 +7114,19 @@ async function applyMigrations(params) {
|
|
|
6871
7114
|
const { executor, migrations } = params;
|
|
6872
7115
|
const dialect = params.dialect ?? "postgres";
|
|
6873
7116
|
if (params.history !== undefined && params.history !== "complete" && params.history !== "subset")
|
|
6874
|
-
throw new
|
|
7117
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
|
|
6875
7118
|
if (params.history === "complete")
|
|
6876
7119
|
assertDistinctMigrationNames({ migrations });
|
|
6877
7120
|
for (const migration of migrations)
|
|
6878
7121
|
assertMigrationMetadata({ migration, dialect });
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
try {
|
|
6884
|
-
const moduleRecords = migrations.some((migration) => migration.moduleOrigin !== undefined) ? await readModuleJournal({ executor, dialect }) : undefined;
|
|
6885
|
-
const recorded = moduleRecords === undefined ? await recordedRows({ executor, dialect }) : new Map([...moduleRecords].map(([name, record3]) => [name, record3.row]));
|
|
6886
|
-
if (moduleRecords !== undefined) {
|
|
7122
|
+
return withMigrationLock({ executor, dialect, run: async (executor) => {
|
|
7123
|
+
let moduleRecords = await readModuleJournal({ executor, dialect });
|
|
7124
|
+
const recorded = new Map([...moduleRecords].map(([name, record]) => [name, record.row]));
|
|
7125
|
+
if (migrations.some((migration) => migration.moduleOrigin !== undefined) || [...moduleRecords.values()].some((record) => record.entry !== undefined)) {
|
|
6887
7126
|
assertModuleUpgradeHistory({ migrations, records: moduleRecords });
|
|
6888
7127
|
const namespace = await migrationNamespace({ executor, dialect });
|
|
6889
7128
|
if (migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
|
|
6890
|
-
throw new
|
|
7129
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
|
|
6891
7130
|
}
|
|
6892
7131
|
if (params.history === "complete")
|
|
6893
7132
|
assertCompleteHistory({ migrations, recorded, dialect });
|
|
@@ -6898,7 +7137,7 @@ async function applyMigrations(params) {
|
|
|
6898
7137
|
});
|
|
6899
7138
|
validateMigrationReplay({ ...params, migrations: pending, dialect });
|
|
6900
7139
|
await ensureMigrationsTable({ executor, dialect });
|
|
6901
|
-
if (
|
|
7140
|
+
if (migrations.some((migration) => migration.moduleOrigin !== undefined))
|
|
6902
7141
|
await ensureModuleJournal({ executor, dialect });
|
|
6903
7142
|
const applied = [];
|
|
6904
7143
|
const skipped = [];
|
|
@@ -6908,7 +7147,7 @@ async function applyMigrations(params) {
|
|
|
6908
7147
|
await assertLiveModuleOwnership({ executor, origin: migration.moduleOrigin, resume: stored !== undefined });
|
|
6909
7148
|
if (stored !== undefined) {
|
|
6910
7149
|
if (stored.statementIndex < 0) {
|
|
6911
|
-
throw new
|
|
7150
|
+
throw new VibeError32({
|
|
6912
7151
|
code: "VIBE_MIGRATION",
|
|
6913
7152
|
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`,
|
|
6914
7153
|
meta: { migration: migration.name, statementIndex: stored.statementIndex }
|
|
@@ -6929,11 +7168,14 @@ async function applyMigrations(params) {
|
|
|
6929
7168
|
dialect,
|
|
6930
7169
|
migration,
|
|
6931
7170
|
start: stored.statementIndex,
|
|
7171
|
+
moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }),
|
|
6932
7172
|
...params.probe === undefined ? {} : { probe: params.probe },
|
|
6933
7173
|
insertRecord: false,
|
|
6934
7174
|
...stored.error === undefined ? {} : { storedError: stored.error }
|
|
6935
7175
|
})
|
|
6936
7176
|
});
|
|
7177
|
+
if (migration.moduleOrigin !== undefined)
|
|
7178
|
+
moduleRecords = await readModuleJournal({ executor, dialect });
|
|
6937
7179
|
applied.push(migration.name);
|
|
6938
7180
|
continue;
|
|
6939
7181
|
}
|
|
@@ -6942,11 +7184,13 @@ async function applyMigrations(params) {
|
|
|
6942
7184
|
executor,
|
|
6943
7185
|
dialect,
|
|
6944
7186
|
...migration.execution === undefined ? {} : { execution: migration.execution },
|
|
6945
|
-
run: () => runNonTransactional({ executor, dialect, migration, start: 0, insertRecord: true, ...params.probe === undefined ? {} : { probe: params.probe } })
|
|
7187
|
+
run: () => runNonTransactional({ executor, dialect, migration, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), start: 0, insertRecord: true, ...params.probe === undefined ? {} : { probe: params.probe } })
|
|
6946
7188
|
});
|
|
6947
7189
|
} else {
|
|
6948
|
-
await runTransactional({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
7190
|
+
await runTransactional({ executor, dialect, migration, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
6949
7191
|
}
|
|
7192
|
+
if (migration.moduleOrigin !== undefined)
|
|
7193
|
+
moduleRecords = await readModuleJournal({ executor, dialect });
|
|
6950
7194
|
applied.push(migration.name);
|
|
6951
7195
|
}
|
|
6952
7196
|
const final = migrations.at(-1);
|
|
@@ -6961,9 +7205,7 @@ async function applyMigrations(params) {
|
|
|
6961
7205
|
}
|
|
6962
7206
|
}
|
|
6963
7207
|
return { applied, skipped };
|
|
6964
|
-
}
|
|
6965
|
-
await releaseLock({ executor, dialect });
|
|
6966
|
-
}
|
|
7208
|
+
} });
|
|
6967
7209
|
}
|
|
6968
7210
|
async function migrationStatus(params) {
|
|
6969
7211
|
const { executor, migrations } = params;
|
|
@@ -7033,19 +7275,14 @@ function classifyJournal(params) {
|
|
|
7033
7275
|
return journal;
|
|
7034
7276
|
}
|
|
7035
7277
|
async function resolveMigration(params) {
|
|
7036
|
-
if (params.executor.withSession !== undefined) {
|
|
7037
|
-
return params.executor.withSession({ run: (executor2) => resolveMigration({ ...params, executor: executor2 }) });
|
|
7038
|
-
}
|
|
7039
|
-
const { executor } = params;
|
|
7040
7278
|
const dialect = params.dialect ?? "postgres";
|
|
7041
|
-
await
|
|
7042
|
-
await repairMigration({ ...params, executor, dialect }).finally(() => releaseLock({ executor, dialect }));
|
|
7279
|
+
await withMigrationLock({ executor: params.executor, dialect, run: (executor) => repairMigration({ ...params, executor, dialect }) });
|
|
7043
7280
|
}
|
|
7044
7281
|
async function repairMigration(params) {
|
|
7045
7282
|
const { executor, name, dialect } = params;
|
|
7046
7283
|
const moduleRecord = (await readModuleJournal({ executor, dialect })).get(name);
|
|
7047
7284
|
if (moduleRecord?.entry !== undefined)
|
|
7048
|
-
throw new
|
|
7285
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Module history cannot be marked applied or deleted without verified module recovery", meta: { reason: "module-resolution-required", moduleId: moduleRecord.entry.migration.moduleOrigin.moduleId } });
|
|
7049
7286
|
await ensureMigrationsTable({ executor, dialect });
|
|
7050
7287
|
if (params.as === "applied") {
|
|
7051
7288
|
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})`;
|
|
@@ -7062,38 +7299,30 @@ async function rollbackMigration(params) {
|
|
|
7062
7299
|
const { executor, migration } = params;
|
|
7063
7300
|
const dialect = params.dialect ?? "postgres";
|
|
7064
7301
|
const downSql = validateRollbackFile({ ...params, dialect });
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
}
|
|
7068
|
-
await acquireLock({ executor, dialect });
|
|
7069
|
-
await Promise.resolve().then(async () => {
|
|
7070
|
-
const recorded = await recordedRows({ executor, dialect, modules: migration.moduleOrigin !== undefined });
|
|
7302
|
+
await withMigrationLock({ executor, dialect, run: async (executor) => {
|
|
7303
|
+
const recorded = await recordedRows({ executor, dialect, modules: true });
|
|
7071
7304
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(migration.name), dialect });
|
|
7072
7305
|
const newest = [...recorded.keys()].at(-1);
|
|
7073
7306
|
if (newest !== migration.name)
|
|
7074
|
-
throw new
|
|
7307
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${migration.name}" is not the newest recorded migration; select rollback again`, meta: { reason: "rollback-not-latest", migration: migration.name, newest } });
|
|
7075
7308
|
await validateRollbackRecovery({ executor, migration, downSql, stored, dialect });
|
|
7076
|
-
await executeRollback({ ...params, dialect, downSql, stored });
|
|
7077
|
-
}
|
|
7309
|
+
await executeRollback({ ...params, executor, dialect, downSql, stored });
|
|
7310
|
+
} });
|
|
7078
7311
|
}
|
|
7079
7312
|
async function rollbackMigrations(params) {
|
|
7080
7313
|
const { executor, migrations } = params;
|
|
7081
7314
|
const dialect = params.dialect ?? "postgres";
|
|
7082
7315
|
assertDistinctMigrationNames({ migrations });
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
}
|
|
7086
|
-
await acquireLock({ executor, dialect });
|
|
7087
|
-
return Promise.resolve().then(async () => {
|
|
7088
|
-
const recorded = await recordedRows({ executor, dialect, modules: migrations.some((migration) => migration.moduleOrigin !== undefined) });
|
|
7316
|
+
return withMigrationLock({ executor, dialect, run: async (executor) => {
|
|
7317
|
+
const recorded = await recordedRows({ executor, dialect, modules: true });
|
|
7089
7318
|
const journal = classifyJournal({ migrations, recorded, dialect });
|
|
7090
7319
|
const byName = new Map(migrations.map((migration) => [migration.name, migration]));
|
|
7091
7320
|
let start = Math.max(0, journal.length - 1);
|
|
7092
7321
|
if (params.to !== undefined) {
|
|
7093
|
-
const target = journal.findIndex((
|
|
7322
|
+
const target = journal.findIndex((row) => row.name === params.to);
|
|
7094
7323
|
const row = journal[target];
|
|
7095
7324
|
if (row?.state !== "applied")
|
|
7096
|
-
throw new
|
|
7325
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Rollback target "${params.to}" is not a known applied migration`, meta: { reason: "rollback-target-invalid", to: params.to, state: row?.state } });
|
|
7097
7326
|
assertMigrationMetadata({ migration: byName.get(row.name), dialect });
|
|
7098
7327
|
start = target + 1;
|
|
7099
7328
|
}
|
|
@@ -7101,7 +7330,7 @@ async function rollbackMigrations(params) {
|
|
|
7101
7330
|
for (const row of journal.slice(start).reverse()) {
|
|
7102
7331
|
const migration = byName.get(row.name);
|
|
7103
7332
|
if (migration === undefined)
|
|
7104
|
-
throw new
|
|
7333
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
|
|
7105
7334
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(row.name), dialect });
|
|
7106
7335
|
const downSql = validateRollbackFile({ ...params, migration, dialect });
|
|
7107
7336
|
plan.push({ migration, downSql, stored });
|
|
@@ -7110,28 +7339,28 @@ async function rollbackMigrations(params) {
|
|
|
7110
7339
|
await validateRollbackRecovery({ executor, dialect, ...entry });
|
|
7111
7340
|
const rolledBack = [];
|
|
7112
7341
|
for (const entry of plan) {
|
|
7113
|
-
await executeRollback({ ...params, dialect, ...entry });
|
|
7342
|
+
await executeRollback({ ...params, executor, dialect, ...entry });
|
|
7114
7343
|
rolledBack.push(entry.migration.name);
|
|
7115
7344
|
}
|
|
7116
7345
|
return { rolledBack };
|
|
7117
|
-
}
|
|
7346
|
+
} });
|
|
7118
7347
|
}
|
|
7119
7348
|
function validateRollbackFile(params) {
|
|
7120
7349
|
const { migration, dialect } = params;
|
|
7121
7350
|
if (migration.desiredStatePending === true)
|
|
7122
|
-
throw new
|
|
7351
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
|
|
7123
7352
|
if (migration.moduleOrigin !== undefined) {
|
|
7124
7353
|
assertMaterializedModuleMigration({ migration });
|
|
7125
7354
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
7126
7355
|
}
|
|
7127
7356
|
const downSql = migration.downSql;
|
|
7128
7357
|
if (downSql === undefined)
|
|
7129
|
-
throw new
|
|
7358
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" has no down.sql; roll it back manually, then repair with resolveMigration (as: "rolled-back")`, meta: { reason: "rollback-down-missing", migration: migration.name } });
|
|
7130
7359
|
assertProbeMode({ dialect, probe: params.probe });
|
|
7131
7360
|
if (migration.desiredState !== undefined) {
|
|
7132
7361
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
7133
7362
|
if (migration.desiredState.dialect !== dialect)
|
|
7134
|
-
throw new
|
|
7363
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
|
|
7135
7364
|
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 } });
|
|
7136
7365
|
}
|
|
7137
7366
|
assertNativeMigrationMetadata({ migration, sql: downSql, dialect });
|
|
@@ -7144,10 +7373,10 @@ function validateRollbackFile(params) {
|
|
|
7144
7373
|
function assertRollbackRecord(params) {
|
|
7145
7374
|
const { migration, stored, dialect } = params;
|
|
7146
7375
|
if (stored === undefined)
|
|
7147
|
-
throw new
|
|
7376
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
|
|
7148
7377
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
7149
7378
|
if (stored.statementIndex >= 0 && (!isCompleteRecord({ dialect, migration, stored }) || stored.error !== undefined))
|
|
7150
|
-
throw new
|
|
7379
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is only partially applied or has an unresolved error; its down mirrors the FULL up. Finish or repair with resolveMigration`, meta: { reason: "rollback-partial-up", migration: migration.name, statementIndex: stored.statementIndex } });
|
|
7151
7380
|
return stored;
|
|
7152
7381
|
}
|
|
7153
7382
|
async function validateRollbackRecovery(params) {
|
|
@@ -7156,7 +7385,7 @@ async function validateRollbackRecovery(params) {
|
|
|
7156
7385
|
return;
|
|
7157
7386
|
const start = -stored.statementIndex - 1;
|
|
7158
7387
|
if (start > downSql.length)
|
|
7159
|
-
throw new
|
|
7388
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
|
|
7160
7389
|
if (dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql }))
|
|
7161
7390
|
assertSelfManagedResumeSafe({ sql: downSql, start, migration: migration.name, direction: "down" });
|
|
7162
7391
|
if (!marksInflight({ dialect, execution: migration.downExecution }) || start >= downSql.length)
|
|
@@ -7169,16 +7398,17 @@ async function validateRollbackRecovery(params) {
|
|
|
7169
7398
|
}
|
|
7170
7399
|
async function executeRollback(params) {
|
|
7171
7400
|
const { executor, dialect, migration, downSql, stored } = params;
|
|
7401
|
+
const moduleOrigins = applicationModuleOrigins({ migration, records: await readModuleJournal({ executor, dialect }) });
|
|
7172
7402
|
if (usesStatementBookkeeping({ dialect, sql: downSql, ...migration.downExecution === undefined ? {} : { execution: migration.downExecution } })) {
|
|
7173
7403
|
const start = stored.statementIndex < 0 ? -stored.statementIndex - 1 : 0;
|
|
7174
7404
|
await withSessionLimits({
|
|
7175
7405
|
executor,
|
|
7176
7406
|
dialect,
|
|
7177
7407
|
execution: migration.downExecution,
|
|
7178
|
-
run: () => runNonTransactionalDown({ executor, dialect, migration, downSql, start, probe: params.probe, storedError: stored.error })
|
|
7408
|
+
run: () => runNonTransactionalDown({ executor, dialect, migration, moduleOrigins, downSql, start, probe: params.probe, storedError: stored.error })
|
|
7179
7409
|
});
|
|
7180
7410
|
} else
|
|
7181
|
-
await runTransactionalDown({ executor, dialect, migration, downSql, probe: params.probe });
|
|
7411
|
+
await runTransactionalDown({ executor, dialect, migration, moduleOrigins, downSql, probe: params.probe });
|
|
7182
7412
|
}
|
|
7183
7413
|
async function runTransactionalDown(params) {
|
|
7184
7414
|
const { executor, dialect, migration, downSql } = params;
|
|
@@ -7190,14 +7420,15 @@ async function runTransactionalDown(params) {
|
|
|
7190
7420
|
const limits = migration.downExecution;
|
|
7191
7421
|
if (wrap && dialect === "postgres" && limits !== undefined) {
|
|
7192
7422
|
if ((limits.lockTimeoutMs ?? 0) > 0)
|
|
7193
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7423
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
|
|
7194
7424
|
if ((limits.statementTimeoutMs ?? 0) > 0)
|
|
7195
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7425
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
|
|
7196
7426
|
}
|
|
7197
7427
|
if (migration.desiredState !== undefined)
|
|
7198
7428
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7199
7429
|
if (migration.rls !== undefined)
|
|
7200
7430
|
await introspectPostgres({ executor, rlsSchema: migration.rls.to, inTransaction: true, requireInstalledRls: true });
|
|
7431
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7201
7432
|
for (const statement of downSql) {
|
|
7202
7433
|
await executeStatement({ executor, statement, dialect });
|
|
7203
7434
|
}
|
|
@@ -7205,6 +7436,7 @@ async function runTransactionalDown(params) {
|
|
|
7205
7436
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7206
7437
|
if (migration.rls !== undefined)
|
|
7207
7438
|
await introspectPostgres({ executor, rlsSchema: migration.rls.from, inTransaction: true, requireInstalledRls: true });
|
|
7439
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7208
7440
|
await executor({
|
|
7209
7441
|
text: `DELETE FROM ${q5(dialect, MIGRATIONS_TABLE_NAME)} WHERE ${q5(dialect, "name")} = ${ph(dialect, 1)}`,
|
|
7210
7442
|
values: [migration.name]
|
|
@@ -7219,7 +7451,7 @@ async function runTransactionalDown(params) {
|
|
|
7219
7451
|
} else {
|
|
7220
7452
|
await cleanupSelfManagedFailure({ executor });
|
|
7221
7453
|
}
|
|
7222
|
-
throw new
|
|
7454
|
+
throw new VibeError32({
|
|
7223
7455
|
code: "VIBE_MIGRATION",
|
|
7224
7456
|
message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7225
7457
|
meta: { migration: migration.name, ...timeoutDiagnostic({ error }) },
|
|
@@ -7251,6 +7483,7 @@ async function runNonTransactionalDown(params) {
|
|
|
7251
7483
|
throw uncertainStatement({ migration: migration.name, statements: downSql, index: start, dialect, direction: "down" });
|
|
7252
7484
|
}
|
|
7253
7485
|
const resumeState = resumeOperation === undefined ? undefined : await establishOperationState({ executor, dialect, operation: resumeOperation });
|
|
7486
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7254
7487
|
if (start === 0) {
|
|
7255
7488
|
if (migration.desiredState !== undefined)
|
|
7256
7489
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
@@ -7260,6 +7493,8 @@ async function runNonTransactionalDown(params) {
|
|
|
7260
7493
|
if (resumeOperation !== undefined && resumeState === "satisfied") {
|
|
7261
7494
|
if (start + 1 === downSql.length && migration.desiredState !== undefined)
|
|
7262
7495
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7496
|
+
if (start + 1 === downSql.length)
|
|
7497
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7263
7498
|
await recordProgress(start + 1);
|
|
7264
7499
|
cursor = start + 1;
|
|
7265
7500
|
}
|
|
@@ -7278,11 +7513,14 @@ async function runNonTransactionalDown(params) {
|
|
|
7278
7513
|
if (commitsBlock) {
|
|
7279
7514
|
if (migration.desiredState !== undefined && downSql.slice(index + 1).every((statement) => /^\s*PRAGMA\s+foreign_keys\s*=\s*ON\s*;?\s*$/i.test(statement)))
|
|
7280
7515
|
assertMigrationState({ report: await verifySqliteTransitionBeforeRestore({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7516
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe, sqliteRestorePending: true });
|
|
7281
7517
|
await recordProgress(index + 1);
|
|
7282
7518
|
}
|
|
7283
7519
|
await executeStatement({ executor, statement: downSql[index], dialect });
|
|
7284
7520
|
if (index === downSql.length - 1 && migration.desiredState !== undefined)
|
|
7285
7521
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7522
|
+
if (index === downSql.length - 1)
|
|
7523
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7286
7524
|
if (atomic) {
|
|
7287
7525
|
await recordProgress(index + 1);
|
|
7288
7526
|
await executor({ text: "COMMIT" });
|
|
@@ -7296,7 +7534,7 @@ async function runNonTransactionalDown(params) {
|
|
|
7296
7534
|
values: [marks ? `${INFLIGHT_PREFIX}${index}` : error instanceof Error ? error.message : String(error), migration.name]
|
|
7297
7535
|
});
|
|
7298
7536
|
} catch {}
|
|
7299
|
-
throw new
|
|
7537
|
+
throw new VibeError32({
|
|
7300
7538
|
code: "VIBE_MIGRATION",
|
|
7301
7539
|
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."}`,
|
|
7302
7540
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeoutDiagnostic({ error }) },
|
|
@@ -7323,11 +7561,12 @@ async function runTransactional(params) {
|
|
|
7323
7561
|
const limits = migration.execution;
|
|
7324
7562
|
if (wrap && dialect === "postgres" && limits !== undefined) {
|
|
7325
7563
|
if ((limits.lockTimeoutMs ?? 0) > 0)
|
|
7326
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7564
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
|
|
7327
7565
|
if ((limits.statementTimeoutMs ?? 0) > 0)
|
|
7328
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7566
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
|
|
7329
7567
|
}
|
|
7330
7568
|
await assertForwardStart({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
7569
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7331
7570
|
if (migration.rls !== undefined)
|
|
7332
7571
|
await introspectPostgres({ executor, rlsSchema: migration.rls.from, inTransaction: true, requireInstalledRls: true });
|
|
7333
7572
|
let statementIndex = 0;
|
|
@@ -7339,6 +7578,7 @@ async function runTransactional(params) {
|
|
|
7339
7578
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7340
7579
|
if (migration.rls !== undefined)
|
|
7341
7580
|
await introspectPostgres({ executor, rlsSchema: migration.rls.to, inTransaction: true, requireInstalledRls: true });
|
|
7581
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7342
7582
|
if (migration.moduleOrigin !== undefined)
|
|
7343
7583
|
await insertModuleJournal({ executor, dialect, migration, index: statementIndex, complete: true });
|
|
7344
7584
|
else
|
|
@@ -7356,7 +7596,7 @@ async function runTransactional(params) {
|
|
|
7356
7596
|
} else {
|
|
7357
7597
|
await cleanupSelfManagedFailure({ executor });
|
|
7358
7598
|
}
|
|
7359
|
-
throw new
|
|
7599
|
+
throw new VibeError32({
|
|
7360
7600
|
code: "VIBE_MIGRATION",
|
|
7361
7601
|
message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7362
7602
|
meta: { migration: migration.name },
|
|
@@ -7376,6 +7616,7 @@ async function runNonTransactional(params) {
|
|
|
7376
7616
|
const declared = execution?.transaction === "none";
|
|
7377
7617
|
const marks = marksInflight({ dialect, ...execution === undefined ? {} : { execution } });
|
|
7378
7618
|
const start = selfManaged ? selfManagedResumeStart({ sql: migration.sql, start: params.start }) : params.start;
|
|
7619
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7379
7620
|
if (insertRecord) {
|
|
7380
7621
|
if (migration.desiredState !== undefined) {
|
|
7381
7622
|
await assertForwardStart({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
@@ -7400,10 +7641,12 @@ async function runNonTransactional(params) {
|
|
|
7400
7641
|
if (marks && !insertRecord && start < migration.sql.length) {
|
|
7401
7642
|
const operation = declared ? operationFor({ ...execution === undefined ? {} : { execution }, statement: start }) : undefined;
|
|
7402
7643
|
if (operation !== undefined) {
|
|
7403
|
-
const
|
|
7404
|
-
if (
|
|
7644
|
+
const state = await establishOperationState({ executor, dialect, operation });
|
|
7645
|
+
if (state === "satisfied") {
|
|
7405
7646
|
if (start + 1 === migration.sql.length && migration.desiredState !== undefined)
|
|
7406
7647
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7648
|
+
if (start + 1 === migration.sql.length)
|
|
7649
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7407
7650
|
await checkpoint(start + 1);
|
|
7408
7651
|
cursor = start + 1;
|
|
7409
7652
|
}
|
|
@@ -7426,11 +7669,14 @@ async function runNonTransactional(params) {
|
|
|
7426
7669
|
if (commitsBlock) {
|
|
7427
7670
|
if (migration.desiredState !== undefined && migration.sql.slice(index + 1).every((statement) => /^\s*PRAGMA\s+foreign_keys\s*=\s*ON\s*;?\s*$/i.test(statement)))
|
|
7428
7671
|
assertMigrationState({ report: await verifySqliteTransitionBeforeRestore({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7672
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe, sqliteRestorePending: true });
|
|
7429
7673
|
await checkpoint(index + 1);
|
|
7430
7674
|
}
|
|
7431
7675
|
await executeStatement({ executor, statement: migration.sql[index], dialect });
|
|
7432
7676
|
if (index === migration.sql.length - 1 && migration.desiredState !== undefined)
|
|
7433
7677
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7678
|
+
if (index === migration.sql.length - 1)
|
|
7679
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7434
7680
|
if (atomic) {
|
|
7435
7681
|
await checkpoint(index + 1);
|
|
7436
7682
|
await executor({ text: "COMMIT" });
|
|
@@ -7446,7 +7692,7 @@ async function runNonTransactional(params) {
|
|
|
7446
7692
|
} catch {}
|
|
7447
7693
|
const sqlState = sqlStateOf(error);
|
|
7448
7694
|
const timeout = sqlState === "55P03" ? "lock-timeout" : sqlState === "57014" ? "statement-timeout" : undefined;
|
|
7449
|
-
throw new
|
|
7695
|
+
throw new VibeError32({
|
|
7450
7696
|
code: "VIBE_MIGRATION",
|
|
7451
7697
|
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."}`,
|
|
7452
7698
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeout === undefined ? {} : { reason: timeout, sqlState } },
|
|
@@ -7474,7 +7720,7 @@ async function acquireLock(params) {
|
|
|
7474
7720
|
});
|
|
7475
7721
|
const acquired = Number(rows[0]?.vibe_lock) === 1;
|
|
7476
7722
|
if (!acquired) {
|
|
7477
|
-
throw new
|
|
7723
|
+
throw new VibeError32({
|
|
7478
7724
|
code: "VIBE_MIGRATION",
|
|
7479
7725
|
message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
|
|
7480
7726
|
meta: { lock: MYSQL_LOCK_NAME }
|
|
@@ -7484,13 +7730,11 @@ async function acquireLock(params) {
|
|
|
7484
7730
|
}
|
|
7485
7731
|
async function releaseLock(params) {
|
|
7486
7732
|
const { executor, dialect } = params;
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
}
|
|
7493
|
-
} catch {}
|
|
7733
|
+
if (dialect === "postgres") {
|
|
7734
|
+
await executor({ text: `SELECT pg_advisory_unlock(${PG_ADVISORY_LOCK_KEY})` });
|
|
7735
|
+
} else if (dialect === "mysql") {
|
|
7736
|
+
await executor({ text: `SELECT RELEASE_LOCK('${MYSQL_LOCK_NAME}')` });
|
|
7737
|
+
}
|
|
7494
7738
|
}
|
|
7495
7739
|
var INFLIGHT_PREFIX = "VIBE_INFLIGHT:";
|
|
7496
7740
|
function inflightStatement(params) {
|
|
@@ -7511,7 +7755,7 @@ function uncertainStatement(params) {
|
|
|
7511
7755
|
const subject = direction === "down" ? `rollback of "${migration}"` : `migration "${migration}"`;
|
|
7512
7756
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${statements.length}`;
|
|
7513
7757
|
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.";
|
|
7514
|
-
return new
|
|
7758
|
+
return new VibeError32({
|
|
7515
7759
|
code: "VIBE_MIGRATION",
|
|
7516
7760
|
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)}`,
|
|
7517
7761
|
meta: {
|
|
@@ -7529,7 +7773,7 @@ function assertExecutable(params) {
|
|
|
7529
7773
|
if (execution === undefined || isDefaultExecution({ execution }))
|
|
7530
7774
|
return;
|
|
7531
7775
|
if (dialect !== "postgres") {
|
|
7532
|
-
throw new
|
|
7776
|
+
throw new VibeError32({
|
|
7533
7777
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
7534
7778
|
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`,
|
|
7535
7779
|
meta: { reason: "nontransactional-unsupported", dialect, migration: migration.name, feature: "nontransactionalMigrations" }
|
|
@@ -7552,7 +7796,7 @@ async function withSessionLimits(params) {
|
|
|
7552
7796
|
const statement = execution?.statementTimeoutMs ?? 0;
|
|
7553
7797
|
if (dialect !== "postgres" || lock === 0 && statement === 0)
|
|
7554
7798
|
return params.run();
|
|
7555
|
-
const sqlDialect =
|
|
7799
|
+
const sqlDialect = getSqlDialect7({ dialect });
|
|
7556
7800
|
const previous = (await executor({ text: renderMigrationSessionRead({ dialect: sqlDialect }).text, values: [] }))[0];
|
|
7557
7801
|
if (lock > 0)
|
|
7558
7802
|
await executor({ text: renderMigrationSessionSet({ dialect: sqlDialect, setting: "lock_timeout", milliseconds: lock }).text });
|
|
@@ -7571,12 +7815,12 @@ async function withSessionLimits(params) {
|
|
|
7571
7815
|
}
|
|
7572
7816
|
}
|
|
7573
7817
|
function usesStatementBookkeeping(params) {
|
|
7574
|
-
const { dialect, sql
|
|
7818
|
+
const { dialect, sql } = params;
|
|
7575
7819
|
if (params.execution?.transaction === "none")
|
|
7576
7820
|
return true;
|
|
7577
7821
|
if (!DIALECT_CAPABILITIES3[dialect].transactionalDdl)
|
|
7578
7822
|
return true;
|
|
7579
|
-
return dialect === "sqlite" && usesSelfManagedTransactions({ sql
|
|
7823
|
+
return dialect === "sqlite" && usesSelfManagedTransactions({ sql });
|
|
7580
7824
|
}
|
|
7581
7825
|
function isCompleteRecord(params) {
|
|
7582
7826
|
const { dialect, migration, stored } = params;
|
|
@@ -7591,7 +7835,7 @@ function isCompleteRecord(params) {
|
|
|
7591
7835
|
async function recordedRows(params) {
|
|
7592
7836
|
const { executor, dialect } = params;
|
|
7593
7837
|
if (params.modules === true)
|
|
7594
|
-
return new Map([...await readModuleJournal({ executor, dialect })].map(([name,
|
|
7838
|
+
return new Map([...await readModuleJournal({ executor, dialect })].map(([name, record]) => [name, record.row]));
|
|
7595
7839
|
const rows = await readJournalRows({ executor, dialect, query: {
|
|
7596
7840
|
text: `SELECT ${q5(dialect, "name")}, ${q5(dialect, "checksum")}, ${q5(dialect, "statement_index")}, ${q5(dialect, "error")} FROM ${q5(dialect, MIGRATIONS_TABLE_NAME)} ORDER BY ${q5(dialect, "id")}`,
|
|
7597
7841
|
values: []
|
|
@@ -7605,7 +7849,7 @@ function assertChecksumMatches(params) {
|
|
|
7605
7849
|
const actual = checksumOf({ migration });
|
|
7606
7850
|
if (stored === actual)
|
|
7607
7851
|
return;
|
|
7608
|
-
throw new
|
|
7852
|
+
throw new VibeError32({
|
|
7609
7853
|
code: "VIBE_MIGRATION",
|
|
7610
7854
|
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)`,
|
|
7611
7855
|
meta: { migration: migration.name, recorded: stored, current: actual }
|
|
@@ -7616,7 +7860,7 @@ function assertResumable(params) {
|
|
|
7616
7860
|
const actual = checksumOf({ migration });
|
|
7617
7861
|
if (stored.checksum === actual)
|
|
7618
7862
|
return;
|
|
7619
|
-
throw new
|
|
7863
|
+
throw new VibeError32({
|
|
7620
7864
|
code: "VIBE_MIGRATION",
|
|
7621
7865
|
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`,
|
|
7622
7866
|
meta: {
|
|
@@ -7636,7 +7880,7 @@ function assertDistinctMigrationNames(params) {
|
|
|
7636
7880
|
const names = new Set;
|
|
7637
7881
|
for (const migration of params.migrations) {
|
|
7638
7882
|
if (names.has(migration.name))
|
|
7639
|
-
throw new
|
|
7883
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
|
|
7640
7884
|
names.add(migration.name);
|
|
7641
7885
|
}
|
|
7642
7886
|
}
|
|
@@ -7644,15 +7888,15 @@ function assertCompleteHistory(params) {
|
|
|
7644
7888
|
const byName = new Map(params.migrations.map((migration) => [migration.name, migration]));
|
|
7645
7889
|
for (const name of params.recorded.keys()) {
|
|
7646
7890
|
if (!byName.has(name))
|
|
7647
|
-
throw new
|
|
7891
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
|
|
7648
7892
|
}
|
|
7649
7893
|
let index = 0;
|
|
7650
7894
|
for (const [name, stored] of params.recorded) {
|
|
7651
7895
|
const migration = params.migrations[index];
|
|
7652
7896
|
if (migration?.name !== name)
|
|
7653
|
-
throw new
|
|
7897
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Recorded migration order is not a prefix of the supplied complete chain", meta: { reason: "history-order-conflict", migration: name, expected: migration?.name, position: index } });
|
|
7654
7898
|
if (stored.statementIndex >= 0 && !isCompleteRecord({ dialect: params.dialect, migration, stored }) && index !== params.recorded.size - 1)
|
|
7655
|
-
throw new
|
|
7899
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
|
|
7656
7900
|
index++;
|
|
7657
7901
|
}
|
|
7658
7902
|
}
|
|
@@ -7661,16 +7905,16 @@ async function assertApplyHistory(params) {
|
|
|
7661
7905
|
const byName = new Map(migrations.map((migration) => [migration.name, migration]));
|
|
7662
7906
|
for (const [name, stored] of recorded) {
|
|
7663
7907
|
if (stored.statementIndex < 0) {
|
|
7664
|
-
throw new
|
|
7908
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" is mid-rollback; finish or resolve it before applying`, meta: { reason: "history-rolling-back", migration: name, statementIndex: stored.statementIndex } });
|
|
7665
7909
|
}
|
|
7666
7910
|
const migration = byName.get(name);
|
|
7667
7911
|
if (migration === undefined) {
|
|
7668
7912
|
if (stored.error !== undefined)
|
|
7669
|
-
throw new
|
|
7913
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error but no supplied file`, meta: { reason: "history-unknown-dirty", migration: name } });
|
|
7670
7914
|
continue;
|
|
7671
7915
|
}
|
|
7672
7916
|
if (isCompleteRecord({ dialect, migration, stored }) && stored.error !== undefined)
|
|
7673
|
-
throw new
|
|
7917
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
|
|
7674
7918
|
if (isCompleteRecord({ dialect, migration, stored }))
|
|
7675
7919
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
7676
7920
|
else
|
|
@@ -7695,10 +7939,10 @@ async function assertApplyHistory(params) {
|
|
|
7695
7939
|
}
|
|
7696
7940
|
}
|
|
7697
7941
|
function q5(dialect, name) {
|
|
7698
|
-
return
|
|
7942
|
+
return getSqlDialect7({ dialect }).quoteIdent({ name });
|
|
7699
7943
|
}
|
|
7700
7944
|
function ph(dialect, index) {
|
|
7701
|
-
return
|
|
7945
|
+
return getSqlDialect7({ dialect }).placeholder({ index });
|
|
7702
7946
|
}
|
|
7703
7947
|
|
|
7704
7948
|
// packages/migrate/src/fs.ts
|
|
@@ -7706,7 +7950,7 @@ function absent(error) {
|
|
|
7706
7950
|
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
7707
7951
|
}
|
|
7708
7952
|
function failure(params) {
|
|
7709
|
-
return new
|
|
7953
|
+
return new VibeError33({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
|
|
7710
7954
|
}
|
|
7711
7955
|
async function optionalFile(params) {
|
|
7712
7956
|
return readFile(params.path, "utf-8").catch((cause) => {
|
|
@@ -7718,15 +7962,15 @@ async function optionalFile(params) {
|
|
|
7718
7962
|
function rlsMetadata(params) {
|
|
7719
7963
|
return Promise.resolve().then(() => {
|
|
7720
7964
|
const metadata = JSON.parse(params.text);
|
|
7721
|
-
const
|
|
7722
|
-
if (metadata?.version !== 1 || typeof metadata.destructive !== "boolean" || metadata.rls === undefined || !
|
|
7723
|
-
throw new
|
|
7965
|
+
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
7966
|
+
if (metadata?.version !== 1 || typeof metadata.destructive !== "boolean" || metadata.rls === undefined || !strings(metadata.securityChanges) || !strings(metadata.downSecurityChanges) || metadata.sqlChecksum !== migrationChecksum({ sql: params.migration.sql }) || metadata.downChecksum !== migrationChecksum({ sql: params.migration.downSql ?? [] })) {
|
|
7967
|
+
throw new VibeError33({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
|
|
7724
7968
|
}
|
|
7725
7969
|
assertValidSchemaIR4({ schema: metadata.rls.from });
|
|
7726
7970
|
assertValidSchemaIR4({ schema: metadata.rls.to });
|
|
7727
7971
|
return { ...params.migration, rls: metadata.rls, destructive: metadata.destructive, securityChanges: metadata.securityChanges, downSecurityChanges: metadata.downSecurityChanges };
|
|
7728
7972
|
}).catch((cause) => {
|
|
7729
|
-
throw new
|
|
7973
|
+
throw new VibeError33({ code: "VIBE_MIGRATION", message: `Invalid native RLS metadata for migration "${params.migration.name}"`, meta: { reason: "rls-migration-metadata-invalid", migration: params.migration.name }, cause });
|
|
7730
7974
|
});
|
|
7731
7975
|
}
|
|
7732
7976
|
async function loadMigrationFiles(params) {
|
|
@@ -7759,7 +8003,7 @@ async function loadMigrationFiles(params) {
|
|
|
7759
8003
|
const desired = await optionalFile({ path: join(directory, DESIRED_STATE_METADATA.filename) });
|
|
7760
8004
|
if (desired !== undefined) {
|
|
7761
8005
|
const pending = await Promise.resolve().then(() => isUnfinalizedDesiredState({ text: desired })).catch((cause) => {
|
|
7762
|
-
throw new
|
|
8006
|
+
throw new VibeError33({ code: "VIBE_MIGRATION", message: `Malformed desired state for ${name}`, meta: { reason: "desired-state-invalid", migration: name }, cause });
|
|
7763
8007
|
});
|
|
7764
8008
|
migration = pending ? { ...migration, desiredStatePending: true } : { ...migration, desiredState: parseDesiredStateSidecar({ text: desired, migration: name }) };
|
|
7765
8009
|
}
|
|
@@ -7771,9 +8015,9 @@ async function loadMigrationFiles(params) {
|
|
|
7771
8015
|
return migrations;
|
|
7772
8016
|
}
|
|
7773
8017
|
export {
|
|
7774
|
-
|
|
8018
|
+
joinSqlStatements,
|
|
7775
8019
|
loadMigrationFiles,
|
|
7776
|
-
|
|
8020
|
+
splitSqlStatements
|
|
7777
8021
|
};
|
|
7778
8022
|
|
|
7779
|
-
//# debugId=
|
|
8023
|
+
//# debugId=F0D645556CA0676064756E2164756E21
|