Skip to content

Commit 4164cba

Browse files
committed
perf: parallelize jsDelivr README fallback probes
1 parent 87cf141 commit 4164cba

File tree

2 files changed

+96
-11
lines changed

2 files changed

+96
-11
lines changed

server/utils/readme-loaders.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,23 @@ 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+
function buildReadmeFetchCandidates(readmeFilename: string | undefined): string[] {
29+
if (!readmeFilename) {
30+
return standardReadmeFilenames
31+
}
32+
33+
return [readmeFilename, ...standardReadmeFilenames.filter(name => name !== readmeFilename)]
34+
}
35+
2736
/**
2837
* Fetch README from jsdelivr CDN for a specific package version.
29-
* Falls back through common README filenames.
38+
* Falls back through candidate README filenames in small parallel batches.
3039
*/
3140
export async function fetchReadmeFromJsdelivr(
3241
packageName: string,
@@ -35,15 +44,27 @@ export async function fetchReadmeFromJsdelivr(
3544
): Promise<string | null> {
3645
const versionSuffix = version ? `@${version}` : ''
3746

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()
44-
}
45-
} catch {
46-
// Try next filename
47+
for (let index = 0; index < readmeFilenames.length; index += JSDELIVR_README_FETCH_BATCH_SIZE) {
48+
const batch = readmeFilenames.slice(index, index + JSDELIVR_README_FETCH_BATCH_SIZE)
49+
const responses = await Promise.all(
50+
batch.map(async filename => {
51+
try {
52+
const url = `https://cdn.jsdelivr.net/npm/${packageName}${versionSuffix}/${filename}`
53+
const response = await fetch(url)
54+
if (!response.ok) {
55+
return null
56+
}
57+
58+
return await response.text()
59+
} catch {
60+
return null
61+
}
62+
}),
63+
)
64+
65+
const matchedReadme = responses.find((response): response is string => response !== null)
66+
if (matchedReadme) {
67+
return matchedReadme
4768
}
4869
}
4970

@@ -84,10 +105,11 @@ export const resolvePackageReadmeSource = defineCachedFunction(
84105
!isStandardReadme(readmeFilename) ||
85106
readmeContent!.length >= NPM_README_TRUNCATION_THRESHOLD
86107
) {
108+
const readmeCandidates = buildReadmeFetchCandidates(readmeFilename)
87109
const resolvedVersion = version ?? packageData['dist-tags']?.latest
88110
const jsdelivrReadme = await fetchReadmeFromJsdelivr(
89111
packageName,
90-
standardReadmeFilenames,
112+
readmeCandidates,
91113
resolvedVersion,
92114
)
93115
if (jsdelivrReadme) {

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

Lines changed: 63 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,59 @@ 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+
})
76138
})
77139

78140
describe('resolvePackageReadmeSource', () => {
@@ -172,6 +234,7 @@ describe('resolvePackageReadmeSource', () => {
172234
const result = await resolvePackageReadmeSource('pkg')
173235

174236
expect(result).toMatchObject({ markdown: jsdelivrContent })
237+
expect(fetchMock).toHaveBeenNthCalledWith(1, 'https://cdn.jsdelivr.net/npm/pkg/DOCS.md')
175238
})
176239

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

0 commit comments

Comments
 (0)