what-isr 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +653 -0
- package/dist/index.js.map +7 -0
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +7 -0
- package/index.d.ts +151 -0
- package/package.json +47 -0
- package/src/cdn/cloudflare.js +17 -0
- package/src/cdn/fastly.js +25 -0
- package/src/cdn/index.js +7 -0
- package/src/cdn/vercel.js +22 -0
- package/src/headers.js +31 -0
- package/src/index.js +28 -0
- package/src/isr.js +125 -0
- package/src/key.js +0 -0
- package/src/paths.js +43 -0
- package/src/scheduler.js +73 -0
- package/src/stores/filesystem-store.js +127 -0
- package/src/stores/memory-store.js +85 -0
- package/src/stores/redis-store.js +62 -0
- package/src/stores/store-interface.js +60 -0
- package/src/webhook.js +55 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/key.js", "../src/stores/store-interface.js", "../src/headers.js", "../src/isr.js", "../src/stores/memory-store.js", "../src/stores/filesystem-store.js", "../src/stores/redis-store.js", "../src/paths.js", "../src/webhook.js", "../src/scheduler.js", "../src/cdn/cloudflare.js", "../src/cdn/fastly.js", "../src/cdn/vercel.js"],
|
|
4
|
+
"sourcesContent": ["// Cache key scheme. A key uniquely identifies a cacheable render:\n// <normalizedPath>\u0000<normalizedQuery>\u0000<varyString>\n// NUL separators avoid cross-field collisions. Tracking params are stripped so\n// /p?utm_source=x and /p share one cache entry.\n\nimport { createHash } from 'node:crypto';\n\nconst STRIP_EXACT = new Set(['fbclid', 'gclid', '_', 'mc_cid', 'mc_eid']);\nconst STRIP_PREFIXES = ['utm_'];\n\nfunction shouldStrip(name) {\n if (STRIP_EXACT.has(name)) return true;\n for (const p of STRIP_PREFIXES) if (name.startsWith(p)) return true;\n return false;\n}\n\n/** Normalize a path: drop query/hash, strip trailing slash (except root). */\nexport function normalizePath(path) {\n if (!path) return '/';\n let p = String(path).split('?')[0].split('#')[0];\n if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p || '/';\n}\n\nfunction queryToEntries(query) {\n if (!query) return [];\n if (typeof query === 'string') {\n const s = query.startsWith('?') ? query.slice(1) : query;\n if (!s) return [];\n return [...new URLSearchParams(s).entries()];\n }\n // plain object \u2014 values may be arrays\n const out = [];\n for (const [k, v] of Object.entries(query)) {\n if (Array.isArray(v)) for (const item of v) out.push([k, String(item)]);\n else out.push([k, String(v)]);\n }\n return out;\n}\n\n/** Normalize a query (object or string) \u2192 sorted \"k=v&k=v\", tracking params dropped. */\nexport function normalizeQuery(query) {\n const entries = queryToEntries(query)\n .filter(([k]) => !shouldStrip(k))\n .sort((a, b) => (a[0] === b[0] ? (a[1] < b[1] ? -1 : 1) : a[0] < b[0] ? -1 : 1));\n return entries.map(([k, v]) => `${k}=${v}`).join('&');\n}\n\nfunction varyString(vary) {\n if (!vary) return '';\n return Object.entries(vary)\n .sort((a, b) => (a[0] < b[0] ? -1 : 1))\n .map(([k, v]) => `${k}=${v}`)\n .join('&');\n}\n\n/** Build the canonical cache key for a request. */\nexport function cacheKey({ path, query, vary } = {}) {\n return [normalizePath(path), normalizeQuery(query), varyString(vary)].join('\u0000');\n}\n\n/** Hash a key to a sharded, filesystem-safe path: aa/bb/<hex>. */\nexport function hashKey(key) {\n const hex = createHash('sha256').update(key).digest('hex');\n return `${hex.slice(0, 2)}/${hex.slice(2, 4)}/${hex.slice(4)}`;\n}\n", "// CacheStore contract (JSDoc only \u2014 JS source, .d.ts ships types) + helpers.\n//\n// A store maps a cache key -> Entry. All methods are async so memory, filesystem\n// and Redis adapters share one interface (memory just resolves synchronously).\n//\n// @typedef {Object} Entry\n// @property {string} html Rendered body HTML.\n// @property {string} [head] Collected <head> HTML.\n// @property {*} [state] Serialized hydration state (loaderData/resources).\n// @property {string[]} [tags] Tags for group invalidation (revalidateTag).\n// @property {string} [path] Normalized path (for revalidatePath).\n// @property {number} renderedAt ms epoch when rendered.\n// @property {number} maxAge Revalidate seconds (0 = never time-stale).\n// @property {number} expiresAt renderedAt + maxAge*1000 (precomputed).\n// @property {number} swrWindow Grace seconds an expired entry is still served.\n// @property {number} [status] HTTP status (for 404 stubs / fallback skeletons).\n// @property {boolean} [partial] True for fallback skeletons (never durable).\n//\n// @typedef {Object} CacheStore\n// @property {(key:string)=>Promise<Entry|null>} get\n// @property {(key:string, entry:Entry)=>Promise<void>} set\n// @property {(key:string)=>Promise<boolean>} delete\n// @property {(tag:string)=>Promise<string[]>} deleteByTag returns deleted keys\n// @property {(path:string)=>Promise<string[]>} deleteByPath returns deleted keys\n// @property {()=>Promise<void>} clear\n// @property {()=>Promise<string[]>} keys\n\n/**\n * Fill an Entry's time fields from `now` + a route config. Used by the ISR\n * engine so every store receives consistent expiry metadata.\n */\nexport function makeEntry(out, config = {}, now = Date.now()) {\n const maxAge = Number(config.revalidate) || 0;\n const swrWindow = config.swr != null ? Number(config.swr) : maxAge;\n return {\n html: out.html || '',\n head: out.head || '',\n state: out.state ?? null,\n tags: out.tags || config.tags || [],\n path: out.path || config.path,\n status: out.status || 200,\n partial: !!out.partial,\n renderedAt: now,\n maxAge,\n swrWindow,\n expiresAt: maxAge > 0 ? now + maxAge * 1000 : Infinity,\n };\n}\n\n/** Freshness check against a clock. */\nexport function isFresh(entry, now = Date.now()) {\n return entry.expiresAt === Infinity || now < entry.expiresAt;\n}\n\n/** Within the stale-while-revalidate grace window (servable while regenerating). */\nexport function isServableStale(entry, now = Date.now()) {\n if (isFresh(entry, now)) return true;\n if (entry.swrWindow == null) return false;\n return now < entry.expiresAt + entry.swrWindow * 1000;\n}\n", "// Cache-Control header builder. Origin ISR works without these, but emitting\n// them lets ANY standards-compliant CDN do edge ISR for free (s-maxage +\n// stale-while-revalidate), and Cache-Tag/Surrogate-Key enable CDN tag purge.\n\n/**\n * @param {object} entry { maxAge, swrWindow, tags?, partial? }\n * @param {object} config route page config { mode, ... }\n * @param {string} cacheStatus HIT | STALE | MISS | BYPASS\n */\nexport function buildCacheHeaders(entry = {}, config = {}, cacheStatus = 'MISS') {\n const headers = { 'X-What-Cache': cacheStatus };\n\n const cacheable = (entry.maxAge > 0 || config.mode === 'static' || config.mode === 'hybrid')\n && config.mode !== 'server';\n\n if (!cacheable) {\n headers['Cache-Control'] = 'private, no-store';\n return headers;\n }\n\n const sMaxAge = entry.partial ? 0 : (entry.maxAge || 0);\n const swr = entry.swrWindow != null ? entry.swrWindow : sMaxAge;\n headers['Cache-Control'] = `public, s-maxage=${sMaxAge}, stale-while-revalidate=${swr}`;\n\n if (entry.tags && entry.tags.length) {\n headers['Cache-Tag'] = entry.tags.join(','); // Cloudflare / Fastly\n headers['Surrogate-Key'] = entry.tags.join(' '); // Fastly surrogate keys\n }\n\n return headers;\n}\n", "// ISR engine \u2014 origin-first Incremental Static Regeneration.\n//\n// Stale-while-revalidate at the ORIGIN: a fresh entry is served from cache; a\n// stale entry (past `revalidate`, within `swr`) is served IMMEDIATELY while a\n// single background regeneration refreshes it; a cold/expired entry blocks and\n// renders. Concurrent regenerations of the same key are deduped to ONE render.\n//\n// `render(routeMatch, ctx)` is INJECTED by the adapter (wraps renderPage +\n// serializeState) \u2014 the engine never imports what-server, keeping it standalone.\n\nimport { cacheKey, normalizePath } from './key.js';\nimport { makeEntry, isFresh, isServableStale } from './stores/store-interface.js';\nimport { buildCacheHeaders } from './headers.js';\n\nexport function createCacheEngine({ store, render, cdn, now = Date.now, logger = console } = {}) {\n const inFlight = new Map(); // key -> Promise<entry> (dedupe)\n\n function keyFor(routeMatch) {\n return cacheKey({ path: routeMatch.path, query: routeMatch.query, vary: routeMatch.vary });\n }\n\n // Render + store, deduping concurrent calls for the same key. `renderOverride`\n // lets a caller (e.g. the deploy adapter) supply the render for this route\n // without baking it into the engine \u2014 keeps the engine decoupled.\n function regenerate(key, routeMatch, renderOverride) {\n const existing = inFlight.get(key);\n if (existing) return existing;\n const doRender = renderOverride || render;\n const p = (async () => {\n const out = await doRender(routeMatch, {});\n const entry = makeEntry({ ...out, path: routeMatch.path }, routeMatch.config || {}, now());\n await store.set(key, entry);\n return entry;\n })().finally(() => inFlight.delete(key));\n inFlight.set(key, p);\n return p;\n }\n\n function serve(entry, cacheStatus, config) {\n return {\n html: entry.html,\n head: entry.head,\n state: entry.state,\n status: entry.status || 200,\n cacheStatus,\n headers: buildCacheHeaders(entry, config || {}, cacheStatus),\n };\n }\n\n async function handle(routeMatch, renderOverride) {\n const config = routeMatch.config || {};\n\n // Uncacheable (server-rendered) routes: always render, never store.\n if (config.mode === 'server') {\n const out = await (renderOverride || render)(routeMatch, {});\n const entry = makeEntry({ ...out, path: routeMatch.path }, config, now());\n return serve(entry, 'BYPASS', config);\n }\n\n const key = keyFor(routeMatch);\n const entry = await store.get(key);\n const t = now();\n\n if (entry && isFresh(entry, t)) {\n return serve(entry, 'HIT', config);\n }\n\n if (entry && isServableStale(entry, t)) {\n // Serve stale immediately; refresh in the background (deduped, non-blocking).\n regenerate(key, routeMatch, renderOverride).catch((e) => logger.error?.('[what-isr] background regenerate failed:', e));\n return serve(entry, 'STALE', config);\n }\n\n // Cold miss or expired beyond the swr window.\n if (entry && config.onMiss === 'stale-if-error') {\n try {\n const fresh = await regenerate(key, routeMatch, renderOverride);\n return serve(fresh, 'MISS', config);\n } catch (e) {\n logger.error?.('[what-isr] regenerate failed, serving stale:', e);\n return serve(entry, 'STALE', config);\n }\n }\n\n const fresh = await regenerate(key, routeMatch, renderOverride);\n return serve(fresh, 'MISS', config);\n }\n\n // --- On-demand invalidation (origin purge + optional CDN fan-out) ---\n\n async function revalidatePath(path, { regenerate: regen = false, routeResolver } = {}) {\n const norm = normalizePath(path);\n const deleted = await store.deleteByPath(norm);\n if (cdn && cdn.purge) await cdn.purge([path]);\n if (regen) {\n const route = routeResolver ? routeResolver(norm) : { path: norm, query: {}, config: {} };\n await regenerate(keyFor(route), route).catch((e) => logger.error?.('[what-isr] regen after revalidatePath failed:', e));\n }\n return deleted;\n }\n\n async function revalidateTag(tag, { regenerate: regen = false, routeResolver } = {}) {\n const deleted = await store.deleteByTag(tag);\n if (cdn && cdn.purgeTags) await cdn.purgeTags([tag]);\n if (regen && routeResolver) {\n for (const key of deleted) {\n const route = routeResolver(key);\n if (route) await regenerate(keyFor(route), route).catch(() => {});\n }\n }\n return deleted;\n }\n\n return {\n handle,\n regenerate: (routeMatch) => regenerate(keyFor(routeMatch), routeMatch),\n revalidatePath,\n revalidateTag,\n keyFor,\n store,\n _inFlight: inFlight,\n _now: now,\n _cdn: cdn,\n };\n}\n", "// In-memory cache store \u2014 the zero-config default. LRU eviction + reverse\n// indexes (tag -> keys, path -> keys) for O(1) group invalidation.\n//\n// Insertion order of a Map IS the LRU order: get() re-inserts (moves to newest),\n// eviction removes from the front (oldest).\n\nexport function createMemoryStore({ maxEntries = 1000 } = {}) {\n const map = new Map(); // key -> entry\n const tagIndex = new Map(); // tag -> Set<key>\n const pathIndex = new Map(); // path -> Set<key>\n\n function addToIndex(index, name, key) {\n if (name == null) return;\n let set = index.get(name);\n if (!set) index.set(name, (set = new Set()));\n set.add(key);\n }\n function removeFromIndex(index, name, key) {\n if (name == null) return;\n const set = index.get(name);\n if (set) {\n set.delete(key);\n if (set.size === 0) index.delete(name);\n }\n }\n function indexEntry(key, entry) {\n if (entry.tags) for (const t of entry.tags) addToIndex(tagIndex, t, key);\n addToIndex(pathIndex, entry.path, key);\n }\n function deindexEntry(key, entry) {\n if (entry.tags) for (const t of entry.tags) removeFromIndex(tagIndex, t, key);\n removeFromIndex(pathIndex, entry.path, key);\n }\n function removeKey(key) {\n const e = map.get(key);\n if (!e) return false;\n map.delete(key);\n deindexEntry(key, e);\n return true;\n }\n function deleteByIndex(index, name) {\n const set = index.get(name);\n if (!set) return [];\n const deleted = [...set];\n for (const k of deleted) removeKey(k);\n return deleted;\n }\n\n return {\n async get(key) {\n const e = map.get(key);\n if (!e) return null;\n // LRU touch: move to newest.\n map.delete(key);\n map.set(key, e);\n return e;\n },\n async set(key, entry) {\n if (map.has(key)) deindexEntry(key, map.get(key));\n map.set(key, entry);\n indexEntry(key, entry);\n while (map.size > maxEntries) {\n const oldest = map.keys().next().value;\n removeKey(oldest);\n }\n },\n async delete(key) {\n return removeKey(key);\n },\n async deleteByTag(tag) {\n return deleteByIndex(tagIndex, tag);\n },\n async deleteByPath(path) {\n return deleteByIndex(pathIndex, path);\n },\n async clear() {\n map.clear();\n tagIndex.clear();\n pathIndex.clear();\n },\n async keys() {\n return [...map.keys()];\n },\n };\n}\n", "// Filesystem cache store \u2014 survives restarts and is shareable by multiple\n// worker processes on one box. Entries are sharded JSON files; writes are atomic\n// (write to .tmp then rename). Tag/path reverse indexes are sidecar JSON lists.\n\nimport { mkdir, writeFile, readFile, rename, rm, readdir, stat } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { hashKey } from '../key.js';\n\nfunction safeName(s) {\n return createHash('sha256').update(String(s)).digest('hex');\n}\n\nexport function createFilesystemStore({ dir }) {\n const entriesDir = join(dir, 'entries');\n const tagsDir = join(dir, 'tags');\n const pathsDir = join(dir, 'paths');\n\n const entryFile = (key) => join(entriesDir, hashKey(key) + '.json');\n const indexFile = (base, name) => join(base, safeName(name) + '.json');\n\n async function atomicWrite(file, contents) {\n await mkdir(dirname(file), { recursive: true });\n const tmp = `${file}.${process.pid}.${safeName(file).slice(0, 8)}.tmp`;\n await writeFile(tmp, contents);\n await rename(tmp, file);\n }\n\n async function readJson(file) {\n try {\n return JSON.parse(await readFile(file, 'utf8'));\n } catch {\n return null;\n }\n }\n\n async function addToIndex(base, name, key) {\n const file = indexFile(base, name);\n const list = (await readJson(file)) || [];\n if (!list.includes(key)) {\n list.push(key);\n await atomicWrite(file, JSON.stringify(list));\n }\n }\n\n async function removeFromIndex(base, name, key) {\n const file = indexFile(base, name);\n const list = await readJson(file);\n if (!list) return;\n const next = list.filter((k) => k !== key);\n if (next.length) await atomicWrite(file, JSON.stringify(next));\n else await rm(file, { force: true });\n }\n\n async function deleteByIndex(base, name) {\n const file = indexFile(base, name);\n const keys = await readJson(file);\n if (!keys) return [];\n for (const k of keys) await removeKey(k);\n await rm(file, { force: true });\n return keys;\n }\n\n async function removeKey(key) {\n const record = await readJson(entryFile(key));\n if (!record) return false;\n await rm(entryFile(key), { force: true });\n const entry = record.entry || {};\n for (const t of entry.tags || []) await removeFromIndex(tagsDir, t, key);\n if (entry.path) await removeFromIndex(pathsDir, entry.path, key);\n return true;\n }\n\n async function walkKeys() {\n const out = [];\n let shards;\n try { shards = await readdir(entriesDir); } catch { return out; }\n for (const a of shards) {\n const aDir = join(entriesDir, a);\n let st; try { st = await stat(aDir); } catch { continue; }\n if (!st.isDirectory()) continue;\n for (const b of await readdir(aDir)) {\n const bDir = join(aDir, b);\n try { if (!(await stat(bDir)).isDirectory()) continue; } catch { continue; }\n for (const f of await readdir(bDir)) {\n if (!f.endsWith('.json')) continue;\n const rec = await readJson(join(bDir, f));\n if (rec && rec.key != null) out.push(rec.key);\n }\n }\n }\n return out;\n }\n\n return {\n async get(key) {\n const rec = await readJson(entryFile(key));\n return rec ? rec.entry : null;\n },\n async set(key, entry) {\n // de-index any previous version's tags/path first\n const prev = await readJson(entryFile(key));\n if (prev && prev.entry) {\n for (const t of prev.entry.tags || []) await removeFromIndex(tagsDir, t, key);\n if (prev.entry.path) await removeFromIndex(pathsDir, prev.entry.path, key);\n }\n await atomicWrite(entryFile(key), JSON.stringify({ key, entry }));\n for (const t of entry.tags || []) await addToIndex(tagsDir, t, key);\n if (entry.path) await addToIndex(pathsDir, entry.path, key);\n },\n async delete(key) {\n return removeKey(key);\n },\n async deleteByTag(tag) {\n return deleteByIndex(tagsDir, tag);\n },\n async deleteByPath(path) {\n return deleteByIndex(pathsDir, path);\n },\n async clear() {\n await rm(dir, { recursive: true, force: true });\n },\n async keys() {\n return walkKeys();\n },\n };\n}\n", "// Redis/KV cache store \u2014 the multi-instance story (N app servers share one\n// cache). Takes an INJECTED client (ioredis / node-redis shaped:\n// get/set/del/sadd/srem/smembers, optional keys) so this package keeps zero deps.\n\nexport function createRedisStore({ client, namespace = 'what' } = {}) {\n if (!client) throw new Error('[what-isr] createRedisStore requires { client }');\n\n const ck = (key) => `${namespace}:cache:${key}`;\n const tk = (tag) => `${namespace}:tag:${tag}`;\n const pk = (path) => `${namespace}:path:${path}`;\n\n async function deindex(key, entry) {\n if (!entry) return;\n for (const t of entry.tags || []) await client.srem(tk(t), key);\n if (entry.path) await client.srem(pk(entry.path), key);\n }\n\n async function deleteBySet(setKey) {\n const keys = (await client.smembers(setKey)) || [];\n for (const k of keys) await client.del(ck(k));\n await client.del(setKey);\n return keys;\n }\n\n return {\n async get(key) {\n const v = await client.get(ck(key));\n return v ? JSON.parse(v) : null;\n },\n async set(key, entry) {\n const prev = await this.get(key);\n if (prev) await deindex(key, prev);\n await client.set(ck(key), JSON.stringify(entry));\n for (const t of entry.tags || []) await client.sadd(tk(t), key);\n if (entry.path) await client.sadd(pk(entry.path), key);\n },\n async delete(key) {\n const entry = await this.get(key);\n await client.del(ck(key));\n await deindex(key, entry);\n return !!entry;\n },\n async deleteByTag(tag) {\n return deleteBySet(tk(tag));\n },\n async deleteByPath(path) {\n return deleteBySet(pk(path));\n },\n async clear() {\n if (typeof client.keys === 'function') {\n const all = await client.keys(`${namespace}:*`);\n for (const k of all) await client.del(k);\n }\n },\n async keys() {\n if (typeof client.keys !== 'function') return [];\n const prefix = `${namespace}:cache:`;\n const all = await client.keys(`${prefix}*`);\n return all.map((k) => k.slice(prefix.length));\n },\n };\n}\n", "// getStaticPaths resolution + fallback decisions for dynamic routes.\n//\n// Known paths (in getStaticPaths) are pre-rendered at build. Unknown params at\n// request time follow the route's `fallback`:\n// 'blocking' -> render on first hit, then cache (ISR)\n// true -> serve a skeleton immediately, regenerate in the background\n// false -> 404\n\n/** Run a page's getStaticPaths (if any). Returns { paths, fallback }. */\nexport async function resolveStaticPaths(getStaticPaths, ctx = {}) {\n if (typeof getStaticPaths !== 'function') return { paths: [], fallback: false };\n const result = await getStaticPaths(ctx);\n return {\n paths: (result && result.paths) || [],\n fallback: result && 'fallback' in result ? result.fallback : false,\n };\n}\n\n/** Build a concrete URL from a route pattern + params. Supports :param and *catchall. */\nexport function buildPath(pattern, params = {}) {\n return pattern.replace(/[:*]([A-Za-z0-9_]+)/g, (_, name) => {\n const v = params[name];\n return v == null ? '' : String(v);\n });\n}\n\n/** Is this param set among the pre-built static paths? */\nexport function isKnownParams(staticPaths, params) {\n return staticPaths.some((entry) => {\n const p = entry.params || {};\n const keys = new Set([...Object.keys(p), ...Object.keys(params)]);\n for (const k of keys) if (String(p[k]) !== String(params[k])) return false;\n return true;\n });\n}\n\n/** Decide what to do for a requested dynamic path. */\nexport function decideFallback(fallback, isKnown) {\n if (isKnown) return 'serve';\n if (fallback === 'blocking') return 'render';\n if (fallback === true) return 'skeleton';\n return 'notfound';\n}\n", "// On-demand revalidation webhook \u2014 the CMS-trigger path. A POST with a shared\n// secret purges paths/tags so a Sanity/Contentful/WP \"published\" event can warm\n// or drop cache entries. The adapter mounts this at e.g. /__what_revalidate.\n\n// Constant-time string compare (timing-attack safe). Self-contained so the cache\n// package stays dependency-free and decoupled from what-server.\nfunction safeEqual(a, b) {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return result === 0;\n}\n\n/**\n * @param engine cache engine (revalidatePath / revalidateTag)\n * @param options { secret, header='x-what-revalidate-secret', regenerate=false }\n * @returns async (reqLike:{headers, body:{paths?,tags?,regenerate?}}) -> { status, body }\n */\nexport function createRevalidateWebhook(engine, options = {}) {\n const { secret, header = 'x-what-revalidate-secret', regenerate = false } = options;\n\n return async function handle(reqLike) {\n const provided = (reqLike.headers || {})[header] || (reqLike.headers || {})[header.toLowerCase()];\n if (!secret || !safeEqual(provided || '', secret)) {\n return { status: 401, body: { message: 'Unauthorized' } };\n }\n\n const body = reqLike.body;\n if (!body || typeof body !== 'object') {\n return { status: 400, body: { message: 'Invalid body' } };\n }\n\n const { paths, tags, regenerate: regen = regenerate } = body;\n if (!Array.isArray(paths) && !Array.isArray(tags)) {\n return { status: 400, body: { message: 'Provide `paths` and/or `tags` arrays' } };\n }\n\n const revalidated = { paths: [], tags: [] };\n if (Array.isArray(paths)) {\n for (const p of paths) {\n await engine.revalidatePath(p, { regenerate: regen });\n revalidated.paths.push(p);\n }\n }\n if (Array.isArray(tags)) {\n for (const t of tags) {\n await engine.revalidateTag(t, { regenerate: regen });\n revalidated.tags.push(t);\n }\n }\n\n return { status: 200, body: { revalidated: true, ...revalidated } };\n };\n}\n", "// Poll/scheduled regeneration \u2014 keeps the cache warm regardless of traffic.\n// Runs in the long-lived Node adapter process. Self-rescheduling setTimeout\n// (no drift/overlap), jitter (anti-thundering-herd after a restart), a global\n// concurrency cap, and it joins the engine's in-flight lock so a scheduled tick\n// during a traffic-triggered regeneration is a no-op.\n\nexport function createScheduler(engine, options = {}) {\n const {\n maxConcurrent = 4,\n random = Math.random,\n setTimer = setTimeout,\n clearTimer = clearTimeout,\n logger = console,\n } = options;\n\n const tasks = []; // { route, intervalMs, timer }\n const queue = [];\n let running = false;\n let active = 0;\n\n function jittered(intervalMs) {\n return Math.round(intervalMs * (1 + random() * 0.1));\n }\n\n function schedule(task) {\n task.timer = setTimer(() => fire(task), jittered(task.intervalMs));\n if (task.timer && typeof task.timer.unref === 'function') task.timer.unref();\n }\n\n function fire(task) {\n if (!running) return;\n if (active < maxConcurrent) runTask(task);\n else queue.push(task);\n }\n\n async function runTask(task) {\n active++;\n try {\n await engine.regenerate(task.route);\n } catch (e) {\n logger.error?.('[what-isr] scheduled regenerate failed:', e);\n } finally {\n active--;\n if (running) schedule(task);\n drain();\n }\n }\n\n function drain() {\n while (running && active < maxConcurrent && queue.length) {\n runTask(queue.shift());\n }\n }\n\n return {\n register(route, { intervalMs }) {\n tasks.push({ route, intervalMs, timer: null });\n return this;\n },\n start() {\n running = true;\n for (const t of tasks) schedule(t);\n return this;\n },\n stop() {\n running = false;\n for (const t of tasks) if (t.timer != null) clearTimer(t.timer);\n queue.length = 0;\n return this;\n },\n _tasks: tasks,\n };\n}\n", "// Cloudflare CDN adapter \u2014 edge purge by URL or cache-tag (Enterprise tag purge).\n// Uses global fetch (Node 18+); credentials injected by the deploy adapter.\n\nexport function createCloudflareCDN({ zoneId, apiToken } = {}) {\n const endpoint = `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`;\n const headers = { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json' };\n\n async function post(body) {\n if (!zoneId || !apiToken) return;\n await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(body) });\n }\n\n return {\n purge: (urls) => post({ files: urls }),\n purgeTags: (tags) => post({ tags }),\n };\n}\n", "// Fastly CDN adapter \u2014 surrogate-key purge (tags) and URL purge.\n\nexport function createFastlyCDN({ serviceId, apiToken } = {}) {\n const headers = { 'Fastly-Key': apiToken, Accept: 'application/json' };\n\n async function purgeKey(key) {\n if (!serviceId || !apiToken) return;\n await fetch(`https://api.fastly.com/service/${serviceId}/purge/${encodeURIComponent(key)}`, {\n method: 'POST',\n headers,\n });\n }\n\n return {\n async purge(urls) {\n for (const url of urls) {\n if (!apiToken) return;\n await fetch(url, { method: 'PURGE', headers });\n }\n },\n async purgeTags(tags) {\n for (const t of tags) await purgeKey(t);\n },\n };\n}\n", "// Vercel CDN adapter. Vercel ISR is mostly header-driven (s-maxage), so this is\n// a thin on-demand purge via the project API when a token is supplied; it\n// no-ops (header-only mode) without one.\n\nexport function createVercelCDN({ token, projectId, teamId } = {}) {\n const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };\n const qs = teamId ? `?teamId=${teamId}` : '';\n\n async function purgeTags(tags) {\n if (!token || !projectId) return;\n await fetch(`https://api.vercel.com/v1/projects/${projectId}/cache/purge${qs}`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tags }),\n });\n }\n\n return {\n purge: () => Promise.resolve(), // Vercel purges by tag; URL purge is header-driven\n purgeTags,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["entry", "fresh", "createHash", "createHash"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createHash as O}from"node:crypto";var z=new Set(["fbclid","gclid","_","mc_cid","mc_eid"]),R=["utm_"];function J(t){if(z.has(t))return!0;for(let e of R)if(t.startsWith(e))return!0;return!1}function b(t){if(!t)return"/";let e=String(t).split("?")[0].split("#")[0];return e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e||"/"}function H(t){if(!t)return[];if(typeof t=="string"){let a=t.startsWith("?")?t.slice(1):t;return a?[...new URLSearchParams(a).entries()]:[]}let e=[];for(let[a,n]of Object.entries(t))if(Array.isArray(n))for(let l of n)e.push([a,String(l)]);else e.push([a,String(n)]);return e}function E(t){return H(t).filter(([a])=>!J(a)).sort((a,n)=>a[0]===n[0]?a[1]<n[1]?-1:1:a[0]<n[0]?-1:1).map(([a,n])=>`${a}=${n}`).join("&")}function U(t){return t?Object.entries(t).sort((e,a)=>e[0]<a[0]?-1:1).map(([e,a])=>`${e}=${a}`).join("&"):""}function P({path:t,query:e,vary:a}={}){return[b(t),E(e),U(a)].join("\0")}function F(t){let e=O("sha256").update(t).digest("hex");return`${e.slice(0,2)}/${e.slice(2,4)}/${e.slice(4)}`}function C(t,e={},a=Date.now()){let n=Number(e.revalidate)||0,l=e.swr!=null?Number(e.swr):n;return{html:t.html||"",head:t.head||"",state:t.state??null,tags:t.tags||e.tags||[],path:t.path||e.path,status:t.status||200,partial:!!t.partial,renderedAt:a,maxAge:n,swrWindow:l,expiresAt:n>0?a+n*1e3:1/0}}function A(t,e=Date.now()){return t.expiresAt===1/0||e<t.expiresAt}function D(t,e=Date.now()){return A(t,e)?!0:t.swrWindow==null?!1:e<t.expiresAt+t.swrWindow*1e3}function N(t={},e={},a="MISS"){let n={"X-What-Cache":a};if(!((t.maxAge>0||e.mode==="static"||e.mode==="hybrid")&&e.mode!=="server"))return n["Cache-Control"]="private, no-store",n;let p=t.partial?0:t.maxAge||0,m=t.swrWindow!=null?t.swrWindow:p;return n["Cache-Control"]=`public, s-maxage=${p}, stale-while-revalidate=${m}`,t.tags&&t.tags.length&&(n["Cache-Tag"]=t.tags.join(","),n["Surrogate-Key"]=t.tags.join(" ")),n}function k({store:t,render:e,cdn:a,now:n=Date.now,logger:l=console}={}){let p=new Map;function m(i){return P({path:i.path,query:i.query,vary:i.vary})}function s(i,r,f){let h=p.get(i);if(h)return h;let o=f||e,w=(async()=>{let x=await o(r,{}),y=C({...x,path:r.path},r.config||{},n());return await t.set(i,y),y})().finally(()=>p.delete(i));return p.set(i,w),w}function u(i,r,f){return{html:i.html,head:i.head,state:i.state,status:i.status||200,cacheStatus:r,headers:N(i,f||{},r)}}async function g(i,r){let f=i.config||{};if(f.mode==="server"){let y=await(r||e)(i,{}),v=C({...y,path:i.path},f,n());return u(v,"BYPASS",f)}let h=m(i),o=await t.get(h),w=n();if(o&&A(o,w))return u(o,"HIT",f);if(o&&D(o,w))return s(h,i,r).catch(y=>l.error?.("[what-isr] background regenerate failed:",y)),u(o,"STALE",f);if(o&&f.onMiss==="stale-if-error")try{let y=await s(h,i,r);return u(y,"MISS",f)}catch(y){return l.error?.("[what-isr] regenerate failed, serving stale:",y),u(o,"STALE",f)}let x=await s(h,i,r);return u(x,"MISS",f)}async function c(i,{regenerate:r=!1,routeResolver:f}={}){let h=b(i),o=await t.deleteByPath(h);if(a&&a.purge&&await a.purge([i]),r){let w=f?f(h):{path:h,query:{},config:{}};await s(m(w),w).catch(x=>l.error?.("[what-isr] regen after revalidatePath failed:",x))}return o}async function d(i,{regenerate:r=!1,routeResolver:f}={}){let h=await t.deleteByTag(i);if(a&&a.purgeTags&&await a.purgeTags([i]),r&&f)for(let o of h){let w=f(o);w&&await s(m(w),w).catch(()=>{})}return h}return{handle:g,regenerate:i=>s(m(i),i),revalidatePath:c,revalidateTag:d,keyFor:m,store:t,_inFlight:p,_now:n,_cdn:a}}function M({maxEntries:t=1e3}={}){let e=new Map,a=new Map,n=new Map;function l(c,d,i){if(d==null)return;let r=c.get(d);r||c.set(d,r=new Set),r.add(i)}function p(c,d,i){if(d==null)return;let r=c.get(d);r&&(r.delete(i),r.size===0&&c.delete(d))}function m(c,d){if(d.tags)for(let i of d.tags)l(a,i,c);l(n,d.path,c)}function s(c,d){if(d.tags)for(let i of d.tags)p(a,i,c);p(n,d.path,c)}function u(c){let d=e.get(c);return d?(e.delete(c),s(c,d),!0):!1}function g(c,d){let i=c.get(d);if(!i)return[];let r=[...i];for(let f of r)u(f);return r}return{async get(c){let d=e.get(c);return d?(e.delete(c),e.set(c,d),d):null},async set(c,d){for(e.has(c)&&s(c,e.get(c)),e.set(c,d),m(c,d);e.size>t;){let i=e.keys().next().value;u(i)}},async delete(c){return u(c)},async deleteByTag(c){return g(a,c)},async deleteByPath(c){return g(n,c)},async clear(){e.clear(),a.clear(),n.clear()},async keys(){return[...e.keys()]}}}import{mkdir as V,writeFile as X,readFile as L,rename as Q,rm as $,readdir as j,stat as I}from"node:fs/promises";import{join as S,dirname as q}from"node:path";import{createHash as G}from"node:crypto";function B(t){return G("sha256").update(String(t)).digest("hex")}function Y({dir:t}){let e=S(t,"entries"),a=S(t,"tags"),n=S(t,"paths"),l=r=>S(e,F(r)+".json"),p=(r,f)=>S(r,B(f)+".json");async function m(r,f){await V(q(r),{recursive:!0});let h=`${r}.${process.pid}.${B(r).slice(0,8)}.tmp`;await X(h,f),await Q(h,r)}async function s(r){try{return JSON.parse(await L(r,"utf8"))}catch{return null}}async function u(r,f,h){let o=p(r,f),w=await s(o)||[];w.includes(h)||(w.push(h),await m(o,JSON.stringify(w)))}async function g(r,f,h){let o=p(r,f),w=await s(o);if(!w)return;let x=w.filter(y=>y!==h);x.length?await m(o,JSON.stringify(x)):await $(o,{force:!0})}async function c(r,f){let h=p(r,f),o=await s(h);if(!o)return[];for(let w of o)await d(w);return await $(h,{force:!0}),o}async function d(r){let f=await s(l(r));if(!f)return!1;await $(l(r),{force:!0});let h=f.entry||{};for(let o of h.tags||[])await g(a,o,r);return h.path&&await g(n,h.path,r),!0}async function i(){let r=[],f;try{f=await j(e)}catch{return r}for(let h of f){let o=S(e,h),w;try{w=await I(o)}catch{continue}if(w.isDirectory())for(let x of await j(o)){let y=S(o,x);try{if(!(await I(y)).isDirectory())continue}catch{continue}for(let v of await j(y)){if(!v.endsWith(".json"))continue;let T=await s(S(y,v));T&&T.key!=null&&r.push(T.key)}}}return r}return{async get(r){let f=await s(l(r));return f?f.entry:null},async set(r,f){let h=await s(l(r));if(h&&h.entry){for(let o of h.entry.tags||[])await g(a,o,r);h.entry.path&&await g(n,h.entry.path,r)}await m(l(r),JSON.stringify({key:r,entry:f}));for(let o of f.tags||[])await u(a,o,r);f.path&&await u(n,f.path,r)},async delete(r){return d(r)},async deleteByTag(r){return c(a,r)},async deleteByPath(r){return c(n,r)},async clear(){await $(t,{recursive:!0,force:!0})},async keys(){return i()}}}function Z({client:t,namespace:e="what"}={}){if(!t)throw new Error("[what-isr] createRedisStore requires { client }");let a=s=>`${e}:cache:${s}`,n=s=>`${e}:tag:${s}`,l=s=>`${e}:path:${s}`;async function p(s,u){if(u){for(let g of u.tags||[])await t.srem(n(g),s);u.path&&await t.srem(l(u.path),s)}}async function m(s){let u=await t.smembers(s)||[];for(let g of u)await t.del(a(g));return await t.del(s),u}return{async get(s){let u=await t.get(a(s));return u?JSON.parse(u):null},async set(s,u){let g=await this.get(s);g&&await p(s,g),await t.set(a(s),JSON.stringify(u));for(let c of u.tags||[])await t.sadd(n(c),s);u.path&&await t.sadd(l(u.path),s)},async delete(s){let u=await this.get(s);return await t.del(a(s)),await p(s,u),!!u},async deleteByTag(s){return m(n(s))},async deleteByPath(s){return m(l(s))},async clear(){if(typeof t.keys=="function"){let s=await t.keys(`${e}:*`);for(let u of s)await t.del(u)}},async keys(){if(typeof t.keys!="function")return[];let s=`${e}:cache:`;return(await t.keys(`${s}*`)).map(g=>g.slice(s.length))}}}async function tt(t,e={}){if(typeof t!="function")return{paths:[],fallback:!1};let a=await t(e);return{paths:a&&a.paths||[],fallback:a&&"fallback"in a?a.fallback:!1}}function et(t,e={}){return t.replace(/[:*]([A-Za-z0-9_]+)/g,(a,n)=>{let l=e[n];return l==null?"":String(l)})}function rt(t,e){return t.some(a=>{let n=a.params||{},l=new Set([...Object.keys(n),...Object.keys(e)]);for(let p of l)if(String(n[p])!==String(e[p]))return!1;return!0})}function at(t,e){return e?"serve":t==="blocking"?"render":t===!0?"skeleton":"notfound"}function nt(t,e){if(typeof t!="string"||typeof e!="string"||t.length!==e.length)return!1;let a=0;for(let n=0;n<t.length;n++)a|=t.charCodeAt(n)^e.charCodeAt(n);return a===0}function st(t,e={}){let{secret:a,header:n="x-what-revalidate-secret",regenerate:l=!1}=e;return async function(m){let s=(m.headers||{})[n]||(m.headers||{})[n.toLowerCase()];if(!a||!nt(s||"",a))return{status:401,body:{message:"Unauthorized"}};let u=m.body;if(!u||typeof u!="object")return{status:400,body:{message:"Invalid body"}};let{paths:g,tags:c,regenerate:d=l}=u;if(!Array.isArray(g)&&!Array.isArray(c))return{status:400,body:{message:"Provide `paths` and/or `tags` arrays"}};let i={paths:[],tags:[]};if(Array.isArray(g))for(let r of g)await t.revalidatePath(r,{regenerate:d}),i.paths.push(r);if(Array.isArray(c))for(let r of c)await t.revalidateTag(r,{regenerate:d}),i.tags.push(r);return{status:200,body:{revalidated:!0,...i}}}}function ot(t,e={}){let{maxConcurrent:a=4,random:n=Math.random,setTimer:l=setTimeout,clearTimer:p=clearTimeout,logger:m=console}=e,s=[],u=[],g=!1,c=0;function d(o){return Math.round(o*(1+n()*.1))}function i(o){o.timer=l(()=>r(o),d(o.intervalMs)),o.timer&&typeof o.timer.unref=="function"&&o.timer.unref()}function r(o){g&&(c<a?f(o):u.push(o))}async function f(o){c++;try{await t.regenerate(o.route)}catch(w){m.error?.("[what-isr] scheduled regenerate failed:",w)}finally{c--,g&&i(o),h()}}function h(){for(;g&&c<a&&u.length;)f(u.shift())}return{register(o,{intervalMs:w}){return s.push({route:o,intervalMs:w,timer:null}),this},start(){g=!0;for(let o of s)i(o);return this},stop(){g=!1;for(let o of s)o.timer!=null&&p(o.timer);return u.length=0,this},_tasks:s}}function K({zoneId:t,apiToken:e}={}){let a=`https://api.cloudflare.com/client/v4/zones/${t}/purge_cache`,n={Authorization:`Bearer ${e}`,"Content-Type":"application/json"};async function l(p){!t||!e||await fetch(a,{method:"POST",headers:n,body:JSON.stringify(p)})}return{purge:p=>l({files:p}),purgeTags:p=>l({tags:p})}}function W({serviceId:t,apiToken:e}={}){let a={"Fastly-Key":e,Accept:"application/json"};async function n(l){!t||!e||await fetch(`https://api.fastly.com/service/${t}/purge/${encodeURIComponent(l)}`,{method:"POST",headers:a})}return{async purge(l){for(let p of l){if(!e)return;await fetch(p,{method:"PURGE",headers:a})}},async purgeTags(l){for(let p of l)await n(p)}}}function _({token:t,projectId:e,teamId:a}={}){let n={Authorization:`Bearer ${t}`,"Content-Type":"application/json"},l=a?`?teamId=${a}`:"";async function p(m){!t||!e||await fetch(`https://api.vercel.com/v1/projects/${e}/cache/purge${l}`,{method:"POST",headers:n,body:JSON.stringify({tags:m})})}return{purge:()=>Promise.resolve(),purgeTags:p}}export{N as buildCacheHeaders,et as buildPath,P as cacheKey,k as createCacheEngine,K as createCloudflareCDN,W as createFastlyCDN,Y as createFilesystemStore,M as createMemoryStore,Z as createRedisStore,st as createRevalidateWebhook,ot as createScheduler,_ as createVercelCDN,at as decideFallback,F as hashKey,A as isFresh,rt as isKnownParams,D as isServableStale,C as makeEntry,b as normalizePath,E as normalizeQuery,tt as resolveStaticPaths};
|
|
2
|
+
//# sourceMappingURL=index.min.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/key.js", "../src/stores/store-interface.js", "../src/headers.js", "../src/isr.js", "../src/stores/memory-store.js", "../src/stores/filesystem-store.js", "../src/stores/redis-store.js", "../src/paths.js", "../src/webhook.js", "../src/scheduler.js", "../src/cdn/cloudflare.js", "../src/cdn/fastly.js", "../src/cdn/vercel.js"],
|
|
4
|
+
"sourcesContent": ["// Cache key scheme. A key uniquely identifies a cacheable render:\n// <normalizedPath>\u0000<normalizedQuery>\u0000<varyString>\n// NUL separators avoid cross-field collisions. Tracking params are stripped so\n// /p?utm_source=x and /p share one cache entry.\n\nimport { createHash } from 'node:crypto';\n\nconst STRIP_EXACT = new Set(['fbclid', 'gclid', '_', 'mc_cid', 'mc_eid']);\nconst STRIP_PREFIXES = ['utm_'];\n\nfunction shouldStrip(name) {\n if (STRIP_EXACT.has(name)) return true;\n for (const p of STRIP_PREFIXES) if (name.startsWith(p)) return true;\n return false;\n}\n\n/** Normalize a path: drop query/hash, strip trailing slash (except root). */\nexport function normalizePath(path) {\n if (!path) return '/';\n let p = String(path).split('?')[0].split('#')[0];\n if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p || '/';\n}\n\nfunction queryToEntries(query) {\n if (!query) return [];\n if (typeof query === 'string') {\n const s = query.startsWith('?') ? query.slice(1) : query;\n if (!s) return [];\n return [...new URLSearchParams(s).entries()];\n }\n // plain object \u2014 values may be arrays\n const out = [];\n for (const [k, v] of Object.entries(query)) {\n if (Array.isArray(v)) for (const item of v) out.push([k, String(item)]);\n else out.push([k, String(v)]);\n }\n return out;\n}\n\n/** Normalize a query (object or string) \u2192 sorted \"k=v&k=v\", tracking params dropped. */\nexport function normalizeQuery(query) {\n const entries = queryToEntries(query)\n .filter(([k]) => !shouldStrip(k))\n .sort((a, b) => (a[0] === b[0] ? (a[1] < b[1] ? -1 : 1) : a[0] < b[0] ? -1 : 1));\n return entries.map(([k, v]) => `${k}=${v}`).join('&');\n}\n\nfunction varyString(vary) {\n if (!vary) return '';\n return Object.entries(vary)\n .sort((a, b) => (a[0] < b[0] ? -1 : 1))\n .map(([k, v]) => `${k}=${v}`)\n .join('&');\n}\n\n/** Build the canonical cache key for a request. */\nexport function cacheKey({ path, query, vary } = {}) {\n return [normalizePath(path), normalizeQuery(query), varyString(vary)].join('\u0000');\n}\n\n/** Hash a key to a sharded, filesystem-safe path: aa/bb/<hex>. */\nexport function hashKey(key) {\n const hex = createHash('sha256').update(key).digest('hex');\n return `${hex.slice(0, 2)}/${hex.slice(2, 4)}/${hex.slice(4)}`;\n}\n", "// CacheStore contract (JSDoc only \u2014 JS source, .d.ts ships types) + helpers.\n//\n// A store maps a cache key -> Entry. All methods are async so memory, filesystem\n// and Redis adapters share one interface (memory just resolves synchronously).\n//\n// @typedef {Object} Entry\n// @property {string} html Rendered body HTML.\n// @property {string} [head] Collected <head> HTML.\n// @property {*} [state] Serialized hydration state (loaderData/resources).\n// @property {string[]} [tags] Tags for group invalidation (revalidateTag).\n// @property {string} [path] Normalized path (for revalidatePath).\n// @property {number} renderedAt ms epoch when rendered.\n// @property {number} maxAge Revalidate seconds (0 = never time-stale).\n// @property {number} expiresAt renderedAt + maxAge*1000 (precomputed).\n// @property {number} swrWindow Grace seconds an expired entry is still served.\n// @property {number} [status] HTTP status (for 404 stubs / fallback skeletons).\n// @property {boolean} [partial] True for fallback skeletons (never durable).\n//\n// @typedef {Object} CacheStore\n// @property {(key:string)=>Promise<Entry|null>} get\n// @property {(key:string, entry:Entry)=>Promise<void>} set\n// @property {(key:string)=>Promise<boolean>} delete\n// @property {(tag:string)=>Promise<string[]>} deleteByTag returns deleted keys\n// @property {(path:string)=>Promise<string[]>} deleteByPath returns deleted keys\n// @property {()=>Promise<void>} clear\n// @property {()=>Promise<string[]>} keys\n\n/**\n * Fill an Entry's time fields from `now` + a route config. Used by the ISR\n * engine so every store receives consistent expiry metadata.\n */\nexport function makeEntry(out, config = {}, now = Date.now()) {\n const maxAge = Number(config.revalidate) || 0;\n const swrWindow = config.swr != null ? Number(config.swr) : maxAge;\n return {\n html: out.html || '',\n head: out.head || '',\n state: out.state ?? null,\n tags: out.tags || config.tags || [],\n path: out.path || config.path,\n status: out.status || 200,\n partial: !!out.partial,\n renderedAt: now,\n maxAge,\n swrWindow,\n expiresAt: maxAge > 0 ? now + maxAge * 1000 : Infinity,\n };\n}\n\n/** Freshness check against a clock. */\nexport function isFresh(entry, now = Date.now()) {\n return entry.expiresAt === Infinity || now < entry.expiresAt;\n}\n\n/** Within the stale-while-revalidate grace window (servable while regenerating). */\nexport function isServableStale(entry, now = Date.now()) {\n if (isFresh(entry, now)) return true;\n if (entry.swrWindow == null) return false;\n return now < entry.expiresAt + entry.swrWindow * 1000;\n}\n", "// Cache-Control header builder. Origin ISR works without these, but emitting\n// them lets ANY standards-compliant CDN do edge ISR for free (s-maxage +\n// stale-while-revalidate), and Cache-Tag/Surrogate-Key enable CDN tag purge.\n\n/**\n * @param {object} entry { maxAge, swrWindow, tags?, partial? }\n * @param {object} config route page config { mode, ... }\n * @param {string} cacheStatus HIT | STALE | MISS | BYPASS\n */\nexport function buildCacheHeaders(entry = {}, config = {}, cacheStatus = 'MISS') {\n const headers = { 'X-What-Cache': cacheStatus };\n\n const cacheable = (entry.maxAge > 0 || config.mode === 'static' || config.mode === 'hybrid')\n && config.mode !== 'server';\n\n if (!cacheable) {\n headers['Cache-Control'] = 'private, no-store';\n return headers;\n }\n\n const sMaxAge = entry.partial ? 0 : (entry.maxAge || 0);\n const swr = entry.swrWindow != null ? entry.swrWindow : sMaxAge;\n headers['Cache-Control'] = `public, s-maxage=${sMaxAge}, stale-while-revalidate=${swr}`;\n\n if (entry.tags && entry.tags.length) {\n headers['Cache-Tag'] = entry.tags.join(','); // Cloudflare / Fastly\n headers['Surrogate-Key'] = entry.tags.join(' '); // Fastly surrogate keys\n }\n\n return headers;\n}\n", "// ISR engine \u2014 origin-first Incremental Static Regeneration.\n//\n// Stale-while-revalidate at the ORIGIN: a fresh entry is served from cache; a\n// stale entry (past `revalidate`, within `swr`) is served IMMEDIATELY while a\n// single background regeneration refreshes it; a cold/expired entry blocks and\n// renders. Concurrent regenerations of the same key are deduped to ONE render.\n//\n// `render(routeMatch, ctx)` is INJECTED by the adapter (wraps renderPage +\n// serializeState) \u2014 the engine never imports what-server, keeping it standalone.\n\nimport { cacheKey, normalizePath } from './key.js';\nimport { makeEntry, isFresh, isServableStale } from './stores/store-interface.js';\nimport { buildCacheHeaders } from './headers.js';\n\nexport function createCacheEngine({ store, render, cdn, now = Date.now, logger = console } = {}) {\n const inFlight = new Map(); // key -> Promise<entry> (dedupe)\n\n function keyFor(routeMatch) {\n return cacheKey({ path: routeMatch.path, query: routeMatch.query, vary: routeMatch.vary });\n }\n\n // Render + store, deduping concurrent calls for the same key. `renderOverride`\n // lets a caller (e.g. the deploy adapter) supply the render for this route\n // without baking it into the engine \u2014 keeps the engine decoupled.\n function regenerate(key, routeMatch, renderOverride) {\n const existing = inFlight.get(key);\n if (existing) return existing;\n const doRender = renderOverride || render;\n const p = (async () => {\n const out = await doRender(routeMatch, {});\n const entry = makeEntry({ ...out, path: routeMatch.path }, routeMatch.config || {}, now());\n await store.set(key, entry);\n return entry;\n })().finally(() => inFlight.delete(key));\n inFlight.set(key, p);\n return p;\n }\n\n function serve(entry, cacheStatus, config) {\n return {\n html: entry.html,\n head: entry.head,\n state: entry.state,\n status: entry.status || 200,\n cacheStatus,\n headers: buildCacheHeaders(entry, config || {}, cacheStatus),\n };\n }\n\n async function handle(routeMatch, renderOverride) {\n const config = routeMatch.config || {};\n\n // Uncacheable (server-rendered) routes: always render, never store.\n if (config.mode === 'server') {\n const out = await (renderOverride || render)(routeMatch, {});\n const entry = makeEntry({ ...out, path: routeMatch.path }, config, now());\n return serve(entry, 'BYPASS', config);\n }\n\n const key = keyFor(routeMatch);\n const entry = await store.get(key);\n const t = now();\n\n if (entry && isFresh(entry, t)) {\n return serve(entry, 'HIT', config);\n }\n\n if (entry && isServableStale(entry, t)) {\n // Serve stale immediately; refresh in the background (deduped, non-blocking).\n regenerate(key, routeMatch, renderOverride).catch((e) => logger.error?.('[what-isr] background regenerate failed:', e));\n return serve(entry, 'STALE', config);\n }\n\n // Cold miss or expired beyond the swr window.\n if (entry && config.onMiss === 'stale-if-error') {\n try {\n const fresh = await regenerate(key, routeMatch, renderOverride);\n return serve(fresh, 'MISS', config);\n } catch (e) {\n logger.error?.('[what-isr] regenerate failed, serving stale:', e);\n return serve(entry, 'STALE', config);\n }\n }\n\n const fresh = await regenerate(key, routeMatch, renderOverride);\n return serve(fresh, 'MISS', config);\n }\n\n // --- On-demand invalidation (origin purge + optional CDN fan-out) ---\n\n async function revalidatePath(path, { regenerate: regen = false, routeResolver } = {}) {\n const norm = normalizePath(path);\n const deleted = await store.deleteByPath(norm);\n if (cdn && cdn.purge) await cdn.purge([path]);\n if (regen) {\n const route = routeResolver ? routeResolver(norm) : { path: norm, query: {}, config: {} };\n await regenerate(keyFor(route), route).catch((e) => logger.error?.('[what-isr] regen after revalidatePath failed:', e));\n }\n return deleted;\n }\n\n async function revalidateTag(tag, { regenerate: regen = false, routeResolver } = {}) {\n const deleted = await store.deleteByTag(tag);\n if (cdn && cdn.purgeTags) await cdn.purgeTags([tag]);\n if (regen && routeResolver) {\n for (const key of deleted) {\n const route = routeResolver(key);\n if (route) await regenerate(keyFor(route), route).catch(() => {});\n }\n }\n return deleted;\n }\n\n return {\n handle,\n regenerate: (routeMatch) => regenerate(keyFor(routeMatch), routeMatch),\n revalidatePath,\n revalidateTag,\n keyFor,\n store,\n _inFlight: inFlight,\n _now: now,\n _cdn: cdn,\n };\n}\n", "// In-memory cache store \u2014 the zero-config default. LRU eviction + reverse\n// indexes (tag -> keys, path -> keys) for O(1) group invalidation.\n//\n// Insertion order of a Map IS the LRU order: get() re-inserts (moves to newest),\n// eviction removes from the front (oldest).\n\nexport function createMemoryStore({ maxEntries = 1000 } = {}) {\n const map = new Map(); // key -> entry\n const tagIndex = new Map(); // tag -> Set<key>\n const pathIndex = new Map(); // path -> Set<key>\n\n function addToIndex(index, name, key) {\n if (name == null) return;\n let set = index.get(name);\n if (!set) index.set(name, (set = new Set()));\n set.add(key);\n }\n function removeFromIndex(index, name, key) {\n if (name == null) return;\n const set = index.get(name);\n if (set) {\n set.delete(key);\n if (set.size === 0) index.delete(name);\n }\n }\n function indexEntry(key, entry) {\n if (entry.tags) for (const t of entry.tags) addToIndex(tagIndex, t, key);\n addToIndex(pathIndex, entry.path, key);\n }\n function deindexEntry(key, entry) {\n if (entry.tags) for (const t of entry.tags) removeFromIndex(tagIndex, t, key);\n removeFromIndex(pathIndex, entry.path, key);\n }\n function removeKey(key) {\n const e = map.get(key);\n if (!e) return false;\n map.delete(key);\n deindexEntry(key, e);\n return true;\n }\n function deleteByIndex(index, name) {\n const set = index.get(name);\n if (!set) return [];\n const deleted = [...set];\n for (const k of deleted) removeKey(k);\n return deleted;\n }\n\n return {\n async get(key) {\n const e = map.get(key);\n if (!e) return null;\n // LRU touch: move to newest.\n map.delete(key);\n map.set(key, e);\n return e;\n },\n async set(key, entry) {\n if (map.has(key)) deindexEntry(key, map.get(key));\n map.set(key, entry);\n indexEntry(key, entry);\n while (map.size > maxEntries) {\n const oldest = map.keys().next().value;\n removeKey(oldest);\n }\n },\n async delete(key) {\n return removeKey(key);\n },\n async deleteByTag(tag) {\n return deleteByIndex(tagIndex, tag);\n },\n async deleteByPath(path) {\n return deleteByIndex(pathIndex, path);\n },\n async clear() {\n map.clear();\n tagIndex.clear();\n pathIndex.clear();\n },\n async keys() {\n return [...map.keys()];\n },\n };\n}\n", "// Filesystem cache store \u2014 survives restarts and is shareable by multiple\n// worker processes on one box. Entries are sharded JSON files; writes are atomic\n// (write to .tmp then rename). Tag/path reverse indexes are sidecar JSON lists.\n\nimport { mkdir, writeFile, readFile, rename, rm, readdir, stat } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { hashKey } from '../key.js';\n\nfunction safeName(s) {\n return createHash('sha256').update(String(s)).digest('hex');\n}\n\nexport function createFilesystemStore({ dir }) {\n const entriesDir = join(dir, 'entries');\n const tagsDir = join(dir, 'tags');\n const pathsDir = join(dir, 'paths');\n\n const entryFile = (key) => join(entriesDir, hashKey(key) + '.json');\n const indexFile = (base, name) => join(base, safeName(name) + '.json');\n\n async function atomicWrite(file, contents) {\n await mkdir(dirname(file), { recursive: true });\n const tmp = `${file}.${process.pid}.${safeName(file).slice(0, 8)}.tmp`;\n await writeFile(tmp, contents);\n await rename(tmp, file);\n }\n\n async function readJson(file) {\n try {\n return JSON.parse(await readFile(file, 'utf8'));\n } catch {\n return null;\n }\n }\n\n async function addToIndex(base, name, key) {\n const file = indexFile(base, name);\n const list = (await readJson(file)) || [];\n if (!list.includes(key)) {\n list.push(key);\n await atomicWrite(file, JSON.stringify(list));\n }\n }\n\n async function removeFromIndex(base, name, key) {\n const file = indexFile(base, name);\n const list = await readJson(file);\n if (!list) return;\n const next = list.filter((k) => k !== key);\n if (next.length) await atomicWrite(file, JSON.stringify(next));\n else await rm(file, { force: true });\n }\n\n async function deleteByIndex(base, name) {\n const file = indexFile(base, name);\n const keys = await readJson(file);\n if (!keys) return [];\n for (const k of keys) await removeKey(k);\n await rm(file, { force: true });\n return keys;\n }\n\n async function removeKey(key) {\n const record = await readJson(entryFile(key));\n if (!record) return false;\n await rm(entryFile(key), { force: true });\n const entry = record.entry || {};\n for (const t of entry.tags || []) await removeFromIndex(tagsDir, t, key);\n if (entry.path) await removeFromIndex(pathsDir, entry.path, key);\n return true;\n }\n\n async function walkKeys() {\n const out = [];\n let shards;\n try { shards = await readdir(entriesDir); } catch { return out; }\n for (const a of shards) {\n const aDir = join(entriesDir, a);\n let st; try { st = await stat(aDir); } catch { continue; }\n if (!st.isDirectory()) continue;\n for (const b of await readdir(aDir)) {\n const bDir = join(aDir, b);\n try { if (!(await stat(bDir)).isDirectory()) continue; } catch { continue; }\n for (const f of await readdir(bDir)) {\n if (!f.endsWith('.json')) continue;\n const rec = await readJson(join(bDir, f));\n if (rec && rec.key != null) out.push(rec.key);\n }\n }\n }\n return out;\n }\n\n return {\n async get(key) {\n const rec = await readJson(entryFile(key));\n return rec ? rec.entry : null;\n },\n async set(key, entry) {\n // de-index any previous version's tags/path first\n const prev = await readJson(entryFile(key));\n if (prev && prev.entry) {\n for (const t of prev.entry.tags || []) await removeFromIndex(tagsDir, t, key);\n if (prev.entry.path) await removeFromIndex(pathsDir, prev.entry.path, key);\n }\n await atomicWrite(entryFile(key), JSON.stringify({ key, entry }));\n for (const t of entry.tags || []) await addToIndex(tagsDir, t, key);\n if (entry.path) await addToIndex(pathsDir, entry.path, key);\n },\n async delete(key) {\n return removeKey(key);\n },\n async deleteByTag(tag) {\n return deleteByIndex(tagsDir, tag);\n },\n async deleteByPath(path) {\n return deleteByIndex(pathsDir, path);\n },\n async clear() {\n await rm(dir, { recursive: true, force: true });\n },\n async keys() {\n return walkKeys();\n },\n };\n}\n", "// Redis/KV cache store \u2014 the multi-instance story (N app servers share one\n// cache). Takes an INJECTED client (ioredis / node-redis shaped:\n// get/set/del/sadd/srem/smembers, optional keys) so this package keeps zero deps.\n\nexport function createRedisStore({ client, namespace = 'what' } = {}) {\n if (!client) throw new Error('[what-isr] createRedisStore requires { client }');\n\n const ck = (key) => `${namespace}:cache:${key}`;\n const tk = (tag) => `${namespace}:tag:${tag}`;\n const pk = (path) => `${namespace}:path:${path}`;\n\n async function deindex(key, entry) {\n if (!entry) return;\n for (const t of entry.tags || []) await client.srem(tk(t), key);\n if (entry.path) await client.srem(pk(entry.path), key);\n }\n\n async function deleteBySet(setKey) {\n const keys = (await client.smembers(setKey)) || [];\n for (const k of keys) await client.del(ck(k));\n await client.del(setKey);\n return keys;\n }\n\n return {\n async get(key) {\n const v = await client.get(ck(key));\n return v ? JSON.parse(v) : null;\n },\n async set(key, entry) {\n const prev = await this.get(key);\n if (prev) await deindex(key, prev);\n await client.set(ck(key), JSON.stringify(entry));\n for (const t of entry.tags || []) await client.sadd(tk(t), key);\n if (entry.path) await client.sadd(pk(entry.path), key);\n },\n async delete(key) {\n const entry = await this.get(key);\n await client.del(ck(key));\n await deindex(key, entry);\n return !!entry;\n },\n async deleteByTag(tag) {\n return deleteBySet(tk(tag));\n },\n async deleteByPath(path) {\n return deleteBySet(pk(path));\n },\n async clear() {\n if (typeof client.keys === 'function') {\n const all = await client.keys(`${namespace}:*`);\n for (const k of all) await client.del(k);\n }\n },\n async keys() {\n if (typeof client.keys !== 'function') return [];\n const prefix = `${namespace}:cache:`;\n const all = await client.keys(`${prefix}*`);\n return all.map((k) => k.slice(prefix.length));\n },\n };\n}\n", "// getStaticPaths resolution + fallback decisions for dynamic routes.\n//\n// Known paths (in getStaticPaths) are pre-rendered at build. Unknown params at\n// request time follow the route's `fallback`:\n// 'blocking' -> render on first hit, then cache (ISR)\n// true -> serve a skeleton immediately, regenerate in the background\n// false -> 404\n\n/** Run a page's getStaticPaths (if any). Returns { paths, fallback }. */\nexport async function resolveStaticPaths(getStaticPaths, ctx = {}) {\n if (typeof getStaticPaths !== 'function') return { paths: [], fallback: false };\n const result = await getStaticPaths(ctx);\n return {\n paths: (result && result.paths) || [],\n fallback: result && 'fallback' in result ? result.fallback : false,\n };\n}\n\n/** Build a concrete URL from a route pattern + params. Supports :param and *catchall. */\nexport function buildPath(pattern, params = {}) {\n return pattern.replace(/[:*]([A-Za-z0-9_]+)/g, (_, name) => {\n const v = params[name];\n return v == null ? '' : String(v);\n });\n}\n\n/** Is this param set among the pre-built static paths? */\nexport function isKnownParams(staticPaths, params) {\n return staticPaths.some((entry) => {\n const p = entry.params || {};\n const keys = new Set([...Object.keys(p), ...Object.keys(params)]);\n for (const k of keys) if (String(p[k]) !== String(params[k])) return false;\n return true;\n });\n}\n\n/** Decide what to do for a requested dynamic path. */\nexport function decideFallback(fallback, isKnown) {\n if (isKnown) return 'serve';\n if (fallback === 'blocking') return 'render';\n if (fallback === true) return 'skeleton';\n return 'notfound';\n}\n", "// On-demand revalidation webhook \u2014 the CMS-trigger path. A POST with a shared\n// secret purges paths/tags so a Sanity/Contentful/WP \"published\" event can warm\n// or drop cache entries. The adapter mounts this at e.g. /__what_revalidate.\n\n// Constant-time string compare (timing-attack safe). Self-contained so the cache\n// package stays dependency-free and decoupled from what-server.\nfunction safeEqual(a, b) {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return result === 0;\n}\n\n/**\n * @param engine cache engine (revalidatePath / revalidateTag)\n * @param options { secret, header='x-what-revalidate-secret', regenerate=false }\n * @returns async (reqLike:{headers, body:{paths?,tags?,regenerate?}}) -> { status, body }\n */\nexport function createRevalidateWebhook(engine, options = {}) {\n const { secret, header = 'x-what-revalidate-secret', regenerate = false } = options;\n\n return async function handle(reqLike) {\n const provided = (reqLike.headers || {})[header] || (reqLike.headers || {})[header.toLowerCase()];\n if (!secret || !safeEqual(provided || '', secret)) {\n return { status: 401, body: { message: 'Unauthorized' } };\n }\n\n const body = reqLike.body;\n if (!body || typeof body !== 'object') {\n return { status: 400, body: { message: 'Invalid body' } };\n }\n\n const { paths, tags, regenerate: regen = regenerate } = body;\n if (!Array.isArray(paths) && !Array.isArray(tags)) {\n return { status: 400, body: { message: 'Provide `paths` and/or `tags` arrays' } };\n }\n\n const revalidated = { paths: [], tags: [] };\n if (Array.isArray(paths)) {\n for (const p of paths) {\n await engine.revalidatePath(p, { regenerate: regen });\n revalidated.paths.push(p);\n }\n }\n if (Array.isArray(tags)) {\n for (const t of tags) {\n await engine.revalidateTag(t, { regenerate: regen });\n revalidated.tags.push(t);\n }\n }\n\n return { status: 200, body: { revalidated: true, ...revalidated } };\n };\n}\n", "// Poll/scheduled regeneration \u2014 keeps the cache warm regardless of traffic.\n// Runs in the long-lived Node adapter process. Self-rescheduling setTimeout\n// (no drift/overlap), jitter (anti-thundering-herd after a restart), a global\n// concurrency cap, and it joins the engine's in-flight lock so a scheduled tick\n// during a traffic-triggered regeneration is a no-op.\n\nexport function createScheduler(engine, options = {}) {\n const {\n maxConcurrent = 4,\n random = Math.random,\n setTimer = setTimeout,\n clearTimer = clearTimeout,\n logger = console,\n } = options;\n\n const tasks = []; // { route, intervalMs, timer }\n const queue = [];\n let running = false;\n let active = 0;\n\n function jittered(intervalMs) {\n return Math.round(intervalMs * (1 + random() * 0.1));\n }\n\n function schedule(task) {\n task.timer = setTimer(() => fire(task), jittered(task.intervalMs));\n if (task.timer && typeof task.timer.unref === 'function') task.timer.unref();\n }\n\n function fire(task) {\n if (!running) return;\n if (active < maxConcurrent) runTask(task);\n else queue.push(task);\n }\n\n async function runTask(task) {\n active++;\n try {\n await engine.regenerate(task.route);\n } catch (e) {\n logger.error?.('[what-isr] scheduled regenerate failed:', e);\n } finally {\n active--;\n if (running) schedule(task);\n drain();\n }\n }\n\n function drain() {\n while (running && active < maxConcurrent && queue.length) {\n runTask(queue.shift());\n }\n }\n\n return {\n register(route, { intervalMs }) {\n tasks.push({ route, intervalMs, timer: null });\n return this;\n },\n start() {\n running = true;\n for (const t of tasks) schedule(t);\n return this;\n },\n stop() {\n running = false;\n for (const t of tasks) if (t.timer != null) clearTimer(t.timer);\n queue.length = 0;\n return this;\n },\n _tasks: tasks,\n };\n}\n", "// Cloudflare CDN adapter \u2014 edge purge by URL or cache-tag (Enterprise tag purge).\n// Uses global fetch (Node 18+); credentials injected by the deploy adapter.\n\nexport function createCloudflareCDN({ zoneId, apiToken } = {}) {\n const endpoint = `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`;\n const headers = { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json' };\n\n async function post(body) {\n if (!zoneId || !apiToken) return;\n await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(body) });\n }\n\n return {\n purge: (urls) => post({ files: urls }),\n purgeTags: (tags) => post({ tags }),\n };\n}\n", "// Fastly CDN adapter \u2014 surrogate-key purge (tags) and URL purge.\n\nexport function createFastlyCDN({ serviceId, apiToken } = {}) {\n const headers = { 'Fastly-Key': apiToken, Accept: 'application/json' };\n\n async function purgeKey(key) {\n if (!serviceId || !apiToken) return;\n await fetch(`https://api.fastly.com/service/${serviceId}/purge/${encodeURIComponent(key)}`, {\n method: 'POST',\n headers,\n });\n }\n\n return {\n async purge(urls) {\n for (const url of urls) {\n if (!apiToken) return;\n await fetch(url, { method: 'PURGE', headers });\n }\n },\n async purgeTags(tags) {\n for (const t of tags) await purgeKey(t);\n },\n };\n}\n", "// Vercel CDN adapter. Vercel ISR is mostly header-driven (s-maxage), so this is\n// a thin on-demand purge via the project API when a token is supplied; it\n// no-ops (header-only mode) without one.\n\nexport function createVercelCDN({ token, projectId, teamId } = {}) {\n const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };\n const qs = teamId ? `?teamId=${teamId}` : '';\n\n async function purgeTags(tags) {\n if (!token || !projectId) return;\n await fetch(`https://api.vercel.com/v1/projects/${projectId}/cache/purge${qs}`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tags }),\n });\n }\n\n return {\n purge: () => Promise.resolve(), // Vercel purges by tag; URL purge is header-driven\n purgeTags,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["createHash", "STRIP_EXACT", "STRIP_PREFIXES", "shouldStrip", "name", "p", "normalizePath", "path", "queryToEntries", "query", "s", "out", "k", "v", "item", "normalizeQuery", "b", "varyString", "vary", "a", "cacheKey", "hashKey", "key", "hex", "makeEntry", "out", "config", "now", "maxAge", "swrWindow", "isFresh", "entry", "isServableStale", "buildCacheHeaders", "entry", "config", "cacheStatus", "headers", "sMaxAge", "swr", "createCacheEngine", "store", "render", "cdn", "now", "logger", "inFlight", "keyFor", "routeMatch", "cacheKey", "regenerate", "key", "renderOverride", "existing", "doRender", "p", "out", "entry", "makeEntry", "serve", "cacheStatus", "config", "buildCacheHeaders", "handle", "t", "isFresh", "isServableStale", "e", "fresh", "revalidatePath", "path", "regen", "routeResolver", "norm", "normalizePath", "deleted", "route", "revalidateTag", "tag", "createMemoryStore", "maxEntries", "map", "tagIndex", "pathIndex", "addToIndex", "index", "name", "key", "set", "removeFromIndex", "indexEntry", "entry", "t", "deindexEntry", "removeKey", "e", "deleteByIndex", "deleted", "k", "oldest", "tag", "path", "mkdir", "writeFile", "readFile", "rename", "rm", "readdir", "stat", "join", "dirname", "createHash", "safeName", "s", "createHash", "createFilesystemStore", "dir", "entriesDir", "join", "tagsDir", "pathsDir", "entryFile", "key", "hashKey", "indexFile", "base", "name", "atomicWrite", "file", "contents", "mkdir", "dirname", "tmp", "writeFile", "rename", "readJson", "readFile", "addToIndex", "list", "removeFromIndex", "next", "k", "rm", "deleteByIndex", "keys", "removeKey", "record", "entry", "t", "walkKeys", "out", "shards", "readdir", "a", "aDir", "st", "stat", "b", "bDir", "f", "rec", "prev", "tag", "path", "createRedisStore", "client", "namespace", "ck", "key", "tk", "tag", "pk", "path", "deindex", "entry", "t", "deleteBySet", "setKey", "keys", "k", "v", "prev", "all", "prefix", "resolveStaticPaths", "getStaticPaths", "ctx", "result", "buildPath", "pattern", "params", "_", "name", "v", "isKnownParams", "staticPaths", "entry", "p", "keys", "k", "decideFallback", "fallback", "isKnown", "safeEqual", "a", "b", "result", "i", "createRevalidateWebhook", "engine", "options", "secret", "header", "regenerate", "reqLike", "provided", "body", "paths", "tags", "regen", "revalidated", "p", "t", "createScheduler", "engine", "options", "maxConcurrent", "random", "setTimer", "clearTimer", "logger", "tasks", "queue", "running", "active", "jittered", "intervalMs", "schedule", "task", "fire", "runTask", "e", "drain", "route", "t", "createCloudflareCDN", "zoneId", "apiToken", "endpoint", "headers", "post", "body", "urls", "tags", "createFastlyCDN", "serviceId", "apiToken", "headers", "purgeKey", "key", "urls", "url", "tags", "t", "createVercelCDN", "token", "projectId", "teamId", "headers", "qs", "purgeTags", "tags"]
|
|
7
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// what-isr — origin-first ISR engine. TypeScript definitions.
|
|
2
|
+
|
|
3
|
+
// --- Page caching config (the JSON-safe `page` export) ---
|
|
4
|
+
export interface PageCacheConfig {
|
|
5
|
+
mode?: 'static' | 'hybrid' | 'server';
|
|
6
|
+
/** Seconds until a cached entry becomes stale. */
|
|
7
|
+
revalidate?: number;
|
|
8
|
+
/** Extra seconds an entry may be served stale while regenerating (stale-while-revalidate / stale-if-error). */
|
|
9
|
+
swr?: number;
|
|
10
|
+
/** Purge handles for revalidateTag. */
|
|
11
|
+
tags?: string[];
|
|
12
|
+
/** Split the cache by these request signals, e.g. 'cookie:theme'. */
|
|
13
|
+
vary?: string[];
|
|
14
|
+
fallback?: 'blocking' | boolean;
|
|
15
|
+
onMiss?: 'blocking' | string;
|
|
16
|
+
/** Background regeneration interval, in seconds. */
|
|
17
|
+
pollInterval?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// --- Stores ---
|
|
21
|
+
export interface CacheEntry {
|
|
22
|
+
html: string;
|
|
23
|
+
head?: string;
|
|
24
|
+
state?: unknown;
|
|
25
|
+
status?: number;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
path?: string;
|
|
28
|
+
/** Epoch ms when the entry was created. */
|
|
29
|
+
createdAt: number;
|
|
30
|
+
/** Seconds-to-stale snapshot from the page config. */
|
|
31
|
+
revalidate?: number;
|
|
32
|
+
swr?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CacheStore {
|
|
36
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
37
|
+
set(key: string, entry: CacheEntry): Promise<void>;
|
|
38
|
+
delete(key: string): Promise<boolean>;
|
|
39
|
+
deleteByTag(tag: string): Promise<string[]>;
|
|
40
|
+
deleteByPath?(path: string): Promise<string[]>;
|
|
41
|
+
clear(): Promise<void>;
|
|
42
|
+
keys(): Promise<string[]>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createMemoryStore(options?: { max?: number }): CacheStore;
|
|
46
|
+
export function createFilesystemStore(options: { dir: string; shards?: number }): CacheStore;
|
|
47
|
+
export function createRedisStore(options: { client: unknown; prefix?: string }): CacheStore;
|
|
48
|
+
|
|
49
|
+
export function makeEntry(out: Partial<CacheEntry>, config?: PageCacheConfig, now?: number): CacheEntry;
|
|
50
|
+
export function isFresh(entry: CacheEntry, now?: number): boolean;
|
|
51
|
+
export function isServableStale(entry: CacheEntry, now?: number): boolean;
|
|
52
|
+
|
|
53
|
+
// --- Keys ---
|
|
54
|
+
export function cacheKey(routeMatch: RouteMatch): string;
|
|
55
|
+
export function normalizePath(path: string): string;
|
|
56
|
+
export function normalizeQuery(query: Record<string, string> | string): string;
|
|
57
|
+
export function hashKey(key: string): string;
|
|
58
|
+
|
|
59
|
+
// --- CDN adapters (optional) ---
|
|
60
|
+
export interface CDNAdapter {
|
|
61
|
+
purge(urls: string[]): Promise<void>;
|
|
62
|
+
purgeTags(tags: string[]): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export function createCloudflareCDN(options: { zoneId: string; apiToken: string }): CDNAdapter;
|
|
65
|
+
export function createFastlyCDN(options: { serviceId: string; apiToken: string }): CDNAdapter;
|
|
66
|
+
export function createVercelCDN(options: { projectId: string; token: string; teamId?: string }): CDNAdapter;
|
|
67
|
+
|
|
68
|
+
// --- Headers ---
|
|
69
|
+
export interface CacheHeaderOptions {
|
|
70
|
+
cdn?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export function buildCacheHeaders(entry?: CacheEntry, status?: 'HIT' | 'STALE' | 'MISS', options?: CacheHeaderOptions): Record<string, string>;
|
|
73
|
+
|
|
74
|
+
// --- Static paths ---
|
|
75
|
+
export interface StaticPathEntry {
|
|
76
|
+
params: Record<string, string>;
|
|
77
|
+
}
|
|
78
|
+
export interface StaticPathsResult {
|
|
79
|
+
paths: StaticPathEntry[];
|
|
80
|
+
fallback: 'blocking' | boolean;
|
|
81
|
+
}
|
|
82
|
+
export function resolveStaticPaths(getStaticPaths: () => StaticPathsResult | Promise<StaticPathsResult>): Promise<StaticPathsResult>;
|
|
83
|
+
export function buildPath(pattern: string, params?: Record<string, string>): string;
|
|
84
|
+
export function decideFallback(fallback: 'blocking' | boolean, isKnown: boolean): 'blocking' | 'skeleton' | '404' | 'serve';
|
|
85
|
+
export function isKnownParams(staticPaths: StaticPathEntry[], params: Record<string, string>): boolean;
|
|
86
|
+
|
|
87
|
+
// --- Route match (shape passed into the engine) ---
|
|
88
|
+
export interface RouteMatch {
|
|
89
|
+
path: string;
|
|
90
|
+
query?: Record<string, string>;
|
|
91
|
+
config?: PageCacheConfig;
|
|
92
|
+
params?: Record<string, string>;
|
|
93
|
+
route?: unknown;
|
|
94
|
+
request?: Request;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RenderResult {
|
|
98
|
+
html: string;
|
|
99
|
+
head?: string;
|
|
100
|
+
state?: unknown;
|
|
101
|
+
status?: number;
|
|
102
|
+
tags?: string[];
|
|
103
|
+
}
|
|
104
|
+
export type RenderFn = (routeMatch: RouteMatch) => RenderResult | Promise<RenderResult>;
|
|
105
|
+
|
|
106
|
+
// --- ISR engine ---
|
|
107
|
+
export interface ServeResult {
|
|
108
|
+
html: string;
|
|
109
|
+
status: number;
|
|
110
|
+
headers: Record<string, string>;
|
|
111
|
+
cacheStatus: 'HIT' | 'STALE' | 'MISS';
|
|
112
|
+
}
|
|
113
|
+
export interface RevalidateOptions {
|
|
114
|
+
regenerate?: boolean;
|
|
115
|
+
routeResolver?: (key: string) => RouteMatch | undefined;
|
|
116
|
+
}
|
|
117
|
+
export interface CacheEngine {
|
|
118
|
+
handle(routeMatch: RouteMatch, renderOverride?: RenderFn): Promise<ServeResult>;
|
|
119
|
+
revalidatePath(path: string, options?: RevalidateOptions): Promise<void>;
|
|
120
|
+
revalidateTag(tag: string, options?: RevalidateOptions): Promise<void>;
|
|
121
|
+
store: CacheStore;
|
|
122
|
+
}
|
|
123
|
+
export function createCacheEngine(options?: {
|
|
124
|
+
store?: CacheStore;
|
|
125
|
+
render?: RenderFn;
|
|
126
|
+
cdn?: CDNAdapter;
|
|
127
|
+
now?: () => number;
|
|
128
|
+
logger?: Pick<Console, 'error' | 'warn' | 'log'>;
|
|
129
|
+
}): CacheEngine;
|
|
130
|
+
|
|
131
|
+
// --- Revalidation webhook ---
|
|
132
|
+
export interface WebhookRequest {
|
|
133
|
+
headers?: Record<string, string>;
|
|
134
|
+
body?: { paths?: string[]; tags?: string[]; secret?: string; regenerate?: boolean };
|
|
135
|
+
}
|
|
136
|
+
export interface WebhookResponse {
|
|
137
|
+
status: number;
|
|
138
|
+
body: unknown;
|
|
139
|
+
}
|
|
140
|
+
export function createRevalidateWebhook(
|
|
141
|
+
engine: CacheEngine,
|
|
142
|
+
options: { secret: string }
|
|
143
|
+
): (req: WebhookRequest) => Promise<WebhookResponse>;
|
|
144
|
+
|
|
145
|
+
// --- Poll scheduler ---
|
|
146
|
+
export interface Scheduler {
|
|
147
|
+
register(route: RouteMatch, options: { intervalMs: number }): void;
|
|
148
|
+
start(): void;
|
|
149
|
+
stop(): void;
|
|
150
|
+
}
|
|
151
|
+
export function createScheduler(engine: CacheEngine, options?: { concurrency?: number; jitter?: number }): Scheduler;
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "what-isr",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "What Framework - Origin-first ISR cache engine: stale-while-revalidate, on-demand & poll regeneration, no CDN required",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"production": "./dist/index.min.js",
|
|
12
|
+
"import": "./src/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"dist",
|
|
18
|
+
"index.d.ts"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"keywords": [
|
|
22
|
+
"isr",
|
|
23
|
+
"ssr",
|
|
24
|
+
"cache",
|
|
25
|
+
"stale-while-revalidate",
|
|
26
|
+
"incremental-static-regeneration",
|
|
27
|
+
"what-framework"
|
|
28
|
+
],
|
|
29
|
+
"author": "ZVN DEV (https://zvndev.com)",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"what-server": "^0.10.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"what-server": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/CelsianJs/what-framework"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/CelsianJs/what-framework/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://whatfw.com"
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Cloudflare CDN adapter — edge purge by URL or cache-tag (Enterprise tag purge).
|
|
2
|
+
// Uses global fetch (Node 18+); credentials injected by the deploy adapter.
|
|
3
|
+
|
|
4
|
+
export function createCloudflareCDN({ zoneId, apiToken } = {}) {
|
|
5
|
+
const endpoint = `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`;
|
|
6
|
+
const headers = { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json' };
|
|
7
|
+
|
|
8
|
+
async function post(body) {
|
|
9
|
+
if (!zoneId || !apiToken) return;
|
|
10
|
+
await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(body) });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
purge: (urls) => post({ files: urls }),
|
|
15
|
+
purgeTags: (tags) => post({ tags }),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Fastly CDN adapter — surrogate-key purge (tags) and URL purge.
|
|
2
|
+
|
|
3
|
+
export function createFastlyCDN({ serviceId, apiToken } = {}) {
|
|
4
|
+
const headers = { 'Fastly-Key': apiToken, Accept: 'application/json' };
|
|
5
|
+
|
|
6
|
+
async function purgeKey(key) {
|
|
7
|
+
if (!serviceId || !apiToken) return;
|
|
8
|
+
await fetch(`https://api.fastly.com/service/${serviceId}/purge/${encodeURIComponent(key)}`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
async purge(urls) {
|
|
16
|
+
for (const url of urls) {
|
|
17
|
+
if (!apiToken) return;
|
|
18
|
+
await fetch(url, { method: 'PURGE', headers });
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
async purgeTags(tags) {
|
|
22
|
+
for (const t of tags) await purgeKey(t);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
package/src/cdn/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// CDN adapters — all OPTIONAL. The origin cache does full ISR without them;
|
|
2
|
+
// these add edge purge on revalidatePath/revalidateTag. A CDNAdapter is just
|
|
3
|
+
// { purge(urls), purgeTags(tags) }.
|
|
4
|
+
|
|
5
|
+
export { createCloudflareCDN } from './cloudflare.js';
|
|
6
|
+
export { createFastlyCDN } from './fastly.js';
|
|
7
|
+
export { createVercelCDN } from './vercel.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Vercel CDN adapter. Vercel ISR is mostly header-driven (s-maxage), so this is
|
|
2
|
+
// a thin on-demand purge via the project API when a token is supplied; it
|
|
3
|
+
// no-ops (header-only mode) without one.
|
|
4
|
+
|
|
5
|
+
export function createVercelCDN({ token, projectId, teamId } = {}) {
|
|
6
|
+
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
|
|
7
|
+
const qs = teamId ? `?teamId=${teamId}` : '';
|
|
8
|
+
|
|
9
|
+
async function purgeTags(tags) {
|
|
10
|
+
if (!token || !projectId) return;
|
|
11
|
+
await fetch(`https://api.vercel.com/v1/projects/${projectId}/cache/purge${qs}`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers,
|
|
14
|
+
body: JSON.stringify({ tags }),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
purge: () => Promise.resolve(), // Vercel purges by tag; URL purge is header-driven
|
|
20
|
+
purgeTags,
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/headers.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Cache-Control header builder. Origin ISR works without these, but emitting
|
|
2
|
+
// them lets ANY standards-compliant CDN do edge ISR for free (s-maxage +
|
|
3
|
+
// stale-while-revalidate), and Cache-Tag/Surrogate-Key enable CDN tag purge.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} entry { maxAge, swrWindow, tags?, partial? }
|
|
7
|
+
* @param {object} config route page config { mode, ... }
|
|
8
|
+
* @param {string} cacheStatus HIT | STALE | MISS | BYPASS
|
|
9
|
+
*/
|
|
10
|
+
export function buildCacheHeaders(entry = {}, config = {}, cacheStatus = 'MISS') {
|
|
11
|
+
const headers = { 'X-What-Cache': cacheStatus };
|
|
12
|
+
|
|
13
|
+
const cacheable = (entry.maxAge > 0 || config.mode === 'static' || config.mode === 'hybrid')
|
|
14
|
+
&& config.mode !== 'server';
|
|
15
|
+
|
|
16
|
+
if (!cacheable) {
|
|
17
|
+
headers['Cache-Control'] = 'private, no-store';
|
|
18
|
+
return headers;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sMaxAge = entry.partial ? 0 : (entry.maxAge || 0);
|
|
22
|
+
const swr = entry.swrWindow != null ? entry.swrWindow : sMaxAge;
|
|
23
|
+
headers['Cache-Control'] = `public, s-maxage=${sMaxAge}, stale-while-revalidate=${swr}`;
|
|
24
|
+
|
|
25
|
+
if (entry.tags && entry.tags.length) {
|
|
26
|
+
headers['Cache-Tag'] = entry.tags.join(','); // Cloudflare / Fastly
|
|
27
|
+
headers['Surrogate-Key'] = entry.tags.join(' '); // Fastly surrogate keys
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return headers;
|
|
31
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// what-isr — origin-first ISR engine for What Framework.
|
|
2
|
+
//
|
|
3
|
+
// Works on ANY host with no CDN: the origin store does stale-while-revalidate,
|
|
4
|
+
// on-demand purge (revalidatePath/revalidateTag), and poll regeneration. With a
|
|
5
|
+
// CDN, Cache-Control headers + the CDN adapters add edge ISR + edge purge as a
|
|
6
|
+
// pure bonus — no infra lock-in.
|
|
7
|
+
|
|
8
|
+
export { createCacheEngine } from './isr.js';
|
|
9
|
+
|
|
10
|
+
// Stores
|
|
11
|
+
export { createMemoryStore } from './stores/memory-store.js';
|
|
12
|
+
export { createFilesystemStore } from './stores/filesystem-store.js';
|
|
13
|
+
export { createRedisStore } from './stores/redis-store.js';
|
|
14
|
+
export { makeEntry, isFresh, isServableStale } from './stores/store-interface.js';
|
|
15
|
+
|
|
16
|
+
// Keying + headers
|
|
17
|
+
export { cacheKey, normalizePath, normalizeQuery, hashKey } from './key.js';
|
|
18
|
+
export { buildCacheHeaders } from './headers.js';
|
|
19
|
+
|
|
20
|
+
// Dynamic paths (getStaticPaths)
|
|
21
|
+
export { resolveStaticPaths, buildPath, decideFallback, isKnownParams } from './paths.js';
|
|
22
|
+
|
|
23
|
+
// On-demand invalidation webhook + poll scheduler
|
|
24
|
+
export { createRevalidateWebhook } from './webhook.js';
|
|
25
|
+
export { createScheduler } from './scheduler.js';
|
|
26
|
+
|
|
27
|
+
// CDN adapters (optional)
|
|
28
|
+
export { createCloudflareCDN, createFastlyCDN, createVercelCDN } from './cdn/index.js';
|