mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Support dynamic expressions (#3295)
This commit is contained in:
@@ -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,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';
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {};
|
||||
});
|
||||
Reference in New Issue
Block a user