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
+104 -59
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,
@@ -255,10 +261,12 @@ const getSpace = withCacheKey(
}); });
} }
) )
); );
const getChangeRequest = withCacheKey( const getChangeRequest = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -293,10 +301,12 @@ const getChangeRequest = withCacheKey(
); );
} }
) )
); );
const getRevision = withCacheKey( const getRevision = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -323,17 +333,21 @@ 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,
@@ -385,10 +402,12 @@ const getRevisionFile = withCacheKey(
); );
} }
) )
); );
const getRevisionPageMarkdown = withCacheKey( const getRevisionPageMarkdown = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -424,10 +443,12 @@ const getRevisionPageMarkdown = withCacheKey(
); );
} }
) )
); );
const getRevisionPageDocument = withCacheKey( const getRevisionPageDocument = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -460,10 +481,12 @@ const getRevisionPageDocument = withCacheKey(
); );
} }
) )
); );
const getRevisionPageByPath = withCacheKey( const getRevisionPageByPath = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -493,10 +516,12 @@ 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 () => {
@@ -517,10 +542,12 @@ const getDocument = withCacheKey(
}); });
} }
) )
); );
const getComputedDocument = withCacheKey( const getComputedDocument = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -566,10 +593,12 @@ const getComputedDocument = withCacheKey(
); );
} }
) )
); );
const getReusableContent = withCacheKey( const getReusableContent = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -598,11 +627,17 @@ 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({
@@ -632,10 +667,12 @@ const getLatestOpenAPISpecVersionContent = withCacheKey(
); );
} }
) )
); );
const getPublishedContentSite = withCacheKey( const getPublishedContentSite = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -672,10 +709,12 @@ const getPublishedContentSite = withCacheKey(
); );
} }
) )
); );
const getSiteRedirectBySource = withCacheKey( const getSiteRedirectBySource = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -718,10 +757,12 @@ 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(
@@ -750,10 +791,12 @@ const getEmbedByUrl = withCacheKey(
}); });
} }
) )
); );
const searchSiteContent = withCacheKey( const searchSiteContent = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -793,10 +836,12 @@ const searchSiteContent = withCacheKey(
); );
} }
) )
); );
const renderIntegrationUi = withCacheKey( const renderIntegrationUi = (dataFetcher: GitBookDataFetcher) =>
withCacheKey(
withoutConcurrentExecution( withoutConcurrentExecution(
dataFetcher,
async ( async (
_, _,
input: DataFetcherInput, input: DataFetcherInput,
@@ -827,7 +872,7 @@ const renderIntegrationUi = withCacheKey(
}); });
} }
) )
); );
async function* streamAIResponse( async function* streamAIResponse(
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,