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

147 lines (146 loc) • 5.86 kB
// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. import { rethrowIfTestIsolationError } from "../../core/errors.js"; import { formatRegistryError } from "../../core/registry-url.js"; import { warn } from "../../core/warn.js"; import { closeDatabase, openIndexDatabase } from "./index-connection.js"; /** * Upsert a registry index cache entry in index.db. * * @param db - Open index.db connection (from openDatabase / openExistingDatabase). * @param registryUrl - Canonical URL of the registry (used as primary key). * @param indexJson - Serialised registry index document (JSON string). * @param opts.etag - HTTP ETag from the response (optional). * @param opts.lastModified - HTTP Last-Modified from the response (optional). */ export function upsertRegistryIndexCache(db, registryUrl, indexJson, opts) { db.prepare(` INSERT INTO registry_index_cache (registry_url, fetched_at, etag, last_modified, index_json) VALUES (?, ?, ?, ?, ?) ON CONFLICT(registry_url) DO UPDATE SET fetched_at = excluded.fetched_at, etag = excluded.etag, last_modified = excluded.last_modified, index_json = excluded.index_json `).run(registryUrl, new Date().toISOString(), opts?.etag ?? null, opts?.lastModified ?? null, indexJson); } /** * Look up a cached registry index entry from index.db. * Returns undefined when not found or when the entry is older than `maxAgeMs`. * * TTL check: if `Date.now() - new Date(fetched_at).getTime() > maxAgeMs` the * entry is considered a cache miss and undefined is returned. * * @param db - Open index.db connection. * @param registryUrl - Canonical URL of the registry (primary key). * @param maxAgeMs - Maximum age in milliseconds before the entry is stale (default: 1 hour). */ export function getRegistryIndexCache(db, registryUrl, maxAgeMs = 3_600_000 /* 1 hour */) { const row = db .prepare(`SELECT fetched_at, etag, last_modified, index_json FROM registry_index_cache WHERE registry_url = ?`) .get(registryUrl); if (!row) return undefined; const fetchedAt = Date.parse(row.fetched_at); if (Number.isNaN(fetchedAt) || Date.now() - fetchedAt > maxAgeMs) return undefined; return { indexJson: row.index_json, etag: row.etag, lastModified: row.last_modified }; } /** * Open the cache DB (a failed open yields `db = undefined`; the bun-test * isolation guard is re-thrown), run `fn`, and close the DB only after `fn` * has fully settled — the callbacks are async, and closing earlier would tear * the DB down mid-write. */ export async function withRegistryCacheDb(fn) { let db; try { db = openIndexDatabase(); } catch (err) { rethrowIfTestIsolationError(err); db = undefined; } try { return await fn(db); } finally { if (db) { try { closeDatabase(db); } catch { /* ignore */ } } } } /** * Returns a fresh cache hit when present, otherwise fetches live (writing the * result back best-effort), and falls back to a stale cache row when the * fetch fails. */ export async function fetchCachedJson(opts) { const { cacheKey, ttlMs, parseCache, fetchFresh } = opts; return withRegistryCacheDb(async (db) => { let dbCacheResult; try { if (db) { dbCacheResult = getRegistryIndexCache(db, cacheKey, ttlMs); } } catch (err) { // Never mask the bun-test isolation guard as "DB unavailable" — see // rethrowIfTestIsolationError in src/core/errors.ts. rethrowIfTestIsolationError(err); // index.db read failed (pre-migration install or test env) — fall through } if (dbCacheResult) { const cached = parseCache(dbCacheResult.indexJson, { stale: false }); if (cached !== undefined) { return cached; } } try { const { value, cacheJson } = await fetchFresh(); if (db) { try { upsertRegistryIndexCache(db, cacheKey, cacheJson); } catch { /* best-effort */ } } return value; } catch (err) { // Fetch failed — use stale DB cache if available. if (dbCacheResult) { const stale = parseCache(dbCacheResult.indexJson, { stale: true }); if (stale !== undefined) return stale; } // No in-TTL row — consult the cache PAST its TTL before giving up: a // briefly unreachable registry should degrade to the last-known index, // loudly, not hard-fail the command. try { const expiredRow = db ? getRegistryIndexCache(db, cacheKey, Number.POSITIVE_INFINITY) : undefined; if (expiredRow) { const stale = parseCache(expiredRow.indexJson, { stale: true }); if (stale !== undefined) { warn(`Registry fetch failed (${formatRegistryError(err)}); ` + "serving the last cached index, which is past its refresh interval."); return stale; } } } catch (cacheErr) { rethrowIfTestIsolationError(cacheErr); // cache read failed — fall through to the original fetch error } throw err; } }); }