/** * SQLite setup for extension support. * * Platform behavior: * - Linux/Windows: Bun's bundled SQLite supports extensions natively * - macOS: Apple's SQLite disables extension loading, requires custom SQLite * * MUST be imported before any Database is created. * * @module src/store/sqlite/setup */ import { Database } from "bun:sqlite"; // node:fs: existsSync for checking file existence (no async needed at module load) import { existsSync } from "node:fs"; // node:os: platform detection (no Bun equivalent) import { platform } from "node:os"; // ───────────────────────────────────────────────────────────────────────────── // Types // ───────────────────────────────────────────────────────────────────────────── /** * How SQLite extensions are loaded on this platform: * - 'native': Bundled SQLite supports extensions (Linux/Windows) * - 'custom': Custom SQLite library loaded successfully (macOS with Homebrew) * - 'unavailable': Extension loading not possible */ export type ExtensionLoadingMode = "native" | "custom" | "unavailable"; /** * Record of a SQLite load attempt for diagnostics. */ export interface LoadAttempt { path: string; error: string; } // ───────────────────────────────────────────────────────────────────────────── // State // ───────────────────────────────────────────────────────────────────────────── // Possible paths to Homebrew SQLite with extension support const SQLITE_PATHS = [ "/opt/homebrew/opt/sqlite3/lib/libsqlite3.dylib", // macOS Apple Silicon "/usr/local/opt/sqlite3/lib/libsqlite3.dylib", // macOS Intel ]; let setupCompleted = false; let customSqlitePath: string | null = null; const loadAttempts: LoadAttempt[] = []; // ───────────────────────────────────────────────────────────────────────────── // Setup // ───────────────────────────────────────────────────────────────────────────── /** * Configure Bun to use system SQLite with extension support. * Safe to call multiple times - only runs once. */ function setupCustomSqlite(): void { if (setupCompleted) { return; } // Linux/Windows: bundled SQLite supports extensions natively if (platform() !== "darwin") { setupCompleted = true; return; } // macOS: try Homebrew paths for (const path of SQLITE_PATHS) { if (!existsSync(path)) { loadAttempts.push({ path, error: "file not found" }); continue; } try { Database.setCustomSQLite(path); customSqlitePath = path; setupCompleted = true; return; } catch (e) { const message = e instanceof Error ? e.message : String(e); loadAttempts.push({ path, error: message }); } } setupCompleted = true; } // Run setup immediately on import setupCustomSqlite(); // ───────────────────────────────────────────────────────────────────────────── // Public API // ───────────────────────────────────────────────────────────────────────────── /** * Get the extension loading mode for this platform. */ export function getExtensionLoadingMode(): ExtensionLoadingMode { if (platform() !== "darwin") { return "native"; // Linux/Windows: bundled SQLite supports extensions } return customSqlitePath ? "custom" : "unavailable"; } /** * Get the path to the custom SQLite library, or null if using bundled/native. */ export function getCustomSqlitePath(): string | null { return customSqlitePath; } /** * Get all SQLite load attempts for diagnostics. * Useful for debugging why extension loading failed. */ export function getLoadAttempts(): LoadAttempt[] { return loadAttempts.map((a) => ({ ...a })); } /** * @deprecated Use getExtensionLoadingMode() !== 'unavailable' instead */ export function hasExtensionSupport(): boolean { return getExtensionLoadingMode() !== "unavailable"; }