Skip to content

Commit

Permalink
Merge pull request #20 from cmgriffing/issue-5
Browse files Browse the repository at this point in the history
feat: add runtime db migrations using kysely
  • Loading branch information
cmgriffing authored Jan 23, 2024
2 parents ea6eb5e + 793882f commit 9b727fc
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 46 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"models": "tsx scripts/get-models.ts",
"deploy": "gh-pages -d dist"
"deploy": "gh-pages -d dist",
"migration:new": "tsx scripts/migration-new.ts"
},
"dependencies": {
"@mantine/core": "^7.3.2",
Expand Down
15 changes: 12 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions scripts/migration-new.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env node

import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const name = process.argv[2];
const date = new Date().toISOString();

const template = `
import { Kysely, sql } from 'kysely'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function up(db: Kysely<any>): Promise<void> {
// await db.schema
// .createTable('person')
// .addColumn('id', 'integer', (col) => col.primaryKey())
// .addColumn('first_name', 'text', (col) => col.notNull())
// .addColumn('last_name', 'text')
// .addColumn('gender', 'text', (col) => col.notNull())
// .addColumn('created_at', 'text', (col) =>
// col.defaultTo(sql\`CURRENT_TIMESTAMP\`).notNull()
// )
// .execute()
// await db.schema
// .createTable('pet')
// .addColumn('id', 'integer', (col) => col.primaryKey())
// .addColumn('name', 'text', (col) => col.notNull().unique())
// .addColumn('owner_id', 'integer', (col) =>
// col.references('person.id').onDelete('cascade').notNull()
// )
// .addColumn('species', 'text', (col) => col.notNull())
// .execute()
// await db.schema
// .createIndex('pet_owner_id_index')
// .on('pet')
// .column('owner_id')
// .execute()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function down(db: Kysely<any>): Promise<void> {
// await db.schema.dropTable('pet').execute()
// await db.schema.dropTable('person').execute()
}
`;

fs.writeFileSync(
path.resolve(__dirname, "../src/data/migrations", `${date}-${name}.ts`),
template
);

fs.appendFileSync(
path.resolve(__dirname, "../src/data/migrations/index.ts"),
`\nexport * as migration${date
.replaceAll("-", "")
.replaceAll(":", "")
.replaceAll(".", "")} from "./${date}-${name}.ts"`
);
58 changes: 17 additions & 41 deletions src/data/db.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { SQLocalDrizzle } from "sqlocal/drizzle";
import { SQLocalKysely } from "sqlocal/kysely";
import { drizzle } from "drizzle-orm/sqlite-proxy";
import { Migrator, Kysely } from "kysely";

import * as bookSchema from "./models/books";
import * as chapterSchema from "./models/chapters";
import * as snippetSchema from "./models/snippets";

import { RuntimeMigrationProvider } from "./migrations/provider";

const { driver, sql, getDatabaseFile, overwriteDatabaseFile } =
new SQLocalDrizzle("database.sqlite3");

Expand All @@ -16,6 +20,17 @@ export const db = drizzle(driver, {
},
});

const { dialect } = new SQLocalKysely("database.sqlite3");
const kyselyDb = new Kysely({
dialect,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);

const migrator = new Migrator({
db: kyselyDb,
provider: new RuntimeMigrationProvider(),
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).resetDB = async function () {
await sql`DROP TABLE IF EXISTS books`;
Expand All @@ -28,50 +43,11 @@ export const db = drizzle(driver, {
(async () => {
await sql`PRAGMA foreign_keys = ON;`;

await sql`CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL
)`;

await sql`CREATE TABLE IF NOT EXISTS chapters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT NOT NULL,
sort_order REAL NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
book_id INTEGER NOT NULL,
FOREIGN KEY(book_id) REFERENCES books(id) ON DELETE CASCADE
)`;

await sql`CREATE TABLE IF NOT EXISTS snippets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT NOT NULL,
content TEXT NOT NULL DEFAULT "",
sort_order REAL NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
recorded_at INTEGER NOT NULL DEFAULT 0,
processed_at INTEGER NOT NULL DEFAULT 0,
finished_at INTEGER NOT NULL DEFAULT 0,
raw_recording_content TEXT NOT NULL DEFAULT "",
chapter_id INTEGER NOT NULL,
FOREIGN KEY(chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
)`;

await sql`CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL UNIQUE,
threads INTEGER NOT NULL DEFAULT 2,
selected_model TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL
)`;

await sql`INSERT OR IGNORE INTO settings(id,user_id,threads,selected_model,created_at,modified_at)
VALUES (0,1,2,'',0,0)`;

await migrator.migrateToLatest();

// get available books
// if empty, run seed process

Expand Down
81 changes: 81 additions & 0 deletions src/data/migrations/2024-01-23T23:15:28.004Z-initial-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Kysely } from "kysely";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable("books")
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
.addColumn("title", "text", (col) => col.notNull())
.addColumn("created_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.addColumn("modified_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.ifNotExists()
.execute();

await db.schema
.createTable("chapters")
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
.addColumn("label", "text", (col) => col.notNull())
.addColumn("sort_order", "real", (col) => col.notNull())
.addColumn("created_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.addColumn("modified_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.addColumn("book_id", "integer", (col) => col.notNull().onDelete("cascade"))
.addForeignKeyConstraint("book_id_fk", ["book_id"], "books", ["id"])
.ifNotExists()
.execute();

await db.schema
.createTable("snippets")
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
.addColumn("label", "text", (col) => col.notNull())
.addColumn("content", "text", (col) => col.notNull().defaultTo(""))
.addColumn("sort_order", "real", (col) => col.notNull())
.addColumn("created_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.addColumn("modified_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.addColumn("recorded_at", "integer", (col) => col.defaultTo(0))
.addColumn("processed_at", "integer", (col) => col.defaultTo(0))
.addColumn("finished_at", "integer", (col) => col.defaultTo(0))
.addColumn("raw_recording_content", "text", (col) => col.defaultTo(""))
.addColumn("chapter_id", "integer", (col) =>
col.notNull().onDelete("cascade")
)
.addForeignKeyConstraint("chapter_id_fk", ["chapter_id"], "chapters", [
"id",
])
.ifNotExists()
.execute();

await db.schema
.createTable("settings")
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
.addColumn("user_id", "integer", (col) => col.notNull().unique())
.addColumn("threads", "integer", (col) => col.notNull().defaultTo(2))
.addColumn("selected_model", "text", (col) => col.notNull().defaultTo(""))
.addColumn("created_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.addColumn("modified_at", "integer", (col) =>
col.defaultTo(Date.now()).notNull()
)
.ifNotExists()
.execute();
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable("books").execute();
await db.schema.dropTable("chapters").execute();
await db.schema.dropTable("snippets").execute();
await db.schema.dropTable("settings").execute();
}
1 change: 1 addition & 0 deletions src/data/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as migration20240123T231528004Z from "./2024-01-23T23:15:28.004Z-initial-schema.ts";
8 changes: 8 additions & 0 deletions src/data/migrations/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { MigrationProvider } from "kysely";
import * as migrations from "./index";

export class RuntimeMigrationProvider implements MigrationProvider {
async getMigrations() {
return migrations;
}
}
2 changes: 1 addition & 1 deletion src/routes/Snippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ export function Snippet() {

{recordingDuration + 20 < processingDuration && (
<Flex maw={"300px"}>
<Text color="red">
<Text c="red">
This is taking longer than expected. You may want to fine tune
the amount of threads used by Whisper. It will often be
fastest with a couple less than the totally available cores.
Expand Down

0 comments on commit 9b727fc

Please sign in to comment.