import { inject, injectable } from "@codemation/core"; import type { CallerContext, CredentialMaterialProvider, CredentialMaterialRef, MaterialBundle, } from "@codemation/core"; import { ApplicationTokens } from "../applicationTokens"; import type { Logger, LoggerFactory } from "../application/logging/Logger"; type CacheEntry = Readonly<{ material: MaterialBundle; expiresAt: number; }>; type InFlightEntry = Promise; @injectable() export class CachingCredentialMaterialProvider implements CredentialMaterialProvider { private static readonly HARD_CAP_MS = 5 * 60 * 1000; private static readonly EXPIRY_SAFETY_WINDOW_MS = 60 * 1000; private readonly cache = new Map(); private readonly inFlight = new Map(); private readonly logger: Logger; constructor( @inject(ApplicationTokens.CredentialMaterialInnerProvider) private readonly inner: CredentialMaterialProvider, @inject(ApplicationTokens.LoggerFactory) loggerFactory: LoggerFactory, ) { this.logger = loggerFactory.create("codemation.credentials.material-cache"); } async getMaterial(ref: CredentialMaterialRef, context: CallerContext): Promise { const key = this.keyFor(ref); const now = Date.now(); const entry = this.cache.get(key); if (entry && entry.expiresAt > now) { this.logger.debug(`material-cache hit key=${key}`); return entry.material; } if (entry) { this.logger.debug(`material-cache expired key=${key}`); this.cache.delete(key); } else { this.logger.debug(`material-cache miss key=${key}`); } const existing = this.inFlight.get(key); if (existing) { this.logger.debug(`material-cache in-flight join key=${key}`); return existing; } const fetch = this.inner.getMaterial(ref, context).then( (material) => { this.inFlight.delete(key); const ttlExpiry = this.computeCacheExpiry(material, Date.now()); if (ttlExpiry !== null) { this.cache.set(key, { material, expiresAt: ttlExpiry }); } return material; }, (err: unknown) => { this.inFlight.delete(key); throw err; }, ); this.inFlight.set(key, fetch); return fetch; } async setMaterial(ref: CredentialMaterialRef, material: MaterialBundle): Promise { await this.inner.setMaterial(ref, material); this.cache.delete(this.keyFor(ref)); } private keyFor(ref: CredentialMaterialRef): string { return `${ref.source}::${ref.id}`; } private computeCacheExpiry(material: MaterialBundle, now: number): number | null { const hardCapExpiry = now + CachingCredentialMaterialProvider.HARD_CAP_MS; if (material.expiresAt === undefined) { return hardCapExpiry; } const parsed = Date.parse(material.expiresAt); if (Number.isNaN(parsed)) { return hardCapExpiry; } const safeExpiry = parsed - CachingCredentialMaterialProvider.EXPIRY_SAFETY_WINDOW_MS; const clamped = Math.min(safeExpiry, hardCapExpiry); if (clamped <= now) { return null; } return clamped; } }