Files
OmniRoute/docs/ops/SQLITE_RUNTIME.md
diegosouzapw 8f915b18b0 feat(runtime): dynamic SQLite runtime installer with 5-step fallback chain
Adds bin/cli/runtime/sqliteRuntime.mjs that resolves better-sqlite3 from:
(1) bundled optionalDependency, (2) ~/.omniroute/runtime/ install,
(3) lazy npm install into runtime dir, (4) node:sqlite stdlib (Node >=22.5),
(5) bundled sql.js WASM. Each native binary is validated against expected
platform magic bytes (ELF/Mach-O/PE) before load.

Adds bin/cli/runtime/magicBytes.mjs with validateBinaryMagic() helper
(9 tests). Adds bin/cli/runtime/index.mjs as warmUpRuntimes() orchestrator.

Adds scripts/postinstall.mjs warm-up hook (non-fatal, skipped in CI).
Integrates it as the last step of scripts/build/postinstall.mjs.

Extends src/lib/db/core.ts with ensureDbInitialized() (async, idempotent)
and getDriverInfo() so the startup orchestrator can await the resolver
before any DB access, enabling graceful degradation without crashing the
process on missing better-sqlite3.

Solves Windows EBUSY error on 'npm install -g omniroute@latest' while the
previous version is still running, and works in environments without C++
build tools or with unreachable npm registry.

Documents OMNIROUTE_SKIP_POSTINSTALL in .env.example and ENVIRONMENT.md.

Ref: 9router/cli/hooks/sqliteRuntime.js (pattern origin).
2026-05-15 00:05:49 -03:00

81 lines
3.2 KiB
Markdown

# SQLite Runtime Resolution
OmniRoute resolves its SQLite driver at startup through a 5-step fallback chain:
1. **Bundled `better-sqlite3`** (via `dependencies` in `package.json`)
— fastest, native binary, installed by `npm install` when build tools are present.
2. **Runtime-installed `better-sqlite3`** (in `~/.omniroute/runtime/`)
— installed lazily on first run **OR** by `scripts/build/postinstall.mjs → scripts/postinstall.mjs`.
Validates native `.node` magic bytes (ELF / Mach-O / PE) before loading
to guard against corrupt or wrong-platform binaries.
3. **`node:sqlite`** (Node ≥22.5 stdlib) — no native build needed; used when
both better-sqlite3 paths fail. Limited feature set.
4. **`sql.js`** (WASM) — final fallback. Works everywhere but is slower
and writes data on an interval rather than synchronously.
## Why this complexity?
- **Windows EBUSY**: `npm install -g omniroute@latest` can fail if the previous
version's `better_sqlite3.node` is locked by a running process. The runtime
install in `~/.omniroute/runtime/` sidesteps the global npm cache.
- **No build tools**: Some environments (corporate Windows without VS Build
Tools, minimal Docker images) cannot compile `better-sqlite3`. The runtime
installer resolves a pre-built binary from the npm registry; the fallback
drivers ensure OmniRoute still boots even if that fails.
- **Air-gapped systems**: If the npm registry is unreachable, `node:sqlite`
or `sql.js` guarantee baseline functionality.
## Magic-byte validation
Before loading a runtime-installed `.node` file, OmniRoute reads the first 8
bytes and matches against known platform magics:
| Platform | Bytes (hex) | Label |
| --------------------- | ------------- | ----------- |
| Linux | `7F 45 4C 46` | `elf` |
| macOS 64-bit BE | `FE ED FA CF` | `macho` |
| macOS 64-bit LE | `CF FA ED FE` | `macho-le` |
| macOS fat (universal) | `CA FE BA BE` | `macho-fat` |
| Windows | `4D 5A` (MZ) | `pe` |
A mismatched magic → file is ignored, fallback continues to the next step.
## Checking the active driver
```typescript
import { getDriverInfo } from "@/lib/db/core";
const info = getDriverInfo();
// { source: "bundled" | "runtime" | "runtime-installed-now" | "node-sqlite" | "sql-js",
// kind: "better-sqlite3" | "node-sqlite" | "sql-js" }
```
## Manual control
```bash
# Skip postinstall warm-up (for fast CI installs)
OMNIROUTE_SKIP_POSTINSTALL=1 npm install -g omniroute
# Force-reinstall runtime better-sqlite3
rm -rf ~/.omniroute/runtime
omniroute # will reinstall on next start
# Check what driver is active
omniroute config db-info # (if CLI command exists)
```
## Reference
Pattern adapted from `9router/cli/hooks/sqliteRuntime.js`.
Implementation:
- `bin/cli/runtime/magicBytes.mjs` — binary magic-byte validation helpers
- `bin/cli/runtime/sqliteRuntime.mjs` — 5-step runtime resolver + lazy installer
- `bin/cli/runtime/index.mjs` — startup orchestrator (`warmUpRuntimes()`)
- `scripts/postinstall.mjs` — npm post-install hook (non-fatal warm-up)
- `src/lib/db/core.ts``ensureDbInitialized()` / `getDriverInfo()` exports