Skip to content

Commit e5f0cc8

Browse files
authored
Respond with all tags of all cache used for page generation (#2973)
1 parent 9b5f971 commit e5f0cc8

File tree

6 files changed

+71
-6
lines changed

6 files changed

+71
-6
lines changed

packages/gitbook/src/cloudflare-entrypoint.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @ts-ignore
22
import nextOnPagesHandler from '@cloudflare/next-on-pages/fetch-handler';
33

4+
import { withResponseCacheTags } from './lib/cache/response';
45
import { withMiddlewareHeadersStorage } from './lib/middleware';
56

67
/**
@@ -9,8 +10,8 @@ import { withMiddlewareHeadersStorage } from './lib/middleware';
910
*/
1011
export default {
1112
async fetch(request, env, ctx) {
12-
const response = await withMiddlewareHeadersStorage(() =>
13-
nextOnPagesHandler.fetch(request, env, ctx)
13+
const response = await withResponseCacheTags(() =>
14+
withMiddlewareHeadersStorage(() => nextOnPagesHandler.fetch(request, env, ctx))
1415
);
1516

1617
return response;

packages/gitbook/src/lib/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export const getUserById = cache({
178178
tag: 'user',
179179
user: userId,
180180
}),
181+
tagImmutable: true,
181182
get: async (userId: string, options: CacheFunctionOptions) => {
182183
try {
183184
const apiCtx = await api();
@@ -379,6 +380,7 @@ export const getRevision = cache({
379380
name: 'api.getRevision.v2',
380381
tag: (spaceId, revisionId) =>
381382
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
383+
tagImmutable: true,
382384
getKeySuffix: getAPIContextId,
383385
get: async (
384386
spaceId: string,
@@ -411,6 +413,7 @@ export const getRevisionPages = cache({
411413
name: 'api.getRevisionPages.v4',
412414
tag: (spaceId, revisionId) =>
413415
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
416+
tagImmutable: true,
414417
getKeySuffix: getAPIContextId,
415418
get: async (
416419
spaceId: string,
@@ -446,6 +449,7 @@ export const getRevisionPageByPath = cache({
446449
name: 'api.getRevisionPageByPath.v3',
447450
tag: (spaceId, revisionId) =>
448451
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
452+
tagImmutable: true,
449453
getKeySuffix: getAPIContextId,
450454
get: async (
451455
spaceId: string,
@@ -492,6 +496,7 @@ const getRevisionFileById = cache({
492496
name: 'api.getRevisionFile.v3',
493497
tag: (spaceId, revisionId) =>
494498
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
499+
tagImmutable: true,
495500
get: async (
496501
spaceId: string,
497502
revisionId: string,
@@ -528,6 +533,7 @@ const getRevisionReusableContentById = cache({
528533
name: 'api.getRevisionReusableContentById.v1',
529534
tag: (spaceId, revisionId) =>
530535
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
536+
tagImmutable: true,
531537
getKeySuffix: getAPIContextId,
532538
get: async (
533539
spaceId: string,
@@ -569,6 +575,7 @@ const getRevisionAllFiles = cache({
569575
name: 'api.getRevisionAllFiles.v2',
570576
tag: (spaceId, revisionId) =>
571577
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
578+
tagImmutable: true,
572579
get: async (spaceId: string, revisionId: string, options: CacheFunctionOptions) => {
573580
const response = await getAll(
574581
async (params) => {
@@ -684,6 +691,7 @@ export const getDocument = cache({
684691
name: 'api.getDocument.v2',
685692
tag: (spaceId, documentId) =>
686693
getCacheTag({ tag: 'document', space: spaceId, document: documentId }),
694+
tagImmutable: true,
687695
getKeySuffix: getAPIContextId,
688696
get: async (spaceId: string, documentId: string, options: CacheFunctionOptions) => {
689697
const apiCtx = await api();

packages/gitbook/src/lib/cache/cache.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { assertIsNotV2 } from '../v2';
77
import { waitUntil } from '../waitUntil';
88
import { cacheBackends } from './backends';
99
import { memoryCache } from './memory';
10+
import { addResponseCacheTag } from './response';
1011
import type { CacheBackend, CacheEntry } from './types';
1112

1213
export type CacheFunctionOptions = {
@@ -54,6 +55,9 @@ export interface CacheDefinition<Args extends any[], Result> {
5455
/** Tag to associate to the entry */
5556
tag?: (...args: Args) => string;
5657

58+
/** If true, the tag will not be sent to the HTTP response, as we consider it immutable */
59+
tagImmutable?: boolean;
60+
5761
/** Filter the arguments that should be taken into consideration for the cache key */
5862
getKeyArgs?: (args: Args) => any[];
5963

@@ -134,6 +138,11 @@ export function cache<Args extends any[], Result>(
134138
let result: readonly [CacheEntry, string] | null = null;
135139
const tag = cacheDef.tag?.(...args);
136140

141+
// Add the cache tag to the HTTP response
142+
if (tag && !cacheDef.tagImmutable) {
143+
addResponseCacheTag(tag);
144+
}
145+
137146
// Try the memory backend, independently of the other backends as it doesn't have a network cost
138147
const memoryEntry = await memoryCache.get({ key, tag });
139148
if (memoryEntry) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { AsyncLocalStorage } from 'node:async_hooks';
2+
3+
const symbolResponseCacheTagsLocalStorage = Symbol.for('__gitbook-response-cache-tags__');
4+
5+
/**
6+
* Wrap a response handler with a storage to store cache tags and write them as header to the response.
7+
*/
8+
export async function withResponseCacheTags(handler: () => Promise<Response>): Promise<Response> {
9+
const responseCacheTagsLocalStorage =
10+
// @ts-ignore
11+
(globalThis[symbolResponseCacheTagsLocalStorage] as AsyncLocalStorage<Set<string>>) ??
12+
new AsyncLocalStorage<Set<string>>();
13+
// @ts-ignore
14+
globalThis[symbolResponseCacheTagsLocalStorage] = responseCacheTagsLocalStorage;
15+
16+
const responseCacheTags = new Set<string>();
17+
const response = await responseCacheTagsLocalStorage.run(responseCacheTags, handler);
18+
19+
if (responseCacheTags.size > 0) {
20+
const headerCacheTag = Array.from(responseCacheTags).join(',');
21+
response.headers.set('cache-tag', headerCacheTag);
22+
response.headers.set('x-gitbook-cache-tag', headerCacheTag);
23+
}
24+
25+
return response;
26+
}
27+
28+
/**
29+
* Add a cache tag to the HTTP response.
30+
* This is a hack for v1, for v2, we use ISR and do not cache the whole response.
31+
*/
32+
export function addResponseCacheTag(...tags: string[]) {
33+
const responseCacheTagsLocalStorage =
34+
// @ts-ignore
35+
(globalThis[symbolResponseCacheTagsLocalStorage] as AsyncLocalStorage<Set<string>>) ??
36+
new AsyncLocalStorage<Set<string>>();
37+
// @ts-ignore
38+
globalThis[symbolResponseCacheTagsLocalStorage] = responseCacheTagsLocalStorage;
39+
40+
const store = responseCacheTagsLocalStorage.getStore();
41+
tags.forEach((tag) => {
42+
store?.add(tag);
43+
});
44+
}

packages/gitbook/src/lib/middleware.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { AsyncLocalStorage } from 'node:async_hooks';
22

3+
/*
4+
* This code is ONLY for v1.
5+
*/
6+
37
/**
48
* Set a header on the middleware response.
59
* We do this because of https://github.com/opennextjs/opennextjs-cloudflare/issues/92

packages/gitbook/src/middleware.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { joinPath, normalizePathname } from '@/lib/paths';
3434
import { getProxyModeBasePath } from '@/lib/proxy';
3535
import { MiddlewareHeaders } from '@v2/lib/middleware';
36+
import { addResponseCacheTag } from './lib/cache/response';
3637

3738
export const config = {
3839
matcher:
@@ -287,10 +288,8 @@ export async function middleware(request: NextRequest) {
287288
}
288289
}
289290

290-
if (resolved.cacheTags && resolved.cacheTags.length > 0) {
291-
const headerCacheTag = resolved.cacheTags.join(',');
292-
setMiddlewareHeader(response, 'cache-tag', headerCacheTag);
293-
setMiddlewareHeader(response, 'x-gitbook-cache-tag', headerCacheTag);
291+
if (resolved.cacheTags) {
292+
addResponseCacheTag(...resolved.cacheTags);
294293
}
295294

296295
return response;

0 commit comments

Comments
 (0)