diff --git a/.changeset/real-elephants-lie.md b/.changeset/real-elephants-lie.md new file mode 100644 index 000000000..8519eb0b2 --- /dev/null +++ b/.changeset/real-elephants-lie.md @@ -0,0 +1,6 @@ +--- +'gitbook-v2': patch +'gitbook': patch +--- + +Improving data cache management for computed content diff --git a/.changeset/two-fans-joke.md b/.changeset/two-fans-joke.md new file mode 100644 index 000000000..c7442bee9 --- /dev/null +++ b/.changeset/two-fans-joke.md @@ -0,0 +1,5 @@ +--- +'@gitbook/cache-tags': minor +--- + +Initial version of the package diff --git a/bun.lock b/bun.lock index a983f15be..ddcf20b0f 100644 --- a/bun.lock +++ b/bun.lock @@ -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"], diff --git a/packages/cache-tags/.gitignore b/packages/cache-tags/.gitignore new file mode 100644 index 000000000..849ddff3b --- /dev/null +++ b/packages/cache-tags/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/packages/cache-tags/README.md b/packages/cache-tags/README.md new file mode 100644 index 000000000..14fc5c943 --- /dev/null +++ b/packages/cache-tags/README.md @@ -0,0 +1,3 @@ +# `@gitbook/cache-tags` + +Utility to generate cache tags for GitBook Open. \ No newline at end of file diff --git a/packages/cache-tags/package.json b/packages/cache-tags/package.json new file mode 100644 index 000000000..aa960d125 --- /dev/null +++ b/packages/cache-tags/package.json @@ -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"] +} diff --git a/packages/cache-tags/src/index.ts b/packages/cache-tags/src/index.ts new file mode 100644 index 000000000..8c8701f0b --- /dev/null +++ b/packages/cache-tags/src/index.ts @@ -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; +} diff --git a/packages/cache-tags/tsconfig.json b/packages/cache-tags/tsconfig.json new file mode 100644 index 000000000..92db2d902 --- /dev/null +++ b/packages/cache-tags/tsconfig.json @@ -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"] +} diff --git a/packages/gitbook-v2/package.json b/packages/gitbook-v2/package.json index 95097532c..1c7a6fd72 100644 --- a/packages/gitbook-v2/package.json +++ b/packages/gitbook-v2/package.json @@ -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" }, diff --git a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[pagePath]/page.tsx b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[pagePath]/page.tsx index 87a8cbbf8..43b9be8c4 100644 --- a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[pagePath]/page.tsx +++ b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[pagePath]/page.tsx @@ -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 ; } diff --git a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/layout.tsx b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/layout.tsx index 9e8cb36aa..2d5de4284 100644 --- a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/layout.tsx +++ b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/layout.tsx @@ -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 ( diff --git a/packages/gitbook-v2/src/lib/cache.ts b/packages/gitbook-v2/src/lib/cache.ts deleted file mode 100644 index 38e1d334c..000000000 --- a/packages/gitbook-v2/src/lib/cache.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Get the cache tag for a site. - */ -export function getSiteCacheTag(siteId: string) { - return `site:${siteId}`; -} - -/** - * Get the cache tag for a hostname. - */ -export function getHostnameCacheTag(hostname: string) { - return `url:${hostname}`; -} - -/** - * Get the cache tag for an OpenAPI spec. - */ -export function getOpenAPISpecCacheTag(organizationId: string, slug: string) { - return `organization:${organizationId}:openapi:${slug}`; -} - -/** - * Get the cache tag for a space. - */ -export function getSpaceCacheTag(spaceId: string) { - return `space:${spaceId}`; -} - -/** - * Get the cache tag for a change request. - */ -export function getChangeRequestCacheTag(spaceId: string, changeRequestId: string) { - return `space:${spaceId}:change-request:${changeRequestId}`; -} diff --git a/packages/gitbook-v2/src/lib/data/api.ts b/packages/gitbook-v2/src/lib/data/api.ts index 72698b430..a3a254a28 100644 --- a/packages/gitbook-v2/src/lib/data/api.ts +++ b/packages/gitbook-v2/src/lib/data/api.ts @@ -1,13 +1,7 @@ import { type ComputedContentSource, GitBookAPI } from '@gitbook/api'; +import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags'; import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env'; import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'; -import { - getChangeRequestCacheTag, - getHostnameCacheTag, - getOpenAPISpecCacheTag, - getSiteCacheTag, - getSpaceCacheTag, -} from '../cache'; import type { GitBookDataFetcher } from './types'; interface DataFetcherInput { @@ -125,6 +119,7 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo }, getComputedDocument(params) { return getComputedDocument(input, { + organizationId: params.organizationId, spaceId: params.spaceId, source: params.source, }); @@ -183,7 +178,12 @@ async function getSpace( 'use cache'; cacheLife('days'); - cacheTag(getSpaceCacheTag(params.spaceId)); + cacheTag( + getCacheTag({ + tag: 'space', + space: params.spaceId, + }) + ); const res = await getAPI(input).spaces.getSpaceById(params.spaceId, { shareKey: params.shareKey, @@ -207,7 +207,13 @@ async function getChangeRequest( params.spaceId, params.changeRequestId ); - cacheTag(getChangeRequestCacheTag(params.spaceId, res.data.id)); + cacheTag( + getCacheTag({ + tag: 'change-request', + space: params.spaceId, + changeRequest: res.data.id, + }) + ); return res.data; } catch (error) { if (checkHasErrorCode(error, 404)) { @@ -335,14 +341,24 @@ async function getComputedDocument( input: DataFetcherInput, params: { spaceId: string; + organizationId: string; source: ComputedContentSource; } ) { 'use cache'; - // TODO: we need to resolve dependencies and pass them in the cache key cacheLife('days'); + cacheTag( + ...getComputedContentSourceCacheTags( + { + spaceId: params.spaceId, + organizationId: params.organizationId, + }, + params.source + ) + ); + const res = await getAPI(input).spaces.getComputedDocument(params.spaceId, { source: params.source, }); @@ -386,7 +402,13 @@ async function getLatestOpenAPISpecVersionContent( ) { 'use cache'; - cacheTag(getOpenAPISpecCacheTag(params.organizationId, params.slug)); + cacheTag( + getCacheTag({ + tag: 'openapi', + organization: params.organizationId, + openAPISpec: params.slug, + }) + ); cacheLife('days'); try { @@ -417,7 +439,12 @@ async function getPublishedContentByUrl( const { url, visitorAuthToken, redirectOnError } = params; const hostname = new URL(url).hostname; - cacheTag(getHostnameCacheTag(hostname)); + cacheTag( + getCacheTag({ + tag: 'url', + hostname, + }) + ); cacheLife('days'); const res = await getAPI(input).urls.getPublishedContentByUrl({ @@ -427,7 +454,12 @@ async function getPublishedContentByUrl( }); if ('site' in res.data) { - cacheTag(getSiteCacheTag(res.data.site)); + cacheTag( + getCacheTag({ + tag: 'site', + site: res.data.site, + }) + ); } return res.data; @@ -443,7 +475,12 @@ async function getPublishedContentSite( ) { 'use cache'; - cacheTag(getSiteCacheTag(params.siteId)); + cacheTag( + getCacheTag({ + tag: 'site', + site: params.siteId, + }) + ); cacheLife('days'); const res = await getAPI(input).orgs.getPublishedContentSite( @@ -467,7 +504,12 @@ async function getSiteRedirectBySource( ) { 'use cache'; - cacheTag(getSiteCacheTag(params.siteId)); + cacheTag( + getCacheTag({ + tag: 'site', + site: params.siteId, + }) + ); cacheLife('days'); try { diff --git a/packages/gitbook-v2/src/lib/data/types.ts b/packages/gitbook-v2/src/lib/data/types.ts index 0a0bbe2a0..b86fb09fc 100644 --- a/packages/gitbook-v2/src/lib/data/types.ts +++ b/packages/gitbook-v2/src/lib/data/types.ts @@ -103,6 +103,7 @@ export interface GitBookDataFetcher { * Get a computed document by its space ID and computed source. */ getComputedDocument(params: { + organizationId: string; spaceId: string; source: api.ComputedContentSource; }): Promise; diff --git a/packages/gitbook-v2/src/lib/data/utils.ts b/packages/gitbook-v2/src/lib/data/utils.ts index b65a89ae6..4e7f4a436 100644 --- a/packages/gitbook-v2/src/lib/data/utils.ts +++ b/packages/gitbook-v2/src/lib/data/utils.ts @@ -1,4 +1,4 @@ -import type { RevisionPageDocument } from '@gitbook/api'; +import type { RevisionPageDocument, Space } from '@gitbook/api'; import type { GitBookDataFetcher } from './types'; /** @@ -6,14 +6,18 @@ import type { GitBookDataFetcher } from './types'; */ export async function getPageDocument( dataFetcher: GitBookDataFetcher, - spaceId: string, + space: Space, page: RevisionPageDocument ) { if (page.documentId) { - return dataFetcher.getDocument({ spaceId, documentId: page.documentId }); + return dataFetcher.getDocument({ spaceId: space.id, documentId: page.documentId }); } if (page.computed) { - return dataFetcher.getComputedDocument({ spaceId, source: page.computed }); + return dataFetcher.getComputedDocument({ + organizationId: space.organization, + spaceId: space.id, + source: page.computed, + }); } return null; diff --git a/packages/gitbook/package.json b/packages/gitbook/package.json index bbbcf467b..8c696cb8b 100644 --- a/packages/gitbook/package.json +++ b/packages/gitbook/package.json @@ -18,6 +18,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:*", diff --git a/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx b/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx index e698a7c9f..691ec72c9 100644 --- a/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx +++ b/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx @@ -231,7 +231,7 @@ async function PDFPageDocument(props: { }) { const { page, context } = props; const { space } = context; - const document = await getPageDocument(context.dataFetcher, space.id, page); + const document = await getPageDocument(context.dataFetcher, space, page); return ( diff --git a/packages/gitbook/src/components/SitePage/SitePage.tsx b/packages/gitbook/src/components/SitePage/SitePage.tsx index c8af02d28..50ee21f33 100644 --- a/packages/gitbook/src/components/SitePage/SitePage.tsx +++ b/packages/gitbook/src/components/SitePage/SitePage.tsx @@ -67,7 +67,7 @@ export async function SitePage(props: SitePageProps) { const withSections = Boolean(sections && sections.list.length > 0); const headerOffset = { sectionsHeader: withSections, topHeader: withTopHeader }; - const document = await getPageDocument(context.dataFetcher, context.space.id, page); + const document = await getPageDocument(context.dataFetcher, context.space, page); return ( <> diff --git a/packages/gitbook/src/lib/api.ts b/packages/gitbook/src/lib/api.ts index ecd403946..6ce3d41bf 100644 --- a/packages/gitbook/src/lib/api.ts +++ b/packages/gitbook/src/lib/api.ts @@ -1,4 +1,5 @@ import { AsyncLocalStorage } from 'node:async_hooks'; +import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags'; import 'server-only'; import { @@ -14,7 +15,6 @@ import { type RevisionReusableContent, } from '@gitbook/api'; import type { GitBookDataFetcher } from '@v2/lib/data/types'; -import assertNever from 'assert-never'; import { headers } from 'next/headers'; import { batch } from './async'; @@ -171,7 +171,7 @@ export type PublishedContentWithCache = export const getUserById = cache({ name: 'api.getUserById', tag: (userId) => - getAPICacheTag({ + getCacheTag({ tag: 'user', user: userId, }), @@ -204,7 +204,7 @@ export const getUserById = cache({ export const getLatestOpenAPISpecVersionContent = cache({ name: 'api.getLatestOpenApiSpecVersionContent', tag: (organization, openAPISpec) => - getAPICacheTag({ + getCacheTag({ tag: 'openapi', organization, openAPISpec, @@ -240,7 +240,7 @@ export const getLatestOpenAPISpecVersionContent = cache({ export const getPublishedContentByUrl = cache({ name: 'api.getPublishedContentByUrl.v4', tag: (url) => - getAPICacheTag({ + getCacheTag({ tag: 'url', hostname: new URL(url).hostname, }), @@ -301,7 +301,7 @@ export const getPublishedContentByUrl = cache({ */ export const getSpace = cache({ name: 'api.getSpace', - tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }), + tag: (spaceId) => getCacheTag({ tag: 'space', space: spaceId }), get: async (spaceId: string, shareKey: string | undefined, options: CacheFunctionOptions) => { const apiCtx = await api(); const response = await apiCtx.client.spaces.getSpaceById( @@ -330,7 +330,7 @@ function checkHasErrorCode(error: unknown, code: number) { export const getChangeRequest = cache({ name: 'api.getChangeRequest', tag: (spaceId, changeRequestId) => - getAPICacheTag({ tag: 'change-request', space: spaceId, changeRequest: changeRequestId }), + getCacheTag({ tag: 'change-request', space: spaceId, changeRequest: changeRequestId }), get: async (spaceId: string, changeRequestId: string, options: CacheFunctionOptions) => { const apiCtx = await api(); try { @@ -379,7 +379,7 @@ const getAPIContextId = async () => { export const getRevision = cache({ name: 'api.getRevision.v2', tag: (spaceId, revisionId) => - getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), getKeySuffix: getAPIContextId, get: async ( spaceId: string, @@ -411,7 +411,7 @@ export const getRevision = cache({ export const getRevisionPages = cache({ name: 'api.getRevisionPages.v4', tag: (spaceId, revisionId) => - getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), getKeySuffix: getAPIContextId, get: async ( spaceId: string, @@ -446,7 +446,7 @@ export const getRevisionPages = cache({ export const getRevisionPageByPath = cache({ name: 'api.getRevisionPageByPath.v3', tag: (spaceId, revisionId) => - getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), getKeySuffix: getAPIContextId, get: async ( spaceId: string, @@ -492,7 +492,7 @@ export const getRevisionPageByPath = cache({ const getRevisionFileById = cache({ name: 'api.getRevisionFile.v3', tag: (spaceId, revisionId) => - getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), get: async ( spaceId: string, revisionId: string, @@ -528,7 +528,7 @@ const getRevisionFileById = cache({ const getRevisionReusableContentById = cache({ name: 'api.getRevisionReusableContentById.v1', tag: (spaceId, revisionId) => - getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), getKeySuffix: getAPIContextId, get: async ( spaceId: string, @@ -569,7 +569,7 @@ const getRevisionReusableContentById = cache({ const getRevisionAllFiles = cache({ name: 'api.getRevisionAllFiles.v2', tag: (spaceId, revisionId) => - getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), + getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }), get: async (spaceId: string, revisionId: string, options: CacheFunctionOptions) => { const response = await getAll( async (params) => { @@ -684,7 +684,7 @@ export const getReusableContent = async ( export const getDocument = cache({ name: 'api.getDocument.v2', tag: (spaceId, documentId) => - getAPICacheTag({ tag: 'document', space: spaceId, document: documentId }), + getCacheTag({ tag: 'document', space: spaceId, document: documentId }), getKeySuffix: getAPIContextId, get: async (spaceId: string, documentId: string, options: CacheFunctionOptions) => { const apiCtx = await api(); @@ -712,14 +712,21 @@ export const getDocument = cache({ */ export const getComputedDocument = cache({ name: 'api.getComputedDocument', - tag: (spaceId, source) => - getAPICacheTag({ - tag: 'computed-document', - space: spaceId, - integration: source.integration, - }), + tag: (organizationId, spaceId, source) => + getComputedContentSourceCacheTags( + { + organizationId, + spaceId, + }, + source + )[0], getKeySuffix: getAPIContextId, - get: async (spaceId: string, source: ComputedContentSource, options: CacheFunctionOptions) => { + get: async ( + _organizationId: string, + spaceId: string, + source: ComputedContentSource, + options: CacheFunctionOptions + ) => { const apiCtx = await api(); const response = await apiCtx.client.spaces.getComputedDocument( spaceId, @@ -750,7 +757,7 @@ function validateSiteRedirectSource(source: string) { */ export const getSiteRedirectBySource = cache({ name: 'api.getSiteRedirectBySource', - tag: ({ siteId }) => getAPICacheTag({ tag: 'site', site: siteId }), + tag: ({ siteId }) => getCacheTag({ tag: 'site', site: siteId }), getKeySuffix: getAPIContextId, get: async ( args: { @@ -811,7 +818,7 @@ export const getSiteRedirectBySource = cache({ */ export const getSite = cache({ name: 'api.getSite', - tag: (_organizationId, siteId) => getAPICacheTag({ tag: 'site', site: siteId }), + tag: (_organizationId, siteId) => getCacheTag({ tag: 'site', site: siteId }), getKeySuffix: getAPIContextId, get: async (organizationId: string, siteId: string, options: CacheFunctionOptions) => { const apiCtx = await api(); @@ -830,7 +837,7 @@ export const getSite = cache({ */ export const getPublishedContentSite = cache({ name: 'api.getPublishedContentSite', - tag: ({ siteId }) => getAPICacheTag({ tag: 'site', site: siteId }), + tag: ({ siteId }) => getCacheTag({ tag: 'site', site: siteId }), getKeySuffix: getAPIContextId, get: async ( args: { @@ -907,7 +914,7 @@ export async function getSpaceContentData( */ export const searchSiteContent = cache({ name: 'api.searchSiteContent', - tag: (_organizationId, siteId) => getAPICacheTag({ tag: 'site', site: siteId }), + tag: (_organizationId, siteId) => getCacheTag({ tag: 'site', site: siteId }), getKeySuffix: getAPIContextId, get: async ( organizationId: string, @@ -947,7 +954,7 @@ export const searchSiteContent = cache({ */ export const renderIntegrationUi = cache({ name: 'api.renderIntegrationUi', - tag: (integrationName) => getAPICacheTag({ tag: 'integration', integration: integrationName }), + tag: (integrationName) => getCacheTag({ tag: 'integration', integration: integrationName }), get: async ( integrationName: string, request: RequestRenderIntegrationUI, @@ -971,7 +978,7 @@ export const renderIntegrationUi = cache({ */ export const getEmbedByUrlInSpace = cache({ name: 'api.getEmbedByUrlInSpace', - tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }), + tag: (spaceId) => getCacheTag({ tag: 'space', space: spaceId }), get: async (spaceId: string, url: string, options: CacheFunctionOptions) => { const apiCtx = await api(); const response = await apiCtx.client.spaces.getEmbedByUrlInSpace( @@ -986,99 +993,6 @@ export const getEmbedByUrlInSpace = cache({ }, }); -/** - * Create a cache tag for the API. - */ -export function getAPICacheTag( - spec: // All data related to a user - | { - 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 - | { - tag: 'revision'; - space: string; - revision: string; - } - // Immutable data related to a document - | { - tag: 'document'; - space: string; - document: string; - } - // Immutable data related to a computed document - | { - 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 collection - | { - tag: 'collection'; - collection: 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 'collection': - return `collection:${spec.collection}`; - case 'site': - return `site:${spec.site}`; - case 'integration': - return `integration:${spec.integration}`; - case 'openapi': - return `organization:${spec.organization}:openapi:${spec.openAPISpec}`; - default: - assertNever(spec); - } -} - /** * Return the user agent to use for API requests. */ diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index eed8261c3..e2f6416ef 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -130,7 +130,7 @@ export async function resolveContentRef( text = `#${anchor}`; if (resolveAnchorText) { - const document = await getPageDocument(dataFetcher, space.id, page); + const document = await getPageDocument(dataFetcher, space, page); if (document) { const block = getBlockById(document, anchor); if (block) { diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index cc0900613..d4314c896 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -128,7 +128,7 @@ async function getDataFetcherV1(): Promise { }, getComputedDocument(params) { - return getComputedDocument(params.spaceId, params.source); + return getComputedDocument(params.organizationId, params.spaceId, params.source); }, getRevisionPages(params) {