v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user