Spaces:
Runtime error
Runtime error
| import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"; | |
| import { join, resolve } from "node:path"; | |
| import { homedir } from "node:os"; | |
| import { execSync } from "node:child_process"; | |
| import { pathToFileURL } from "node:url"; | |
| import { validateBinaryMagic, platformBinaryLabel } from "./magicBytes.mjs"; | |
| const RUNTIME_DIR = join(homedir(), ".omniroute", "runtime"); | |
| const BETTER_SQLITE3_VERSION = "better-sqlite3@^12.10.1"; | |
| let resolvedCached = null; | |
| /** | |
| * Resolves a SQLite driver through a 5-step fallback chain: | |
| * 1. Bundled better-sqlite3 (optionalDependency) | |
| * 2. Runtime-installed better-sqlite3 in ~/.omniroute/runtime/ | |
| * 3. Lazy npm install into runtime dir | |
| * 4. node:sqlite (Node ≥22.5 stdlib) | |
| * 5. sql.js (bundled WASM, always available) | |
| * | |
| * Returns { driver, source } where source is one of: | |
| * "bundled" | "runtime" | "runtime-installed-now" | "node-sqlite" | "sql-js" | |
| */ | |
| export async function loadSqliteRuntime() { | |
| if (resolvedCached) return resolvedCached; | |
| const bundled = await tryLoadBundled(); | |
| if (bundled) { | |
| resolvedCached = { driver: bundled, source: "bundled" }; | |
| return resolvedCached; | |
| } | |
| const runtimeInstalled = await tryLoadRuntimeInstalled(); | |
| if (runtimeInstalled) { | |
| resolvedCached = { driver: runtimeInstalled, source: "runtime" }; | |
| return resolvedCached; | |
| } | |
| try { | |
| await installRuntime(); | |
| const after = await tryLoadRuntimeInstalled(); | |
| if (after) { | |
| resolvedCached = { driver: after, source: "runtime-installed-now" }; | |
| return resolvedCached; | |
| } | |
| } catch (err) { | |
| console.warn(`[omniroute] runtime install failed: ${err.message}`); | |
| } | |
| try { | |
| const nodeSqlite = await import("node:sqlite"); | |
| resolvedCached = { | |
| driver: { kind: "node-sqlite", DatabaseSync: nodeSqlite.DatabaseSync }, | |
| source: "node-sqlite", | |
| }; | |
| return resolvedCached; | |
| } catch {} | |
| const sqljs = await import("sql.js"); | |
| resolvedCached = { | |
| driver: { kind: "sql-js", initSqlJs: sqljs.default ?? sqljs.initSqlJs }, | |
| source: "sql-js", | |
| }; | |
| return resolvedCached; | |
| } | |
| async function tryLoadBundled() { | |
| try { | |
| const mod = await import("better-sqlite3"); | |
| return { kind: "better-sqlite3", Database: mod.default ?? mod }; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| async function tryLoadRuntimeInstalled() { | |
| const runtimeNodeModules = resolve(RUNTIME_DIR, "node_modules"); | |
| const pkgRoot = resolve(runtimeNodeModules, "better-sqlite3"); | |
| if (!pkgRoot.startsWith(`${runtimeNodeModules}/`)) return null; | |
| if (!existsSync(join(pkgRoot, "package.json"))) return null; | |
| const buildDir = join(pkgRoot, "build", "Release"); | |
| if (existsSync(buildDir)) { | |
| const nodeFile = readdirSync(buildDir).find((f) => f.endsWith(".node")); | |
| if (nodeFile) { | |
| const magic = validateBinaryMagic(join(buildDir, nodeFile)); | |
| const expected = platformBinaryLabel(); | |
| if (!magic || (magic !== expected && magic !== "macho-le" && magic !== "macho-fat")) { | |
| console.warn( | |
| `[omniroute] runtime sqlite binary magic mismatch (${magic} ≠ ${expected}) — skipping` | |
| ); | |
| return null; | |
| } | |
| } | |
| } | |
| try { | |
| const mod = await import(/* webpackIgnore: true */ pathToFileURL(pkgRoot).href); | |
| return { kind: "better-sqlite3", Database: mod.default ?? mod }; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| function ensureRuntimeDir() { | |
| if (!existsSync(RUNTIME_DIR)) mkdirSync(RUNTIME_DIR, { recursive: true }); | |
| const pkg = join(RUNTIME_DIR, "package.json"); | |
| if (!existsSync(pkg)) { | |
| writeFileSync( | |
| pkg, | |
| JSON.stringify({ name: "omniroute-runtime", private: true, type: "commonjs" }), | |
| "utf-8" | |
| ); | |
| } | |
| } | |
| async function installRuntime() { | |
| ensureRuntimeDir(); | |
| const npm = process.platform === "win32" ? "npm.cmd" : "npm"; | |
| execSync( | |
| `${npm} install --prefix "${RUNTIME_DIR}" ${BETTER_SQLITE3_VERSION} --no-audit --no-fund --silent`, | |
| { stdio: ["ignore", "ignore", "pipe"], timeout: 180_000 } | |
| ); | |
| } | |
| /** Clears the cached resolved driver (for testing). */ | |
| export function clearRuntimeCache() { | |
| resolvedCached = null; | |
| } | |