// Pure, dependency-free helpers shared by the HTTP handler (http.ts) and the // asset-resolution query (lib.ts). Kept separate so lib.ts can reuse them // without importing the HTTP router. const MIME_TYPES: Record = { ".html": "text/html; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".mjs": "application/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".webp": "image/webp", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".txt": "text/plain; charset=utf-8", ".map": "application/json", ".webmanifest": "application/manifest+json", ".xml": "application/xml", }; export function getSetupHtml(): string { return ` Convex Static Hosting

Almost there!

Your Convex backend is running, but no static files have been deployed yet.

1 Build your frontend
npm run build
2 Deploy your static files
npx @convex-dev/static-hosting deploy

Or deploy everything in one command:

npm run deploy

Learn more at github.com/get-convex/static-hosting

`; } export function getMimeType(path: string): string { const ext = path.substring(path.lastIndexOf(".")).toLowerCase(); return MIME_TYPES[ext] || "application/octet-stream"; } export function hasFileExtension(path: string): boolean { const lastSegment = path.split("/").pop() || ""; return lastSegment.includes(".") && !lastSegment.startsWith("."); } // Bundler content-hash suffix: e.g. `index-lj_vq_aF.js`, // `index-D3LP-ukt.js`, or `style-B71cUw87.css`. Requiring at least one // non-letter avoids treating ordinary names such as `privacy-policy.html` as // content-addressed assets. export function isHashedAsset(path: string): boolean { const match = path.match(/[-.]([\dA-Za-z_-]{6,32})\.[A-Za-z\d]+$/); return match !== null && /[\d_-]/.test(match[1]); } export function isHtmlContentType(contentType: string): boolean { return contentType.startsWith("text/html"); } export function cacheControlFor(path: string): string { return !path.toLowerCase().endsWith(".html") && isHashedAsset(path) ? "public, max-age=31536000, immutable" : "public, max-age=0, must-revalidate"; } /** * Decode the URL pathname before looking it up in the asset table. A malformed * escape sequence is a bad request, not a missing static asset. */ export function decodeRequestPath(pathname: string): string | null { try { return decodeURIComponent(pathname); } catch { return null; } } /** * If-None-Match uses weak comparison for GET and HEAD requests. Browsers and * proxies may send a weak validator or a comma-separated list of validators. */ export function etagMatches( ifNoneMatch: string | null, currentEtag: string, ): boolean { if (!ifNoneMatch) return false; const normalize = (value: string) => value.trim().replace(/^W\//, "").trim(); const normalizedCurrent = normalize(currentEtag); return ifNoneMatch.split(",").some((candidate) => { const normalizedCandidate = normalize(candidate); return ( normalizedCandidate === "*" || normalizedCandidate === normalizedCurrent ); }); }