feat(ADM-018): completed feature

This commit is contained in:
chattie
2026-08-17 22:23:10 +02:00
parent cf1c69fc8b
commit d595b4871f
871 changed files with 47411 additions and 281 deletions

View File

@@ -0,0 +1,61 @@
import type { CacheAdapter } from '../domain/ports.js';
import type { CacheContract } from '../domain/cache.js';
export class CacheService {
private readonly contracts = new Map<string, CacheContract>();
private hits = 0;
private misses = 0;
private invalidations = 0;
constructor(private readonly adapter: CacheAdapter) {}
registerContract(contract: CacheContract): void {
this.contracts.set(contract.name, contract);
}
listContracts(): CacheContract[] {
return [...this.contracts.values()];
}
async read<T>(contractName: string, key: string, loader: () => Promise<T>): Promise<T> {
const contract = this.contracts.get(contractName);
if (!contract) {
// Unknown contracts still load from source.
return loader();
}
const cached = await this.adapter.get<T>(key);
if (cached !== undefined) {
this.hits += 1;
return cached;
}
this.misses += 1;
const value = await loader();
await this.adapter.set(key, value, contract.ttlSeconds).catch(() => undefined);
return value;
}
async invalidate(key: string): Promise<void> {
this.invalidations += 1;
await this.adapter.invalidate(key);
}
async invalidateByName(name: string): Promise<number> {
const contract = this.contracts.get(name);
if (!contract) return 0;
this.invalidations += 1;
// Without an index, we invalidate the key pattern prefix; the in-memory adapter
// handles this. Real Redis adapter would use SCAN.
await this.adapter.invalidate(contract.keyPattern.replace(/[:*]/g, ''));
return 1;
}
metrics(): { hits: number; misses: number; invalidations: number; hitRatio: number } {
const total = this.hits + this.misses;
return {
hits: this.hits,
misses: this.misses,
invalidations: this.invalidations,
hitRatio: total === 0 ? 0 : this.hits / total,
};
}
}