From 0f96d5e3b80c568550e8f566c343dcd9decd906b Mon Sep 17 00:00:00 2001 From: marcuspaico Date: Mon, 17 Aug 2026 14:04:56 -0700 Subject: [PATCH] feat(db): drizzle schema (settings, sessions) + boot migrations --- server/drizzle.config.ts | 7 +++ server/drizzle/0000_init.sql | 10 ++++ server/drizzle/meta/0000_snapshot.json | 73 ++++++++++++++++++++++++++ server/drizzle/meta/_journal.json | 13 +++++ server/src/db/index.ts | 17 ++++++ server/src/db/schema.ts | 12 +++++ server/test/db.test.ts | 16 ++++++ 7 files changed, 148 insertions(+) create mode 100644 server/drizzle.config.ts create mode 100644 server/drizzle/0000_init.sql create mode 100644 server/drizzle/meta/0000_snapshot.json create mode 100644 server/drizzle/meta/_journal.json create mode 100644 server/src/db/index.ts create mode 100644 server/src/db/schema.ts create mode 100644 server/test/db.test.ts diff --git a/server/drizzle.config.ts b/server/drizzle.config.ts new file mode 100644 index 0000000..9156b13 --- /dev/null +++ b/server/drizzle.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "sqlite", + schema: "./src/db/schema.ts", + out: "./drizzle", +}); diff --git a/server/drizzle/0000_init.sql b/server/drizzle/0000_init.sql new file mode 100644 index 0000000..adef46b --- /dev/null +++ b/server/drizzle/0000_init.sql @@ -0,0 +1,10 @@ +CREATE TABLE `sessions` ( + `id` text PRIMARY KEY NOT NULL, + `created_at` integer NOT NULL, + `expires_at` integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE `settings` ( + `key` text PRIMARY KEY NOT NULL, + `value` text NOT NULL +); diff --git a/server/drizzle/meta/0000_snapshot.json b/server/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..82a4bbf --- /dev/null +++ b/server/drizzle/meta/0000_snapshot.json @@ -0,0 +1,73 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "383d7bd7-3ebf-456e-8c5c-f39cebab14c4", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json new file mode 100644 index 0000000..99f3b33 --- /dev/null +++ b/server/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1787000672259, + "tag": "0000_init", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/server/src/db/index.ts b/server/src/db/index.ts new file mode 100644 index 0000000..a327f4a --- /dev/null +++ b/server/src/db/index.ts @@ -0,0 +1,17 @@ +import { Database } from "bun:sqlite"; +import { drizzle } from "drizzle-orm/bun-sqlite"; +import { migrate } from "drizzle-orm/bun-sqlite/migrator"; +import { mkdirSync } from "node:fs"; +import { join } from "node:path"; +import * as schema from "./schema"; + +export type Db = ReturnType; + +export function openDb(dataDir: string) { + mkdirSync(dataDir, { recursive: true }); + const sqlite = new Database(join(dataDir, "helios.db"), { create: true }); + sqlite.exec("PRAGMA journal_mode = WAL"); + const db = drizzle(sqlite, { schema }); + migrate(db, { migrationsFolder: join(import.meta.dir, "../../drizzle") }); + return db; +} diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts new file mode 100644 index 0000000..e27527b --- /dev/null +++ b/server/src/db/schema.ts @@ -0,0 +1,12 @@ +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const settings = sqliteTable("settings", { + key: text("key").primaryKey(), + value: text("value").notNull(), +}); + +export const sessions = sqliteTable("sessions", { + id: text("id").primaryKey(), + createdAt: integer("created_at").notNull(), + expiresAt: integer("expires_at").notNull(), +}); diff --git a/server/test/db.test.ts b/server/test/db.test.ts new file mode 100644 index 0000000..8dddd95 --- /dev/null +++ b/server/test/db.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "bun:test"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { openDb } from "../src/db"; +import { settings } from "../src/db/schema"; + +describe("db", () => { + test("opens, migrates, round-trips a setting", async () => { + const dir = mkdtempSync(join(tmpdir(), "helios-")); + const db = openDb(dir); + await db.insert(settings).values({ key: "llm_base_url", value: "https://openrouter.ai/api/v1" }); + const rows = await db.select().from(settings); + expect(rows).toEqual([{ key: "llm_base_url", value: "https://openrouter.ai/api/v1" }]); + }); +});