Files
Imhotep/packages/imhotep-bench/src/cache.ts
T

172 lines
4.3 KiB
TypeScript

// cache.ts - Compilation and extraction cache implementations for Imhotep bench harness
// Provides in-memory caching with TTL, versioning, and explicit invalidation.
export interface CacheKey {
/** Content hash or deterministic identifier */
hash: string
/** Cache schema version for invalidation on format changes */
version: string
}
export interface CacheEntry<T> {
/** Stored value */
value: T
/** Unix timestamp when the entry was created */
createdAt: number
/** Optional Unix timestamp when the entry expires */
expiresAt?: number
}
export interface Cache<T> {
get(key: CacheKey): T | undefined
set(key: CacheKey, value: T, ttlMs?: number): void
invalidate(key: CacheKey): boolean
clear(): void
size(): number
}
/** Simple in-memory cache backed by a Map. */
export class MemoryCache<T> implements Cache<T> {
private store = new Map<string, CacheEntry<T>>()
private makeKey(key: CacheKey): string {
return `${key.version}:${key.hash}`
}
get(key: CacheKey): T | undefined {
const k = this.makeKey(key)
const entry = this.store.get(k)
if (!entry) return undefined
if (entry.expiresAt !== undefined && Date.now() > entry.expiresAt) {
this.store.delete(k)
return undefined
}
return entry.value
}
set(key: CacheKey, value: T, ttlMs?: number): void {
const k = this.makeKey(key)
const now = Date.now()
this.store.set(k, {
value,
createdAt: now,
expiresAt: ttlMs !== undefined ? now + ttlMs : undefined,
})
}
invalidate(key: CacheKey): boolean {
return this.store.delete(this.makeKey(key))
}
clear(): void {
this.store.clear()
}
size(): number {
// Prune expired entries before reporting size for accuracy
const now = Date.now()
for (const [k, entry] of this.store) {
if (entry.expiresAt !== undefined && now > entry.expiresAt) {
this.store.delete(k)
}
}
return this.store.size
}
}
/** Cache key factory for compilation inputs. */
export function makeCompilationCacheKey(
source: string,
version: string
): CacheKey {
// Simple hash: in production this should be a real hash function
let hash = 0
for (let i = 0; i < source.length; i++) {
const char = source.charCodeAt(i)
hash = (hash << 5) - hash + char
hash |= 0
}
return { hash: String(hash), version }
}
/** Cache key factory for extraction plans. */
export function makeExtractionCacheKey(
selector: string,
facts: string[],
version: string
): CacheKey {
const combined = `${selector}::${facts.sort().join(',')}`
let hash = 0
for (let i = 0; i < combined.length; i++) {
const char = combined.charCodeAt(i)
hash = (hash << 5) - hash + char
hash |= 0
}
return { hash: String(hash), version }
}
/**
* CompilationCache wraps a generic Cache for CompileResult-like objects.
* Accepts the underlying Cache via constructor injection.
*/
export class CompilationCache<TCompileResult> {
constructor(
private cache: Cache<TCompileResult>,
private version: string
) {}
get(source: string): TCompileResult | undefined {
return this.cache.get(makeCompilationCacheKey(source, this.version))
}
set(source: string, result: TCompileResult, ttlMs?: number): void {
this.cache.set(makeCompilationCacheKey(source, this.version), result, ttlMs)
}
invalidate(source: string): boolean {
return this.cache.invalidate(makeCompilationCacheKey(source, this.version))
}
clear(): void {
this.cache.clear()
}
}
/**
* ExtractionCache wraps a generic Cache for extraction payloads.
* Accepts the underlying Cache via constructor injection.
*/
export class ExtractionCache<TExtractResult> {
constructor(
private cache: Cache<TExtractResult>,
private version: string
) {}
get(selector: string, facts: string[]): TExtractResult | undefined {
return this.cache.get(makeExtractionCacheKey(selector, facts, this.version))
}
set(
selector: string,
facts: string[],
result: TExtractResult,
ttlMs?: number
): void {
this.cache.set(
makeExtractionCacheKey(selector, facts, this.version),
result,
ttlMs
)
}
invalidate(selector: string, facts: string[]): boolean {
return this.cache.invalidate(
makeExtractionCacheKey(selector, facts, this.version)
)
}
clear(): void {
this.cache.clear()
}
}