import type { PersistedResourceValue } from "../types/persistence"; import type { PluginPersistence } from "../types/plugin"; export class MemoryPluginPersistence implements PluginPersistence { private readonly state = new Map(); private readonly resources = new Map>(); getState(key: string, options?: { schemaVersion?: number }): T | null { const record = this.state.get(key); if (!record) return null; if (options?.schemaVersion != null && record.schemaVersion !== options.schemaVersion) { this.state.delete(key); return null; } return record.value as T; } setState(key: string, value: unknown, options?: { schemaVersion?: number }): void { this.state.set(key, { schemaVersion: options?.schemaVersion ?? 1, value }); } deleteState(key: string): void { this.state.delete(key); } getResource( kind: string, key: string, options?: { sourceKey?: string; schemaVersion?: number; allowExpired?: boolean }, ): PersistedResourceValue | null { const resourceKey = this.resourceKey(kind, key, options?.sourceKey); const record = this.resources.get(resourceKey); if (!record) return null; if (options?.schemaVersion != null && record.schemaVersion !== options.schemaVersion) { this.resources.delete(resourceKey); return null; } const value = this.withFreshness(record); if (!options?.allowExpired && value.expired) return null; return value as PersistedResourceValue; } setResource( kind: string, key: string, value: T, options: { cachePolicy: { staleMs: number; expireMs: number }; sourceKey?: string; schemaVersion?: number; provenance?: unknown; }, ): PersistedResourceValue { const now = Date.now(); const record: PersistedResourceValue = { value, fetchedAt: now, staleAt: now + options.cachePolicy.staleMs, expiresAt: now + options.cachePolicy.expireMs, sourceKey: options.sourceKey ?? "", schemaVersion: options.schemaVersion ?? 1, provenance: options.provenance, stale: false, expired: false, }; this.resources.set(this.resourceKey(kind, key, options.sourceKey), record); return record; } deleteResource(kind: string, key: string, options?: { sourceKey?: string }): void { this.resources.delete(this.resourceKey(kind, key, options?.sourceKey)); } seedResource( kind: string, key: string, value: T, options: { sourceKey?: string; stale?: boolean; expired?: boolean; schemaVersion?: number; provenance?: unknown; } = {}, ): void { const now = Date.now(); this.resources.set(this.resourceKey(kind, key, options.sourceKey), { value, fetchedAt: now - 60_000, staleAt: options.stale ? now - 1 : now + 60_000, expiresAt: options.expired ? now - 1 : now + 60_000, sourceKey: options.sourceKey ?? "", schemaVersion: options.schemaVersion ?? 1, provenance: options.provenance, stale: !!options.stale, expired: !!options.expired, }); } private resourceKey(kind: string, key: string, sourceKey = ""): string { return `${kind}:${key}:${sourceKey}`; } private withFreshness(record: PersistedResourceValue): PersistedResourceValue { const now = Date.now(); return { ...record, stale: now >= record.staleAt, expired: now >= record.expiresAt, }; } }