mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-15 23:25:16 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5cec41875 | |||
| 26d7906361 |
@@ -9,7 +9,11 @@ import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-t
|
||||
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 { DataFetcherError, wrapDataFetcherError } from './errors';
|
||||
import { withCacheKey, withoutConcurrentExecution } from './memoize';
|
||||
import {
|
||||
withCacheKey,
|
||||
withoutConcurrentProcessExecution,
|
||||
withoutConcurrentRequestExecution,
|
||||
} from './memoize';
|
||||
import type { GitBookDataFetcher } from './types';
|
||||
|
||||
interface DataFetcherInput {
|
||||
@@ -205,26 +209,30 @@ export function createDataFetcher(
|
||||
}
|
||||
|
||||
const getUserById = withCacheKey(
|
||||
withoutConcurrentExecution(async (_, input: DataFetcherInput, params: { userId: string }) => {
|
||||
'use cache';
|
||||
return trace(`getUserById(${params.userId})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.users.getUserById(params.userId, {
|
||||
...noCacheFetchOptions,
|
||||
withoutConcurrentRequestExecution(
|
||||
async (cacheKey, input: DataFetcherInput, params: { userId: string }) => {
|
||||
'use cache';
|
||||
return trace(`getUserById(${params.userId})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.users.getUserById(params.userId, {
|
||||
...noCacheFetchOptions,
|
||||
})
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('days');
|
||||
return res.data;
|
||||
});
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('days');
|
||||
return res.data;
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const getSpace = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; shareKey: string | undefined }
|
||||
) => {
|
||||
@@ -239,14 +247,16 @@ const getSpace = withCacheKey(
|
||||
return trace(`getSpace(${params.spaceId}, ${params.shareKey})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getSpaceById(
|
||||
params.spaceId,
|
||||
{
|
||||
shareKey: params.shareKey,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getSpaceById(
|
||||
params.spaceId,
|
||||
{
|
||||
shareKey: params.shareKey,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('days');
|
||||
@@ -258,9 +268,9 @@ const getSpace = withCacheKey(
|
||||
);
|
||||
|
||||
const getChangeRequest = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; changeRequestId: string }
|
||||
) => {
|
||||
@@ -278,12 +288,14 @@ const getChangeRequest = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getChangeRequestById(
|
||||
params.spaceId,
|
||||
params.changeRequestId,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getChangeRequestById(
|
||||
params.spaceId,
|
||||
params.changeRequestId,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('minutes');
|
||||
@@ -296,9 +308,9 @@ const getChangeRequest = withCacheKey(
|
||||
);
|
||||
|
||||
const getRevision = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; metadata: boolean }
|
||||
) => {
|
||||
@@ -306,15 +318,17 @@ const getRevision = withCacheKey(
|
||||
return trace(`getRevision(${params.spaceId}, ${params.revisionId})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
{
|
||||
metadata: params.metadata,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
{
|
||||
metadata: params.metadata,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -326,9 +340,9 @@ const getRevision = withCacheKey(
|
||||
);
|
||||
|
||||
const getRevisionPages = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; metadata: boolean }
|
||||
) => {
|
||||
@@ -336,15 +350,17 @@ const getRevisionPages = withCacheKey(
|
||||
return trace(`getRevisionPages(${params.spaceId}, ${params.revisionId})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.listPagesInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
{
|
||||
metadata: params.metadata,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.listPagesInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
{
|
||||
metadata: params.metadata,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -356,9 +372,9 @@ const getRevisionPages = withCacheKey(
|
||||
);
|
||||
|
||||
const getRevisionFile = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; fileId: string }
|
||||
) => {
|
||||
@@ -368,14 +384,16 @@ const getRevisionFile = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getFileInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.fileId,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getFileInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.fileId,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -388,9 +406,9 @@ const getRevisionFile = withCacheKey(
|
||||
);
|
||||
|
||||
const getRevisionPageMarkdown = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; pageId: string }
|
||||
) => {
|
||||
@@ -400,16 +418,18 @@ const getRevisionPageMarkdown = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getPageInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.pageId,
|
||||
{
|
||||
format: 'markdown',
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getPageInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.pageId,
|
||||
{
|
||||
format: 'markdown',
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
@@ -427,9 +447,9 @@ const getRevisionPageMarkdown = withCacheKey(
|
||||
);
|
||||
|
||||
const getRevisionPageDocument = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; pageId: string }
|
||||
) => {
|
||||
@@ -439,16 +459,18 @@ const getRevisionPageDocument = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getPageDocumentInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.pageId,
|
||||
{
|
||||
evaluated: true,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getPageDocumentInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.pageId,
|
||||
{
|
||||
evaluated: true,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
@@ -463,9 +485,9 @@ const getRevisionPageDocument = withCacheKey(
|
||||
);
|
||||
|
||||
const getRevisionPageByPath = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; path: string }
|
||||
) => {
|
||||
@@ -476,14 +498,16 @@ const getRevisionPageByPath = withCacheKey(
|
||||
const encodedPath = encodeURIComponent(params.path);
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getPageInRevisionByPath(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
encodedPath,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getPageInRevisionByPath(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
encodedPath,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -496,19 +520,25 @@ const getRevisionPageByPath = withCacheKey(
|
||||
);
|
||||
|
||||
const getDocument = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
async (_, input: DataFetcherInput, params: { spaceId: string; documentId: string }) => {
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; documentId: string }
|
||||
) => {
|
||||
'use cache';
|
||||
return trace(`getDocument(${params.spaceId}, ${params.documentId})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getDocumentById(
|
||||
params.spaceId,
|
||||
params.documentId,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getDocumentById(
|
||||
params.spaceId,
|
||||
params.documentId,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -520,9 +550,9 @@ const getDocument = withCacheKey(
|
||||
);
|
||||
|
||||
const getComputedDocument = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: {
|
||||
spaceId: string;
|
||||
@@ -547,16 +577,18 @@ const getComputedDocument = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getComputedDocument(
|
||||
params.spaceId,
|
||||
{
|
||||
source: params.source,
|
||||
seed: params.seed,
|
||||
},
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getComputedDocument(
|
||||
params.spaceId,
|
||||
{
|
||||
source: params.source,
|
||||
seed: params.seed,
|
||||
},
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -569,9 +601,9 @@ const getComputedDocument = withCacheKey(
|
||||
);
|
||||
|
||||
const getReusableContent = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { spaceId: string; revisionId: string; reusableContentId: string }
|
||||
) => {
|
||||
@@ -581,14 +613,16 @@ const getReusableContent = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getReusableContentInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.reusableContentId,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getReusableContentInRevisionById(
|
||||
params.spaceId,
|
||||
params.revisionId,
|
||||
params.reusableContentId,
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -601,8 +635,12 @@ const getReusableContent = withCacheKey(
|
||||
);
|
||||
|
||||
const getLatestOpenAPISpecVersionContent = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
async (_, input: DataFetcherInput, params: { organizationId: string; slug: string }) => {
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { organizationId: string; slug: string }
|
||||
) => {
|
||||
'use cache';
|
||||
cacheTag(
|
||||
getCacheTag({
|
||||
@@ -617,12 +655,14 @@ const getLatestOpenAPISpecVersionContent = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.orgs.getLatestOpenApiSpecVersionContent(
|
||||
params.organizationId,
|
||||
params.slug,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.orgs.getLatestOpenApiSpecVersionContent(
|
||||
params.organizationId,
|
||||
params.slug,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('max');
|
||||
@@ -635,9 +675,9 @@ const getLatestOpenAPISpecVersionContent = withCacheKey(
|
||||
);
|
||||
|
||||
const getPublishedContentSite = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { organizationId: string; siteId: string; siteShareKey: string | undefined }
|
||||
) => {
|
||||
@@ -654,15 +694,17 @@ const getPublishedContentSite = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.orgs.getPublishedContentSite(
|
||||
params.organizationId,
|
||||
params.siteId,
|
||||
{
|
||||
shareKey: params.siteShareKey,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.orgs.getPublishedContentSite(
|
||||
params.organizationId,
|
||||
params.siteId,
|
||||
{
|
||||
shareKey: params.siteShareKey,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('days');
|
||||
@@ -675,9 +717,9 @@ const getPublishedContentSite = withCacheKey(
|
||||
);
|
||||
|
||||
const getSiteRedirectBySource = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: {
|
||||
organizationId: string;
|
||||
@@ -699,16 +741,18 @@ const getSiteRedirectBySource = withCacheKey(
|
||||
async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.orgs.getSiteRedirectBySource(
|
||||
params.organizationId,
|
||||
params.siteId,
|
||||
{
|
||||
shareKey: params.siteShareKey,
|
||||
source: params.source,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.orgs.getSiteRedirectBySource(
|
||||
params.organizationId,
|
||||
params.siteId,
|
||||
{
|
||||
shareKey: params.siteShareKey,
|
||||
source: params.source,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('days');
|
||||
@@ -721,8 +765,8 @@ const getSiteRedirectBySource = withCacheKey(
|
||||
);
|
||||
|
||||
const getEmbedByUrl = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
async (_, input: DataFetcherInput, params: { spaceId: string; url: string }) => {
|
||||
withoutConcurrentRequestExecution(
|
||||
async (cacheKey, input: DataFetcherInput, params: { spaceId: string; url: string }) => {
|
||||
'use cache';
|
||||
cacheTag(
|
||||
getCacheTag({
|
||||
@@ -734,14 +778,16 @@ const getEmbedByUrl = withCacheKey(
|
||||
return trace(`getEmbedByUrl(${params.spaceId}, ${params.url})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.spaces.getEmbedByUrlInSpace(
|
||||
params.spaceId,
|
||||
{
|
||||
url: params.url,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.spaces.getEmbedByUrlInSpace(
|
||||
params.spaceId,
|
||||
{
|
||||
url: params.url,
|
||||
},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('weeks');
|
||||
@@ -753,9 +799,9 @@ const getEmbedByUrl = withCacheKey(
|
||||
);
|
||||
|
||||
const searchSiteContent = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: Parameters<GitBookDataFetcher['searchSiteContent']>[0]
|
||||
) => {
|
||||
@@ -773,17 +819,19 @@ const searchSiteContent = withCacheKey(
|
||||
return wrapDataFetcherError(async () => {
|
||||
const { organizationId, siteId, query, scope } = params;
|
||||
const api = apiClient(input);
|
||||
const res = await api.orgs.searchSiteContent(
|
||||
organizationId,
|
||||
siteId,
|
||||
{
|
||||
query,
|
||||
...scope,
|
||||
},
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.orgs.searchSiteContent(
|
||||
organizationId,
|
||||
siteId,
|
||||
{
|
||||
query,
|
||||
...scope,
|
||||
},
|
||||
{},
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('hours');
|
||||
@@ -796,9 +844,9 @@ const searchSiteContent = withCacheKey(
|
||||
);
|
||||
|
||||
const renderIntegrationUi = withCacheKey(
|
||||
withoutConcurrentExecution(
|
||||
withoutConcurrentRequestExecution(
|
||||
async (
|
||||
_,
|
||||
cacheKey,
|
||||
input: DataFetcherInput,
|
||||
params: { integrationName: string; request: RenderIntegrationUI }
|
||||
) => {
|
||||
@@ -813,12 +861,14 @@ const renderIntegrationUi = withCacheKey(
|
||||
return trace(`renderIntegrationUi(${params.integrationName})`, async () => {
|
||||
return wrapDataFetcherError(async () => {
|
||||
const api = apiClient(input);
|
||||
const res = await api.integrations.renderIntegrationUiWithPost(
|
||||
params.integrationName,
|
||||
params.request,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
const res = await withoutConcurrentProcessExecution(cacheKey, async () =>
|
||||
api.integrations.renderIntegrationUiWithPost(
|
||||
params.integrationName,
|
||||
params.request,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
}
|
||||
)
|
||||
);
|
||||
cacheTag(...getCacheTagsFromResponse(res));
|
||||
cacheLife('days');
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { cache } from 'react';
|
||||
import { getCloudflareContext } from './cloudflare';
|
||||
|
||||
// This is used to create a context specific to the current request.
|
||||
// This version works both in cloudflare and in vercel.
|
||||
const getRequestContext = cache(() => ({}));
|
||||
|
||||
const globalMapSymbol = Symbol('globalPromiseCache');
|
||||
|
||||
/**
|
||||
* Wrap a function by preventing concurrent executions of the same function.
|
||||
* With a logic to work per-request in Cloudflare Workers.
|
||||
* With a logic to work per-request.
|
||||
* This is the one to use if you're using `use cache` in your function or any other caching mechanism (i.e. cacheTag) from Next.js.
|
||||
*/
|
||||
export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
|
||||
export function withoutConcurrentRequestExecution<ArgsType extends any[], ReturnType>(
|
||||
wrapped: (key: string, ...args: ArgsType) => Promise<ReturnType>
|
||||
): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> {
|
||||
const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>();
|
||||
@@ -44,6 +48,45 @@ export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This function should not be used for a function that uses `use cache`.
|
||||
* It should only be used for functions inside a `use cache` and should under no circumstances contain any side effects or `cacheTag`
|
||||
*
|
||||
* @param cacheKey The cache key to use for the concurrent execution.
|
||||
* @param wrapped The function to wrap. It should return a Promise and have no arguments.
|
||||
* @returns A Promise that resolves to the result of the wrapped function.
|
||||
*/
|
||||
export function withoutConcurrentProcessExecution<ReturnType>(
|
||||
cacheKey: string,
|
||||
wrapped: () => Promise<ReturnType>
|
||||
): Promise<ReturnType> {
|
||||
// If we are in cloudflare workers, we just want to execute the function to avoid concurrent I/O errors.
|
||||
const cfContext = getCloudflareContext();
|
||||
if (cfContext) {
|
||||
return wrapped();
|
||||
}
|
||||
const globalMapPromiseCache = getGlobalPromiseCache<ReturnType>();
|
||||
|
||||
const concurrent = globalMapPromiseCache.get(cacheKey);
|
||||
if (concurrent) {
|
||||
return concurrent;
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const result = await wrapped();
|
||||
return result;
|
||||
} finally {
|
||||
globalMapPromiseCache.delete(cacheKey);
|
||||
}
|
||||
})();
|
||||
|
||||
globalMapPromiseCache.set(cacheKey, promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a function by passing it a cache key that is computed from the function arguments.
|
||||
*/
|
||||
@@ -51,16 +94,23 @@ export function withCacheKey<ArgsType extends any[], ReturnType>(
|
||||
wrapped: (cacheKey: string, ...args: ArgsType) => Promise<ReturnType>
|
||||
): (...args: ArgsType) => Promise<ReturnType> {
|
||||
return (...args: ArgsType) => {
|
||||
const cacheKey = getCacheKey(args);
|
||||
const cacheKey = getCacheKey(args, wrapped.name);
|
||||
return wrapped(cacheKey, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a cache key from the function arguments.
|
||||
* We need to use the function name and the arguments to ensure that
|
||||
* the cache key is unique for each function and its arguments.
|
||||
* This is useful to avoid cache collisions when using the same arguments
|
||||
* for different functions.
|
||||
*/
|
||||
function getCacheKey(args: any[]) {
|
||||
return JSON.stringify(deepSortValue(args));
|
||||
function getCacheKey(args: any[], name: string) {
|
||||
return JSON.stringify({
|
||||
args: deepSortValue(args),
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
function deepSortValue(value: unknown): unknown {
|
||||
@@ -90,3 +140,21 @@ function deepSortValue(value: unknown): unknown {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns A global cache that is shared across all requests.
|
||||
* This cache is used to store promises that are being executed concurrently.
|
||||
*/
|
||||
export function getGlobalPromiseCache<ReturnType>(): Map<string, Promise<ReturnType>> {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
const globalMapPromiseCache = (globalThis as any)[globalMapSymbol] as
|
||||
| Map<string, Promise<ReturnType>>
|
||||
| undefined;
|
||||
if (!globalMapPromiseCache) {
|
||||
const newCache = new Map<string, Promise<ReturnType>>();
|
||||
(globalThis as any)[globalMapSymbol] = newCache;
|
||||
return newCache;
|
||||
}
|
||||
return globalMapPromiseCache;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user