{"version":3,"file":"cache.cjs","names":[],"sources":["../../src/sw/cache.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — precache, runtime strategies (cache-first, network-\n * first, stale-while-revalidate), expiry and the inspect/clear helpers all address\n * the same Cache Storage namespace, and the naming scheme that keeps them from\n * evicting each other is the file's whole contract.\n */\n/**\n * Service-worker caching helpers — a small, dependency-free subset of what\n * Workbox provides: precaching of the build's app shell (so the app launches\n * offline) plus runtime caching strategies for fonts, APIs and images.\n *\n * Import these inside your own `sw.ts`. They run in the service-worker global\n * scope, not the main thread. Pair `installPrecache` with the\n * `tempestPwaManifest()` Vite plugin (from `tempest-react-sdk/vite`), which\n * emits the `precache-manifest.json` this reads at install time.\n *\n * @example\n *   /// <reference lib=\"webworker\" />\n *   import { installRuntimeCache, installPrecache } from \"tempest-react-sdk/sw\";\n *\n *   // Register specific routes FIRST so they win over the precache catch-all.\n *   installRuntimeCache([\n *     { match: /\\/api\\//, strategy: \"network-first\", cacheName: \"api\", maxAgeSeconds: 300 },\n *   ]);\n *   installPrecache();\n */\n\n/** Minimal shape of the events we touch, to avoid pulling in `lib.webworker`. */\ninterface ExtendableEventLike {\n    waitUntil(promise: Promise<unknown>): void;\n}\n\ninterface FetchEventLike extends ExtendableEventLike {\n    request: Request;\n    respondWith(response: Response | Promise<Response>): void;\n    /** Resolves to the navigation-preload response when it is enabled, else `undefined`. */\n    preloadResponse?: Promise<Response | undefined>;\n}\n\n/** Minimal shape of `registration.navigationPreload` (Navigation Preload API). */\ninterface NavigationPreloadManagerLike {\n    enable(): Promise<void>;\n    disable(): Promise<void>;\n}\n\ninterface CacheSwGlobal {\n    addEventListener(type: \"install\", listener: (event: ExtendableEventLike) => void): void;\n    addEventListener(type: \"activate\", listener: (event: ExtendableEventLike) => void): void;\n    addEventListener(type: \"fetch\", listener: (event: FetchEventLike) => void): void;\n    clients: { claim(): Promise<void> };\n    skipWaiting(): Promise<void>;\n    location: { origin: string };\n    registration?: { navigationPreload?: NavigationPreloadManagerLike };\n}\n\nfunction getSwScope(): CacheSwGlobal {\n    return globalThis as unknown as CacheSwGlobal;\n}\n\n/** Caching strategy for a runtime route. Mirrors the common Workbox trio. */\nexport type RuntimeStrategy = \"cache-first\" | \"network-first\" | \"stale-while-revalidate\";\n\n/** A single runtime-caching rule, matched against each `GET` request. */\nexport interface RuntimeRoute {\n    /** A `RegExp` tested against the full URL, or a predicate over the parsed URL. */\n    match: RegExp | ((url: URL, request: Request) => boolean);\n    /** How to resolve a match. */\n    strategy: RuntimeStrategy;\n    /** Cache bucket name for this route. */\n    cacheName: string;\n    /** Trim the cache to at most this many entries (FIFO) after each write. */\n    maxEntries?: number;\n    /** Treat a cached response older than this (seconds) as a miss. */\n    maxAgeSeconds?: number;\n    /** For `network-first`: fall back to cache after this timeout (seconds). */\n    networkTimeoutSeconds?: number;\n    /**\n     * Serve HTTP `Range` requests (206 Partial Content) by slicing the cached\n     * full response. Enable for audio/video so seeking works offline. The full\n     * resource is cached once (the `Range` header is stripped before caching).\n     */\n    rangeRequests?: boolean;\n}\n\n/** Options for {@link installPrecache}. */\nexport interface InstallPrecacheOptions {\n    /** URL of the manifest emitted by `tempestPwaManifest()`. Default `/precache-manifest.json`. */\n    manifestUrl?: string;\n    /** Cache name prefix; the manifest `version` is appended. Default `tempest-precache`. */\n    cacheName?: string;\n    /** App-shell document served for navigation requests offline. Default `/index.html`. */\n    navigateFallback?: string;\n    /** Navigation paths that should NOT use the fallback (e.g. `[/^\\/api\\//]`). */\n    navigateFallbackDenylist?: RegExp[];\n    /** Activate the new worker immediately after precaching. Default `true`. */\n    skipWaiting?: boolean;\n    /**\n     * Enable the Navigation Preload API on `activate`, so the browser fetches\n     * the navigation request in parallel with the worker boot and the handler\n     * serves `event.preloadResponse` — cutting first-navigation latency after\n     * the worker starts. Silently ignored where unsupported. Default `true`.\n     */\n    navigationPreload?: boolean;\n}\n\ninterface PrecacheManifest {\n    version: string;\n    urls: string[];\n}\n\nlet precacheName = \"\";\nconst precachedPaths = new Set<string>();\n\n/** Whether a cached response has aged past `maxAgeSeconds` (best-effort, via `Date`). */\nfunction isExpired(response: Response, maxAgeSeconds?: number): boolean {\n    if (!maxAgeSeconds) return false;\n    const dateHeader = response.headers.get(\"date\");\n    if (!dateHeader) return false;\n    const age = (Date.now() - new Date(dateHeader).getTime()) / 1000;\n    return age > maxAgeSeconds;\n}\n\n/** Drop oldest entries until the cache holds at most `maxEntries`. */\nasync function trimCache(cacheName: string, maxEntries?: number): Promise<void> {\n    if (!maxEntries) return;\n    const cache = await caches.open(cacheName);\n    const keys = await cache.keys();\n    if (keys.length <= maxEntries) return;\n    for (const key of keys.slice(0, keys.length - maxEntries)) {\n        await cache.delete(key);\n    }\n}\n\nasync function cacheFirst(request: Request, route: RuntimeRoute): Promise<Response> {\n    const cache = await caches.open(route.cacheName);\n    const cached = await cache.match(request);\n    if (cached && !isExpired(cached, route.maxAgeSeconds)) return cached;\n\n    const response = await fetch(request);\n    if (response.ok) {\n        await cache.put(request, response.clone());\n        await trimCache(route.cacheName, route.maxEntries);\n    }\n    return response;\n}\n\nasync function networkFirst(request: Request, route: RuntimeRoute): Promise<Response> {\n    const cache = await caches.open(route.cacheName);\n\n    const network = (async (): Promise<Response> => {\n        const response = await fetch(request);\n        if (response.ok) {\n            await cache.put(request, response.clone());\n            await trimCache(route.cacheName, route.maxEntries);\n        }\n        return response;\n    })();\n\n    const timeoutMs = (route.networkTimeoutSeconds ?? 0) * 1000;\n    try {\n        if (timeoutMs > 0) {\n            const timeout = new Promise<never>((_, reject) =>\n                setTimeout(() => reject(new Error(\"network-timeout\")), timeoutMs),\n            );\n            return await Promise.race([network, timeout]);\n        }\n        return await network;\n    } catch {\n        const cached = await cache.match(request);\n        if (cached) return cached;\n        throw new Error(\"network-first: no network and no cache\");\n    }\n}\n\nasync function staleWhileRevalidate(request: Request, route: RuntimeRoute): Promise<Response> {\n    const cache = await caches.open(route.cacheName);\n    const cached = await cache.match(request);\n\n    const network = fetch(request)\n        .then(async (response) => {\n            if (response.ok) {\n                await cache.put(request, response.clone());\n                await trimCache(route.cacheName, route.maxEntries);\n            }\n            return response;\n        })\n        .catch(() => undefined);\n\n    if (cached && !isExpired(cached, route.maxAgeSeconds)) return cached;\n    const response = await network;\n    if (response) return response;\n    if (cached) return cached;\n    throw new Error(\"stale-while-revalidate: no network and no cache\");\n}\n\nfunction runRoute(request: Request, route: RuntimeRoute): Promise<Response> {\n    switch (route.strategy) {\n        case \"cache-first\":\n            return cacheFirst(request, route);\n        case \"network-first\":\n            return networkFirst(request, route);\n        case \"stale-while-revalidate\":\n            return staleWhileRevalidate(request, route);\n    }\n}\n\nfunction routeMatches(route: RuntimeRoute, url: URL, request: Request): boolean {\n    return typeof route.match === \"function\"\n        ? route.match(url, request)\n        : route.match.test(url.href);\n}\n\n/**\n * Build a `206 Partial Content` response from a full one for an HTTP `Range`\n * request. Supports `bytes=start-end`, open-ended `bytes=start-` and suffix\n * `bytes=-suffixLength`. Returns the original response when there is no usable\n * `Range` header, or a `416` when the range is unsatisfiable.\n *\n * @param request The incoming request (its `Range` header drives the slice).\n * @param response The full (200) response to slice.\n */\nexport async function createPartialResponse(\n    request: Request,\n    response: Response,\n): Promise<Response> {\n    const rangeHeader = request.headers.get(\"range\");\n    if (!rangeHeader) return response;\n\n    const match = /^bytes=(\\d*)-(\\d*)$/.exec(rangeHeader.trim());\n    if (!match || (match[1] === \"\" && match[2] === \"\")) {\n        return new Response(null, { status: 416, statusText: \"Range Not Satisfiable\" });\n    }\n\n    const buffer = await response.clone().arrayBuffer();\n    const total = buffer.byteLength;\n\n    let start: number;\n    let end: number;\n    if (match[1] === \"\") {\n        // Suffix range: last N bytes.\n        const suffix = Number(match[2]);\n        start = Math.max(0, total - suffix);\n        end = total - 1;\n    } else {\n        start = Number(match[1]);\n        end = match[2] === \"\" ? total - 1 : Math.min(Number(match[2]), total - 1);\n    }\n\n    if (start > end || start >= total) {\n        return new Response(null, {\n            status: 416,\n            statusText: \"Range Not Satisfiable\",\n            headers: { \"Content-Range\": `bytes */${total}` },\n        });\n    }\n\n    const slice = buffer.slice(start, end + 1);\n    const headers = new Headers(response.headers);\n    headers.set(\"Content-Range\", `bytes ${start}-${end}/${total}`);\n    headers.set(\"Content-Length\", String(slice.byteLength));\n    headers.set(\"Accept-Ranges\", \"bytes\");\n\n    return new Response(slice, {\n        status: 206,\n        statusText: \"Partial Content\",\n        headers,\n    });\n}\n\n/**\n * Install a `fetch` handler that resolves matching `GET` requests with the\n * given runtime strategies. Non-matching requests are left untouched (no\n * `respondWith`), so a later {@link installPrecache} can handle them.\n *\n * Register this BEFORE `installPrecache` so specific routes win over the\n * precache catch-all.\n *\n * @param routes Ordered rules; the first whose `match` passes handles the request.\n */\nexport function installRuntimeCache(routes: RuntimeRoute[]): void {\n    const sw = getSwScope();\n    sw.addEventListener(\"fetch\", (event) => {\n        const request = event.request;\n        if (request.method !== \"GET\") return;\n\n        const url = new URL(request.url);\n        const route = routes.find((candidate) => routeMatches(candidate, url, request));\n        if (!route) return;\n\n        if (route.rangeRequests && request.headers.has(\"range\")) {\n            // Cache/serve the FULL resource once (no Range), then slice it.\n            const full = new Request(request.url, {\n                headers: stripRange(request.headers),\n                credentials: request.credentials,\n                mode: request.mode === \"navigate\" ? \"same-origin\" : request.mode,\n            });\n            event.respondWith(\n                runRoute(full, route).then((response) => createPartialResponse(request, response)),\n            );\n            return;\n        }\n\n        event.respondWith(runRoute(request, route));\n    });\n}\n\n/** Copy headers without the `Range` header (so the cached entry is the full file). */\nfunction stripRange(headers: Headers): Headers {\n    const out = new Headers(headers);\n    out.delete(\"range\");\n    return out;\n}\n\n/**\n * Precache the app shell at `install` and serve it offline:\n *  - reads `precache-manifest.json` (emitted by `tempestPwaManifest()`),\n *  - caches every listed URL under a versioned cache,\n *  - on `activate`, deletes stale precache versions and claims open clients,\n *  - on `fetch`, serves precached assets cache-first and falls back to the\n *    `navigateFallback` document for offline navigations (SPA routing).\n *\n * Same-origin only. Register this LAST, after any {@link installRuntimeCache}.\n */\nexport function installPrecache(options: InstallPrecacheOptions = {}): void {\n    const sw = getSwScope();\n    const {\n        manifestUrl = \"/precache-manifest.json\",\n        cacheName = \"tempest-precache\",\n        navigateFallback = \"/index.html\",\n        navigateFallbackDenylist = [],\n        skipWaiting = true,\n        navigationPreload = true,\n    } = options;\n\n    sw.addEventListener(\"install\", (event) => {\n        event.waitUntil(\n            (async () => {\n                const response = await fetch(manifestUrl, { cache: \"no-cache\" });\n                const manifest = (await response.json()) as PrecacheManifest;\n                precacheName = `${cacheName}-${manifest.version}`;\n\n                const cache = await caches.open(precacheName);\n                await cache.addAll(manifest.urls);\n                for (const url of manifest.urls) {\n                    precachedPaths.add(new URL(url, sw.location.origin).pathname);\n                }\n                if (skipWaiting) await sw.skipWaiting();\n            })(),\n        );\n    });\n\n    sw.addEventListener(\"activate\", (event) => {\n        event.waitUntil(\n            (async () => {\n                const keys = await caches.keys();\n                await Promise.all(\n                    keys\n                        .filter((key) => key.startsWith(`${cacheName}-`) && key !== precacheName)\n                        .map((key) => caches.delete(key)),\n                );\n                if (navigationPreload && sw.registration?.navigationPreload) {\n                    await sw.registration.navigationPreload.enable();\n                }\n                await sw.clients.claim();\n            })(),\n        );\n    });\n\n    sw.addEventListener(\"fetch\", (event) => {\n        const request = event.request;\n        if (request.method !== \"GET\") return;\n\n        const url = new URL(request.url);\n        if (url.origin !== sw.location.origin) return;\n\n        // SPA navigations: serve the cached app shell when offline.\n        if (request.mode === \"navigate\") {\n            if (navigateFallbackDenylist.some((re) => re.test(url.pathname))) return;\n            event.respondWith(\n                (async () => {\n                    try {\n                        const preloaded = await event.preloadResponse;\n                        if (preloaded) return preloaded;\n                        return await fetch(request);\n                    } catch {\n                        const cache = await caches.open(precacheName);\n                        const shell = await cache.match(navigateFallback);\n                        if (shell) return shell;\n                        throw new Error(\"offline and no cached app shell\");\n                    }\n                })(),\n            );\n            return;\n        }\n\n        // Precached static assets: cache-first.\n        if (precachedPaths.has(url.pathname)) {\n            event.respondWith(\n                (async () => {\n                    const cache = await caches.open(precacheName);\n                    const cached = await cache.match(request);\n                    return cached ?? fetch(request);\n                })(),\n            );\n        }\n    });\n}\n"],"mappings":"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"}