forked from ericc-ch/copilot-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdb.ts
More file actions
95 lines (76 loc) · 2.39 KB
/
db.ts
File metadata and controls
95 lines (76 loc) · 2.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import fs from "node:fs"
import path from "node:path"
import migration001 from "./migrations/001_initial.sql" with { type: "text" }
import { createDatabase, type DbInstance } from "./sqlite-adapter"
export const CURRENT_SCHEMA_VERSION = 1
const MIGRATIONS: Array<{ version: number; sql: string }> = [
{ version: 1, sql: migration001 },
]
let dbInstance: DbInstance | undefined
export function initDb(dbPath: string): DbInstance {
if (dbInstance) return dbInstance
if (dbPath !== ":memory:") {
fs.mkdirSync(path.dirname(dbPath), { recursive: true })
}
const db = createDatabase(dbPath)
// Pragmas — set before any schema work.
db.pragma("journal_mode = WAL")
db.pragma("synchronous = NORMAL")
db.pragma("foreign_keys = ON")
runMigrations(db)
dbInstance = db
return db
}
export function getDb(): DbInstance {
if (!dbInstance) {
throw new Error(
"Database not initialized. Call initDb(path) before getDb().",
)
}
return dbInstance
}
export function withTransaction<T>(fn: (db: DbInstance) => T): T {
const db = getDb()
const tx = db.transaction((arg: () => T) => arg())
return tx(() => fn(db))
}
/**
* Test-only helper. Closes any current instance and clears the singleton so
* the next initDb call starts from scratch. Production code must never call
* this — it exists to keep tests isolated.
*/
export function __resetDbForTests(): void {
if (dbInstance) {
try {
dbInstance.close()
} catch {
// ignore
}
dbInstance = undefined
}
}
function runMigrations(db: DbInstance): void {
// Bootstrap meta table so we can read schema_version.
db.exec(
"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)",
)
const row = db
.prepare("SELECT value FROM meta WHERE key='schema_version'")
.get() as { value: string } | undefined
const currentVersion = row ? Number.parseInt(row.value, 10) : 0
const pending = MIGRATIONS.filter((m) => m.version > currentVersion).sort(
(a, b) => a.version - b.version,
)
if (pending.length === 0) return
const apply = db.transaction(() => {
for (const m of pending) {
db.exec(m.sql)
}
db.prepare(
"INSERT INTO meta (key, value) VALUES (?, ?) "
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
).run("schema_version", String(CURRENT_SCHEMA_VERSION))
})
apply()
}
export { type DbInstance } from "./sqlite-adapter"