|
|
|
@@ -14,17 +14,36 @@ const SCHEMA = readFileSync(
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Every table name the generated mirror declares.
|
|
|
|
|
* Every column the mirror declares, keyed by the database's own table name.
|
|
|
|
|
*
|
|
|
|
|
* Read from the `DB` interface, which is the one place kysely-codegen lists
|
|
|
|
|
* them all, mapping the database's own snake_case name to the interface for
|
|
|
|
|
* that table. Parsing the file as text rather than importing it is deliberate
|
|
|
|
|
* and unchanged from the Drizzle version: these are TypeScript types, erased at
|
|
|
|
|
* run time, so there is nothing to import and inspect.
|
|
|
|
|
* The `DB` interface maps a table name to the interface that declares that
|
|
|
|
|
* table's columns, so the two have to be read together. A flat set of every
|
|
|
|
|
* column name appearing anywhere in the file is not the same assertion and is
|
|
|
|
|
* far weaker than it looks: seventeen column names are declared on more than
|
|
|
|
|
* one table and `created_at` is on fourteen of the eighteen, so a migration
|
|
|
|
|
* adding one of those to a table that lacks it would pass without the mirror
|
|
|
|
|
* knowing anything about it.
|
|
|
|
|
*
|
|
|
|
|
* Parsed as text rather than imported, because these are TypeScript types and
|
|
|
|
|
* are erased at run time — there is nothing to import and inspect.
|
|
|
|
|
*/
|
|
|
|
|
function mirroredTables(): string[] {
|
|
|
|
|
function mirroredColumns(): Map<string, Set<string>> {
|
|
|
|
|
const block = /export interface DB \{([^}]*)\}/.exec(SCHEMA)?.[1] ?? '';
|
|
|
|
|
return [...block.matchAll(/^\s*([a-z0-9_]+):/gm)].map((m) => m[1]!).sort();
|
|
|
|
|
const byTable = new Map<string, Set<string>>();
|
|
|
|
|
for (const [, table, iface] of block.matchAll(/^\s*([a-z0-9_]+):\s*([A-Za-z0-9_]+);/gm)) {
|
|
|
|
|
const declaration =
|
|
|
|
|
new RegExp(`export interface ${iface!} \\{([^}]*)\\}`).exec(SCHEMA)?.[1] ?? '';
|
|
|
|
|
byTable.set(
|
|
|
|
|
table!,
|
|
|
|
|
new Set([...declaration.matchAll(/^\s{2}([a-z0-9_]+):/gm)].map((m) => m[1]!))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return byTable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Every table name the generated mirror declares. */
|
|
|
|
|
function mirroredTables(): string[] {
|
|
|
|
|
return [...mirroredColumns().keys()].sort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function liveTables(): Promise<string[]> {
|
|
|
|
@@ -84,10 +103,11 @@ describe('the generated schema mirror', () => {
|
|
|
|
|
// mirror already knows about is the likelier drift, and the one a table-level
|
|
|
|
|
// check would wave through.
|
|
|
|
|
//
|
|
|
|
|
// One exact match, where the Drizzle version needed two and called itself
|
|
|
|
|
// "deliberately loose" for it. kysely-codegen emits the database's own name
|
|
|
|
|
// as a bare interface key, so ` column_name:` at the start of a line is the
|
|
|
|
|
// whole of the naming rule and there is nothing to re-implement.
|
|
|
|
|
// One exact match per table, where the Drizzle version needed two matches and
|
|
|
|
|
// no table at all, and called itself "deliberately loose" for it.
|
|
|
|
|
// kysely-codegen emits the database's own name as a bare interface key and
|
|
|
|
|
// the DB interface says which interface belongs to which table, so the pair
|
|
|
|
|
// is the whole of the naming rule and there is nothing to re-implement.
|
|
|
|
|
it('declares every column of every table it mirrors', async () => {
|
|
|
|
|
const { rows } = await pool.query<{ table_name: string; column_name: string }>(
|
|
|
|
|
`SELECT table_name, column_name FROM information_schema.columns
|
|
|
|
@@ -95,11 +115,9 @@ describe('the generated schema mirror', () => {
|
|
|
|
|
ORDER BY table_name, column_name`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const declared = new Set(
|
|
|
|
|
[...SCHEMA.matchAll(/^\s{2}([a-z0-9_]+):/gm)].map((m) => m[1]!)
|
|
|
|
|
);
|
|
|
|
|
const columns = mirroredColumns();
|
|
|
|
|
const missing = rows
|
|
|
|
|
.filter((row) => !declared.has(row.column_name))
|
|
|
|
|
.filter((row) => !columns.get(row.table_name)?.has(row.column_name))
|
|
|
|
|
.map((row) => `${row.table_name}.${row.column_name}`);
|
|
|
|
|
|
|
|
|
|
expect(missing).toEqual([]);
|
|
|
|
|