TypeScript · 2762 bytes Raw Blame History
1 import { Kysely, SqliteDialect } from 'kysely';
2 import SQLite from 'sqlite3';
3 import { readFileSync } from 'fs';
4 import { join } from 'path';
5 import type { Database } from './types.js';
6
7 let db: Kysely<Database> | null = null;
8
9 export function initializeDatabase(dbPath?: string): Kysely<Database> {
10 if (db) {
11 return db;
12 }
13
14 const databasePath = dbPath || process.env.DATABASE_PATH || ':memory:';
15
16 const dialect = new SqliteDialect({
17 database: new SQLite.Database(databasePath),
18 });
19
20 db = new Kysely<Database>({
21 dialect,
22 });
23
24 return db;
25 }
26
27 export async function setupDatabase(): Promise<void> {
28 const database = getDatabase();
29
30 // Read and execute schema
31 const schemaPath = join(new URL(import.meta.url).pathname, '../schema.sql');
32 const schema = readFileSync(schemaPath, 'utf-8');
33
34 // Split and execute each statement
35 const statements = schema
36 .split(';')
37 .map(stmt => stmt.trim())
38 .filter(stmt => stmt.length > 0);
39
40 for (const statement of statements) {
41 try {
42 await database.executeQuery({
43 sql: statement,
44 parameters: [],
45 });
46 } catch (error) {
47 // Ignore table already exists errors
48 if (!error.message?.includes('already exists')) {
49 console.error('Database setup error:', error);
50 throw error;
51 }
52 }
53 }
54 }
55
56 export function getDatabase(): Kysely<Database> {
57 if (!db) {
58 throw new Error('Database not initialized. Call initializeDatabase() first.');
59 }
60 return db;
61 }
62
63 export async function closeDatabase(): Promise<void> {
64 if (db) {
65 await db.destroy();
66 db = null;
67 }
68 }
69
70 // Helper function to convert snake_case to camelCase for database rows
71 export function toCamelCase<T>(obj: any): T {
72 if (obj === null || obj === undefined) {
73 return obj;
74 }
75
76 if (Array.isArray(obj)) {
77 return obj.map(toCamelCase) as T;
78 }
79
80 if (typeof obj === 'object' && obj.constructor === Object) {
81 const result: any = {};
82 for (const [key, value] of Object.entries(obj)) {
83 const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
84 result[camelKey] = toCamelCase(value);
85 }
86 return result;
87 }
88
89 return obj;
90 }
91
92 // Helper function to convert camelCase to snake_case for database queries
93 export function toSnakeCase<T>(obj: any): T {
94 if (obj === null || obj === undefined) {
95 return obj;
96 }
97
98 if (Array.isArray(obj)) {
99 return obj.map(toSnakeCase) as T;
100 }
101
102 if (typeof obj === 'object' && obj.constructor === Object) {
103 const result: any = {};
104 for (const [key, value] of Object.entries(obj)) {
105 const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
106 result[snakeKey] = toSnakeCase(value);
107 }
108 return result;
109 }
110
111 return obj;
112 }