| | import { DetachedPromise } from '../../lib/detached-promise' |
| | import type { ResponseCacheEntry, ResponseGenerator } from './types' |
| |
|
| | |
| | |
| | |
| | |
| | export default class WebResponseCache { |
| | pendingResponses: Map<string, Promise<ResponseCacheEntry | null>> |
| | previousCacheItem?: { |
| | key: string |
| | entry: ResponseCacheEntry | null |
| | expiresAt: number |
| | } |
| | minimalMode?: boolean |
| |
|
| | constructor(minimalMode: boolean) { |
| | this.pendingResponses = new Map() |
| | |
| | |
| | Object.assign(this, { minimalMode }) |
| | } |
| |
|
| | public get( |
| | key: string | null, |
| | responseGenerator: ResponseGenerator, |
| | context: { |
| | isOnDemandRevalidate?: boolean |
| | isPrefetch?: boolean |
| | incrementalCache: any |
| | } |
| | ): Promise<ResponseCacheEntry | null> { |
| | |
| | const pendingResponseKey = key |
| | ? `${key}-${context.isOnDemandRevalidate ? '1' : '0'}` |
| | : null |
| |
|
| | const pendingResponse = pendingResponseKey |
| | ? this.pendingResponses.get(pendingResponseKey) |
| | : null |
| | if (pendingResponse) { |
| | return pendingResponse |
| | } |
| |
|
| | const { |
| | promise, |
| | resolve: resolver, |
| | reject: rejecter, |
| | } = new DetachedPromise<ResponseCacheEntry | null>() |
| | if (pendingResponseKey) { |
| | this.pendingResponses.set(pendingResponseKey, promise) |
| | } |
| |
|
| | let hasResolved = false |
| | const resolve = (cacheEntry: ResponseCacheEntry | null) => { |
| | if (pendingResponseKey) { |
| | |
| | this.pendingResponses.set( |
| | pendingResponseKey, |
| | Promise.resolve(cacheEntry) |
| | ) |
| | } |
| | if (!hasResolved) { |
| | hasResolved = true |
| | resolver(cacheEntry) |
| | } |
| | } |
| |
|
| | |
| | |
| | if ( |
| | pendingResponseKey && |
| | this.minimalMode && |
| | this.previousCacheItem?.key === pendingResponseKey && |
| | this.previousCacheItem.expiresAt > Date.now() |
| | ) { |
| | resolve(this.previousCacheItem.entry) |
| | this.pendingResponses.delete(pendingResponseKey) |
| | return promise |
| | } |
| |
|
| | |
| | |
| | |
| | ;(async () => { |
| | try { |
| | const cacheEntry = await responseGenerator({ hasResolved }) |
| | const resolveValue = |
| | cacheEntry === null |
| | ? null |
| | : { |
| | ...cacheEntry, |
| | isMiss: true, |
| | } |
| |
|
| | |
| | if (!context.isOnDemandRevalidate) { |
| | resolve(resolveValue) |
| | } |
| |
|
| | if (key && cacheEntry && cacheEntry.cacheControl) { |
| | this.previousCacheItem = { |
| | key: pendingResponseKey || key, |
| | entry: cacheEntry, |
| | expiresAt: Date.now() + 1000, |
| | } |
| | } else { |
| | this.previousCacheItem = undefined |
| | } |
| |
|
| | if (context.isOnDemandRevalidate) { |
| | resolve(resolveValue) |
| | } |
| | } catch (err) { |
| | |
| | |
| | if (hasResolved) { |
| | console.error(err) |
| | } else { |
| | rejecter(err as Error) |
| | } |
| | } finally { |
| | if (pendingResponseKey) { |
| | this.pendingResponses.delete(pendingResponseKey) |
| | } |
| | } |
| | })() |
| | return promise |
| | } |
| | } |
| |
|