Feature/305 kysely swap #306
@@ -21,15 +21,6 @@ backend/unit-results.json
|
|||||||
backend/integration-results.json
|
backend/integration-results.json
|
||||||
frontend/playwright-results.json
|
frontend/playwright-results.json
|
||||||
|
|
||||||
# drizzle-kit pull writes the schema mirror into backend/src/db-drizzle (see
|
|
||||||
# backend/drizzle.config.ts), but `out` is also where it would put generated
|
|
||||||
# migrations and their journal. This project's migration history is
|
|
||||||
# backend/migrations — hand-written, and mostly prose. #219 has not chosen
|
|
||||||
# otherwise, so a stray 0000_*.sql in src/ is at best noise and at worst
|
|
||||||
# mistaken for real migration history. Keep the mirror, drop the rest.
|
|
||||||
backend/src/db-drizzle/*.sql
|
|
||||||
backend/src/db-drizzle/meta/
|
|
||||||
|
|
||||||
# Where an end-to-end run against the throwaway database writes its uploads.
|
# Where an end-to-end run against the throwaway database writes its uploads.
|
||||||
# Disposable with the database it belongs to (#186).
|
# Disposable with the database it belongs to (#186).
|
||||||
backend/.e2e-uploads/
|
backend/.e2e-uploads/
|
||||||
|
|||||||
@@ -30,18 +30,17 @@ const advisory = (config) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
// src/db-drizzle/schema.ts and relations.ts are `drizzle-kit pull` output, not
|
// src/db-kysely/schema.ts is `kysely-codegen` output, not written by anyone
|
||||||
// written by anyone here. #261 hand-fixed an unused-parameter warning in the
|
// here. #261 hand-fixed an unused-parameter warning in the equivalent Drizzle
|
||||||
// schema and #217's re-pull put it straight back, which is the whole argument:
|
// file and #217's regeneration put it straight back, which is the whole
|
||||||
// linting generated code buys a fix that the next regeneration undoes. The
|
// argument: linting generated code buys a fix that the next regeneration
|
||||||
// hand-written files in that directory are still linted.
|
// undoes. The hand-written files in that directory are still linted.
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
'dist/**',
|
'dist/**',
|
||||||
'coverage/**',
|
'coverage/**',
|
||||||
'eslint.config.mjs',
|
'eslint.config.mjs',
|
||||||
'src/db-drizzle/schema.ts',
|
'src/db-kysely/schema.ts'
|
||||||
'src/db-drizzle/relations.ts'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* The `DB` interface maps a table name to the interface that declares that
|
||||||
* them all, mapping the database's own snake_case name to the interface for
|
* table's columns, so the two have to be read together. A flat set of every
|
||||||
* that table. Parsing the file as text rather than importing it is deliberate
|
* column name appearing anywhere in the file is not the same assertion and is
|
||||||
* and unchanged from the Drizzle version: these are TypeScript types, erased at
|
* far weaker than it looks: seventeen column names are declared on more than
|
||||||
* run time, so there is nothing to import and inspect.
|
* 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] ?? '';
|
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[]> {
|
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
|
// mirror already knows about is the likelier drift, and the one a table-level
|
||||||
// check would wave through.
|
// check would wave through.
|
||||||
//
|
//
|
||||||
// One exact match, where the Drizzle version needed two and called itself
|
// One exact match per table, where the Drizzle version needed two matches and
|
||||||
// "deliberately loose" for it. kysely-codegen emits the database's own name
|
// no table at all, and called itself "deliberately loose" for it.
|
||||||
// as a bare interface key, so ` column_name:` at the start of a line is the
|
// kysely-codegen emits the database's own name as a bare interface key and
|
||||||
// whole of the naming rule and there is nothing to re-implement.
|
// 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 () => {
|
it('declares every column of every table it mirrors', async () => {
|
||||||
const { rows } = await pool.query<{ table_name: string; column_name: string }>(
|
const { rows } = await pool.query<{ table_name: string; column_name: string }>(
|
||||||
`SELECT table_name, column_name FROM information_schema.columns
|
`SELECT table_name, column_name FROM information_schema.columns
|
||||||
@@ -95,11 +115,9 @@ describe('the generated schema mirror', () => {
|
|||||||
ORDER BY table_name, column_name`
|
ORDER BY table_name, column_name`
|
||||||
);
|
);
|
||||||
|
|
||||||
const declared = new Set(
|
const columns = mirroredColumns();
|
||||||
[...SCHEMA.matchAll(/^\s{2}([a-z0-9_]+):/gm)].map((m) => m[1]!)
|
|
||||||
);
|
|
||||||
const missing = rows
|
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}`);
|
.map((row) => `${row.table_name}.${row.column_name}`);
|
||||||
|
|
||||||
expect(missing).toEqual([]);
|
expect(missing).toEqual([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user