bun-query-builder 0.2.35 → 0.2.37
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/actions/schema-drift.d.ts +74 -0
- package/dist/bin/cli.js +151 -11
- package/dist/index.d.ts +2 -0
- package/dist/src/index.js +155 -10
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { MigrationPlan, NormalizedColumnType } from '../migrations';
|
|
2
|
+
import type { SupportedDialect } from '../types';
|
|
3
|
+
/** The family a model's declared type belongs to. */
|
|
4
|
+
export declare function familyOfDeclared(type: NormalizedColumnType): TypeFamily;
|
|
5
|
+
/** The family a raw SQL type reported by a database belongs to. */
|
|
6
|
+
export declare function familyOfSqlType(sqlType: string): TypeFamily;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a live column is an acceptable home for what the model declares.
|
|
9
|
+
*
|
|
10
|
+
* Same family always passes. Beyond that, only widenings are tolerated: a
|
|
11
|
+
* model asking for `varchar` is content in a `text` column, because nothing is
|
|
12
|
+
* lost. The reverse is exactly the bug this exists to find.
|
|
13
|
+
*/
|
|
14
|
+
export declare function satisfies(declared: TypeFamily, actual: TypeFamily): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Audit the live database against a migration plan.
|
|
17
|
+
*
|
|
18
|
+
* Pass the plan built from the models (`buildMigrationPlan`). Tables the models
|
|
19
|
+
* do not declare are ignored: a database is allowed to hold more than one
|
|
20
|
+
* application's models, and framework-owned tables are not this plan's business.
|
|
21
|
+
*/
|
|
22
|
+
export declare function auditSchemaDrift(plan: MigrationPlan, options?: { dialect?: SupportedDialect }): Promise<SchemaDrift>;
|
|
23
|
+
/**
|
|
24
|
+
* A human-readable report, or an empty string when there is nothing to say.
|
|
25
|
+
*
|
|
26
|
+
* Says what to do as well as what is wrong. A drift warning that only names the
|
|
27
|
+
* problem leaves the reader to guess whether it matters, and the honest answer
|
|
28
|
+
* here is that it always does: the schema cannot repair itself, because the
|
|
29
|
+
* migration runner has already decided there is nothing to run.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatSchemaDrift(drift: SchemaDrift): string;
|
|
32
|
+
/**
|
|
33
|
+
* Compare a live database against what the models declare.
|
|
34
|
+
*
|
|
35
|
+
* This exists because the failure it catches is silent by construction. The
|
|
36
|
+
* migration runner only applies migration FILES: it asks "which of these have
|
|
37
|
+
* not run yet", not "does the database actually look like the models". So a
|
|
38
|
+
* database created from an older, wrong migration set reports **"nothing to
|
|
39
|
+
* migrate — your database is already up to date"** forever, while a column that
|
|
40
|
+
* should be `text` sits at `varchar(255)` and one that should hold 99.5 is an
|
|
41
|
+
* integer that stores 100.
|
|
42
|
+
*
|
|
43
|
+
* Every layer says it is fine. The write succeeds, the read comes back rounded
|
|
44
|
+
* or the insert is refused for length, and the only symptom reaches a customer.
|
|
45
|
+
* `CREATE TABLE IF NOT EXISTS` cannot repair it either, so a corrected migration
|
|
46
|
+
* set deploys as a no-op and the wrong schema survives every deploy after it.
|
|
47
|
+
*
|
|
48
|
+
* The comparison is by type FAMILY rather than by exact SQL string. Dialects
|
|
49
|
+
* spell the same type several ways — `character varying`, `varchar`, `int4`,
|
|
50
|
+
* `integer`, `real`, `float4` — and an audit that reported those as drift would
|
|
51
|
+
* be noise, which is the fastest way to teach people to ignore it.
|
|
52
|
+
*/
|
|
53
|
+
export declare interface ColumnDrift {
|
|
54
|
+
table: string
|
|
55
|
+
column: string
|
|
56
|
+
expected: string
|
|
57
|
+
actual: string
|
|
58
|
+
actualSqlType: string
|
|
59
|
+
}
|
|
60
|
+
export declare interface SchemaDrift {
|
|
61
|
+
missingTables: string[]
|
|
62
|
+
missingColumns: Array<{ table: string, column: string, expected: string }>
|
|
63
|
+
typeMismatches: ColumnDrift[]
|
|
64
|
+
clean: boolean
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The coarse family a column type belongs to.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately coarse. `bigint` and `integer` are both `integer` here: widening
|
|
70
|
+
* an id is not the class of mistake this looks for, and flagging it would bury
|
|
71
|
+
* the ones that are. What it does separate is the pairs that silently lose
|
|
72
|
+
* data — text from bounded varchar, and fractional from whole.
|
|
73
|
+
*/
|
|
74
|
+
export type TypeFamily = 'text' | 'varchar' | 'integer' | 'fractional' | 'boolean' | 'date' | 'json' | 'binary' | 'other';
|
package/dist/bin/cli.js
CHANGED
|
@@ -19489,13 +19489,27 @@ async function resetDatabase(dir, opts = {}) {
|
|
|
19489
19489
|
const dialect = opts.dialect || config5.dialect || "postgres";
|
|
19490
19490
|
const driver = getDialectDriver(dialect);
|
|
19491
19491
|
const workspaceRoot = getWorkspaceRoot();
|
|
19492
|
+
const dropTableForReset = async (tableName) => {
|
|
19493
|
+
const dropSql = driver.dropTable(tableName);
|
|
19494
|
+
await withFreshConnection(async (bunSql2) => {
|
|
19495
|
+
if (dialect === "sqlite")
|
|
19496
|
+
await bunSql2.unsafe("PRAGMA foreign_keys = OFF").execute();
|
|
19497
|
+
if (dialect === "mysql" || dialect === "vitess")
|
|
19498
|
+
await bunSql2.unsafe("SET FOREIGN_KEY_CHECKS = 0").execute();
|
|
19499
|
+
try {
|
|
19500
|
+
await bunSql2.unsafe(dropSql).execute();
|
|
19501
|
+
} finally {
|
|
19502
|
+
if (dialect === "sqlite")
|
|
19503
|
+
await bunSql2.unsafe("PRAGMA foreign_keys = ON").execute();
|
|
19504
|
+
if (dialect === "mysql" || dialect === "vitess")
|
|
19505
|
+
await bunSql2.unsafe("SET FOREIGN_KEY_CHECKS = 1").execute();
|
|
19506
|
+
}
|
|
19507
|
+
});
|
|
19508
|
+
};
|
|
19492
19509
|
try {
|
|
19493
|
-
const dropMigrationsSql = driver.dropTable("migrations");
|
|
19494
19510
|
try {
|
|
19495
|
-
await
|
|
19496
|
-
|
|
19497
|
-
info("-- Dropped migrations table");
|
|
19498
|
-
});
|
|
19511
|
+
await dropTableForReset("migrations");
|
|
19512
|
+
info("-- Dropped migrations table");
|
|
19499
19513
|
} catch (err) {
|
|
19500
19514
|
console.error(err);
|
|
19501
19515
|
}
|
|
@@ -19526,11 +19540,8 @@ async function resetDatabase(dir, opts = {}) {
|
|
|
19526
19540
|
info(`-- Dropping ${tableNames.length} tables: ${tableNames.join(", ")}`);
|
|
19527
19541
|
for (const tableName of tableNames.reverse()) {
|
|
19528
19542
|
try {
|
|
19529
|
-
|
|
19530
|
-
|
|
19531
|
-
await bunSql2.unsafe(dropSql).execute();
|
|
19532
|
-
info(`-- Dropped table: ${tableName}`);
|
|
19533
|
-
});
|
|
19543
|
+
await dropTableForReset(tableName);
|
|
19544
|
+
info(`-- Dropped table: ${tableName}`);
|
|
19534
19545
|
} catch (err) {
|
|
19535
19546
|
console.error(err);
|
|
19536
19547
|
info(`-- Table ${tableName} may not exist, skipping drop`);
|
|
@@ -30092,10 +30103,139 @@ class Seeder {
|
|
|
30092
30103
|
function defineSeeder(seederClass) {
|
|
30093
30104
|
return seederClass;
|
|
30094
30105
|
}
|
|
30106
|
+
// src/actions/schema-drift.ts
|
|
30107
|
+
function familyOfDeclared(type) {
|
|
30108
|
+
switch (type) {
|
|
30109
|
+
case "text":
|
|
30110
|
+
return "text";
|
|
30111
|
+
case "string":
|
|
30112
|
+
case "enum":
|
|
30113
|
+
return "varchar";
|
|
30114
|
+
case "integer":
|
|
30115
|
+
case "bigint":
|
|
30116
|
+
return "integer";
|
|
30117
|
+
case "float":
|
|
30118
|
+
case "double":
|
|
30119
|
+
case "decimal":
|
|
30120
|
+
return "fractional";
|
|
30121
|
+
case "boolean":
|
|
30122
|
+
return "boolean";
|
|
30123
|
+
case "date":
|
|
30124
|
+
case "datetime":
|
|
30125
|
+
case "timestamptz":
|
|
30126
|
+
return "date";
|
|
30127
|
+
case "json":
|
|
30128
|
+
return "json";
|
|
30129
|
+
default:
|
|
30130
|
+
return "other";
|
|
30131
|
+
}
|
|
30132
|
+
}
|
|
30133
|
+
function familyOfSqlType(sqlType) {
|
|
30134
|
+
const type = String(sqlType ?? "").toLowerCase().trim();
|
|
30135
|
+
if (/^(?:bool|boolean|bit)\b/.test(type) || type === "tinyint(1)")
|
|
30136
|
+
return "boolean";
|
|
30137
|
+
if (/^(?:json|jsonb)\b/.test(type))
|
|
30138
|
+
return "json";
|
|
30139
|
+
if (/^(?:bytea|blob|binary|varbinary)\b/.test(type))
|
|
30140
|
+
return "binary";
|
|
30141
|
+
if (/^(?:date|time|timestamp|datetime)\b/.test(type))
|
|
30142
|
+
return "date";
|
|
30143
|
+
if (/(?:^|\b)(?:varchar|character varying|nvarchar|char)\b/.test(type))
|
|
30144
|
+
return "varchar";
|
|
30145
|
+
if (/^(?:text|longtext|mediumtext|tinytext|clob|citext)\b/.test(type))
|
|
30146
|
+
return "text";
|
|
30147
|
+
if (/^(?:decimal|numeric|real|double|money)\b/.test(type) || /^float\d*\b/.test(type))
|
|
30148
|
+
return "fractional";
|
|
30149
|
+
if (/^(?:integer|bigint|smallint|tinyint|serial|bigserial|smallserial|mediumint)\b/.test(type) || /^int\d*\b/.test(type))
|
|
30150
|
+
return "integer";
|
|
30151
|
+
return "other";
|
|
30152
|
+
}
|
|
30153
|
+
function satisfies(declared, actual) {
|
|
30154
|
+
if (declared === actual)
|
|
30155
|
+
return true;
|
|
30156
|
+
if (declared === "varchar" && actual === "text")
|
|
30157
|
+
return true;
|
|
30158
|
+
if (declared === "integer" && actual === "fractional")
|
|
30159
|
+
return true;
|
|
30160
|
+
if (declared === "other" || actual === "other")
|
|
30161
|
+
return true;
|
|
30162
|
+
return false;
|
|
30163
|
+
}
|
|
30164
|
+
async function auditSchemaDrift(plan, options = {}) {
|
|
30165
|
+
const dialect = options.dialect ?? config5.dialect;
|
|
30166
|
+
const declaredTables = plan.tables.map((table) => table.table);
|
|
30167
|
+
const live = await introspectDatabase({ tables: declaredTables });
|
|
30168
|
+
const byTable = new Map(live.map((model) => [model.table.toLowerCase(), model]));
|
|
30169
|
+
const missingTables = [];
|
|
30170
|
+
const missingColumns = [];
|
|
30171
|
+
const typeMismatches = [];
|
|
30172
|
+
for (const table of plan.tables) {
|
|
30173
|
+
const liveTable = byTable.get(table.table.toLowerCase());
|
|
30174
|
+
if (!liveTable) {
|
|
30175
|
+
missingTables.push(table.table);
|
|
30176
|
+
continue;
|
|
30177
|
+
}
|
|
30178
|
+
const liveColumns = new Map(liveTable.columns.map((column) => [column.name.toLowerCase(), column]));
|
|
30179
|
+
for (const column of table.columns) {
|
|
30180
|
+
const liveColumn = liveColumns.get(column.name.toLowerCase());
|
|
30181
|
+
const declared = familyOfDeclared(column.type);
|
|
30182
|
+
if (!liveColumn) {
|
|
30183
|
+
missingColumns.push({ table: table.table, column: column.name, expected: declared });
|
|
30184
|
+
continue;
|
|
30185
|
+
}
|
|
30186
|
+
const actual = familyOfSqlType(liveColumn.sqlType);
|
|
30187
|
+
if (!satisfies(declared, actual)) {
|
|
30188
|
+
typeMismatches.push({
|
|
30189
|
+
table: table.table,
|
|
30190
|
+
column: column.name,
|
|
30191
|
+
expected: declared,
|
|
30192
|
+
actual,
|
|
30193
|
+
actualSqlType: liveColumn.sqlType
|
|
30194
|
+
});
|
|
30195
|
+
}
|
|
30196
|
+
}
|
|
30197
|
+
}
|
|
30198
|
+
return {
|
|
30199
|
+
missingTables,
|
|
30200
|
+
missingColumns,
|
|
30201
|
+
typeMismatches,
|
|
30202
|
+
clean: missingTables.length === 0 && missingColumns.length === 0 && typeMismatches.length === 0
|
|
30203
|
+
};
|
|
30204
|
+
}
|
|
30205
|
+
function formatSchemaDrift(drift) {
|
|
30206
|
+
if (drift.clean)
|
|
30207
|
+
return "";
|
|
30208
|
+
const lines = [];
|
|
30209
|
+
if (drift.missingTables.length > 0)
|
|
30210
|
+
lines.push(` Missing tables: ${drift.missingTables.join(", ")}`);
|
|
30211
|
+
for (const column of drift.missingColumns.slice(0, 10))
|
|
30212
|
+
lines.push(` \u2022 ${column.table}.${column.column} is missing (models declare ${column.expected})`);
|
|
30213
|
+
for (const mismatch of drift.typeMismatches.slice(0, 10))
|
|
30214
|
+
lines.push(` \u2022 ${mismatch.table}.${mismatch.column} is ${mismatch.actualSqlType}, models declare ${mismatch.expected}`);
|
|
30215
|
+
const shown = Math.min(drift.missingColumns.length, 10) + Math.min(drift.typeMismatches.length, 10);
|
|
30216
|
+
const total = drift.missingColumns.length + drift.typeMismatches.length;
|
|
30217
|
+
if (total > shown)
|
|
30218
|
+
lines.push(` + ${total - shown} more`);
|
|
30219
|
+
return [
|
|
30220
|
+
"The live schema does not match the models.",
|
|
30221
|
+
...lines,
|
|
30222
|
+
"",
|
|
30223
|
+
"Migrations will NOT fix this on their own: the runner only applies files that have",
|
|
30224
|
+
"not run yet, and a corrected `CREATE TABLE IF NOT EXISTS` is a no-op against a table",
|
|
30225
|
+
"that already exists. Regenerate and apply the difference, or rebuild the schema."
|
|
30226
|
+
].join(`
|
|
30227
|
+
`);
|
|
30228
|
+
}
|
|
30229
|
+
var init_schema_drift = __esm(() => {
|
|
30230
|
+
init_config();
|
|
30231
|
+
init_introspect_db();
|
|
30232
|
+
});
|
|
30233
|
+
|
|
30095
30234
|
// src/index.ts
|
|
30096
30235
|
var init_src2 = __esm(() => {
|
|
30097
30236
|
init_model2();
|
|
30098
30237
|
init_model2();
|
|
30238
|
+
init_schema_drift();
|
|
30099
30239
|
init_actions();
|
|
30100
30240
|
init_browser();
|
|
30101
30241
|
init_client();
|
|
@@ -32189,7 +32329,7 @@ function getPrefix() {
|
|
|
32189
32329
|
}
|
|
32190
32330
|
var prefix = getPrefix();
|
|
32191
32331
|
// package.json
|
|
32192
|
-
var version2 = "0.2.
|
|
32332
|
+
var version2 = "0.2.37";
|
|
32193
32333
|
|
|
32194
32334
|
// bin/cli.ts
|
|
32195
32335
|
init_actions();
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type {
|
|
|
24
24
|
ModelCreateDataLoose,
|
|
25
25
|
RelationCardinality,
|
|
26
26
|
} from './type-inference';
|
|
27
|
+
export type { ColumnDrift, SchemaDrift, TypeFamily } from './actions/schema-drift';
|
|
27
28
|
export * from './actions/index';
|
|
28
29
|
export * from './browser';
|
|
29
30
|
export * from './client';
|
|
@@ -51,3 +52,4 @@ export { type ModelDefinition, defineModel } from './model';
|
|
|
51
52
|
// Explicit re-exports for model registry functions
|
|
52
53
|
export { getModel, getAllModels, getModelRegistry, hasModel, clearModelRegistry, registerModel } from './model';
|
|
53
54
|
export { toDialectPlaceholders } from './sql-fragments';
|
|
55
|
+
export { auditSchemaDrift, familyOfDeclared, familyOfSqlType, formatSchemaDrift, satisfies } from './actions/schema-drift';
|
package/dist/src/index.js
CHANGED
|
@@ -19488,13 +19488,27 @@ async function resetDatabase(dir, opts = {}) {
|
|
|
19488
19488
|
const dialect = opts.dialect || config5.dialect || "postgres";
|
|
19489
19489
|
const driver = getDialectDriver(dialect);
|
|
19490
19490
|
const workspaceRoot = getWorkspaceRoot();
|
|
19491
|
+
const dropTableForReset = async (tableName) => {
|
|
19492
|
+
const dropSql = driver.dropTable(tableName);
|
|
19493
|
+
await withFreshConnection(async (bunSql2) => {
|
|
19494
|
+
if (dialect === "sqlite")
|
|
19495
|
+
await bunSql2.unsafe("PRAGMA foreign_keys = OFF").execute();
|
|
19496
|
+
if (dialect === "mysql" || dialect === "vitess")
|
|
19497
|
+
await bunSql2.unsafe("SET FOREIGN_KEY_CHECKS = 0").execute();
|
|
19498
|
+
try {
|
|
19499
|
+
await bunSql2.unsafe(dropSql).execute();
|
|
19500
|
+
} finally {
|
|
19501
|
+
if (dialect === "sqlite")
|
|
19502
|
+
await bunSql2.unsafe("PRAGMA foreign_keys = ON").execute();
|
|
19503
|
+
if (dialect === "mysql" || dialect === "vitess")
|
|
19504
|
+
await bunSql2.unsafe("SET FOREIGN_KEY_CHECKS = 1").execute();
|
|
19505
|
+
}
|
|
19506
|
+
});
|
|
19507
|
+
};
|
|
19491
19508
|
try {
|
|
19492
|
-
const dropMigrationsSql = driver.dropTable("migrations");
|
|
19493
19509
|
try {
|
|
19494
|
-
await
|
|
19495
|
-
|
|
19496
|
-
info("-- Dropped migrations table");
|
|
19497
|
-
});
|
|
19510
|
+
await dropTableForReset("migrations");
|
|
19511
|
+
info("-- Dropped migrations table");
|
|
19498
19512
|
} catch (err) {
|
|
19499
19513
|
console.error(err);
|
|
19500
19514
|
}
|
|
@@ -19525,11 +19539,8 @@ async function resetDatabase(dir, opts = {}) {
|
|
|
19525
19539
|
info(`-- Dropping ${tableNames.length} tables: ${tableNames.join(", ")}`);
|
|
19526
19540
|
for (const tableName of tableNames.reverse()) {
|
|
19527
19541
|
try {
|
|
19528
|
-
|
|
19529
|
-
|
|
19530
|
-
await bunSql2.unsafe(dropSql).execute();
|
|
19531
|
-
info(`-- Dropped table: ${tableName}`);
|
|
19532
|
-
});
|
|
19542
|
+
await dropTableForReset(tableName);
|
|
19543
|
+
info(`-- Dropped table: ${tableName}`);
|
|
19533
19544
|
} catch (err) {
|
|
19534
19545
|
console.error(err);
|
|
19535
19546
|
info(`-- Table ${tableName} may not exist, skipping drop`);
|
|
@@ -30091,10 +30102,139 @@ class Seeder {
|
|
|
30091
30102
|
function defineSeeder(seederClass) {
|
|
30092
30103
|
return seederClass;
|
|
30093
30104
|
}
|
|
30105
|
+
// src/actions/schema-drift.ts
|
|
30106
|
+
function familyOfDeclared(type) {
|
|
30107
|
+
switch (type) {
|
|
30108
|
+
case "text":
|
|
30109
|
+
return "text";
|
|
30110
|
+
case "string":
|
|
30111
|
+
case "enum":
|
|
30112
|
+
return "varchar";
|
|
30113
|
+
case "integer":
|
|
30114
|
+
case "bigint":
|
|
30115
|
+
return "integer";
|
|
30116
|
+
case "float":
|
|
30117
|
+
case "double":
|
|
30118
|
+
case "decimal":
|
|
30119
|
+
return "fractional";
|
|
30120
|
+
case "boolean":
|
|
30121
|
+
return "boolean";
|
|
30122
|
+
case "date":
|
|
30123
|
+
case "datetime":
|
|
30124
|
+
case "timestamptz":
|
|
30125
|
+
return "date";
|
|
30126
|
+
case "json":
|
|
30127
|
+
return "json";
|
|
30128
|
+
default:
|
|
30129
|
+
return "other";
|
|
30130
|
+
}
|
|
30131
|
+
}
|
|
30132
|
+
function familyOfSqlType(sqlType) {
|
|
30133
|
+
const type = String(sqlType ?? "").toLowerCase().trim();
|
|
30134
|
+
if (/^(?:bool|boolean|bit)\b/.test(type) || type === "tinyint(1)")
|
|
30135
|
+
return "boolean";
|
|
30136
|
+
if (/^(?:json|jsonb)\b/.test(type))
|
|
30137
|
+
return "json";
|
|
30138
|
+
if (/^(?:bytea|blob|binary|varbinary)\b/.test(type))
|
|
30139
|
+
return "binary";
|
|
30140
|
+
if (/^(?:date|time|timestamp|datetime)\b/.test(type))
|
|
30141
|
+
return "date";
|
|
30142
|
+
if (/(?:^|\b)(?:varchar|character varying|nvarchar|char)\b/.test(type))
|
|
30143
|
+
return "varchar";
|
|
30144
|
+
if (/^(?:text|longtext|mediumtext|tinytext|clob|citext)\b/.test(type))
|
|
30145
|
+
return "text";
|
|
30146
|
+
if (/^(?:decimal|numeric|real|double|money)\b/.test(type) || /^float\d*\b/.test(type))
|
|
30147
|
+
return "fractional";
|
|
30148
|
+
if (/^(?:integer|bigint|smallint|tinyint|serial|bigserial|smallserial|mediumint)\b/.test(type) || /^int\d*\b/.test(type))
|
|
30149
|
+
return "integer";
|
|
30150
|
+
return "other";
|
|
30151
|
+
}
|
|
30152
|
+
function satisfies(declared, actual) {
|
|
30153
|
+
if (declared === actual)
|
|
30154
|
+
return true;
|
|
30155
|
+
if (declared === "varchar" && actual === "text")
|
|
30156
|
+
return true;
|
|
30157
|
+
if (declared === "integer" && actual === "fractional")
|
|
30158
|
+
return true;
|
|
30159
|
+
if (declared === "other" || actual === "other")
|
|
30160
|
+
return true;
|
|
30161
|
+
return false;
|
|
30162
|
+
}
|
|
30163
|
+
async function auditSchemaDrift(plan, options = {}) {
|
|
30164
|
+
const dialect = options.dialect ?? config5.dialect;
|
|
30165
|
+
const declaredTables = plan.tables.map((table) => table.table);
|
|
30166
|
+
const live = await introspectDatabase({ tables: declaredTables });
|
|
30167
|
+
const byTable = new Map(live.map((model) => [model.table.toLowerCase(), model]));
|
|
30168
|
+
const missingTables = [];
|
|
30169
|
+
const missingColumns = [];
|
|
30170
|
+
const typeMismatches = [];
|
|
30171
|
+
for (const table of plan.tables) {
|
|
30172
|
+
const liveTable = byTable.get(table.table.toLowerCase());
|
|
30173
|
+
if (!liveTable) {
|
|
30174
|
+
missingTables.push(table.table);
|
|
30175
|
+
continue;
|
|
30176
|
+
}
|
|
30177
|
+
const liveColumns = new Map(liveTable.columns.map((column) => [column.name.toLowerCase(), column]));
|
|
30178
|
+
for (const column of table.columns) {
|
|
30179
|
+
const liveColumn = liveColumns.get(column.name.toLowerCase());
|
|
30180
|
+
const declared = familyOfDeclared(column.type);
|
|
30181
|
+
if (!liveColumn) {
|
|
30182
|
+
missingColumns.push({ table: table.table, column: column.name, expected: declared });
|
|
30183
|
+
continue;
|
|
30184
|
+
}
|
|
30185
|
+
const actual = familyOfSqlType(liveColumn.sqlType);
|
|
30186
|
+
if (!satisfies(declared, actual)) {
|
|
30187
|
+
typeMismatches.push({
|
|
30188
|
+
table: table.table,
|
|
30189
|
+
column: column.name,
|
|
30190
|
+
expected: declared,
|
|
30191
|
+
actual,
|
|
30192
|
+
actualSqlType: liveColumn.sqlType
|
|
30193
|
+
});
|
|
30194
|
+
}
|
|
30195
|
+
}
|
|
30196
|
+
}
|
|
30197
|
+
return {
|
|
30198
|
+
missingTables,
|
|
30199
|
+
missingColumns,
|
|
30200
|
+
typeMismatches,
|
|
30201
|
+
clean: missingTables.length === 0 && missingColumns.length === 0 && typeMismatches.length === 0
|
|
30202
|
+
};
|
|
30203
|
+
}
|
|
30204
|
+
function formatSchemaDrift(drift) {
|
|
30205
|
+
if (drift.clean)
|
|
30206
|
+
return "";
|
|
30207
|
+
const lines = [];
|
|
30208
|
+
if (drift.missingTables.length > 0)
|
|
30209
|
+
lines.push(` Missing tables: ${drift.missingTables.join(", ")}`);
|
|
30210
|
+
for (const column of drift.missingColumns.slice(0, 10))
|
|
30211
|
+
lines.push(` \u2022 ${column.table}.${column.column} is missing (models declare ${column.expected})`);
|
|
30212
|
+
for (const mismatch of drift.typeMismatches.slice(0, 10))
|
|
30213
|
+
lines.push(` \u2022 ${mismatch.table}.${mismatch.column} is ${mismatch.actualSqlType}, models declare ${mismatch.expected}`);
|
|
30214
|
+
const shown = Math.min(drift.missingColumns.length, 10) + Math.min(drift.typeMismatches.length, 10);
|
|
30215
|
+
const total = drift.missingColumns.length + drift.typeMismatches.length;
|
|
30216
|
+
if (total > shown)
|
|
30217
|
+
lines.push(` + ${total - shown} more`);
|
|
30218
|
+
return [
|
|
30219
|
+
"The live schema does not match the models.",
|
|
30220
|
+
...lines,
|
|
30221
|
+
"",
|
|
30222
|
+
"Migrations will NOT fix this on their own: the runner only applies files that have",
|
|
30223
|
+
"not run yet, and a corrected `CREATE TABLE IF NOT EXISTS` is a no-op against a table",
|
|
30224
|
+
"that already exists. Regenerate and apply the difference, or rebuild the schema."
|
|
30225
|
+
].join(`
|
|
30226
|
+
`);
|
|
30227
|
+
}
|
|
30228
|
+
var init_schema_drift = __esm(() => {
|
|
30229
|
+
init_config();
|
|
30230
|
+
init_introspect_db();
|
|
30231
|
+
});
|
|
30232
|
+
|
|
30094
30233
|
// src/index.ts
|
|
30095
30234
|
var init_src2 = __esm(() => {
|
|
30096
30235
|
init_model2();
|
|
30097
30236
|
init_model2();
|
|
30237
|
+
init_schema_drift();
|
|
30098
30238
|
init_actions();
|
|
30099
30239
|
init_browser();
|
|
30100
30240
|
init_client();
|
|
@@ -30139,6 +30279,7 @@ export {
|
|
|
30139
30279
|
setConfig,
|
|
30140
30280
|
seedModel,
|
|
30141
30281
|
saveMigrationSnapshot,
|
|
30282
|
+
satisfies,
|
|
30142
30283
|
runSeeders,
|
|
30143
30284
|
runSeeder,
|
|
30144
30285
|
runBenchmark,
|
|
@@ -30202,7 +30343,10 @@ export {
|
|
|
30202
30343
|
relationDiagram as generateDiagram,
|
|
30203
30344
|
generateAccessPatterns,
|
|
30204
30345
|
freshDatabase,
|
|
30346
|
+
formatSchemaDrift,
|
|
30205
30347
|
file,
|
|
30348
|
+
familyOfSqlType,
|
|
30349
|
+
familyOfDeclared,
|
|
30206
30350
|
extractTableDefinition,
|
|
30207
30351
|
extractModelSchema,
|
|
30208
30352
|
extractInsertId,
|
|
@@ -30257,6 +30401,7 @@ export {
|
|
|
30257
30401
|
buildDatabaseSchema,
|
|
30258
30402
|
browserQuery,
|
|
30259
30403
|
browserAuth,
|
|
30404
|
+
auditSchemaDrift,
|
|
30260
30405
|
applySqliteBootstrapPragmas,
|
|
30261
30406
|
VitessDriver,
|
|
30262
30407
|
SingleTableRepository,
|
package/package.json
CHANGED