Support dynamic expressions (#3295)

This commit is contained in:
Samy Pessé
2025-06-12 12:25:28 +02:00
committed by GitHub
parent 9316ccd1f4
commit fecdbe8dde
12 changed files with 168 additions and 11 deletions
+45
View File
@@ -113,6 +113,15 @@ export function createDataFetcher(
})
);
},
getRevisionPageDocument(params) {
return trace('getRevisionPageDocument', () =>
getRevisionPageDocument(input, {
spaceId: params.spaceId,
revisionId: params.revisionId,
pageId: params.pageId,
})
);
},
getReusableContent(params) {
return trace('getReusableContent', () =>
getReusableContent(input, {
@@ -417,6 +426,42 @@ const getRevisionPageMarkdown = withCacheKey(
)
);
const getRevisionPageDocument = withCacheKey(
withoutConcurrentExecution(
async (
_,
input: DataFetcherInput,
params: { spaceId: string; revisionId: string; pageId: string }
) => {
'use cache';
return trace(
`getRevisionPageDocument(${params.spaceId}, ${params.revisionId}, ${params.pageId})`,
async () => {
return wrapDataFetcherError(async () => {
const api = apiClient(input);
const res = await api.spaces.getPageDocumentInRevisionById(
params.spaceId,
params.revisionId,
params.pageId,
{
evaluated: true,
},
{
...noCacheFetchOptions,
}
);
cacheTag(...getCacheTagsFromResponse(res));
cacheLife('max');
return res.data;
});
}
);
}
)
);
const getRevisionPageByPath = withCacheKey(
withoutConcurrentExecution(
async (
+1 -1
View File
@@ -1,7 +1,7 @@
export * from './api';
export * from './types';
export * from './pages';
export * from './urls';
export * from './errors';
export * from './lookup';
export * from './visitor';
export * from './pages';
+44 -4
View File
@@ -1,15 +1,30 @@
import type { JSONDocument, RevisionPageDocument, Space } from '@gitbook/api';
import { waitUntil } from '@/lib/waitUntil';
import type { JSONDocument, RevisionPageDocument } from '@gitbook/api';
import type { GitBookSiteContext, GitBookSpaceContext } from '../context';
import { getDataOrNull } from './errors';
import type { GitBookDataFetcher } from './types';
/**
* Get the document for a page.
*/
export async function getPageDocument(
dataFetcher: GitBookDataFetcher,
space: Space,
context: GitBookSpaceContext | GitBookSiteContext,
page: RevisionPageDocument
): Promise<JSONDocument | null> {
const { dataFetcher, space } = context;
if (
'site' in context &&
(context.site.id === 'site_JOVzv' || context.site.id === 'site_IxAYj')
) {
return getDataOrNull(
dataFetcher.getRevisionPageDocument({
spaceId: space.id,
revisionId: space.revision,
pageId: page.id,
})
);
}
if (page.documentId) {
return getDataOrNull(
dataFetcher.getDocument({ spaceId: space.id, documentId: page.documentId })
@@ -26,5 +41,30 @@ export async function getPageDocument(
);
}
// Pre-fetch the document to start filling the cache before we migrate to this API.
if (isInPercentRollout(space.id, 10)) {
await waitUntil(
getDataOrNull(
dataFetcher.getRevisionPageDocument({
spaceId: space.id,
revisionId: space.revision,
pageId: page.id,
})
)
);
}
return null;
}
function isInPercentRollout(value: string, rollout: number) {
return getRandomPercent(value) < rollout;
}
function getRandomPercent(value: string) {
const hash = value.split('').reduce((acc, char) => {
return acc + char.charCodeAt(0);
}, 0);
return hash % 100;
}
@@ -106,6 +106,15 @@ export interface GitBookDataFetcher {
pageId: string;
}): Promise<DataFetcherResponse<string>>;
/**
* Get the document of a page by its path.
*/
getRevisionPageDocument(params: {
spaceId: string;
revisionId: string;
pageId: string;
}): Promise<DataFetcherResponse<api.JSONDocument>>;
/**
* Get a document by its space ID and document ID.
*/
+1 -1
View File
@@ -10,7 +10,7 @@
"typecheck": "tsc --noEmit",
"e2e": "playwright test e2e/internal.spec.ts",
"e2e-customers": "playwright test e2e/customers.spec.ts",
"unit": "bun test {src,packages}",
"unit": "bun test {src,packages} --preload ./tests/preload-bun.ts",
"generate": "gitbook-icons ./public/~gitbook/static/icons custom-icons && gitbook-math ./public/~gitbook/static/math",
"copy:icons": "gitbook-icons ./public/~gitbook/static/icons",
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
@@ -9,7 +9,6 @@ import {
} from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import type { GitBookSiteContext, GitBookSpaceContext } from '@v2/lib/context';
import { getPageDocument } from '@v2/lib/data';
import type { GitBookLinker } from '@v2/lib/links';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
@@ -29,6 +28,7 @@ import { PageControlButtons } from './PageControlButtons';
import { PrintButton } from './PrintButton';
import './pdf.css';
import { sanitizeGitBookAppURL } from '@/lib/app';
import { getPageDocument } from '@v2/lib/data';
const DEFAULT_LIMIT = 100;
@@ -224,8 +224,7 @@ async function PDFPageDocument(props: {
context: GitBookSpaceContext;
}) {
const { page, context } = props;
const { space } = context;
const document = await getPageDocument(context.dataFetcher, space, page);
const document = await getPageDocument(context, page);
return (
<PrintPage id={getPagePDFContainerId(page)}>
@@ -62,7 +62,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, page);
const document = await getPageDocument(context, page);
return (
<PageContextProvider pageId={page.id} spaceId={context.space.id} title={page.title}>
+33
View File
@@ -493,6 +493,39 @@ export const getRevisionPageByPath = cache({
},
});
/**
* Get a document from a page by its ID
*/
export const getRevisionPageDocument = cache({
name: 'api.getRevisionPageDocument.v1',
tag: (spaceId, revisionId) =>
getCacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
tagImmutable: true,
getKeySuffix: getAPIContextId,
get: async (
spaceId: string,
revisionId: string,
pageId: string,
options: CacheFunctionOptions
) => {
const apiCtx = await api();
const response = await apiCtx.client.spaces.getPageDocumentInRevisionById(
spaceId,
revisionId,
pageId,
{
evaluated: true,
},
{
...noCacheFetchOptions,
signal: options.signal,
}
);
return cacheResponse(response, cacheTtl_7days);
},
});
/**
* Resolve a file by its ID.
* It should not be used directly, use `getRevisionFile` instead.
+1 -1
View File
@@ -155,7 +155,7 @@ export async function resolveContentRef(
});
if (resolveAnchorText) {
const document = await getPageDocument(dataFetcher, space, page);
const document = await getPageDocument(context, page);
if (document) {
const block = getBlockById(document, anchor);
if (block) {
+13
View File
@@ -25,6 +25,7 @@ import {
getRevision,
getRevisionFile,
getRevisionPageByPath,
getRevisionPageDocument,
getRevisionPages,
getSiteRedirectBySource,
getSpace,
@@ -241,6 +242,18 @@ function getDataFetcherV1(apiTokenOverride?: string): GitBookDataFetcher {
);
},
getRevisionPageDocument(params) {
return withAPI(() =>
wrapDataFetcherError(async () => {
return getRevisionPageDocument(
params.spaceId,
params.revisionId,
params.pageId
);
})
);
},
getRevisionPageByPath(params) {
return withAPI(() =>
wrapDataFetcherError(async () => {
+10
View File
@@ -1,4 +1,6 @@
import type { ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types';
import { getCloudflareContext as getCloudflareContextV2 } from '@v2/lib/data/cloudflare';
import { isV2 } from './v2';
let pendings: Array<Promise<unknown>> = [];
@@ -47,6 +49,14 @@ export async function waitUntil(promise: Promise<unknown>) {
return;
}
if (isV2()) {
const context = getCloudflareContextV2();
if (context) {
context.ctx.waitUntil(promise);
return;
}
}
const cloudflareContext = await getGlobalContext();
if ('waitUntil' in cloudflareContext) {
cloudflareContext.waitUntil(promise);
+8
View File
@@ -0,0 +1,8 @@
import { mock } from 'bun:test';
/**
* Mock the `server-only` module to avoid errors when running tests as it doesn't work well in Bun
*/
mock.module('server-only', () => {
return {};
});