akm docs

Runtime Boundary Design — AKM 0.9.0

Implementation note (added later): both target files below now exist — src/storage/database.ts and src/runtime.ts — so this design has landed. The "3 hard-import files" named in the original checklist (src/indexer/db.ts, src/core/state-db.ts, src/workflows/db.ts) no longer describes the current tree: src/indexer/db.ts was later split into src/storage/repositories/index-*.ts (its Database opener lives in src/storage/repositories/index-connection.ts, which imports openDatabase/Database from src/storage/database.ts as this design specifies). src/workflows/db.ts did exist historically but has since been removed — workflow run persistence now lives in src/storage/repositories/workflow-runs-repository.ts. src/core/state-db.ts still exists and also imports from src/storage/database.ts. The checklist items below are kept as the historical design record.

Problem

Runtime-specific dependencies (bun:sqlite, Bun.* APIs) leak directly into 33+ source files. This creates unnecessary coupling, makes Node.js compatibility hard, and means every future runtime change has a large blast radius.

Goal

Two files own the entire runtime boundary. Everything else is clean application code.


File 1: src/storage/database.ts

Single source of truth for SQLite. The rest of the app never imports from bun:sqlite or better-sqlite3 directly — only from here.

Responsibilities:

Design constraints:

Architect: investigate and fill in:


File 2: src/runtime.ts

~60 lines. Named exports for every Bun.* API called outside of the SQLite layer. Each export works on both runtimes.

Known Bun. call sites to cover (architect: verify these are complete):*

Export name Bun API Node equivalent
spawnSync(cmd, opts) Bun.spawnSync child_process.spawnSync
spawn(cmd, opts) Bun.spawn child_process.spawn
readStdin(limitBytes) Bun.stdin.stream() for await (chunk of process.stdin)
writeResponseToFile(path, res) Bun.write(path, response) stream/promises pipeline
sha256Hex(data) new Bun.CryptoHasher('sha256') node:crypto createHash
md5Hex(data) new Bun.CryptoHasher('md5') node:crypto createHash
semverOrder(a, b) Bun.semver.order semver.compare (add dep)
getDirname(importMetaUrl) import.meta.dir path.dirname(new URL(url).pathname)
resolveModule(spec, from) Bun.resolveSync require.resolve (already has fallback)
sleepSync(ms) Bun.sleepSync Atomics.wait (already has fallback)
mainPath Bun.main process.argv[1]

Design constraints:

Architect: investigate and fill in:


Call Site Changes

Once both files exist, the remaining work is mechanical:

SQLite type-only imports (~30 files):

// Before
import type { Database } from 'bun:sqlite'
// After
import type { Database } from '../storage/database'

SQLite hard imports (3 files — indexer/db.ts, core/state-db.ts, workflows/db.ts):

Bun. call sites (~10 files):*

// Before
Bun.spawnSync(['git', 'ls-files', ...])
// After
import { spawnSync } from '../runtime'
spawnSync(['git', 'ls-files', ...])

Architect: investigate and fill in:


package.json Changes


What This Is NOT

Just two modules that own the runtime details so the rest of the codebase doesn't have to.


Child Issues to Create (after design is complete)

  1. src/storage/database.ts + migrate 3 hard-import files
  2. Mechanical: update 30 type-only imports (sed pass + tsc verify)
  3. src/runtime.ts + update ~10 call sites
  4. package.json + relax preinstall guard + CI Node matrix

Plus: audit open GitHub issues on itlackey/akm and close any that are superseded by this work or otherwise stale (milestone 0.9.0 items already addressed, etc.).