Compare commits

...

1 Commits

Author SHA1 Message Date
Nicolas Dorseuil 6edf3b78f0 use dataFetcher itself as the key for the weakmap 2025-06-12 14:24:11 +02:00
2 changed files with 654 additions and 612 deletions
+86 -41
View File
@@ -51,7 +51,7 @@ export function createDataFetcher(
// //
getPublishedContentSite(params) { getPublishedContentSite(params) {
return trace('getPublishedContentSite', () => return trace('getPublishedContentSite', () =>
getPublishedContentSite(input, { getPublishedContentSite(this)(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
siteId: params.siteId, siteId: params.siteId,
siteShareKey: params.siteShareKey, siteShareKey: params.siteShareKey,
@@ -60,7 +60,7 @@ export function createDataFetcher(
}, },
getSiteRedirectBySource(params) { getSiteRedirectBySource(params) {
return trace('getSiteRedirectBySource', () => return trace('getSiteRedirectBySource', () =>
getSiteRedirectBySource(input, { getSiteRedirectBySource(this)(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
siteId: params.siteId, siteId: params.siteId,
siteShareKey: params.siteShareKey, siteShareKey: params.siteShareKey,
@@ -70,7 +70,7 @@ export function createDataFetcher(
}, },
getRevision(params) { getRevision(params) {
return trace('getRevision', () => return trace('getRevision', () =>
getRevision(input, { getRevision(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
metadata: params.metadata, metadata: params.metadata,
@@ -79,7 +79,7 @@ export function createDataFetcher(
}, },
getRevisionPages(params) { getRevisionPages(params) {
return trace('getRevisionPages', () => return trace('getRevisionPages', () =>
getRevisionPages(input, { getRevisionPages(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
metadata: params.metadata, metadata: params.metadata,
@@ -88,7 +88,7 @@ export function createDataFetcher(
}, },
getRevisionFile(params) { getRevisionFile(params) {
return trace('getRevisionFile', () => return trace('getRevisionFile', () =>
getRevisionFile(input, { getRevisionFile(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
fileId: params.fileId, fileId: params.fileId,
@@ -97,7 +97,7 @@ export function createDataFetcher(
}, },
getRevisionPageByPath(params) { getRevisionPageByPath(params) {
return trace('getRevisionPageByPath', () => return trace('getRevisionPageByPath', () =>
getRevisionPageByPath(input, { getRevisionPageByPath(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
path: params.path, path: params.path,
@@ -106,7 +106,7 @@ export function createDataFetcher(
}, },
getRevisionPageMarkdown(params) { getRevisionPageMarkdown(params) {
return trace('getRevisionPageMarkdown', () => return trace('getRevisionPageMarkdown', () =>
getRevisionPageMarkdown(input, { getRevisionPageMarkdown(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
pageId: params.pageId, pageId: params.pageId,
@@ -115,7 +115,7 @@ export function createDataFetcher(
}, },
getRevisionPageDocument(params) { getRevisionPageDocument(params) {
return trace('getRevisionPageDocument', () => return trace('getRevisionPageDocument', () =>
getRevisionPageDocument(input, { getRevisionPageDocument(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
pageId: params.pageId, pageId: params.pageId,
@@ -124,7 +124,7 @@ export function createDataFetcher(
}, },
getReusableContent(params) { getReusableContent(params) {
return trace('getReusableContent', () => return trace('getReusableContent', () =>
getReusableContent(input, { getReusableContent(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
reusableContentId: params.reusableContentId, reusableContentId: params.reusableContentId,
@@ -133,7 +133,7 @@ export function createDataFetcher(
}, },
getLatestOpenAPISpecVersionContent(params) { getLatestOpenAPISpecVersionContent(params) {
return trace('getLatestOpenAPISpecVersionContent', () => return trace('getLatestOpenAPISpecVersionContent', () =>
getLatestOpenAPISpecVersionContent(input, { getLatestOpenAPISpecVersionContent(this)(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
slug: params.slug, slug: params.slug,
}) })
@@ -141,7 +141,7 @@ export function createDataFetcher(
}, },
getSpace(params) { getSpace(params) {
return trace('getSpace', () => return trace('getSpace', () =>
getSpace(input, { getSpace(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
shareKey: params.shareKey, shareKey: params.shareKey,
}) })
@@ -149,7 +149,7 @@ export function createDataFetcher(
}, },
getChangeRequest(params) { getChangeRequest(params) {
return trace('getChangeRequest', () => return trace('getChangeRequest', () =>
getChangeRequest(input, { getChangeRequest(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
changeRequestId: params.changeRequestId, changeRequestId: params.changeRequestId,
}) })
@@ -157,7 +157,7 @@ export function createDataFetcher(
}, },
getDocument(params) { getDocument(params) {
return trace('getDocument', () => return trace('getDocument', () =>
getDocument(input, { getDocument(this)(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
documentId: params.documentId, documentId: params.documentId,
}) })
@@ -165,7 +165,7 @@ export function createDataFetcher(
}, },
getComputedDocument(params) { getComputedDocument(params) {
return trace('getComputedDocument', () => return trace('getComputedDocument', () =>
getComputedDocument(input, { getComputedDocument(this)(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
spaceId: params.spaceId, spaceId: params.spaceId,
source: params.source, source: params.source,
@@ -175,19 +175,19 @@ export function createDataFetcher(
}, },
getEmbedByUrl(params) { getEmbedByUrl(params) {
return trace('getEmbedByUrl', () => return trace('getEmbedByUrl', () =>
getEmbedByUrl(input, { getEmbedByUrl(this)(input, {
url: params.url, url: params.url,
spaceId: params.spaceId, spaceId: params.spaceId,
}) })
); );
}, },
searchSiteContent(params) { searchSiteContent(params) {
return trace('searchSiteContent', () => searchSiteContent(input, params)); return trace('searchSiteContent', () => searchSiteContent(this)(input, params));
}, },
renderIntegrationUi(params) { renderIntegrationUi(params) {
return trace('renderIntegrationUi', () => return trace('renderIntegrationUi', () =>
renderIntegrationUi(input, { renderIntegrationUi(this)(input, {
integrationName: params.integrationName, integrationName: params.integrationName,
request: params.request, request: params.request,
}) })
@@ -195,7 +195,7 @@ export function createDataFetcher(
}, },
getUserById(userId) { getUserById(userId) {
return trace('getUserById', () => getUserById(input, { userId })); return trace('getUserById', () => getUserById(this)(input, { userId }));
}, },
streamAIResponse(params) { streamAIResponse(params) {
@@ -204,8 +204,11 @@ export function createDataFetcher(
}; };
} }
const getUserById = withCacheKey( const getUserById = (dataFetcher: GitBookDataFetcher) =>
withoutConcurrentExecution(async (_, input: DataFetcherInput, params: { userId: string }) => { withCacheKey(
withoutConcurrentExecution(
dataFetcher,
async (_, input: DataFetcherInput, params: { userId: string }) => {
'use cache'; 'use cache';
return trace(`getUserById(${params.userId})`, async () => { return trace(`getUserById(${params.userId})`, async () => {
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
@@ -218,11 +221,14 @@ const getUserById = withCacheKey(
return res.data; return res.data;
}); });
}); });
}) }
)
); );
const getSpace = withCacheKey( const getSpace = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -257,8 +263,10 @@ const getSpace = withCacheKey(
) )
); );
const getChangeRequest = withCacheKey( const getChangeRequest = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -295,8 +303,10 @@ const getChangeRequest = withCacheKey(
) )
); );
const getRevision = withCacheKey( const getRevision = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -325,15 +335,19 @@ const getRevision = withCacheKey(
) )
); );
const getRevisionPages = withCacheKey( const getRevisionPages = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
params: { spaceId: string; revisionId: string; metadata: boolean } params: { spaceId: string; revisionId: string; metadata: boolean }
) => { ) => {
'use cache'; 'use cache';
return trace(`getRevisionPages(${params.spaceId}, ${params.revisionId})`, async () => { return trace(
`getRevisionPages(${params.spaceId}, ${params.revisionId})`,
async () => {
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const api = apiClient(input); const api = apiClient(input);
const res = await api.spaces.listPagesInRevisionById( const res = await api.spaces.listPagesInRevisionById(
@@ -350,13 +364,16 @@ const getRevisionPages = withCacheKey(
cacheLife('max'); cacheLife('max');
return res.data.pages; return res.data.pages;
}); });
}); }
);
} }
) )
); );
const getRevisionFile = withCacheKey( const getRevisionFile = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -387,8 +404,10 @@ const getRevisionFile = withCacheKey(
) )
); );
const getRevisionPageMarkdown = withCacheKey( const getRevisionPageMarkdown = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -426,8 +445,10 @@ const getRevisionPageMarkdown = withCacheKey(
) )
); );
const getRevisionPageDocument = withCacheKey( const getRevisionPageDocument = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -462,8 +483,10 @@ const getRevisionPageDocument = withCacheKey(
) )
); );
const getRevisionPageByPath = withCacheKey( const getRevisionPageByPath = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -495,8 +518,10 @@ const getRevisionPageByPath = withCacheKey(
) )
); );
const getDocument = withCacheKey( const getDocument = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async (_, input: DataFetcherInput, params: { spaceId: string; documentId: string }) => { async (_, input: DataFetcherInput, params: { spaceId: string; documentId: string }) => {
'use cache'; 'use cache';
return trace(`getDocument(${params.spaceId}, ${params.documentId})`, async () => { return trace(`getDocument(${params.spaceId}, ${params.documentId})`, async () => {
@@ -519,8 +544,10 @@ const getDocument = withCacheKey(
) )
); );
const getComputedDocument = withCacheKey( const getComputedDocument = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -568,8 +595,10 @@ const getComputedDocument = withCacheKey(
) )
); );
const getReusableContent = withCacheKey( const getReusableContent = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -600,9 +629,15 @@ const getReusableContent = withCacheKey(
) )
); );
const getLatestOpenAPISpecVersionContent = withCacheKey( const getLatestOpenAPISpecVersionContent = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
async (_, input: DataFetcherInput, params: { organizationId: string; slug: string }) => { dataFetcher,
async (
_,
input: DataFetcherInput,
params: { organizationId: string; slug: string }
) => {
'use cache'; 'use cache';
cacheTag( cacheTag(
getCacheTag({ getCacheTag({
@@ -634,8 +669,10 @@ const getLatestOpenAPISpecVersionContent = withCacheKey(
) )
); );
const getPublishedContentSite = withCacheKey( const getPublishedContentSite = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -674,8 +711,10 @@ const getPublishedContentSite = withCacheKey(
) )
); );
const getSiteRedirectBySource = withCacheKey( const getSiteRedirectBySource = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -720,8 +759,10 @@ const getSiteRedirectBySource = withCacheKey(
) )
); );
const getEmbedByUrl = withCacheKey( const getEmbedByUrl = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async (_, input: DataFetcherInput, params: { spaceId: string; url: string }) => { async (_, input: DataFetcherInput, params: { spaceId: string; url: string }) => {
'use cache'; 'use cache';
cacheTag( cacheTag(
@@ -752,8 +793,10 @@ const getEmbedByUrl = withCacheKey(
) )
); );
const searchSiteContent = withCacheKey( const searchSiteContent = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -795,8 +838,10 @@ const searchSiteContent = withCacheKey(
) )
); );
const renderIntegrationUi = withCacheKey( const renderIntegrationUi = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
+3 -6
View File
@@ -1,20 +1,17 @@
import { cache } from 'react'; import type { GitBookDataFetcher } from './types';
// This is used to create a context specific to the current request.
// This version works both in cloudflare and in vercel.
const getRequestContext = cache(() => ({}));
/** /**
* Wrap a function by preventing concurrent executions of the same function. * 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 in Cloudflare Workers.
*/ */
export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>( export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
dataFetcherObject: GitBookDataFetcher,
wrapped: (key: string, ...args: ArgsType) => Promise<ReturnType> wrapped: (key: string, ...args: ArgsType) => Promise<ReturnType>
): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> { ): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> {
const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>(); const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>();
return (key: string, ...args: ArgsType) => { return (key: string, ...args: ArgsType) => {
const globalContext = getRequestContext(); const globalContext = dataFetcherObject;
/** /**
* Cache storage that is scoped to the current request when executed in Cloudflare Workers, * Cache storage that is scoped to the current request when executed in Cloudflare Workers,