diff --git a/drizzle.config.ts b/drizzle.config.ts deleted file mode 100644 index 89d6766..0000000 --- a/drizzle.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'drizzle-kit' -import { env } from './src/env' - -export default defineConfig({ - out: './drizzle', - schema: './src/server/db/schema/index.ts', - dialect: 'postgresql', - dbCredentials: { - url: env.DATABASE_URL, - }, -}) diff --git a/drizzle/0000_loving_thunderbird.sql b/drizzle/0000_loving_thunderbird.sql deleted file mode 100644 index 0ac5d21..0000000 --- a/drizzle/0000_loving_thunderbird.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE "todo" ( - "id" uuid PRIMARY KEY NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - "title" text NOT NULL, - "completed" boolean DEFAULT false NOT NULL -); diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json deleted file mode 100644 index ac2b514..0000000 --- a/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "id": "4ece5479-57bf-473d-b806-c1176c972e7f", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.todo": { - "name": "todo", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "completed": { - "name": "completed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json deleted file mode 100644 index 82b1e07..0000000 --- a/drizzle/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1777096386609, - "tag": "0000_loving_thunderbird", - "breakpoints": true - } - ] -} diff --git a/scripts/embed-migrations.ts b/scripts/embed-migrations.ts deleted file mode 100644 index af91f06..0000000 --- a/scripts/embed-migrations.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { existsSync } from 'node:fs' -import { readFile, writeFile } from 'node:fs/promises' -import { z } from 'zod' - -const JOURNAL = './drizzle/meta/_journal.json' -const OUTPUT = './src/server/db/migrations.gen.ts' - -const journalEntrySchema = z.object({ - idx: z.number().int().nonnegative(), - tag: z.string().regex(/^\d{4}_[a-z0-9_]+$/), - when: z.number().int().nonnegative(), - breakpoints: z.boolean(), -}) -const journalSchema = z.object({ entries: z.array(journalEntrySchema).default([]) }) - -type JournalEntry = z.infer - -const readJournalEntries = async (): Promise => { - if (!existsSync(JOURNAL)) { - return [] - } - const raw: unknown = JSON.parse(await readFile(JOURNAL, 'utf-8')) - return journalSchema.parse(raw).entries.sort((a, b) => a.idx - b.idx) -} - -const main = async () => { - const entries = await readJournalEntries() - - const imports = entries - .map((e) => `import sql_${e.idx} from '#drizzle/${e.tag}.sql' with { type: 'text' }`) - .join('\n') - - const arrayBody = entries.length - ? `[\n${entries.map((e) => ` { tag: '${e.tag}', sql: sql_${e.idx}, when: ${e.when}, breakpoints: ${e.breakpoints} },`).join('\n')}\n]` - : '[]' - - const out = `// AUTO-GENERATED by \`bun run db:embed\`. Do not edit. -${imports ? `${imports}\n` : ''} -export type EmbeddedMigration = { tag: string; sql: string; when: number; breakpoints: boolean } - -export const embeddedMigrations: readonly EmbeddedMigration[] = ${arrayBody} -` - - await writeFile(OUTPUT, out) - console.log(`✓ ${OUTPUT} (${entries.length} migration${entries.length === 1 ? '' : 's'})`) -} - -main().catch((err) => { - console.error('❌', err instanceof Error ? err.message : err) - process.exit(1) -}) diff --git a/src/cli/migrate.ts b/src/cli/migrate.ts deleted file mode 100644 index 31441b1..0000000 --- a/src/cli/migrate.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { defineCommand } from 'citty' - -export default defineCommand({ - meta: { - name: 'migrate', - description: 'Apply pending database migrations', - }, - async run() { - const [{ env }, { drizzle }, { sql }, { embeddedMigrations }, { getLogger }] = await Promise.all([ - import('@/env'), - import('drizzle-orm/postgres-js'), - import('drizzle-orm'), - import('@/server/db/migrations.gen'), - import('@/server/logger'), - ]) - - const logger = getLogger(['cli', 'migrate']) - - if (embeddedMigrations.length === 0) { - logger.info('No migrations bundled into this binary.') - return - } - - const sha256 = (s: string) => Bun.CryptoHasher.hash('sha256', s, 'hex') - - const db = drizzle({ - connection: { - url: env.DATABASE_URL, - max: 1, - onnotice: (n) => logger.debug('pg notice', { notice: n.message }), - }, - }) - try { - await db.execute(sql`CREATE SCHEMA IF NOT EXISTS "drizzle"`) - await db.execute(sql` - CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" ( - id SERIAL PRIMARY KEY, - hash text NOT NULL, - created_at bigint - ) - `) - - const applied = await db.execute<{ hash: string; created_at: string | null }>( - sql`SELECT hash, created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at ASC`, - ) - - // Reject schema drift: any applied migration whose embedded SQL has changed (or is missing) is fatal. - for (const row of applied) { - const when = Number(row.created_at) - const m = embeddedMigrations.find((e) => e.when === when) - if (!m) { - throw new Error(`Applied migration when=${when} is not in this binary; do not roll back applied migrations.`) - } - if (sha256(m.sql) !== row.hash) { - throw new Error(`Migration hash mismatch at when=${when}; do not edit migrations after they are applied.`) - } - } - - const appliedWhens = new Set(applied.map((r) => Number(r.created_at))) - const pending = embeddedMigrations.filter((m) => !appliedWhens.has(m.when)) - if (pending.length === 0) { - logger.info('Database is up to date.') - return - } - - logger.info('Applying {count} migration(s)...', { count: pending.length }) - await db.transaction(async (tx) => { - for (const m of pending) { - for (const rawStmt of m.sql.split('--> statement-breakpoint')) { - const stmt = rawStmt.trim() - if (!stmt) continue - await tx.execute(sql.raw(stmt)) - } - await tx.execute( - sql`INSERT INTO "drizzle"."__drizzle_migrations" ("hash", "created_at") VALUES (${sha256(m.sql)}, ${m.when})`, - ) - } - }) - logger.info('Migrations applied.') - } finally { - await db.$client.end() - } - }, -}) diff --git a/src/server/db/migrations.gen.ts b/src/server/db/migrations.gen.ts deleted file mode 100644 index 4df2dfd..0000000 --- a/src/server/db/migrations.gen.ts +++ /dev/null @@ -1,8 +0,0 @@ -// AUTO-GENERATED by `bun run db:embed`. Do not edit. -import sql_0 from '#drizzle/0000_loving_thunderbird.sql' with { type: 'text' } - -export type EmbeddedMigration = { tag: string; sql: string; when: number; breakpoints: boolean } - -export const embeddedMigrations: readonly EmbeddedMigration[] = [ - { tag: '0000_loving_thunderbird', sql: sql_0, when: 1777096386609, breakpoints: true }, -] diff --git a/src/server/db/sql.d.ts b/src/server/db/sql.d.ts deleted file mode 100644 index 5a29d00..0000000 --- a/src/server/db/sql.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.sql' { - const content: string - export default content -}