Finish setting up basic db migrations

This commit is contained in:
Dominic Ferrando
2026-07-03 22:28:14 -04:00
parent 37ffaa282b
commit 66797e9a9d
8 changed files with 104 additions and 110 deletions
+1 -3
View File
@@ -9,7 +9,5 @@ bun install
To run:
```bash
bun run index.ts
bun run dev
```
This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
-31
View File
@@ -1,31 +0,0 @@
import {
DummyDriver,
PostgresAdapter,
PostgresIntrospector,
PostgresQueryCompiler,
} from 'kysely'
import { defineConfig } from 'kysely-ctl'
export default defineConfig({
dialect: {
createAdapter() {
return new PostgresAdapter()
},
createDriver() {
return new DummyDriver()
},
createIntrospector(db) {
return new PostgresIntrospector(db)
},
createQueryCompiler() {
return new PostgresQueryCompiler()
},
},
migrations: {
migrationFolder: "src/database/migrations",
},
// plugins: [],
// seeds: {
// seedFolder: "seeds",
// }
})
+5 -2
View File
@@ -12,7 +12,11 @@
"dev": "bun run --hot src/index.ts",
"start": "bun run src/index.ts",
"build": "bun build --compile --minify-whitespace --minify-syntax --target bun-linux-x64 --outfile dist/server src/index.ts",
"db:codegen": "bunx kysely-codegen --env-file .env.development --out-file ./src/database/types.d.ts"
"db:types:gen": "bunx kysely-codegen --env-file .env.development --out-file ./src/database/out/db.d.ts",
"db:migrate:latest": "bun run src/database/migrate.ts latest",
"db:migrate:rollback": "bun run src/database/migrate.ts rollback",
"db:migrate:up": "bun run src/database/migrate.ts up",
"db:migrate:down": "bun run src/database/migrate.ts down"
},
"dependencies": {
"@blade-and-brawn/calculator": "workspace:*",
@@ -31,7 +35,6 @@
},
"devDependencies": {
"kysely-codegen": "^0.20.0",
"kysely-ctl": "^0.21.0",
"pino-pretty": "^13.1.3"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { CreateTableBuilder, Kysely, PostgresDialect, sql } from 'kysely';
import { type DB } from "./types";
import { type DB } from "./out/db";
import { Pool } from "pg";
import { env } from '../util';
+95
View File
@@ -0,0 +1,95 @@
import * as path from 'path'
import { promises as fs } from 'fs'
import { FileMigrationProvider, Migrator, type MigrationResultSet } from 'kysely/migration'
import { db } from './db'
import { argv } from 'bun'
import { env, log } from '../util'
type HandlerResult = {
message: string;
};
function processResults(label: string, { error, results }: MigrationResultSet): string {
let message = "";
results?.forEach((it) => {
if (it.status === 'Success') {
message = `migration ${label} succeeded: "${it.migrationName}"`;
} else if (it.status === 'Error') {
throw new Error(`migration ${label} failed: "${it.migrationName}"`);
}
});
if (error) throw error;
return message;
}
const handlers: Record<string, (migrator: Migrator) => Promise<HandlerResult>> = {
"up": async function (migrator: Migrator) {
const resultSet = await migrator.migrateUp();
const message = processResults("up", resultSet);
return { message };
},
"down": async function (migrator: Migrator) {
const resultSet = await migrator.migrateDown();
const message = processResults("down", resultSet);
return { message };
},
"latest": async function (migrator: Migrator) {
const resultSet = await migrator.migrateToLatest();
const message = processResults("to latest", resultSet);
return { message };
},
"rollback": async function (migrator: Migrator) {
let count = 0;
while (true) {
const resultSet = await migrator.migrateDown();
if (!resultSet.results?.length) break;
processResults("rollback", resultSet);
++count;
}
return { message: `rolled back ${count} migration(s)` };
}
};
// Main
(async () => {
const command = argv[2] ?? "";
if (!handlers[command]) throw new Error("Unrecognized command");
const answer = prompt(`Run "${command}" migration against ${env.DATABASE_URL}? (y/N)`);
if (answer?.trim().toLowerCase() !== "y") {
log.info("Aborted");
await db.destroy();
return;
}
const migrator = new Migrator({
db,
provider: new FileMigrationProvider({
fs,
path,
// This needs to be an absolute path.
migrationFolder: path.join(__dirname, "migrations"),
}),
});
try {
const result = await handlers[command](migrator);
log.info({ name: command, result }, "finished");
}
catch (err) {
log.error({ name: command, err }, "failed");
process.exit(1)
}
finally {
await db.destroy()
}
})();
@@ -5,6 +5,8 @@ export async function up(db: Kysely<any>): Promise<void> {
// TABLE: PRODUCT_SYNCS
await db.schema.createTable("product_syncs")
.$call(addDefaultColumns)
.addColumn("session_id", "uuid", (cb) => cb.notNull())
.addColumn("session_name", "text", (cb) => cb.notNull())
.addColumn("printful_product_id_filter", sql`integer[]`)
.addColumn("syncing_printful_product_ids", sql`integer[]`, (cb) => cb
.notNull()
-28
View File
@@ -1,28 +0,0 @@
/**
* This file was generated by kysely-codegen.
* Please do not edit it manually.
*/
import type { ColumnType } from "kysely";
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
export interface ProductSyncs {
created_at: Generated<Timestamp>;
ended_at: Timestamp | null;
has_failed: Generated<boolean>;
id: Generated<Int8>;
printful_product_id_filter: number[] | null;
started_at: Timestamp | null;
syncing_printful_product_ids: Generated<number[]>;
}
export interface DB {
product_syncs: ProductSyncs;
}