import crypto from 'crypto'; import type { ReadCacheOperation } from '../SklEngineOptions'; export interface BuildReadCacheKeyInput { operation: ReadCacheOperation; args: readonly unknown[]; endpointUrl?: string; namespace?: string; keyHint?: string; } function stableStringify(value: unknown): string { if (value === null) return 'null'; const valueType = typeof value; if (valueType === 'number' || valueType === 'boolean') return String(value); if (valueType === 'string') return JSON.stringify(value); if (valueType !== 'object') { const serializedPrimitive = JSON.stringify(value); return serializedPrimitive ?? String(value); } if (Array.isArray(value)) { return `[${value.map(stableStringify).join(',')}]`; } const objectValue = value as Record; // eslint-disable-next-line @typescript-eslint/require-array-sort-compare const keys = Object.keys(objectValue).sort(); return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(objectValue[key])}`).join(',')}}`; } export function buildReadCacheKey(input: BuildReadCacheKeyInput): string { const keyParts = { namespace: input.namespace ?? '', endpointUrl: input.endpointUrl ?? '', operation: input.operation, keyHint: input.keyHint ?? '', args: input.args }; return crypto.createHash('sha256').update(stableStringify(keyParts)).digest('hex'); } export class ReadCacheSingleflight { private readonly inflight = new Map>(); public async do(key: string, fn: () => Promise): Promise { const existing = this.inflight.get(key) as Promise | undefined; if (existing) { return existing; } const promise = (async(): Promise => { try { return await fn(); } finally { this.inflight.delete(key); } })(); this.inflight.set(key, promise as Promise); return promise; } }