UNPKG

next

Version:

The React Framework

269 lines (268 loc) • 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return FetchCache; } }); const _lrucache = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/lru-cache")); const _approuterheaders = require("../../../client/components/app-router-headers"); const _constants = require("../../../lib/constants"); const _utils = require("./utils"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let memoryCache; let rateLimitedUntil = 0; class FetchCache { static isAvailable(ctx) { return !!(ctx._requestHeaders["x-vercel-sc-host"] || process.env.SUSPENSE_CACHE_URL); } constructor(ctx){ this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE; this.headers = {}; this.revalidatedTags = ctx.revalidatedTags; this.headers["Content-Type"] = "application/json"; if (_approuterheaders.FETCH_CACHE_HEADER in ctx._requestHeaders) { const newHeaders = JSON.parse(ctx._requestHeaders[_approuterheaders.FETCH_CACHE_HEADER]); for(const k in newHeaders){ this.headers[k] = newHeaders[k]; } delete ctx._requestHeaders[_approuterheaders.FETCH_CACHE_HEADER]; } const scHost = ctx._requestHeaders["x-vercel-sc-host"] || process.env.SUSPENSE_CACHE_URL; const scBasePath = ctx._requestHeaders["x-vercel-sc-basepath"] || process.env.SUSPENSE_CACHE_BASEPATH; if (process.env.SUSPENSE_CACHE_AUTH_TOKEN) { this.headers["Authorization"] = `Bearer ${process.env.SUSPENSE_CACHE_AUTH_TOKEN}`; } if (scHost) { this.cacheEndpoint = `https://${scHost}${scBasePath || ""}`; if (this.debug) { console.log("using cache endpoint", this.cacheEndpoint); } } else if (this.debug) { console.log("no cache endpoint available"); } if (ctx.maxMemoryCacheSize) { if (!memoryCache) { if (this.debug) { console.log("using memory store for fetch cache"); } memoryCache = new _lrucache.default({ max: ctx.maxMemoryCacheSize, length ({ value }) { var _JSON_stringify; if (!value) { return 25; } else if (value.kind === "REDIRECT") { return JSON.stringify(value.props).length; } else if (value.kind === "IMAGE") { throw new Error("invariant image should not be incremental-cache"); } else if (value.kind === "FETCH") { return JSON.stringify(value.data || "").length; } else if (value.kind === "ROUTE") { return value.body.length; } // rough estimate of size of cache value return value.html.length + (((_JSON_stringify = JSON.stringify(value.pageData)) == null ? void 0 : _JSON_stringify.length) || 0); } }); } } else { if (this.debug) { console.log("not using memory store for fetch cache"); } } } async revalidateTag(tag) { if (this.debug) { console.log("revalidateTag", tag); } if (Date.now() < rateLimitedUntil) { if (this.debug) { console.log("rate limited ", rateLimitedUntil); } return; } try { const res = await fetch(`${this.cacheEndpoint}/v1/suspense-cache/revalidate?tags=${tag}`, { method: "POST", headers: this.headers, // @ts-expect-error next: { internal: true } }); if (res.status === 429) { const retryAfter = res.headers.get("retry-after") || "60000"; rateLimitedUntil = Date.now() + parseInt(retryAfter); } if (!res.ok) { throw new Error(`Request failed with status ${res.status}.`); } } catch (err) { console.warn(`Failed to revalidate tag ${tag}`, err); } } async get(key, fetchCache, fetchUrl, fetchIdx) { var _data_value; if (!fetchCache) return null; if (Date.now() < rateLimitedUntil) { if (this.debug) { console.log("rate limited"); } return null; } let data = memoryCache == null ? void 0 : memoryCache.get(key); // memory cache data is only leveraged for up to 1 seconds // so that revalidation events can be pulled from source if (Date.now() - ((data == null ? void 0 : data.lastModified) || 0) > 2000) { data = undefined; } // get data from fetch cache if (!data && this.cacheEndpoint) { try { const start = Date.now(); const fetchParams = { internal: true, fetchType: "cache-get", fetchUrl: fetchUrl, fetchIdx }; const res = await fetch(`${this.cacheEndpoint}/v1/suspense-cache/${key}`, { method: "GET", headers: { ...this.headers, "X-Vercel-Cache-Item-Name": fetchUrl }, next: fetchParams }); if (res.status === 429) { const retryAfter = res.headers.get("retry-after") || "60000"; rateLimitedUntil = Date.now() + parseInt(retryAfter); } if (res.status === 404) { if (this.debug) { console.log(`no fetch cache entry for ${key}, duration: ${Date.now() - start}ms`); } return null; } if (!res.ok) { console.error(await res.text()); throw new Error(`invalid response from cache ${res.status}`); } const cached = await res.json(); if (!cached || cached.kind !== "FETCH") { this.debug && console.log({ cached }); throw new Error(`invalid cache value`); } const cacheState = res.headers.get("x-vercel-cache-state"); const age = res.headers.get("age"); data = { value: cached, // if it's already stale set it to a time in the past // if not derive last modified from age lastModified: cacheState !== "fresh" ? Date.now() - _constants.CACHE_ONE_YEAR : Date.now() - parseInt(age || "0", 10) * 1000 }; if (this.debug) { console.log(`got fetch cache entry for ${key}, duration: ${Date.now() - start}ms, size: ${Object.keys(cached).length}, cache-state: ${cacheState}`); } if (data) { memoryCache == null ? void 0 : memoryCache.set(key, data); } } catch (err) { // unable to get data from fetch-cache if (this.debug) { console.error(`Failed to get from fetch-cache`, err); } } } // if a tag was revalidated we don't return stale data if ((data == null ? void 0 : (_data_value = data.value) == null ? void 0 : _data_value.kind) === "FETCH") { const innerData = data.value.data; const derivedTags = (0, _utils.getDerivedTags)(innerData.tags || []); if (derivedTags.some((tag)=>{ return this.revalidatedTags.includes(tag); })) { data = undefined; } } return data || null; } async set(key, data, fetchCache, fetchUrl, fetchIdx) { if (!fetchCache) return; if (Date.now() < rateLimitedUntil) { if (this.debug) { console.log("rate limited"); } return; } memoryCache == null ? void 0 : memoryCache.set(key, { value: data, lastModified: Date.now() }); if (this.cacheEndpoint) { try { const start = Date.now(); if (data !== null && "revalidate" in data) { this.headers["x-vercel-revalidate"] = data.revalidate.toString(); } if (!this.headers["x-vercel-revalidate"] && data !== null && "data" in data) { this.headers["x-vercel-cache-control"] = data.data.headers["cache-control"]; } const body = JSON.stringify(data); const headers = { ...this.headers }; if (data !== null && "data" in data && data.data.tags) { headers["x-vercel-cache-tags"] = data.data.tags.join(","); } if (this.debug) { console.log("set cache", key, { tags: headers["x-vercel-cache-tags"] }); } const fetchParams = { internal: true, fetchType: "cache-set", fetchUrl, fetchIdx }; const res = await fetch(`${this.cacheEndpoint}/v1/suspense-cache/${key}`, { method: "POST", headers: { ...headers, "X-Vercel-Cache-Item-Name": fetchUrl || "" }, body: body, next: fetchParams }); if (res.status === 429) { const retryAfter = res.headers.get("retry-after") || "60000"; rateLimitedUntil = Date.now() + parseInt(retryAfter); } if (!res.ok) { this.debug && console.log(await res.text()); throw new Error(`invalid response ${res.status}`); } if (this.debug) { console.log(`successfully set to fetch-cache for ${key}, duration: ${Date.now() - start}ms, size: ${body.length}`); } } catch (err) { // unable to set to fetch-cache if (this.debug) { console.error(`Failed to update fetch cache`, err); } } } return; } } //# sourceMappingURL=fetch-cache.js.map