diff --git a/packages/gitbook/src/lib/api.ts b/packages/gitbook/src/lib/api.ts index 2bb2039bc3..2a9a6b092a 100644 --- a/packages/gitbook/src/lib/api.ts +++ b/packages/gitbook/src/lib/api.ts @@ -26,6 +26,7 @@ import { type CacheFunctionOptions, cache, cacheResponse, + getResponseCacheTags, noCacheFetchOptions, parseCacheResponse, } from './cache'; @@ -370,6 +371,17 @@ interface GetRevisionOptions { * These options don't impact the cache key and it means revisions can be shared between different fetches with different metadata options. */ metadata: boolean; + + /** + * Whether to fetch the revision as a computed revision. + * @default true + */ + computed?: boolean; + + /** + * Additional tags to add to the cache entry. + */ + tags?: string[]; } const getAPIContextId = async () => { @@ -382,8 +394,14 @@ const getAPIContextId = async () => { */ export const getRevision = cache({ name: 'api.getRevision.v2', - tag: (spaceId, revisionId) => - getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + tag: (spaceId, revisionId, fetchOptions) => + // Temporary hack to make it work with OpenAPI on v1 + fetchOptions.tags?.[0] ?? + getCacheTag({ + tag: 'revision', + space: spaceId, + revision: revisionId, + }), tagImmutable: true, getKeySuffix: getAPIContextId, get: async ( @@ -405,9 +423,20 @@ export const getRevision = cache({ } ); - return cacheResponse(response, fetchOptions.metadata ? cacheTtl_7days : cacheTtl_1day); + return cacheResponse(response, { + ...(fetchOptions.metadata ? cacheTtl_7days : cacheTtl_1day), + data: { + ...response.data, + tags: getResponseCacheTags(response), + }, + }); + }, + getKeyArgs: ([spaceId, revisionId, fetchOptions]) => { + if (fetchOptions.computed === true) { + return [spaceId, revisionId, { computed: true }]; + } + return [spaceId, revisionId]; }, - getKeyArgs: (args) => [args[0], args[1]], }); /** @@ -415,8 +444,14 @@ export const getRevision = cache({ */ export const getRevisionPages = cache({ name: 'api.getRevisionPages.v4', - tag: (spaceId, revisionId) => - getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + tag: (spaceId, revisionId, fetchOptions) => + // Temporary hack to make it work with OpenAPI on v1 + fetchOptions.tags?.[0] ?? + getCacheTag({ + tag: 'revision', + space: spaceId, + revision: revisionId, + }), tagImmutable: true, getKeySuffix: getAPIContextId, get: async ( @@ -440,10 +475,15 @@ export const getRevisionPages = cache({ return cacheResponse(response, { ...(fetchOptions.metadata ? cacheTtl_7days : cacheTtl_1day), - data: response.data.pages, + data: { ...response.data, tags: getResponseCacheTags(response) }, }); }, - getKeyArgs: (args) => [args[0], args[1]], + getKeyArgs: ([spaceId, revisionId, fetchOptions]) => { + if (fetchOptions.computed === true) { + return [spaceId, revisionId, { computed: true }]; + } + return [spaceId, revisionId]; + }, }); /** diff --git a/packages/gitbook/src/lib/cache/http.ts b/packages/gitbook/src/lib/cache/http.ts index 22103c6efb..97be32b5f6 100644 --- a/packages/gitbook/src/lib/cache/http.ts +++ b/packages/gitbook/src/lib/cache/http.ts @@ -13,6 +13,14 @@ export const noCacheFetchOptions: Partial = { }, }; +/** + * Return the cache tags from the response. + */ +export function getResponseCacheTags(response: Response): string[] { + const cacheTagHeader = response.headers.get('x-gitbook-cache-tag'); + return !cacheTagHeader ? [] : cacheTagHeader.split(','); +} + /** * Parse an HTTP response into a cache entry. */ @@ -26,8 +34,7 @@ export function parseCacheResponse(response: Response): { const cacheControlHeader = response.headers.get('cache-control'); const cacheControl = cacheControlHeader ? parseCacheControl(cacheControlHeader) : null; - const cacheTagHeader = response.headers.get('x-gitbook-cache-tag'); - const tags = !cacheTagHeader ? [] : cacheTagHeader.split(','); + const tags = getResponseCacheTags(response); const entry = { ttl: 60 * 60 * 24, @@ -47,7 +54,7 @@ export function parseCacheResponse(response: Response): { export function cacheResponse( response: Response & { data: Result }, defaultEntry: Partial> = {} -): CacheResult { +): CacheResult { const parsed = parseCacheResponse(response); return { diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index ea5219816b..edd05f25b9 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -8,6 +8,7 @@ import type { GitBookDataFetcher } from '@v2/lib/data/types'; import { createImageResizer } from '@v2/lib/images'; import { createLinker } from '@v2/lib/links'; +import { RevisionPageType } from '@gitbook/api'; import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data'; import { headers } from 'next/headers'; import { @@ -134,9 +135,29 @@ async function getDataFetcherV1(): Promise { getRevision(params) { return wrapDataFetcherError(async () => { - return getRevision(params.spaceId, params.revisionId, { + const { tags, ...revision } = await getRevision(params.spaceId, params.revisionId, { metadata: params.metadata, + computed: false, }); + + if ( + Object.values(revision.pages).some( + (page) => page.type === RevisionPageType.Computed + ) + ) { + const { tags: _tags, ...revision } = await getRevision( + params.spaceId, + params.revisionId, + { + metadata: params.metadata, + computed: true, + tags, + } + ); + return revision; + } + + return revision; }); }, @@ -183,9 +204,22 @@ async function getDataFetcherV1(): Promise { getRevisionPages(params) { return wrapDataFetcherError(async () => { - return getRevisionPages(params.spaceId, params.revisionId, { + const { pages, tags } = await getRevisionPages(params.spaceId, params.revisionId, { metadata: params.metadata, + computed: false, }); + + if (pages.some((page) => page.type === RevisionPageType.Computed)) { + const { pages } = await getRevisionPages(params.spaceId, params.revisionId, { + metadata: params.metadata, + computed: true, + tags, + }); + + return pages; + } + + return pages; }); },