UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

154 lines (117 loc) • 6.88 kB
# 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:** - Detect runtime (bun vs node) once at module load - Load the appropriate SQLite driver (`bun:sqlite` on Bun, `better-sqlite3` on Node) - Export `Database` type — the common structural type both drivers share - Export `openDatabase(path, opts?)` factory **Design constraints:** - No adapter classes, no interface hierarchies — the driver APIs are already nearly identical - The exported `Database` type should be the structural intersection of what both drivers expose - `openDatabase()` should be a simple function, not a factory class - `db.query()` (Bun-specific) must be normalised — replace with `db.prepare().all()` at this boundary **Architect: investigate and fill in:** - [ ] Read `src/indexer/db.ts`, `src/core/state-db.ts`, `src/workflows/db.ts` — the 3 hard-import files - [ ] Identify every method AKM actually calls on a `Database` instance (exec, prepare, transaction, close, query, etc.) - [ ] Check whether `better-sqlite3`'s type definitions can be used as the exported `Database` type directly, or if a small structural type alias is needed - [ ] Check `sqliteVec.load(db)` — confirm whether it accepts both driver handles or needs a raw handle escape hatch - [ ] Write the complete file --- ## 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:** - Runtime detection: `const isBun = !!process.versions?.bun` at top of file, used inline - No class, no factory — just named function exports - Each export branches once on `isBun`, no per-call overhead - `node:crypto` is available in both Bun and Node — the crypto exports can skip the `isBun` branch entirely and just use `createHash` always (simpler, same perf for a CLI) **Architect: investigate and fill in:** - [ ] `grep -rn "Bun\." src --include="*.ts"` — confirm the table above is complete - [ ] `grep -rn "import\.meta\.dir" src --include="*.ts"` — list all sites, note which have fallbacks - [ ] Confirm `Readable.toWeb()` is the right Node 18 approach for `writeResponseToFile` - [ ] Write the complete file --- ## Call Site Changes Once both files exist, the remaining work is mechanical: **SQLite type-only imports (~30 files):** ```ts // 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`):** - Replace `import { Database } from 'bun:sqlite'` with `import { openDatabase, type Database } from '../storage/database'` - Replace `new Database(path)` with `openDatabase(path)` - Replace `db.query<T>(sql).all(params)` with `db.prepare(sql).all(params)` (Bun-specific API) - Remove `SQLQueryBindings` spread casts **Bun.* call sites (~10 files):** ```ts // Before Bun.spawnSync(['git', 'ls-files', ...]) // After import { spawnSync } from '../runtime' spawnSync(['git', 'ls-files', ...]) ``` **Architect: investigate and fill in:** - [ ] List every call site file and the exact import line change needed - [ ] Flag any call site where the shim return shape differs from the Bun API (e.g. stdout as string vs Buffer) --- ## `package.json` Changes - Add `better-sqlite3` as `optionalDependency` (prebuilt binaries, no compile step on common platforms) - Add `@types/better-sqlite3` to `devDependencies` - Add `semver` to `dependencies` (tiny, zero-deps) - Add `node: ">=22"` to `engines` (raised from 20.12 when Node 20 support was dropped, 2026-07) (`@clack/core` uses `node:util.styleText`, which was added in Node 20.12) - Require Node >= 22 in the npm `preinstall` guard --- ## What This Is NOT - Not an adapter pattern with interface hierarchies - Not a ports-and-adapters architecture - Not a plugin system - Not a DI container 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.).