[READ-ONLY] a fast, modern browser for the npm registry
at main 110 lines 3.2 kB view raw
1/** 2 * Configuration for the stale-while-revalidate fetch cache. 3 * 4 * This cache intercepts external API calls during SSR and caches responses 5 * using Nitro's storage layer (backed by Vercel's runtime cache in production). 6 */ 7 8import { CONSTELLATION_HOST, SLINGSHOT_HOST } from './constants' 9 10/** 11 * Domains that should have their fetch responses cached. 12 * Only requests to these domains will be intercepted and cached. 13 */ 14export const FETCH_CACHE_ALLOWED_DOMAINS = [ 15 // npm registry 16 'registry.npmjs.org', // npm package metadata (packuments) 17 'api.npmjs.org', // npm download statistics 18 19 // JSR registry 20 'jsr.io', // JSR package metadata 21 22 // Git hosting providers (for repo metadata) 23 'ungh.cc', // GitHub proxy (avoids rate limits) 24 'api.github.com', // GitHub API 25 'gitlab.com', // GitLab API 26 'api.bitbucket.org', // Bitbucket API 27 'codeberg.org', // Codeberg (Gitea-based) 28 'gitee.com', // Gitee API 29 // microcosm endpoints for atproto data 30 CONSTELLATION_HOST, 31 SLINGSHOT_HOST, 32] as const 33 34/** 35 * Default TTL for cached fetch responses (in seconds). 36 * After this time, cached data is considered "stale" but will still be 37 * returned immediately while a background revalidation occurs. 38 */ 39export const FETCH_CACHE_DEFAULT_TTL = 60 * 5 // 5 minutes 40 41/** 42 * Cache key version prefix. 43 * Increment this to invalidate all cached entries (e.g., after format changes). 44 */ 45export const FETCH_CACHE_VERSION = 'v1' 46 47/** 48 * Storage key prefix for fetch cache entries. 49 */ 50export const FETCH_CACHE_STORAGE_BASE = 'fetch-cache' 51 52/** 53 * Check if a URL's host is in the allowed domains list. 54 */ 55export function isAllowedDomain(url: string | URL): boolean { 56 try { 57 const urlObj = typeof url === 'string' ? new URL(url) : url 58 return FETCH_CACHE_ALLOWED_DOMAINS.some(domain => urlObj.host === domain) 59 } catch { 60 return false 61 } 62} 63 64/** 65 * Structure of a cached fetch entry stored in Nitro storage. 66 */ 67export interface CachedFetchEntry<T = unknown> { 68 /** The response body/data */ 69 data: T 70 /** HTTP status code */ 71 status: number 72 /** Response headers (subset) */ 73 headers: Record<string, string> 74 /** Unix timestamp when the entry was cached */ 75 cachedAt: number 76 /** TTL in seconds */ 77 ttl: number 78} 79 80/** 81 * Check if a cached entry is stale (past its TTL). 82 */ 83export function isCacheEntryStale(entry: CachedFetchEntry): boolean { 84 const now = Date.now() 85 const expiresAt = entry.cachedAt + entry.ttl * 1000 86 return now > expiresAt 87} 88 89/** 90 * Result returned by cachedFetch with staleness metadata. 91 * This allows consumers to know if the data came from stale cache 92 * and potentially trigger client-side revalidation. 93 */ 94export interface CachedFetchResult<T> { 95 /** The response data */ 96 data: T 97 /** Whether the data came from stale cache (past TTL) */ 98 isStale: boolean 99 /** Unix timestamp when the data was cached, or null if fresh fetch */ 100 cachedAt: number | null 101} 102 103/** 104 * Type for the cachedFetch function attached to event context. 105 */ 106export type CachedFetchFunction = <T = unknown>( 107 url: string, 108 options?: Parameters<typeof $fetch>[1], 109 ttl?: number, 110) => Promise<CachedFetchResult<T>>