3.7 KiB
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(default128) get(sql)— returns cached statement orundefined; updates LRU orderset(sql, stmt)— inserts or updates; evicts LRU entry when at capacity, callingstmt.finalize()on the evicted statementinvalidate()— clears all entries and callsfinalize()on each held statement; called bySqliteAdapter.migrate()to prevent stale plan reuse after schema changessize/maxSizeread-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
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
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.