Skip to content

Commit 07eab98

Browse files
authored
Optimize fetching of revisions (#3382)
1 parent 711cf38 commit 07eab98

File tree

24 files changed

+137
-572
lines changed

24 files changed

+137
-572
lines changed

packages/gitbook-v2/src/lib/context.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getSiteStructureSections } from '@/lib/sites';
22
import type {
33
ChangeRequest,
44
PublishedSiteContent,
5-
RevisionPage,
5+
Revision,
66
RevisionPageDocument,
77
Site,
88
SiteCustomizationSettings,
@@ -84,11 +84,8 @@ export type GitBookSpaceContext = GitBookBaseContext & {
8484
space: Space;
8585
changeRequest: ChangeRequest | null;
8686

87-
/** ID of the current revision. */
88-
revisionId: string;
89-
90-
/** Pages of the space. */
91-
pages: RevisionPage[];
87+
/** Revision of the space. */
88+
revision: Revision;
9289

9390
/** Share key of the space. */
9491
shareKey: string | undefined;
@@ -351,30 +348,26 @@ export async function fetchSpaceContextByIds(
351348

352349
const revisionId = ids.revision ?? changeRequest?.revision ?? space.revision;
353350

354-
const pages = await getDataOrNull(
355-
dataFetcher.getRevisionPages({
351+
const revision = await getDataOrNull(
352+
dataFetcher.getRevision({
356353
spaceId: ids.space,
357354
revisionId,
358-
// We only care about the Git metadata when the Git sync is enabled,
359-
// otherwise we can optimize performance by not fetching it
360-
metadata: !!space.gitSync,
361355
}),
362356

363357
// When trying to render a revision with an invalid / non-existing ID,
364358
// we should handle gracefully the 404 and throw notFound.
365359
ids.revision ? [404] : undefined
366360
);
367-
if (!pages) {
361+
if (!revision) {
368362
notFound();
369363
}
370364

371365
return {
372366
...baseContext,
373367
organizationId: space.organization,
374368
space,
375-
pages,
369+
revision,
376370
changeRequest,
377-
revisionId,
378371
shareKey: ids.shareKey,
379372
};
380373
}

packages/gitbook-v2/src/lib/data/api.ts

Lines changed: 1 addition & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-t
99
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env';
1010
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache';
1111
import { cache } from '../cache';
12-
import { DataFetcherError, throwIfDataError, wrapCacheDataFetcherError } from './errors';
12+
import { DataFetcherError, wrapCacheDataFetcherError } from './errors';
1313
import type { GitBookDataFetcher } from './types';
1414

1515
interface DataFetcherInput {
@@ -76,24 +76,6 @@ export function createDataFetcher(
7676
})
7777
);
7878
},
79-
getRevisionPages(params) {
80-
return trace('getRevisionPages', () =>
81-
getRevisionPages(input, {
82-
spaceId: params.spaceId,
83-
revisionId: params.revisionId,
84-
metadata: params.metadata,
85-
})
86-
);
87-
},
88-
getRevisionFile(params) {
89-
return trace('getRevisionFile', () =>
90-
getRevisionFile(input, {
91-
spaceId: params.spaceId,
92-
revisionId: params.revisionId,
93-
fileId: params.fileId,
94-
})
95-
);
96-
},
9779
getRevisionPageByPath(params) {
9880
return trace('getRevisionPageByPath', () =>
9981
getRevisionPageByPath(input, {
@@ -121,15 +103,6 @@ export function createDataFetcher(
121103
})
122104
);
123105
},
124-
getReusableContent(params) {
125-
return trace('getReusableContent', () =>
126-
getReusableContent(input, {
127-
spaceId: params.spaceId,
128-
revisionId: params.revisionId,
129-
reusableContentId: params.reusableContentId,
130-
})
131-
);
132-
},
133106
getLatestOpenAPISpecVersionContent(params) {
134107
return trace('getLatestOpenAPISpecVersionContent', () =>
135108
getLatestOpenAPISpecVersionContent(input, {
@@ -304,62 +277,6 @@ const getRevision = cache(
304277
}
305278
);
306279

307-
const getRevisionPages = cache(
308-
async (
309-
input: DataFetcherInput,
310-
params: { spaceId: string; revisionId: string; metadata: boolean }
311-
) => {
312-
'use cache';
313-
return wrapCacheDataFetcherError(async () => {
314-
return trace(`getRevisionPages(${params.spaceId}, ${params.revisionId})`, async () => {
315-
const api = apiClient(input);
316-
const res = await api.spaces.listPagesInRevisionById(
317-
params.spaceId,
318-
params.revisionId,
319-
{
320-
metadata: params.metadata,
321-
},
322-
{
323-
...noCacheFetchOptions,
324-
}
325-
);
326-
cacheTag(...getCacheTagsFromResponse(res));
327-
cacheLife('max');
328-
return res.data.pages;
329-
});
330-
});
331-
}
332-
);
333-
334-
const getRevisionFile = cache(
335-
async (
336-
input: DataFetcherInput,
337-
params: { spaceId: string; revisionId: string; fileId: string }
338-
) => {
339-
return wrapCacheDataFetcherError(async () => {
340-
return trace(
341-
`getRevisionFile(${params.spaceId}, ${params.revisionId}, ${params.fileId})`,
342-
async () => {
343-
const revision = await throwIfDataError(
344-
getRevision(input, {
345-
spaceId: params.spaceId,
346-
revisionId: params.revisionId,
347-
})
348-
);
349-
350-
const file = revision.files.find((file) => file.id === params.fileId);
351-
352-
if (!file) {
353-
throw new DataFetcherError('File not found', 404);
354-
}
355-
356-
return file;
357-
}
358-
);
359-
});
360-
}
361-
);
362-
363280
const getRevisionPageMarkdown = cache(
364281
async (
365282
input: DataFetcherInput,
@@ -527,35 +444,6 @@ const getComputedDocument = cache(
527444
}
528445
);
529446

530-
const getReusableContent = cache(
531-
async (
532-
input: DataFetcherInput,
533-
params: { spaceId: string; revisionId: string; reusableContentId: string }
534-
) => {
535-
'use cache';
536-
return wrapCacheDataFetcherError(async () => {
537-
return trace(
538-
`getReusableContent(${params.spaceId}, ${params.revisionId}, ${params.reusableContentId})`,
539-
async () => {
540-
const api = apiClient(input);
541-
const res = await api.spaces.getReusableContentInRevisionById(
542-
params.spaceId,
543-
params.revisionId,
544-
params.reusableContentId,
545-
{},
546-
{
547-
...noCacheFetchOptions,
548-
}
549-
);
550-
cacheTag(...getCacheTagsFromResponse(res));
551-
cacheLife('max');
552-
return res.data;
553-
}
554-
);
555-
});
556-
}
557-
);
558-
559447
const getLatestOpenAPISpecVersionContent = cache(
560448
async (input: DataFetcherInput, params: { organizationId: string; slug: string }) => {
561449
'use cache';

packages/gitbook-v2/src/lib/data/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './errors';
55
export * from './lookup';
66
export * from './visitor';
77
export * from './pages';
8+
export * from './revisions';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Revision, RevisionFile, RevisionReusableContent } from '@gitbook/api';
2+
import * as React from 'react';
3+
4+
const getRevisionReusableContents = React.cache((revision: Revision) => {
5+
return new Map(
6+
revision.reusableContents.map((reusableContent) => [reusableContent.id, reusableContent])
7+
);
8+
});
9+
10+
const getRevisionFiles = React.cache((revision: Revision) => {
11+
return new Map(revision.files.map((file) => [file.id, file]));
12+
});
13+
14+
/**
15+
* Get a revision file by its ID.
16+
*/
17+
export function getRevisionFile(input: {
18+
revision: Revision;
19+
fileId: string;
20+
}): RevisionFile | null {
21+
const { revision, fileId } = input;
22+
const files = getRevisionFiles(revision);
23+
return files.get(fileId) ?? null;
24+
}
25+
26+
/**
27+
* Get a revision reusable content by its ID.
28+
*/
29+
export function getRevisionReusableContent(input: {
30+
revision: Revision;
31+
reusableContentId: string;
32+
}): RevisionReusableContent | null {
33+
const { revision, reusableContentId } = input;
34+
const reusableContents = getRevisionReusableContents(revision);
35+
return reusableContents.get(reusableContentId) ?? null;
36+
}

packages/gitbook-v2/src/lib/data/types.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,24 +69,6 @@ export interface GitBookDataFetcher {
6969
revisionId: string;
7070
}): Promise<DataFetcherResponse<api.Revision>>;
7171

72-
/**
73-
* Get the revision pages by its space ID and revision ID.
74-
*/
75-
getRevisionPages(params: {
76-
spaceId: string;
77-
revisionId: string;
78-
metadata: boolean;
79-
}): Promise<DataFetcherResponse<api.RevisionPage[]>>;
80-
81-
/**
82-
* Get a revision file by its space ID, revision ID and file ID.
83-
*/
84-
getRevisionFile(params: {
85-
spaceId: string;
86-
revisionId: string;
87-
fileId: string;
88-
}): Promise<DataFetcherResponse<api.RevisionFile>>;
89-
9072
/**
9173
* Get a revision page by its path.
9274
*/
@@ -131,15 +113,6 @@ export interface GitBookDataFetcher {
131113
seed: string;
132114
}): Promise<DataFetcherResponse<api.JSONDocument>>;
133115

134-
/**
135-
* Get a reusable content by its space ID, revision ID and reusable content ID.
136-
*/
137-
getReusableContent(params: {
138-
spaceId: string;
139-
revisionId: string;
140-
reusableContentId: string;
141-
}): Promise<DataFetcherResponse<api.RevisionReusableContent>>;
142-
143116
/**
144117
* Get the latest OpenAPI spec version content by its organization ID and slug.
145118
*/

packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { headers } from 'next/headers';
44
import React from 'react';
55

66
import { tcls } from '@/lib/tailwind';
7-
import { throwIfDataError } from '@v2/lib/data';
87

98
import { DateRelative } from '../primitives';
109
import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
@@ -59,7 +58,7 @@ export async function AdminToolbar(props: AdminToolbarProps) {
5958
return <ChangeRequestToolbar context={context} />;
6059
}
6160

62-
if (context.revisionId !== context.space.revision) {
61+
if (context.revision.id !== context.space.revision) {
6362
return <RevisionToolbar context={context} />;
6463
}
6564

@@ -106,14 +105,7 @@ async function ChangeRequestToolbar(props: { context: GitBookSiteContext }) {
106105

107106
async function RevisionToolbar(props: { context: GitBookSiteContext }) {
108107
const { context } = props;
109-
const { space, revisionId } = context;
110-
111-
const revision = await throwIfDataError(
112-
context.dataFetcher.getRevision({
113-
spaceId: space.id,
114-
revisionId,
115-
})
116-
);
108+
const { revision } = context;
117109

118110
return (
119111
<ToolbarLayout>

packages/gitbook/src/components/DocumentView/ReusableContent.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
2323
dataFetcher,
2424
});
2525

26-
if (!resolved?.reusableContent) {
26+
if (!resolved) {
2727
return null;
2828
}
2929

30-
const reusableContent = resolved.reusableContent.revisionReusableContent;
31-
if (!reusableContent.document) {
30+
const { reusableContent } = resolved;
31+
if (!reusableContent || !reusableContent.revisionReusableContent.document) {
3232
return null;
3333
}
3434

3535
const document = await getDataOrNull(
3636
dataFetcher.getDocument({
37-
spaceId: resolved.reusableContent.space.id,
38-
documentId: reusableContent.document,
37+
spaceId: reusableContent.space.id,
38+
documentId: reusableContent.revisionReusableContent.document,
3939
})
4040
);
4141

@@ -47,18 +47,20 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
4747
// the data fetcher with the token from the block meta and the correct
4848
// space and revision pointers.
4949
const reusableContentContext: GitBookSpaceContext =
50-
context.contentContext.space.id === resolved.reusableContent.space.id
50+
context.contentContext.space.id === reusableContent.space.id
5151
? context.contentContext
5252
: {
5353
...context.contentContext,
5454
dataFetcher,
55-
space: resolved.reusableContent.space,
56-
revisionId: resolved.reusableContent.revision,
55+
space: reusableContent.space,
5756
// When the reusable content is in a different space, we don't resolve relative links to pages
5857
// as this space might not be part of the current site.
5958
// In the future, we might expand the logic to look up the space from the list of all spaces in the site
6059
// and adapt the relative links to point to the correct variant.
61-
pages: [],
60+
revision: {
61+
...reusableContent.revision,
62+
pages: [], // TODO: check with Steven
63+
},
6264
shareKey: undefined,
6365
};
6466

packages/gitbook/src/components/PDF/PDFPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export async function PDFPage(props: {
6060
const language = getSpaceLanguage(customization);
6161

6262
// Compute the pages to render
63-
const { pages, total } = selectPages(baseContext.pages, pdfParams);
63+
const { pages, total } = selectPages(baseContext.revision.pages, pdfParams);
6464
const pageIds = pages.map(
6565
({ page }) => [page.id, getPagePDFContainerId(page)] as [string, string]
6666
);

packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ export async function PageBodyBlankslate(props: {
5858
/>
5959
);
6060
}
61-
const href = context.linker.toPathForPage({ pages: context.pages, page: child });
61+
const href = context.linker.toPathForPage({
62+
pages: context.revision.pages,
63+
page: child,
64+
});
6265
return <Card key={child.id} title={child.title} leadingIcon={icon} href={href} />;
6366
})
6467
);

0 commit comments

Comments
 (0)