-
Notifications
You must be signed in to change notification settings - Fork 822
Expand file tree
/
Copy pathfetch-npm-stat-data.ts
More file actions
104 lines (80 loc) · 3.14 KB
/
fetch-npm-stat-data.ts
File metadata and controls
104 lines (80 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import { fetch } from 'bun';
import { fallbackFetchNpmDownloadData } from '~/scripts/fetch-npm-download-data';
import { REQUEST_SLEEP, sleep } from './helpers';
const ATTEMPTS_LIMIT = 2;
const REQUEST_TIMEOUT = 5000;
export async function fetchNpmStatDataBulk(namesArray: string[], attemptsCount = 0) {
try {
const url = urlForPackages(namesArray.join('&package='));
const response = await fetch(url, {
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
});
const downloadData = await response.json();
return await Promise.all(
namesArray.map(async name => {
if (response.status >= 500) {
return {
name,
npm: null,
};
}
const pkgData = downloadData[name];
if (pkgData && Object.keys(pkgData).length > 0) {
return {
name,
npm: formatDownloadData(pkgData),
};
}
console.error(
`📊 [npm-stat] ${name} doesn't not return downloads data in bulk request, falling back to single query!`
);
const singleUrl = urlForPackages(name);
const singleResponse = await fetch(singleUrl);
const singleDownloadData = await singleResponse.json();
const singlePkgData = singleDownloadData[name];
if (singlePkgData && Object.keys(singlePkgData).length <= 0) {
console.error(
`📊 [npm-stat] ${name} doesn't not return downloads data in single request, falling back to npm downloads API!`
);
return await fallbackFetchNpmDownloadData(name);
}
return {
name,
npm: formatDownloadData(singlePkgData),
};
})
);
} catch (error) {
if (error instanceof DOMException) {
console.error(`📊 [npm-stat] ${error.name}: ${error.message} Aborting!`);
return namesArray.map(name => ({ name, npm: null }));
}
if (attemptsCount >= ATTEMPTS_LIMIT) {
console.error('📊 [npm-stat] Looks like we have reach the npm-stat API rate limit!');
console.error(error);
return namesArray.map(name => ({ name, npm: null }));
}
await sleep(REQUEST_SLEEP, REQUEST_SLEEP * 2);
console.log(`📊 [npm-stat] Retrying fetch for ${namesArray.join(', ')} (${attemptsCount + 1})`);
return await fetchNpmStatDataBulk(namesArray, attemptsCount + 1);
}
}
function formatDownloadData(downloadData: Record<string, number>) {
const downloadCounts = Object.values(downloadData);
return {
downloads: downloadCounts.reduce((sum, value) => sum + value, 0),
weekDownloads: downloadCounts.slice(0, 6).reduce((sum, value) => sum + value, 0),
};
}
function formattedDate(date: Date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function urlForPackages(packagesList: string) {
const now = new Date();
const fromDate = new Date(now);
fromDate.setMonth(fromDate.getMonth() - 1);
return `https://npm-stat.com/api/download-counts?package=${packagesList}&from=${formattedDate(fromDate)}&until=${formattedDate(now)}`;
}