172 lines
4.3 KiB
TypeScript
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()
|
|
}
|
|
}
|