| "use strict"; |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| Object.defineProperty(exports, "default", { |
| enumerable: true, |
| get: function() { |
| return ResponseCache; |
| } |
| }); |
| 0 && __export(require("./types")); |
| const _batcher = require("../../lib/batcher"); |
| const _lrucache = require("../lib/lru-cache"); |
| const _log = require("../../build/output/log"); |
| const _scheduler = require("../../lib/scheduler"); |
| const _utils = require("./utils"); |
| _export_star(require("./types"), exports); |
| function _export_star(from, to) { |
| Object.keys(from).forEach(function(k) { |
| if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) { |
| Object.defineProperty(to, k, { |
| enumerable: true, |
| get: function() { |
| return from[k]; |
| } |
| }); |
| } |
| }); |
| return from; |
| } |
| |
| |
| |
| function parsePositiveInt(envValue, fallback) { |
| if (!envValue) return fallback; |
| const parsed = parseInt(envValue, 10); |
| return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const DEFAULT_TTL_MS = parsePositiveInt(process.env.NEXT_PRIVATE_RESPONSE_CACHE_TTL, 10000); |
| |
| |
| |
| const DEFAULT_MAX_SIZE = parsePositiveInt(process.env.NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE, 150); |
| |
| |
| |
| const KEY_SEPARATOR = '\0'; |
| |
| |
| |
| const TTL_SENTINEL = '__ttl_sentinel__'; |
| |
| |
| function createCacheKey(pathname, invocationID) { |
| return `${pathname}${KEY_SEPARATOR}${invocationID ?? TTL_SENTINEL}`; |
| } |
| |
| |
| |
| function extractInvocationID(compoundKey) { |
| const separatorIndex = compoundKey.lastIndexOf(KEY_SEPARATOR); |
| if (separatorIndex === -1) return undefined; |
| const invocationID = compoundKey.slice(separatorIndex + 1); |
| return invocationID === TTL_SENTINEL ? undefined : invocationID; |
| } |
| class ResponseCache { |
| constructor(minimal_mode, maxSize = DEFAULT_MAX_SIZE, ttl = DEFAULT_TTL_MS){ |
| this.batcher = _batcher.Batcher.create({ |
| |
| |
| cacheKeyFn: ({ key, isOnDemandRevalidate })=>`${key}-${isOnDemandRevalidate ? '1' : '0'}`, |
| |
| |
| |
| schedulerFn: _scheduler.scheduleOnNextTick |
| }); |
| this.revalidateBatcher = _batcher.Batcher.create({ |
| |
| |
| |
| schedulerFn: _scheduler.scheduleOnNextTick |
| }); |
| |
| |
| |
| |
| this.evictedInvocationIDs = new Set(); |
| this.minimal_mode = minimal_mode; |
| this.maxSize = maxSize; |
| this.ttl = ttl; |
| |
| this.cache = new _lrucache.LRUCache(maxSize, undefined, (compoundKey)=>{ |
| const invocationID = extractInvocationID(compoundKey); |
| if (invocationID) { |
| |
| |
| |
| |
| |
| |
| |
| |
| if (this.evictedInvocationIDs.size >= 100) { |
| const first = this.evictedInvocationIDs.values().next().value; |
| if (first) this.evictedInvocationIDs.delete(first); |
| } |
| this.evictedInvocationIDs.add(invocationID); |
| } |
| }); |
| } |
| async get(key, responseGenerator, context) { |
| |
| |
| if (!key) { |
| return responseGenerator({ |
| hasResolved: false, |
| previousCacheEntry: null |
| }); |
| } |
| |
| if (this.minimal_mode) { |
| const cacheKey = createCacheKey(key, context.invocationID); |
| const cachedItem = this.cache.get(cacheKey); |
| if (cachedItem) { |
| |
| |
| if (context.invocationID !== undefined) { |
| return (0, _utils.toResponseCacheEntry)(cachedItem.entry); |
| } |
| |
| const now = Date.now(); |
| if (cachedItem.expiresAt > now) { |
| return (0, _utils.toResponseCacheEntry)(cachedItem.entry); |
| } |
| |
| this.cache.remove(cacheKey); |
| } |
| |
| if (context.invocationID && this.evictedInvocationIDs.has(context.invocationID)) { |
| (0, _log.warnOnce)(`Response cache entry was evicted for invocation ${context.invocationID}. ` + `Consider increasing NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE (current: ${this.maxSize}).`); |
| } |
| } |
| const { incrementalCache, isOnDemandRevalidate = false, isFallback = false, isRoutePPREnabled = false, isPrefetch = false, waitUntil, routeKind, invocationID } = context; |
| const response = await this.batcher.batch({ |
| key, |
| isOnDemandRevalidate |
| }, (_cacheKey, resolve)=>{ |
| const promise = this.handleGet(key, responseGenerator, { |
| incrementalCache, |
| isOnDemandRevalidate, |
| isFallback, |
| isRoutePPREnabled, |
| isPrefetch, |
| routeKind, |
| invocationID |
| }, resolve); |
| |
| |
| if (waitUntil) { |
| waitUntil(promise); |
| } |
| return promise; |
| }); |
| return (0, _utils.toResponseCacheEntry)(response); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| async handleGet(key, responseGenerator, context, resolve) { |
| let previousIncrementalCacheEntry = null; |
| let resolved = false; |
| try { |
| |
| previousIncrementalCacheEntry = !this.minimal_mode ? await context.incrementalCache.get(key, { |
| kind: (0, _utils.routeKindToIncrementalCacheKind)(context.routeKind), |
| isRoutePPREnabled: context.isRoutePPREnabled, |
| isFallback: context.isFallback |
| }) : null; |
| if (previousIncrementalCacheEntry && !context.isOnDemandRevalidate) { |
| resolve(previousIncrementalCacheEntry); |
| resolved = true; |
| if (!previousIncrementalCacheEntry.isStale || context.isPrefetch) { |
| |
| return previousIncrementalCacheEntry; |
| } |
| } |
| |
| const incrementalResponseCacheEntry = await this.revalidate(key, context.incrementalCache, context.isRoutePPREnabled, context.isFallback, responseGenerator, previousIncrementalCacheEntry, previousIncrementalCacheEntry !== null && !context.isOnDemandRevalidate, undefined, context.invocationID); |
| |
| if (!incrementalResponseCacheEntry) { |
| |
| if (this.minimal_mode) { |
| const cacheKey = createCacheKey(key, context.invocationID); |
| this.cache.remove(cacheKey); |
| } |
| return null; |
| } |
| |
| if (context.isOnDemandRevalidate && !resolved) { |
| return incrementalResponseCacheEntry; |
| } |
| return incrementalResponseCacheEntry; |
| } catch (err) { |
| |
| |
| if (resolved) { |
| console.error(err); |
| return null; |
| } |
| throw err; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async revalidate(key, incrementalCache, isRoutePPREnabled, isFallback, responseGenerator, previousIncrementalCacheEntry, hasResolved, waitUntil, invocationID) { |
| return this.revalidateBatcher.batch(key, ()=>{ |
| const promise = this.handleRevalidate(key, incrementalCache, isRoutePPREnabled, isFallback, responseGenerator, previousIncrementalCacheEntry, hasResolved, invocationID); |
| |
| if (waitUntil) waitUntil(promise); |
| return promise; |
| }); |
| } |
| async handleRevalidate(key, incrementalCache, isRoutePPREnabled, isFallback, responseGenerator, previousIncrementalCacheEntry, hasResolved, invocationID) { |
| try { |
| |
| const responseCacheEntry = await responseGenerator({ |
| hasResolved, |
| previousCacheEntry: previousIncrementalCacheEntry, |
| isRevalidating: true |
| }); |
| if (!responseCacheEntry) { |
| return null; |
| } |
| |
| const incrementalResponseCacheEntry = await (0, _utils.fromResponseCacheEntry)({ |
| ...responseCacheEntry, |
| isMiss: !previousIncrementalCacheEntry |
| }); |
| |
| |
| if (incrementalResponseCacheEntry.cacheControl) { |
| if (this.minimal_mode) { |
| |
| |
| |
| const cacheKey = createCacheKey(key, invocationID); |
| this.cache.set(cacheKey, { |
| entry: incrementalResponseCacheEntry, |
| expiresAt: Date.now() + this.ttl |
| }); |
| } else { |
| await incrementalCache.set(key, incrementalResponseCacheEntry.value, { |
| cacheControl: incrementalResponseCacheEntry.cacheControl, |
| isRoutePPREnabled, |
| isFallback |
| }); |
| } |
| } |
| return incrementalResponseCacheEntry; |
| } catch (err) { |
| |
| |
| if (previousIncrementalCacheEntry == null ? void 0 : previousIncrementalCacheEntry.cacheControl) { |
| const revalidate = Math.min(Math.max(previousIncrementalCacheEntry.cacheControl.revalidate || 3, 3), 30); |
| const expire = previousIncrementalCacheEntry.cacheControl.expire === undefined ? undefined : Math.max(revalidate + 3, previousIncrementalCacheEntry.cacheControl.expire); |
| await incrementalCache.set(key, previousIncrementalCacheEntry.value, { |
| cacheControl: { |
| revalidate: revalidate, |
| expire: expire |
| }, |
| isRoutePPREnabled, |
| isFallback |
| }); |
| } |
| |
| throw err; |
| } |
| } |
| } |
|
|
| |