@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
68 lines • 2.93 kB
JavaScript
// packages/core/src/ai/diagnosis-cache.ts
/** Default cap on cached diagnoses. Bounds memory regardless of error variety. */
const DEFAULT_MAX_ENTRIES = 500;
/** Default time a cached diagnosis stays fresh: 24h. */
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
/**
* Bounded, TTL'd, per-pod cache of AI diagnoses keyed by exception `familyHash`.
* A family is diagnosed ONCE then served from here, so neither the operator
* clicking "Diagnose" repeatedly nor auto-mode re-running on every occurrence
* burns model tokens.
*
* PER-POD caveat: this is in-process memory. In a multi-replica deployment each
* pod caches independently, so the same family may be diagnosed up to once per
* pod. That's the same per-replica trade-off the `new-exception` tracker makes
* (documented), and it keeps the hot path a plain map lookup with no shared store.
*
* Eviction is insertion-order (`Map` iteration): at the cap the OLDEST inserted
* entry is dropped. TTL is checked lazily on read (an expired hit is a miss and
* is deleted), so stale entries don't linger as false "cached" results.
*/
export class DiagnosisCache {
entries = new Map();
maxEntries;
ttlMs;
now;
constructor(options) {
this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
this.ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
this.now = options?.now ?? Date.now;
}
/**
* Return the cached markdown for `familyHash`, or `null` on a miss / expiry.
* An expired entry is deleted so it can't be reported as cached.
*/
get(familyHash) {
const entry = this.entries.get(familyHash);
if (entry === undefined)
return null;
if (this.now() - entry.storedAt >= this.ttlMs) {
this.entries.delete(familyHash);
return null;
}
return entry.markdown;
}
/** Store (or refresh) the diagnosis for `familyHash`, evicting if over cap. */
set(familyHash, markdown) {
// Delete-then-set so a refreshed family moves to the END of insertion order,
// keeping genuinely-oldest entries at the front for eviction.
this.entries.delete(familyHash);
this.entries.set(familyHash, { markdown, storedAt: this.now() });
while (this.entries.size > this.maxEntries) {
const oldest = this.entries.keys().next().value;
if (oldest === undefined)
break;
this.entries.delete(oldest);
}
}
/** Whether a fresh diagnosis is cached for `familyHash` (TTL-aware). */
has(familyHash) {
return this.get(familyHash) !== null;
}
/** Number of live entries (test/observability seam; does not evict expired). */
get size() {
return this.entries.size;
}
}
export { DEFAULT_MAX_ENTRIES as DEFAULT_DIAGNOSIS_CACHE_MAX, DEFAULT_TTL_MS as DEFAULT_DIAGNOSIS_TTL_MS, };
//# sourceMappingURL=diagnosis-cache.js.map