Compare commits

...

1 Commits

Author SHA1 Message Date
Greg Bergé 617e3854f3 Fix computed revision cache in v1 2025-04-17 09:24:30 +02:00
3 changed files with 94 additions and 13 deletions
+48 -8
View File
@@ -26,6 +26,7 @@ import {
type CacheFunctionOptions, type CacheFunctionOptions,
cache, cache,
cacheResponse, cacheResponse,
getResponseCacheTags,
noCacheFetchOptions, noCacheFetchOptions,
parseCacheResponse, parseCacheResponse,
} from './cache'; } 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. * These options don't impact the cache key and it means revisions can be shared between different fetches with different metadata options.
*/ */
metadata: boolean; 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 () => { const getAPIContextId = async () => {
@@ -382,8 +394,14 @@ const getAPIContextId = async () => {
*/ */
export const getRevision = cache({ export const getRevision = cache({
name: 'api.getRevision.v2', name: 'api.getRevision.v2',
tag: (spaceId, revisionId) => tag: (spaceId, revisionId, fetchOptions) =>
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), // Temporary hack to make it work with OpenAPI on v1
fetchOptions.tags?.[0] ??
getCacheTag({
tag: 'revision',
space: spaceId,
revision: revisionId,
}),
tagImmutable: true, tagImmutable: true,
getKeySuffix: getAPIContextId, getKeySuffix: getAPIContextId,
get: async ( 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({ export const getRevisionPages = cache({
name: 'api.getRevisionPages.v4', name: 'api.getRevisionPages.v4',
tag: (spaceId, revisionId) => tag: (spaceId, revisionId, fetchOptions) =>
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), // Temporary hack to make it work with OpenAPI on v1
fetchOptions.tags?.[0] ??
getCacheTag({
tag: 'revision',
space: spaceId,
revision: revisionId,
}),
tagImmutable: true, tagImmutable: true,
getKeySuffix: getAPIContextId, getKeySuffix: getAPIContextId,
get: async ( get: async (
@@ -440,10 +475,15 @@ export const getRevisionPages = cache({
return cacheResponse(response, { return cacheResponse(response, {
...(fetchOptions.metadata ? cacheTtl_7days : cacheTtl_1day), ...(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];
},
}); });
/** /**
+10 -3
View File
@@ -13,6 +13,14 @@ export const noCacheFetchOptions: Partial<RequestInit> = {
}, },
}; };
/**
* 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. * 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 cacheControlHeader = response.headers.get('cache-control');
const cacheControl = cacheControlHeader ? parseCacheControl(cacheControlHeader) : null; const cacheControl = cacheControlHeader ? parseCacheControl(cacheControlHeader) : null;
const cacheTagHeader = response.headers.get('x-gitbook-cache-tag'); const tags = getResponseCacheTags(response);
const tags = !cacheTagHeader ? [] : cacheTagHeader.split(',');
const entry = { const entry = {
ttl: 60 * 60 * 24, ttl: 60 * 60 * 24,
@@ -47,7 +54,7 @@ export function parseCacheResponse(response: Response): {
export function cacheResponse<Result, DefaultData = Result>( export function cacheResponse<Result, DefaultData = Result>(
response: Response & { data: Result }, response: Response & { data: Result },
defaultEntry: Partial<CacheResult<DefaultData>> = {} defaultEntry: Partial<CacheResult<DefaultData>> = {}
): CacheResult<DefaultData extends Result ? Result : DefaultData> { ): CacheResult<DefaultData extends undefined ? Result : DefaultData> {
const parsed = parseCacheResponse(response); const parsed = parseCacheResponse(response);
return { return {
+36 -2
View File
@@ -8,6 +8,7 @@ import type { GitBookDataFetcher } from '@v2/lib/data/types';
import { createImageResizer } from '@v2/lib/images'; import { createImageResizer } from '@v2/lib/images';
import { createLinker } from '@v2/lib/links'; import { createLinker } from '@v2/lib/links';
import { RevisionPageType } from '@gitbook/api';
import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data'; import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
import { import {
@@ -134,9 +135,29 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
getRevision(params) { getRevision(params) {
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
return getRevision(params.spaceId, params.revisionId, { const { tags, ...revision } = await getRevision(params.spaceId, params.revisionId, {
metadata: params.metadata, 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<GitBookDataFetcher> {
getRevisionPages(params) { getRevisionPages(params) {
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
return getRevisionPages(params.spaceId, params.revisionId, { const { pages, tags } = await getRevisionPages(params.spaceId, params.revisionId, {
metadata: params.metadata, 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;
}); });
}, },