# cache _(no submodules)_ ## Purpose `PreparedStatementCache` is an LRU-bounded cache for `bun:sqlite` prepared statement objects, keyed by SQL text. Reusing prepared statements avoids the overhead of repeated SQL parsing and optimisation — a significant win for hot query paths. The cache is bounded to prevent unbounded memory growth in applications that generate many distinct SQL strings. The cache integrates with OpenTelemetry to surface hit/miss/eviction counters at `sqlite.cache.hits`, `sqlite.cache.misses`, and `sqlite.cache.evictions`. These are no-ops when no OTel provider is registered, keeping the module safe for use in zero-dependency environments. ## Features - LRU eviction when `size >= maxSize` (default `128`) - `get(sql)` — returns cached statement or `undefined`; updates LRU order - `set(sql, stmt)` — inserts or updates; evicts LRU entry when at capacity, calling `stmt.finalize()` on the evicted statement - `invalidate()` — clears all entries and calls `finalize()` on each held statement; called by `SqliteAdapter.migrate()` to prevent stale plan reuse after schema changes - `size` / `maxSize` read-only getters - OTel counters: `sqlite.cache.hits`, `sqlite.cache.misses`, `sqlite.cache.evictions` - `static resetInstruments()` — clears the OTel lazy singleton for test isolation ## Usage: Basic Example ```ts import { PreparedStatementCache } from "@suaveplan/db-sqlite/cache/cache"; const cache = new PreparedStatementCache(); const stmt = sqlite.prepare("SELECT * FROM users WHERE id = ?"); cache.set("SELECT * FROM users WHERE id = ?", stmt); const hit = cache.get("SELECT * FROM users WHERE id = ?"); // hit === stmt ``` ## Usage: Advanced Example ```ts import { PreparedStatementCache } from "@suaveplan/db-sqlite/cache/cache"; // Small cache for testing eviction const cache = new PreparedStatementCache(3); for (let i = 0; i < 4; i++) { const sql = `SELECT ${i} FROM t`; cache.set(sql, { finalize: () => undefined }); // mock statement } console.log(cache.size); // 3 — first entry evicted cache.invalidate(); console.log(cache.size); // 0 — all entries cleared ``` ## API Reference ### `new PreparedStatementCache(maxSize?: number)` Constructs a new cache. `maxSize` defaults to `128`. ### `get(sql: string): Statement | undefined` Returns the cached statement for `sql`, or `undefined` on miss. Increments `sqlite.cache.hits` or `sqlite.cache.misses`. Updates LRU order on hit. ### `set(sql: string, stmt: Statement): void` Inserts or updates the cache entry for `sql`. On capacity overflow, evicts the least-recently-used entry, calling `stmt.finalize()` on it, and increments `sqlite.cache.evictions`. ### `invalidate(): void` Clears all entries. Calls `stmt.finalize()` on each held statement before clearing. Called automatically by `SqliteAdapter.migrate()`. ### `size: number` Current number of cached entries. ### `maxSize: number` Maximum capacity as set at construction. ### `static resetInstruments(): void` Clears the lazy OTel instruments singleton. Use in tests to ensure each test suite starts with a fresh counter baseline. ## Implementation Notes LRU ordering is maintained with a parallel `string[]` array (`#lruOrder`) rather than a doubly-linked list. This is simpler and sufficient for cache sizes up to a few hundred entries — `Array.indexOf` and `splice` on an array of at most 128 strings add negligible overhead compared to a SQLite query round-trip. For much larger caches a linked-list implementation would be preferable. `finalize()` is called defensively with a try/catch because `bun:sqlite` statements may already be finalised if the underlying database was closed before the cache was cleared.