feat(02-01): create database connection factory

- src/db/config.ts: getDbPath() returns ~/.cw/data/cw.db with CW_DB_PATH override
- src/db/index.ts: createDatabase() factory with WAL mode and foreign keys
- drizzle.config.ts: Drizzle Kit configuration for migrations

Pattern: Factory function (not singleton) allows isolated test instances
This commit is contained in:
Lukas May
2026-01-30 14:23:42 +01:00
parent caf8bb0332
commit d7e4649e47
4 changed files with 103 additions and 0 deletions

12
drizzle.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'drizzle-kit';
import { join } from 'node:path';
import { homedir } from 'node:os';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'sqlite',
dbCredentials: {
url: process.env.CW_DB_PATH ?? join(homedir(), '.cw', 'data', 'cw.db'),
},
});

33
src/db/config.ts Normal file
View File

@@ -0,0 +1,33 @@
import { mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { homedir } from 'node:os';
/**
* Get the database path.
*
* - Default: ~/.cw/data/cw.db
* - Override via CW_DB_PATH environment variable
* - For testing, pass ':memory:' as CW_DB_PATH
*/
export function getDbPath(): string {
const envPath = process.env.CW_DB_PATH;
if (envPath) {
return envPath;
}
return join(homedir(), '.cw', 'data', 'cw.db');
}
/**
* Ensure the parent directory for the database file exists.
* No-op for in-memory databases.
*/
export function ensureDbDirectory(dbPath: string): void {
// Skip for in-memory database
if (dbPath === ':memory:') {
return;
}
const dir = dirname(dbPath);
mkdirSync(dir, { recursive: true });
}

43
src/db/index.ts Normal file
View File

@@ -0,0 +1,43 @@
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import { getDbPath, ensureDbDirectory } from './config.js';
import * as schema from './schema.js';
export type DrizzleDatabase = BetterSQLite3Database<typeof schema>;
/**
* Create a new database connection.
*
* This is a factory function (not a singleton) to allow multiple instances
* for testing with isolated databases.
*
* @param path - Optional path override. Defaults to getDbPath().
* Use ':memory:' for in-memory testing database.
* @returns Drizzle database instance with schema
*/
export function createDatabase(path?: string): DrizzleDatabase {
const dbPath = path ?? getDbPath();
// Ensure directory exists for file-based databases
ensureDbDirectory(dbPath);
// Create SQLite connection
const sqlite = new Database(dbPath);
// Enable WAL mode for better concurrent read performance
sqlite.pragma('journal_mode = WAL');
// Enable foreign keys (SQLite has them disabled by default)
sqlite.pragma('foreign_keys = ON');
// Create Drizzle instance with schema
return drizzle(sqlite, { schema });
}
// Re-export config utilities
export { getDbPath, ensureDbDirectory } from './config.js';
// Re-export schema and types
export * from './schema.js';

15
src/db/schema.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* Database schema for Codewalk District.
*
* Defines the four-level task hierarchy:
* - Initiative: Top-level project
* - Phase: Major milestone within initiative
* - Plan: Group of related tasks within phase
* - Task: Individual work item
*
* Schema will be defined in Task 3.
*/
// Placeholder export to allow index.ts to compile
// Full schema defined in Task 3
export {};