Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import fs from "node:fs/promises"; import path from "node:path"; import { cwd } from "node:process"; import { type Kysely, Migrator, sql } from "kysely"; import { TSFileMigrationProvider } from "kysely-ctl"; import { loadAndGetConfig } from "./config"; import { getLogger } from "./log"; import { getManifest, getManifestOrThrow } from "./manifest"; import { ensureDirectoryExists, fileExists } from "./plainstack-fs"; // biome-ignore lint/suspicious/noExplicitAny: <explanation> export type GenericDatabase = Kysely<any>; async function getMigrator() { const config = await loadAndGetConfig(); const { database } = await getManifestOrThrow(["database"], { config, cwd: cwd(), }); return new Migrator({ db: database, provider: new TSFileMigrationProvider({ migrationFolder: path.join(process.cwd(), config.paths.migrations), }), }); } export async function migrateToLatest() { const log = getLogger("database"); const migrator = await getMigrator(); log.info("running migrations"); const result = await migrator.migrateToLatest(); log.info(`applied ${result?.results?.length} migrations`); if (result?.error) log.error("migration error:", result.error); } const migrationFileTemplate = ` import type { Kysely } from "kysely"; // check https://kysely.dev/docs/migrations export async function up(db: Kysely<unknown>): Promise<void> { await db.schema .createTable("person") .addColumn("id", "varchar", (col) => col.primaryKey()) .addColumn("first_name", "text", (col) => col.notNull()) .addColumn("last_name", "text") .addColumn("gender", "text", (col) => col.notNull()) .addColumn("created_at", "integer", (col) => col.notNull()) .execute(); await db.schema .createTable("pet") .addColumn("id", "varchar", (col) => col.primaryKey()) .addColumn("name", "text", (col) => col.notNull().unique()) .addColumn("owner_id", "varchar", (col) => col.notNull()) .addColumn("species", "text", (col) => col.notNull()) .addColumn("created_at", "integer", (col) => col.notNull()) .execute(); await db.schema .createIndex("pet_owner_id_index") .on("pet") .column("owner_id") .execute(); } export async function down(db: Kysely<unknown>): Promise<void> { await db.schema.dropTable("pet").execute(); await db.schema.dropTable("person").execute(); } `; export async function writeMigrationFile(name: string) { const log = getLogger("database"); const config = await loadAndGetConfig(); const sanitizedName = name.toLowerCase().replace(/[^a-z0-9_]/g, "_"); const timestamp = Date.now(); const fileName = `${timestamp}_${sanitizedName}.ts`; await ensureDirectoryExists(config.paths.migrations); const filePath = path.join(config.paths.migrations, fileName); if (await fileExists(filePath)) { throw new Error(`Migration file ${fileName} already exists`); } await fs.writeFile(filePath, migrationFileTemplate); log.info(`Generated migration: ${fileName}`); } /** * Run a a function in a database transaction. * The transaction is automatically rolled back, even if the function doesn't throw an error. * Use during testing, to keep test cases isolated from each other. * */ export async function rollback<T>( db: Kysely<T>, fn: (db: Kysely<T>) => Promise<void>, ) { // TODO check if pending migrations, and print warning if so let err: Error | null = null; try { // Begin the transaction await sql.raw("BEGIN").execute(db); try { await fn(db); } catch (e) { err = e as Error; } } finally { await sql.raw("ROLLBACK").execute(db); } if (err) { throw err; } } export function defineDatabase<T>(db: Kysely<T>): Kysely<T> { return db; } export function isDatabase(db: unknown): db is Kysely<Record<string, unknown>> { return ( typeof db === "object" && db !== null && "schema" in db && "selectFrom" in db ); } |