Skip to content

Commit

Permalink
Proper data cache tagging for computed content (#2915)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamyPesse authored Mar 4, 2025
1 parent 701eaad commit 05ffd0e
Showing 21 changed files with 373 additions and 180 deletions.
6 changes: 6 additions & 0 deletions .changeset/real-elephants-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'gitbook-v2': patch
'gitbook': patch
---

Improving data cache management for computed content
5 changes: 5 additions & 0 deletions .changeset/two-fans-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gitbook/cache-tags': minor
---

Initial version of the package
15 changes: 15 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
@@ -22,6 +22,17 @@
"wrangler": "^3.109.2",
},
},
"packages/cache-tags": {
"name": "@gitbook/cache-tags",
"version": "0.0.0",
"dependencies": {
"@gitbook/api": "0.96.1",
"assert-never": "^1.2.1",
},
"devDependencies": {
"typescript": "^5.5.3",
},
},
"packages/colors": {
"name": "@gitbook/colors",
"version": "0.2.0",
@@ -42,6 +53,7 @@
"dependencies": {
"@gitbook/api": "0.96.1",
"@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
"@gitbook/icons": "workspace:*",
@@ -122,6 +134,7 @@
"version": "0.1.1",
"dependencies": {
"@gitbook/api": "0.96.1",
"@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0",
"next": "^15.2.0",
"react": "^19.0.0",
@@ -618,6 +631,8 @@

"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],

"@gitbook/cache-tags": ["@gitbook/cache-tags@workspace:packages/cache-tags"],

"@gitbook/colors": ["@gitbook/colors@workspace:packages/colors"],

"@gitbook/emoji-codepoints": ["@gitbook/emoji-codepoints@workspace:packages/emoji-codepoints"],
1 change: 1 addition & 0 deletions packages/cache-tags/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
3 changes: 3 additions & 0 deletions packages/cache-tags/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `@gitbook/cache-tags`

Utility to generate cache tags for GitBook Open.
25 changes: 25 additions & 0 deletions packages/cache-tags/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@gitbook/cache-tags",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"development": "./src/index.ts",
"default": "./dist/index.js"
}
},
"version": "0.0.0",
"dependencies": {
"@gitbook/api": "0.96.1",
"assert-never": "^1.2.1"
},
"devDependencies": {
"typescript": "^5.5.3"
},
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"dev": "tsc -w"
},
"files": ["dist", "src", "README.md", "CHANGELOG.md"]
}
175 changes: 175 additions & 0 deletions packages/cache-tags/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import type { ComputedContentSource } from '@gitbook/api';
import assertNever from 'assert-never';

/**
* Get a stringified cache tag for a given object.
*/
export function getCacheTag(
spec: /**
* All data related to a user
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'user';
user: string;
}
/**
* All data related to a space
*/
| {
tag: 'space';
space: string;
}
/**
* All data related to an integration.
*/
| {
tag: 'integration';
integration: string;
}
/**
* All data related to a change request
*/
| {
tag: 'change-request';
space: string;
changeRequest: string;
}
/**
* Immutable data related to a revision
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'revision';
space: string;
revision: string;
}
/**
* Immutable data related to a document
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'document';
space: string;
document: string;
}
/**
* Immutable data related to a computed document
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'computed-document';
space: string;
integration: string;
}
/**
* All data related to the URL of a content
*/
| {
tag: 'url';
hostname: string;
}
/**
* All data related to a site
*/
| {
tag: 'site';
site: string;
}
/**
* All data related to an OpenAPI spec
*/
| {
tag: 'openapi';
organization: string;
openAPISpec: string;
}
): string {
switch (spec.tag) {
case 'user':
return `user:${spec.user}`;
case 'url':
return `url:${spec.hostname}`;
case 'space':
return `space:${spec.space}`;
case 'change-request':
return `space:${spec.space}:change-request:${spec.changeRequest}`;
case 'revision':
return `space:${spec.space}:revision:${spec.revision}`;
case 'document':
return `space:${spec.space}:document:${spec.document}`;
case 'computed-document':
return `space:${spec.space}:computed-document:${spec.integration}`;
case 'site':
return `site:${spec.site}`;
case 'integration':
return `integration:${spec.integration}`;
case 'openapi':
return `organization:${spec.organization}:openapi:${spec.openAPISpec}`;
default:
assertNever(spec);
}
}

/**
* Get the tags for a computed content source.
*/
export function getComputedContentSourceCacheTags(
inContext: {
spaceId: string;
organizationId: string;
},
source: ComputedContentSource
) {
const tags: string[] = [];

// We add the dependencies as tags, to ensure that the computed content is invalidated
// when the dependencies are updated.
const dependencies = Object.values(source.dependencies ?? {});
if (dependencies.length > 0) {
dependencies.forEach((dependency) => {
switch (dependency.ref.kind) {
case 'space':
tags.push(
getCacheTag({
tag: 'space',
space: dependency.ref.space,
})
);
break;
case 'openapi':
tags.push(
getCacheTag({
tag: 'openapi',
organization: inContext.organizationId,
openAPISpec: dependency.ref.spec,
})
);
break;
default:
// Do not throw for unknown dependency types
// as it might mean we are lacking behind the API version
break;
}
});
} else {
// Push a dummy tag, as the v1 is only using the first tag
tags.push(
getCacheTag({
tag: 'computed-document',
space: inContext.spaceId,
integration: source.integration,
})
);
}

// We invalidate the computed content when a new version of the integration is deployed.
tags.push(
getCacheTag({
tag: 'integration',
integration: source.integration,
})
);

return tags;
}
24 changes: 24 additions & 0 deletions packages/cache-tags/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"types": [
"bun-types" // add Bun global
]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
1 change: 1 addition & 0 deletions packages/gitbook-v2/package.json
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@gitbook/api": "0.96.1",
"@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0",
"server-only": "^0.0.1"
},
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ import {
generateSitePageMetadata,
generateSitePageViewport,
} from '@/components/SitePage';
import { getCacheTag } from '@gitbook/cache-tags';
import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@v2/app/utils';
import { getSiteCacheTag } from '@v2/lib/cache';
import type { Metadata, Viewport } from 'next';
import { unstable_cacheTag as cacheTag } from 'next/cache';

@@ -21,7 +21,12 @@ export default async function Page(props: PageProps) {
const context = await getStaticSiteContext(params);
const pathname = getPagePathFromParams(params);

cacheTag(getSiteCacheTag(context.site.id));
cacheTag(
getCacheTag({
tag: 'site',
site: context.site.id,
})
);

return <SitePage context={context} pageParams={{ pathname }} redirectOnFallback={true} />;
}
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ import {
generateSiteLayoutMetadata,
generateSiteLayoutViewport,
} from '@/components/SiteLayout';
import { getCacheTag } from '@gitbook/cache-tags';
import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils';
import { getSiteCacheTag } from '@v2/lib/cache';
import { GITBOOK_DISABLE_TRACKING } from '@v2/lib/env';
import { unstable_cacheTag as cacheTag } from 'next/cache';

@@ -21,7 +21,12 @@ export default async function SiteStaticLayout({

const context = await getStaticSiteContext(await params);

cacheTag(getSiteCacheTag(context.site.id));
cacheTag(
getCacheTag({
tag: 'site',
site: context.site.id,
})
);

return (
<CustomizationRootLayout customization={context.customization}>
34 changes: 0 additions & 34 deletions packages/gitbook-v2/src/lib/cache.ts

This file was deleted.

Loading
Oops, something went wrong.

0 comments on commit 05ffd0e

Please sign in to comment.