From 684eec0ccf78911ae97c1231eb413cfaf28d9b39 Mon Sep 17 00:00:00 2001 From: Gabriel Pinto Date: Wed, 30 Oct 2024 18:19:58 +0000 Subject: [PATCH] fix: remove fetch cache, return byte array as body --- package-lock.json | 2 +- packages/next-on-fleek/package.json | 2 +- .../src/buildApplication/buildWorkerFile.ts | 16 +- .../src/buildApplication/generateGlobalJs.ts | 5 +- .../templates/_worker.js/index.ts | 5 +- .../templates/_worker.js/utils/cache.ts | 126 ------- .../templates/_worker.js/utils/fetch.ts | 9 +- .../templates/_worker.js/utils/request.ts | 4 - .../next-on-fleek/templates/cache/adaptor.ts | 319 ------------------ .../templates/cache/cache-api.ts | 34 -- .../next-on-fleek/templates/cache/index.ts | 1 - packages/next-on-fleek/templates/cache/kv.ts | 34 -- 12 files changed, 14 insertions(+), 543 deletions(-) delete mode 100644 packages/next-on-fleek/templates/_worker.js/utils/cache.ts delete mode 100644 packages/next-on-fleek/templates/cache/adaptor.ts delete mode 100644 packages/next-on-fleek/templates/cache/cache-api.ts delete mode 100644 packages/next-on-fleek/templates/cache/index.ts delete mode 100644 packages/next-on-fleek/templates/cache/kv.ts diff --git a/package-lock.json b/package-lock.json index c7bade2c0..ad826f935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14520,7 +14520,7 @@ }, "packages/next-on-fleek": { "name": "@fleek-platform/next-on-fleek", - "version": "1.15.1", + "version": "1.15.3", "license": "MIT", "dependencies": { "acorn": "^8.8.0", diff --git a/packages/next-on-fleek/package.json b/packages/next-on-fleek/package.json index bfef4fb91..0cd97e5fc 100644 --- a/packages/next-on-fleek/package.json +++ b/packages/next-on-fleek/package.json @@ -1,6 +1,6 @@ { "name": "@fleek-platform/next-on-fleek", - "version": "1.15.2", + "version": "1.15.3", "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/src/index.d.ts", diff --git a/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts b/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts index fae620dc1..01ac613b3 100644 --- a/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts +++ b/packages/next-on-fleek/src/buildApplication/buildWorkerFile.ts @@ -51,7 +51,7 @@ export async function buildWorkerFile( { outputDir, workerJsDir, - nopDistDir, + // nopDistDir, templatesDir, customEntrypoint, minify, @@ -100,13 +100,13 @@ export async function buildWorkerFile( outfile: outputFile, }); - await build({ - ...defaultBuildOpts, - entryPoints: ['adaptor.ts', 'cache-api.ts', 'kv.ts'].map(fileName => - join(templatesDir, 'cache', fileName), - ), - outdir: join(nopDistDir, 'cache'), - }); + // await build({ + // ...defaultBuildOpts, + // entryPoints: ['adaptor.ts', 'cache-api.ts', 'kv.ts'].map(fileName => + // join(templatesDir, 'cache', fileName), + // ), + // outdir: join(nopDistDir, 'cache'), + // }); if (customEntrypoint) { cliLog(`Using custom worker entrypoint '${customEntrypoint}'`); diff --git a/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts b/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts index 6cb5b9942..97cc4f110 100644 --- a/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts +++ b/packages/next-on-fleek/src/buildApplication/generateGlobalJs.ts @@ -86,12 +86,9 @@ export function generateGlobalJs(): string { globalThis.fetch = async (...args) => { const request = new Request(...args); - let response = await handleInlineAssetRequest(request); + const response = await handleInlineAssetRequest(request); if (response) return response; - // response = await handleSuspenseCacheRequest(request); - // if (response) return response; - setRequestUserAgentIfNeeded(request); return originalFetch(request); diff --git a/packages/next-on-fleek/templates/_worker.js/index.ts b/packages/next-on-fleek/templates/_worker.js/index.ts index aa37e277e..dd04bd1f0 100644 --- a/packages/next-on-fleek/templates/_worker.js/index.ts +++ b/packages/next-on-fleek/templates/_worker.js/index.ts @@ -145,8 +145,7 @@ async function adaptFleekRequestToFetch( async function adaptFetchResponseToFleekResponse( response: Response, ): Promise { - const body = await response.text(); - const headers = {}; + const headers: Record = {}; response.headers.forEach((value, key) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -156,6 +155,6 @@ async function adaptFetchResponseToFleekResponse( return { status: response.status, headers, - body, + body: await response.bytes(), }; } diff --git a/packages/next-on-fleek/templates/_worker.js/utils/cache.ts b/packages/next-on-fleek/templates/_worker.js/utils/cache.ts deleted file mode 100644 index 42d9ac9b1..000000000 --- a/packages/next-on-fleek/templates/_worker.js/utils/cache.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { CacheAdaptor, IncrementalCacheValue } from '../../cache'; -import { SUSPENSE_CACHE_URL } from '../../cache'; - -// https://github.com/vercel/next.js/blob/48a566bc/packages/next/src/server/lib/incremental-cache/fetch-cache.ts#L19 -const CACHE_TAGS_HEADER = 'x-vercel-cache-tags'; -// https://github.com/vercel/next.js/blob/ba23d986/packages/next/src/lib/constants.ts#L18 -const NEXT_CACHE_SOFT_TAGS_HEADER = 'x-next-cache-soft-tags'; - -const REQUEST_CONTEXT_KEY = Symbol.for('__fleek-request-context__'); - -/** - * Handles an internal request to the suspense cache. - * - * @param request Incoming request to handle. - * @returns Response to the request, or null if the request is not for the suspense cache. - */ -export async function handleSuspenseCacheRequest(request: Request) { - const baseUrl = `https://${SUSPENSE_CACHE_URL}/v1/suspense-cache/`; - if (!request.url.startsWith(baseUrl)) return null; - - try { - const url = new URL(request.url); - const cache = await getSuspenseCacheAdaptor(); - - if (url.pathname === '/v1/suspense-cache/revalidate') { - // Update the revalidated timestamp for the tags in the tags manifest. - const tags = url.searchParams.get('tags')?.split(',') ?? []; - - for (const tag of tags) { - await cache.revalidateTag(tag); - } - - return new Response(null, { status: 200 }); - } - - // Extract the cache key from the URL. - const cacheKey = url.pathname.replace('/v1/suspense-cache/', ''); - if (!cacheKey.length) { - return new Response('Invalid cache key', { status: 400 }); - } - - switch (request.method) { - case 'GET': { - const softTags = getTagsFromHeader( - request, - NEXT_CACHE_SOFT_TAGS_HEADER, - ); - - // Retrieve the value from the cache. - const data = await cache.get(cacheKey, { softTags }); - if (!data) return new Response(null, { status: 404 }); - - return new Response(JSON.stringify(data.value), { - status: 200, - headers: { - 'Content-Type': 'application/json', - 'x-vercel-cache-state': 'fresh', - age: `${(Date.now() - (data.lastModified ?? Date.now())) / 1000}`, - }, - }); - } - case 'POST': { - // Retrieve request context. - const reqCtx = (globalThis as unknown as Record)[ - REQUEST_CONTEXT_KEY - ] as { ctx: ExecutionContext }; - - const update = async () => { - // Update the value in the cache. - const body = await request.json(); - // Falling back to the cache tags header for Next.js 13.5+ - if (body.data.tags === undefined) { - body.tags ??= getTagsFromHeader(request, CACHE_TAGS_HEADER) ?? []; - } - - await cache.set(cacheKey, body); - }; - - if (reqCtx) { - // Avoid waiting for the cache to update before responding, if possible. - reqCtx.ctx.waitUntil(update()); - } else { - await update(); - } - - return new Response(null, { status: 200 }); - } - default: - return new Response(null, { status: 405 }); - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - return new Response('Error handling cache request', { status: 500 }); - } -} - -/** - * Gets the cache adaptor to use for the suspense cache. - * - * @returns Adaptor for the suspense cache. - */ -export async function getSuspenseCacheAdaptor(): Promise { - if (process.env.__NEXT_ON_PAGES__KV_SUSPENSE_CACHE) { - return getInternalCacheAdaptor('kv'); - } - - return getInternalCacheAdaptor('cache-api'); -} - -/** - * Gets an internal cache adaptor. - * - * @param type The type of adaptor to get. - * @returns A new instance of the adaptor. - */ -async function getInternalCacheAdaptor( - type: 'kv' | 'cache-api', -): Promise { - const adaptor = await import(`./__next-on-fleek-dist__/cache/${type}.js`); - return new adaptor.default(); -} - -function getTagsFromHeader(req: Request, key: string): string[] | undefined { - return req.headers.get(key)?.split(',')?.filter(Boolean); -} diff --git a/packages/next-on-fleek/templates/_worker.js/utils/fetch.ts b/packages/next-on-fleek/templates/_worker.js/utils/fetch.ts index ac00502b8..01d9134bc 100644 --- a/packages/next-on-fleek/templates/_worker.js/utils/fetch.ts +++ b/packages/next-on-fleek/templates/_worker.js/utils/fetch.ts @@ -1,5 +1,3 @@ -import { handleSuspenseCacheRequest } from './cache'; - /** * Patches the global fetch in ways necessary for Next.js (/next-on-fleek) applications * to work @@ -21,12 +19,7 @@ function applyPatch() { globalThis.fetch = async (...args) => { const request = new Request(...args); - let response = await handleInlineAssetRequest(request); - if (response) return response; - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - response = await handleSuspenseCacheRequest(request); + const response = await handleInlineAssetRequest(request); if (response) return response; setRequestUserAgentIfNeeded(request); diff --git a/packages/next-on-fleek/templates/_worker.js/utils/request.ts b/packages/next-on-fleek/templates/_worker.js/utils/request.ts index daff28a42..a01fdd74f 100644 --- a/packages/next-on-fleek/templates/_worker.js/utils/request.ts +++ b/packages/next-on-fleek/templates/_worker.js/utils/request.ts @@ -1,5 +1,3 @@ -import { SUSPENSE_CACHE_URL } from '../../cache'; - /** * Adjusts the request so that it is formatted as if it were provided by Vercel * @@ -26,7 +24,5 @@ export function adjustRequestForVercel(request: Request): Request { ); } - adjustedHeaders.set('x-vercel-sc-host', SUSPENSE_CACHE_URL); - return new Request(request, { headers: adjustedHeaders }); } diff --git a/packages/next-on-fleek/templates/cache/adaptor.ts b/packages/next-on-fleek/templates/cache/adaptor.ts deleted file mode 100644 index 1b9edea3c..000000000 --- a/packages/next-on-fleek/templates/cache/adaptor.ts +++ /dev/null @@ -1,319 +0,0 @@ -// NOTE: This is given the same name that the environment variable has in the Next.js source code. -export const SUSPENSE_CACHE_URL = 'INTERNAL_SUSPENSE_CACHE_HOSTNAME.local'; - -// https://github.com/vercel/next.js/blob/f6babb4/packages/next/src/lib/constants.ts#23 -const NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_'; - -// Set to track the revalidated tags in requests. -const revalidatedTags = new Set(); - -/** Generic adaptor for the Suspense Cache. */ -export class CacheAdaptor { - /** The tags manifest for fetch calls. */ - public tagsManifest: TagsManifest | undefined; - /** The key used for the tags manifest in the cache. */ - public tagsManifestKey = 'tags-manifest'; - /** Promise that resolves when tags manifest is loaded */ - public tagsManifestPromise: Promise | undefined; - - /** - * @param ctx The incremental cache context from Next.js. NOTE: This is not currently utilised in NOP. - */ - constructor(protected ctx: Record = {}) {} - - /** - * Retrieves an entry from the storage mechanism. - * - * @param key Key for the item. - * @returns The value, or null if no entry exists. - */ - public async retrieve(key: string): Promise { - throw new Error(`Method not implemented - ${key}`); - } - - /** - * Updates an entry in the storage mechanism. - * - * @param key Key for the item. - * @param value The value to update. - */ - public async update( - key: string, - value: string, - revalidate?: number, - ): Promise { - throw new Error(`Method not implemented - ${key}, ${value}, ${revalidate}`); - } - - /** - * Puts a new entry in the suspense cache. - * - * @param key Key for the item in the suspense cache. - * @param value The cached value to add to the suspense cache. - */ - public async set(key: string, value: IncrementalCacheValue): Promise { - const newEntry: CacheHandlerValue = { - lastModified: Date.now(), - value, - }; - - // Update the cache entry. - const updateOp = this.update( - key, - JSON.stringify(newEntry), - value.revalidate, - ); - - switch (newEntry.value?.kind) { - case 'FETCH': { - // Update the tags with the cache key. - const tags = getTagsFromEntry(newEntry); - await this.setTags(tags, { cacheKey: key }); - - const derivedTags = getDerivedTags(tags); - const implicitTags = derivedTags.map( - tag => `${NEXT_CACHE_IMPLICIT_TAG_ID}${tag}`, - ); - - [...derivedTags, ...implicitTags].forEach(tag => - revalidatedTags.delete(tag), - ); - } - } - - // Make sure the cache has been updated before returning - await updateOp; - } - - /** - * Retrieves an entry from the suspense cache. - * - * @param key Key for the item in the suspense cache. - * @param opts Soft cache tags used when checking if an entry is stale. - * @returns The cached value, or null if no entry exists. - */ - public async get( - key: string, - { softTags }: { softTags?: string[] }, - ): Promise { - // Get entry from the cache. - const entryPromise = this.retrieve(key); - - // Start loading the tags manifest. - const tagsManifestLoad = this.loadTagsManifest(); - - const entry = await entryPromise; - if (!entry) return null; - - let data: CacheHandlerValue; - try { - data = JSON.parse(entry) as CacheHandlerValue; - } catch (e) { - // Failed to parse the cache entry, so it's invalid. - return null; - } - - switch (data.value?.kind) { - case 'FETCH': { - // Await for the tags manifest to end loading. - await tagsManifestLoad; - - // Check if the cache entry is stale or fresh based on the tags. - const tags = getTagsFromEntry(data); - const combinedTags = softTags - ? [...tags, ...softTags] - : getDerivedTags(tags); - - const isStale = combinedTags.some(tag => { - // If a revalidation has been triggered, the current entry is stale. - if (revalidatedTags.has(tag)) return true; - - const tagEntry = this.tagsManifest?.items?.[tag]; - return ( - tagEntry?.revalidatedAt && - tagEntry?.revalidatedAt >= (data.lastModified ?? Date.now()) - ); - }); - - // Don't return stale data from the cache. - return isStale ? null : data; - } - default: { - return data; - } - } - } - - /** - * Revalidates a tag in the suspense cache's tags manifest. - * - * @param tag Tag to revalidate. - */ - public async revalidateTag(tag: string): Promise { - // Update the revalidated timestamp for the tags in the tags manifest. - await this.setTags([tag], { revalidatedAt: Date.now() }); - - revalidatedTags.add(tag); - } - - /** - * Loads the tags manifest from the suspense cache. - * - * @param force Whether to force a reload of the tags manifest. - */ - public async loadTagsManifest(force = false): Promise { - // Load tags manifest if missing or refresh if forced. - const shouldLoad = force || !this.tagsManifest; - - if (!shouldLoad) { - return; - } - - // If the tags manifest is not already being loaded, kickstart the retrieval. - if (!this.tagsManifestPromise) { - this.tagsManifestPromise = this.loadTagsManifestInternal(); - } - - await this.tagsManifestPromise; - } - - /** - * Internal method to load the tags manifest from the suspense cache. - */ - private async loadTagsManifestInternal(): Promise { - try { - const rawManifest = await this.retrieve(this.tagsManifestKey); - if (rawManifest) { - this.tagsManifest = JSON.parse(rawManifest) as TagsManifest; - } - } catch (e) { - // noop - } - - this.tagsManifest ??= { version: 1, items: {} }; - this.tagsManifestPromise = undefined; - } - - /** - * Saves the local tags manifest in the suspence cache. - */ - public async saveTagsManifest(): Promise { - if (this.tagsManifest) { - const newValue = JSON.stringify(this.tagsManifest); - await this.update(this.tagsManifestKey, newValue); - } - } - - /** - * Sets the tags for an item in the suspense cache's tags manifest. - * - * @param tags Tags for the key. - * @param setTagsInfo Key for the item in the suspense cache, or the new revalidated at timestamp. - */ - public async setTags( - tags: string[], - { cacheKey, revalidatedAt }: { cacheKey?: string; revalidatedAt?: number }, - ): Promise { - await this.loadTagsManifest(true); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const tagsManifest = this.tagsManifest!; - - for (const tag of tags) { - const data = tagsManifest.items[tag] ?? { keys: [] }; - - if (cacheKey && !data.keys.includes(cacheKey)) { - data.keys.push(cacheKey); - } - - if (revalidatedAt) { - data.revalidatedAt = revalidatedAt; - } - - tagsManifest.items[tag] = data; - } - - await this.saveTagsManifest(); - } - - /** - * Builds the full cache key for the suspense cache. - * - * @param key Key for the item in the suspense cache. - * @returns The fully-formed cache key for the suspense cache. - */ - public buildCacheKey(key: string) { - return `https://${SUSPENSE_CACHE_URL}/entry/${key}`; - } -} - -// https://github.com/vercel/next.js/blob/261db49/packages/next/src/server/lib/incremental-cache/file-system-cache.ts#L17 -export type TagsManifest = { - version: 1; - items: { [tag: string]: TagsManifestItem }; -}; -export type TagsManifestItem = { keys: string[]; revalidatedAt?: number }; - -// https://github.com/vercel/next.js/blob/df4c2aa8/packages/next/src/server/response-cache/types.ts#L24 -export type CachedFetchValue = { - kind: 'FETCH'; - data: { - headers: { [k: string]: string }; - body: string; - url: string; - status?: number; - // field used by older versions of Next.js (see: https://github.com/vercel/next.js/blob/fda1ecc/packages/next/src/server/response-cache/types.ts#L23) - tags?: string[]; - }; - // tags are only present with file-system-cache - // fetch cache stores tags outside of cache entry - tags?: string[]; - revalidate: number; -}; - -export type CacheHandlerValue = { - lastModified?: number; - age?: number; - cacheState?: string; - value: IncrementalCacheValue | null; -}; -export type IncrementalCacheValue = CachedFetchValue; - -/** - * Derives a list of tags from the given tags. This is taken from the Next.js source code. - * - * @see https://github.com/vercel/next.js/blob/1286e145/packages/next/src/server/lib/incremental-cache/utils.ts - * - * @param tags Array of tags. - * @returns Derived tags. - */ -export function getDerivedTags(tags: string[]): string[] { - const derivedTags: string[] = ['/']; - - for (const tag of tags || []) { - if (tag.startsWith('/')) { - const pathnameParts = tag.split('/'); - - // we automatically add the current path segments as tags - // for revalidatePath handling - for (let i = 1; i < pathnameParts.length + 1; i++) { - const curPathname = pathnameParts.slice(0, i).join('/'); - - if (curPathname) { - derivedTags.push(curPathname); - - if (!derivedTags.includes(curPathname)) { - derivedTags.push(curPathname); - } - } - } - } else if (!derivedTags.includes(tag)) { - derivedTags.push(tag); - } - } - return derivedTags; -} - -export function getTagsFromEntry(entry: CacheHandlerValue): string[] { - return entry.value?.tags ?? entry.value?.data?.tags ?? []; -} diff --git a/packages/next-on-fleek/templates/cache/cache-api.ts b/packages/next-on-fleek/templates/cache/cache-api.ts deleted file mode 100644 index 80a2f3a83..000000000 --- a/packages/next-on-fleek/templates/cache/cache-api.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { CacheAdaptor } from './adaptor.js'; - -/** Suspense Cache adaptor for the Cache API. */ -export default class CacheApiAdaptor extends CacheAdaptor { - /** Name of the cache to open in the Cache API. */ - public cacheName = 'suspense-cache'; - - constructor(ctx: Record = {}) { - super(ctx); - } - - public override async retrieve(key: string) { - const cache = await caches.open(this.cacheName); - - const response = await cache.match(this.buildCacheKey(key)); - return response ? response.text() : null; - } - - public override async update( - key: string, - value: string, - revalidate?: number, - ) { - const cache = await caches.open(this.cacheName); - - const maxAge = revalidate ?? '31536000'; // 1 year - const response = new Response(value, { - headers: new Headers({ - 'cache-control': `max-age=${maxAge}`, - }), - }); - await cache.put(this.buildCacheKey(key), response); - } -} diff --git a/packages/next-on-fleek/templates/cache/index.ts b/packages/next-on-fleek/templates/cache/index.ts deleted file mode 100644 index 41c162740..000000000 --- a/packages/next-on-fleek/templates/cache/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './adaptor'; diff --git a/packages/next-on-fleek/templates/cache/kv.ts b/packages/next-on-fleek/templates/cache/kv.ts deleted file mode 100644 index af9561c7b..000000000 --- a/packages/next-on-fleek/templates/cache/kv.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { CacheAdaptor } from './adaptor.js'; - -/** Suspense Cache adaptor for Workers KV. */ -export default class KVAdaptor extends CacheAdaptor { - constructor(ctx: Record = {}) { - super(ctx); - } - - public override async retrieve(key: string) { - const value = await process.env.__NEXT_ON_PAGES__KV_SUSPENSE_CACHE?.get( - this.buildCacheKey(key), - ); - - return value ?? null; - } - - public override async update( - key: string, - value: string, - revalidate?: number, - ) { - const expiry = revalidate - ? { - expirationTtl: revalidate, - } - : {}; - - await process.env.__NEXT_ON_PAGES__KV_SUSPENSE_CACHE?.put( - this.buildCacheKey(key), - value, - expiry, - ); - } -}