Implement RPC contract validation baseline
This commit is contained in:
119
packages/shared/src/db/database.ts
Normal file
119
packages/shared/src/db/database.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Database connection and initialization for MosaicIQ
|
||||
*/
|
||||
|
||||
import Database from "better-sqlite3";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { SCHEMA_VERSION, SQL_SCHEMA, DEFAULT_PORTFOLIO_ID } from "./schema.js";
|
||||
|
||||
export type Db = Database.Database;
|
||||
|
||||
export interface DatabaseConfig {
|
||||
path?: string;
|
||||
inMemory?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default data directory for the platform
|
||||
*/
|
||||
export function getDataDir(): string {
|
||||
const platform = process.platform;
|
||||
const base = platform === "darwin"
|
||||
? `${process.env.HOME}/Documents/MosaicIQ`
|
||||
: platform === "win32"
|
||||
? `${process.env.LOCALAPPDATA}\\MosaicIQ`
|
||||
: `${process.env.HOME}/.local/share/mosaiciq`;
|
||||
|
||||
if (!existsSync(base)) {
|
||||
mkdirSync(base, { recursive: true });
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default database path
|
||||
*/
|
||||
export function getDefaultDbPath(): string {
|
||||
return `${getDataDir()}/mosaiciq.db`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database with schema
|
||||
*/
|
||||
export function initDatabase(config: DatabaseConfig = {}): Db {
|
||||
const dbPath = config.inMemory ? ":memory:" : (config.path ?? getDefaultDbPath());
|
||||
|
||||
if (!config.inMemory && dbPath !== ":memory:") {
|
||||
const dbDir = dirname(dbPath);
|
||||
if (!existsSync(dbDir)) {
|
||||
mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Enable WAL mode for better concurrent access
|
||||
db.pragma("journal_mode = WAL");
|
||||
|
||||
// Enable foreign keys
|
||||
db.pragma("foreign_keys = ON");
|
||||
|
||||
// Check if schema exists
|
||||
const hasMeta = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = '_meta'").get();
|
||||
const version = hasMeta
|
||||
? db.prepare("SELECT value FROM _meta WHERE key = 'schema_version'").get() as { value: string } | undefined
|
||||
: undefined;
|
||||
|
||||
if (!version) {
|
||||
// Fresh database - create schema
|
||||
db.exec(SQL_SCHEMA);
|
||||
|
||||
// Set schema version
|
||||
db.prepare("INSERT INTO _meta (key, value) VALUES ('schema_version', ?)").run(String(SCHEMA_VERSION));
|
||||
|
||||
// Create default portfolio
|
||||
db.prepare(`
|
||||
INSERT INTO portfolios (id, name)
|
||||
VALUES (?, ?)
|
||||
`).run(DEFAULT_PORTFOLIO_ID, "Core Retail Coverage");
|
||||
|
||||
console.log(`[DB] Initialized database at ${dbPath}`);
|
||||
} else {
|
||||
const currentVersion = Number(version.value);
|
||||
if (currentVersion !== SCHEMA_VERSION) {
|
||||
console.warn(`[DB] Schema version mismatch: expected ${SCHEMA_VERSION}, got ${currentVersion}`);
|
||||
// TODO: Run migrations
|
||||
}
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
export function closeDatabase(db: Db): void {
|
||||
db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database connection (singleton pattern)
|
||||
*/
|
||||
let dbInstance: Db | null = null;
|
||||
|
||||
export function getDatabase(config?: DatabaseConfig): Db {
|
||||
if (!dbInstance) {
|
||||
dbInstance = initDatabase(config);
|
||||
}
|
||||
return dbInstance;
|
||||
}
|
||||
|
||||
export function resetDatabase(): void {
|
||||
if (dbInstance) {
|
||||
closeDatabase(dbInstance);
|
||||
dbInstance = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user