import { createHash } from 'node:crypto'; /** * The "Smart caching" Module (CONTEXT.md). Names the cache-related helpers * `fetchFeed` weaves together: HTTP conditional headers (layer 1) and MD5 * content-hash short-circuit (layer 2). Layer 3 (intra-payload dedupe) lives * in `article-dedup` because it concerns Article identity, not feed freshness. * * Pure: no I/O. The orchestrator owns the HTTP request and the database. */ /** Identity of the cache validators stored on a Feed row. */ export interface FeedValidators { etag: string | null; lastModified: string | null; } /** * Conditional request headers to add when re-fetching a Feed. Returns an * empty object if `force` is set or if the Feed has no validators stored. */ export function buildConditionalHeaders(feed: FeedValidators, force: boolean): Record { if (force) return {}; const headers: Record = {}; if (feed.etag) headers['If-None-Match'] = feed.etag; if (feed.lastModified) headers['If-Modified-Since'] = feed.lastModified; return headers; } /** MD5 of the raw XML payload, used as a content-change detector. */ export function contentHash(content: string): string { return createHash('md5').update(content).digest('hex'); } /** * True when the new XML hashes to the same value previously stored in the * `fetch_cache` row — i.e. the server returned 200 but the body didn't change. * Always false when there is no prior cached hash. */ export function isXmlUnchanged(xml: string, cachedHash: string | null | undefined): boolean { if (!cachedHash) return false; return contentHash(xml) === cachedHash; }