Skip to content

Commit ac83c56

Browse files
trivikrghostdevv
andauthored
perf: parallelize jsDelivr README fallback probes (#2384)
Co-authored-by: Willow (GHOST) <git@willow.sh>
1 parent 69cdfd4 commit ac83c56

File tree

2 files changed

+218
-14
lines changed

2 files changed

+218
-14
lines changed

server/utils/readme-loaders.ts

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,28 @@ const standardReadmeFilenames = [
1919

2020
/** Matches standard README filenames (case-insensitive, for checking registry metadata) */
2121
const standardReadmePattern = /^readme(?:\.md|\.markdown)?$/i
22+
const JSDELIVR_README_FETCH_BATCH_SIZE = 3
2223

2324
export function isStandardReadme(filename: string | undefined): boolean {
2425
return !!filename && standardReadmePattern.test(filename)
2526
}
2627

28+
async function cancelUnreadBatchResponses(
29+
responses: Array<Response | null>,
30+
startIndex: number,
31+
): Promise<void> {
32+
await Promise.allSettled(responses.slice(startIndex).map(response => response?.body?.cancel()))
33+
}
34+
35+
function buildReadmeFetchCandidates(readmeFilename: string | undefined): string[] {
36+
return readmeFilename
37+
? standardReadmeFilenames.filter(name => name !== readmeFilename)
38+
: standardReadmeFilenames
39+
}
40+
2741
/**
2842
* Fetch README from jsdelivr CDN for a specific package version.
29-
* Falls back through common README filenames.
43+
* Falls back through candidate README filenames in small parallel batches.
3044
*/
3145
export async function fetchReadmeFromJsdelivr(
3246
packageName: string,
@@ -35,15 +49,30 @@ export async function fetchReadmeFromJsdelivr(
3549
): Promise<string | null> {
3650
const versionSuffix = version ? `@${version}` : ''
3751

38-
for (const filename of readmeFilenames) {
39-
try {
40-
const url = `https://cdn.jsdelivr.net/npm/${packageName}${versionSuffix}/${filename}`
41-
const response = await fetch(url)
42-
if (response.ok) {
43-
return await response.text()
52+
for (let index = 0; index < readmeFilenames.length; index += JSDELIVR_README_FETCH_BATCH_SIZE) {
53+
const batch = readmeFilenames.slice(index, index + JSDELIVR_README_FETCH_BATCH_SIZE)
54+
const responses = await Promise.all(
55+
batch.map(async filename => {
56+
try {
57+
const url = `https://cdn.jsdelivr.net/npm/${packageName}${versionSuffix}/${filename}`
58+
const response = await fetch(url)
59+
if (!response.ok) {
60+
return null
61+
}
62+
63+
return response
64+
} catch {
65+
return null
66+
}
67+
}),
68+
)
69+
70+
for (const [responseIndex, response] of responses.entries()) {
71+
const text = await response?.text()
72+
if (text?.trim()) {
73+
await cancelUnreadBatchResponses(responses, responseIndex + 1)
74+
return text
4475
}
45-
} catch {
46-
// Try next filename
4776
}
4877
}
4978

@@ -85,11 +114,23 @@ export const resolvePackageReadmeSource = defineCachedFunction(
85114
readmeContent!.length >= NPM_README_TRUNCATION_THRESHOLD
86115
) {
87116
const resolvedVersion = version ?? packageData['dist-tags']?.latest
88-
const jsdelivrReadme = await fetchReadmeFromJsdelivr(
89-
packageName,
90-
standardReadmeFilenames,
91-
resolvedVersion,
92-
)
117+
118+
// try fetching the given readme file first
119+
let jsdelivrReadme =
120+
readmeFilename &&
121+
(await fetchReadmeFromJsdelivr(packageName, [readmeFilename], resolvedVersion))
122+
123+
// if it's unsuccessful, fetch all known readme filenames
124+
if (!jsdelivrReadme) {
125+
const readmeCandidates = buildReadmeFetchCandidates(readmeFilename)
126+
jsdelivrReadme = await fetchReadmeFromJsdelivr(
127+
packageName,
128+
readmeCandidates,
129+
resolvedVersion,
130+
)
131+
}
132+
133+
// if we found something, use it
93134
if (jsdelivrReadme) {
94135
readmeContent = jsdelivrReadme
95136
}

test/unit/server/utils/readme-loaders.spec.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ describe('isStandardReadme', () => {
3838
})
3939

4040
describe('fetchReadmeFromJsdelivr', () => {
41+
beforeEach(() => {
42+
vi.unstubAllGlobals()
43+
vi.stubGlobal('defineCachedFunction', (fn: Function) => fn)
44+
vi.stubGlobal('$fetch', $fetchMock)
45+
vi.stubGlobal('parsePackageParams', parsePackageParams)
46+
vi.stubGlobal('fetchNpmPackage', fetchNpmPackageMock)
47+
vi.stubGlobal('parseRepositoryInfo', parseRepositoryInfoMock)
48+
})
49+
4150
it('returns content when first filename succeeds', async () => {
4251
const content = '# Package'
4352
const fetchMock = vi.fn().mockResolvedValue({
@@ -73,6 +82,86 @@ describe('fetchReadmeFromJsdelivr', () => {
7382
expect(result).toBeNull()
7483
expect(fetchMock).toHaveBeenCalledTimes(2)
7584
})
85+
86+
it('starts a small batch of candidate fetches in parallel', async () => {
87+
let resolveReadmeMd!: (value: { ok: false }) => void
88+
let resolveLowercase!: (value: { ok: false }) => void
89+
let resolveReadme!: (value: { ok: true; text: () => Promise<string> }) => void
90+
91+
const fetchMock = vi.fn((url: string) => {
92+
if (url.endsWith('/README.md')) {
93+
return new Promise(resolve => {
94+
resolveReadmeMd = resolve
95+
})
96+
}
97+
98+
if (url.endsWith('/readme.md')) {
99+
return new Promise(resolve => {
100+
resolveLowercase = resolve
101+
})
102+
}
103+
104+
if (url.endsWith('/README')) {
105+
return new Promise(resolve => {
106+
resolveReadme = resolve
107+
})
108+
}
109+
110+
return Promise.resolve({ ok: false })
111+
})
112+
vi.stubGlobal('fetch', fetchMock)
113+
114+
const resultPromise = fetchReadmeFromJsdelivr('pkg', [
115+
'README.md',
116+
'readme.md',
117+
'README',
118+
'readme',
119+
])
120+
121+
expect(fetchMock).toHaveBeenCalledTimes(3)
122+
expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
123+
'https://cdn.jsdelivr.net/npm/pkg/README.md',
124+
'https://cdn.jsdelivr.net/npm/pkg/readme.md',
125+
'https://cdn.jsdelivr.net/npm/pkg/README',
126+
])
127+
128+
resolveReadmeMd({ ok: false })
129+
resolveLowercase({ ok: false })
130+
resolveReadme({
131+
ok: true,
132+
text: async () => '# Package',
133+
})
134+
135+
await expect(resultPromise).resolves.toBe('# Package')
136+
expect(fetchMock).toHaveBeenCalledTimes(3)
137+
})
138+
139+
it('reads only the matched successful response body', async () => {
140+
const firstTextMock = vi.fn().mockResolvedValue('# First')
141+
const secondTextMock = vi.fn().mockResolvedValue('# Second')
142+
const secondCancelMock = vi.fn().mockResolvedValue(undefined)
143+
const fetchMock = vi
144+
.fn()
145+
.mockResolvedValueOnce({
146+
ok: true,
147+
text: firstTextMock,
148+
})
149+
.mockResolvedValueOnce({
150+
ok: true,
151+
text: secondTextMock,
152+
body: {
153+
cancel: secondCancelMock,
154+
},
155+
})
156+
vi.stubGlobal('fetch', fetchMock)
157+
158+
const result = await fetchReadmeFromJsdelivr('pkg', ['README.md', 'readme.md'])
159+
160+
expect(result).toBe('# First')
161+
expect(firstTextMock).toHaveBeenCalledTimes(1)
162+
expect(secondTextMock).not.toHaveBeenCalled()
163+
expect(secondCancelMock).toHaveBeenCalledTimes(1)
164+
})
76165
})
77166

78167
describe('resolvePackageReadmeSource', () => {
@@ -172,6 +261,80 @@ describe('resolvePackageReadmeSource', () => {
172261
const result = await resolvePackageReadmeSource('pkg')
173262

174263
expect(result).toMatchObject({ markdown: jsdelivrContent })
264+
expect(fetchMock).toHaveBeenNthCalledWith(1, 'https://cdn.jsdelivr.net/npm/pkg/DOCS.md')
265+
})
266+
267+
it('tries a provided readmeFilename before starting the fallback batch', async () => {
268+
let resolveDocs!: (value: { ok: false }) => void
269+
let resolveReadmeMd!: (value: { ok: false }) => void
270+
let resolveLowercase!: (value: { ok: false }) => void
271+
let resolveReadmeCase!: (value: { ok: true; text: () => Promise<string> }) => void
272+
273+
fetchNpmPackageMock.mockResolvedValue({
274+
readme: undefined,
275+
readmeFilename: 'DOCS.md',
276+
repository: undefined,
277+
versions: {},
278+
})
279+
parseRepositoryInfoMock.mockReturnValue(undefined)
280+
281+
const fetchMock = vi.fn((url: string) => {
282+
if (url.endsWith('/DOCS.md')) {
283+
return new Promise(resolve => {
284+
resolveDocs = resolve
285+
})
286+
}
287+
288+
if (url.endsWith('/README.md')) {
289+
return new Promise(resolve => {
290+
resolveReadmeMd = resolve
291+
})
292+
}
293+
294+
if (url.endsWith('/readme.md')) {
295+
return new Promise(resolve => {
296+
resolveLowercase = resolve
297+
})
298+
}
299+
300+
if (url.endsWith('/Readme.md')) {
301+
return new Promise(resolve => {
302+
resolveReadmeCase = resolve
303+
})
304+
}
305+
306+
return Promise.resolve({ ok: false })
307+
})
308+
vi.stubGlobal('fetch', fetchMock)
309+
310+
const resultPromise = resolvePackageReadmeSource('pkg')
311+
312+
await new Promise(resolve => setTimeout(resolve, 0))
313+
expect(fetchMock).toHaveBeenCalledTimes(1)
314+
expect(fetchMock).toHaveBeenNthCalledWith(1, 'https://cdn.jsdelivr.net/npm/pkg/DOCS.md')
315+
316+
resolveDocs({ ok: false })
317+
318+
await new Promise(resolve => setTimeout(resolve, 0))
319+
expect(fetchMock).toHaveBeenCalledTimes(4)
320+
expect(fetchMock.mock.calls.slice(1).map(([url]) => url)).toEqual([
321+
'https://cdn.jsdelivr.net/npm/pkg/README.md',
322+
'https://cdn.jsdelivr.net/npm/pkg/readme.md',
323+
'https://cdn.jsdelivr.net/npm/pkg/Readme.md',
324+
])
325+
326+
resolveReadmeMd({ ok: false })
327+
resolveLowercase({ ok: false })
328+
resolveReadmeCase({
329+
ok: true,
330+
text: async () => '# From fallback batch',
331+
})
332+
333+
await expect(resultPromise).resolves.toMatchObject({
334+
packageName: 'pkg',
335+
markdown: '# From fallback batch',
336+
repoInfo: undefined,
337+
})
175338
})
176339

177340
it('returns undefined markdown when no content and jsdelivr fails', async () => {

0 commit comments

Comments
 (0)