diff --git a/.github/workflows/download-packages-popularity.yml b/.github/workflows/download-packages-popularity.yml new file mode 100644 index 000000000..f514db15e --- /dev/null +++ b/.github/workflows/download-packages-popularity.yml @@ -0,0 +1,70 @@ +name: Download and save packages popularity + +env: + CI: "true" + +on: + push: + branches: + - master + paths: + - .github/workflows/*.yml + - "cases/*.ts" + - "*.ts" + - package.json + - package-lock.json + - bun.lockb + +jobs: + build: + name: "Node ${{ matrix.node-version }}" + + runs-on: ubuntu-latest + + strategy: + max-parallel: 1 + matrix: + node-version: + - 23.x + + steps: + - uses: actions/checkout@v4 + + - name: Cache node modules + uses: actions/cache@v4 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: npm install + run: npm ci + + - name: download-packages-popularity + run: npm run download-packages-popularity + + ## FIX: https://github.com/github-actions-x/commit/issues/16 + - name: Expose git commit data + uses: rlespinasse/git-commit-data-action@v1 ## https://github.com/rlespinasse/git-commit-data-action + + - name: push + uses: github-actions-x/commit@v2.9 + ## prevents forked repos from comitting results in PRs + if: github.repository == 'moltar/typescript-runtime-type-benchmarks' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + push-branch: master + commit-message: 'feat: adds popularity of packages' + rebase: 'true' + name: ${{ env.GIT_COMMIT_AUTHOR_NAME }} ## FIX: https://github.com/github-actions-x/commit/issues/16 + email: ${{ env.GIT_COMMIT_AUTHOR_EMAIL }} ## FIX: https://github.com/github-actions-x/commit/issues/16 \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 760ea9ab3..f33d88f42 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/app.tsx b/docs/app.tsx index 981907985..06b1de3fd 100644 --- a/docs/app.tsx +++ b/docs/app.tsx @@ -67,6 +67,23 @@ BENCHMARKS.forEach(b => { BENCHMARKS_ORDER[b.name] = b.order; }); +const PACKAGES_POPULARITY: { [k: string]: number } = {}; + +type PackagePopularity = { + name: string; + weeklyDownloads: number; +}; + +async function loadPackagesPopularity() { + await fetch('packagesPopularity.json') + .then(res => res.json() as Promise) + .then(data => { + data.forEach(p => { + PACKAGES_POPULARITY[p.name] = p.weeklyDownloads; + }); + }); +} + function normalizePartialValues(values: BenchmarkResult[]): BenchmarkResult[] { if (!values.length) { return []; @@ -147,7 +164,7 @@ async function graph({ selectedBunVersions: string[]; benchmarkResultsNodejs: BenchmarkResult[]; benchmarkResultsBun: BenchmarkResult[]; - sort?: 'alphabetically' | 'fastest'; + sort?: 'alphabetically' | 'fastest' | 'popularity'; }) { if ( !selectedBenchmarks.length || @@ -230,6 +247,13 @@ async function graph({ sortedValues = [...valuesNodejs, ...valuesBun].sort((a, b) => a.name < b.name ? -1 : 1, ); + } else if (sort === 'popularity') { + sortedValues = [...valuesNodejs, ...valuesBun].sort((a, b) => { + const aPopularity = PACKAGES_POPULARITY[a.name] || 0; + const bPopularity = PACKAGES_POPULARITY[b.name] || 0; + + return bPopularity - aPopularity; + }); } // remove duplicates not sure whether vega-lite can handle that @@ -427,7 +451,7 @@ class App extends Component< selectedBunVersions: { [key: string]: boolean }; valuesNodeJs: BenchmarkResult[]; valuesBun: BenchmarkResult[]; - sortBy: 'fastest' | 'alphabetically'; + sortBy: 'fastest' | 'alphabetically' | 'popularity'; } > { state = { @@ -470,7 +494,9 @@ class App extends Component< return res; } - componentDidMount() { + async componentDidMount() { + await loadPackagesPopularity(); + NODE_VERSIONS.forEach((v, i) => { fetch(`results/node-${v}.json`) .then(response => response.json() as Promise) @@ -649,6 +675,7 @@ class App extends Component< > + diff --git a/docs/dist/app.js b/docs/dist/app.js index a97ee62c0..57f04933a 100644 --- a/docs/dist/app.js +++ b/docs/dist/app.js @@ -143,6 +143,25 @@ define("app", ["require", "exports", "preact", "vega", "vega-lite"], function (r BENCHMARKS.forEach(function (b) { BENCHMARKS_ORDER[b.name] = b.order; }); + var PACKAGES_POPULARITY = {}; + function loadPackagesPopularity() { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, fetch('packagesPopularity.json') + .then(function (res) { return res.json(); }) + .then(function (data) { + data.forEach(function (p) { + PACKAGES_POPULARITY[p.name] = p.weeklyDownloads; + }); + })]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + } function normalizePartialValues(values) { if (!values.length) { return []; @@ -258,6 +277,13 @@ define("app", ["require", "exports", "preact", "vega", "vega-lite"], function (r return a.name < b.name ? -1 : 1; }); } + else if (sort === 'popularity') { + sortedValues = __spreadArray(__spreadArray([], valuesNodejs, true), valuesBun, true).sort(function (a, b) { + var aPopularity = PACKAGES_POPULARITY[a.name] || 0; + var bPopularity = PACKAGES_POPULARITY[b.name] || 0; + return bPopularity - aPopularity; + }); + } sortedNames = []; new Set(sortedValues.map(function (b) { return b.name; })).forEach(function (n) { return sortedNames.push(n); }); vegaSpec = vegaLite.compile({ @@ -441,33 +467,43 @@ define("app", ["require", "exports", "preact", "vega", "vega-lite"], function (r return res; }; App.prototype.componentDidMount = function () { - var _this = this; - NODE_VERSIONS.forEach(function (v, i) { - fetch("results/node-".concat(v, ".json")) - .then(function (response) { return response.json(); }) - .then(function (data) { - _this.setState(function (state) { - var _a; - return (__assign(__assign({}, state), { - // select the first node versions benchmark automatically - selectedNodeJsVersions: i === 0 - ? __assign(__assign({}, state.selectedNodeJsVersions), (_a = {}, _a[data.results[0].runtimeVersion] = true, _a)) : state.selectedNodeJsVersions, valuesNodeJs: __spreadArray(__spreadArray([], state.valuesNodeJs, true), normalizePartialValues(data.results), true) })); - }); - }) - .catch(function (err) { - console.info("no data for node ".concat(v), err); - }); - }); - BUN_VERSIONS.forEach(function (v) { - fetch("results/bun-".concat(v, ".json")) - .then(function (response) { return response.json(); }) - .then(function (data) { - _this.setState(function (state) { return (__assign(__assign({}, state), { - // select the first node versions benchmark automatically - selectedBunVersions: state.selectedBunVersions, valuesBun: __spreadArray(__spreadArray([], state.valuesBun, true), normalizePartialValues(data.results), true) })); }); - }) - .catch(function (err) { - console.info("no data for bun ".concat(v), err); + return __awaiter(this, void 0, void 0, function () { + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, loadPackagesPopularity()]; + case 1: + _a.sent(); + NODE_VERSIONS.forEach(function (v, i) { + fetch("results/node-".concat(v, ".json")) + .then(function (response) { return response.json(); }) + .then(function (data) { + _this.setState(function (state) { + var _a; + return (__assign(__assign({}, state), { + // select the first node versions benchmark automatically + selectedNodeJsVersions: i === 0 + ? __assign(__assign({}, state.selectedNodeJsVersions), (_a = {}, _a[data.results[0].runtimeVersion] = true, _a)) : state.selectedNodeJsVersions, valuesNodeJs: __spreadArray(__spreadArray([], state.valuesNodeJs, true), normalizePartialValues(data.results), true) })); + }); + }) + .catch(function (err) { + console.info("no data for node ".concat(v), err); + }); + }); + BUN_VERSIONS.forEach(function (v) { + fetch("results/bun-".concat(v, ".json")) + .then(function (response) { return response.json(); }) + .then(function (data) { + _this.setState(function (state) { return (__assign(__assign({}, state), { + // select the first node versions benchmark automatically + selectedBunVersions: state.selectedBunVersions, valuesBun: __spreadArray(__spreadArray([], state.valuesBun, true), normalizePartialValues(data.results), true) })); }); + }) + .catch(function (err) { + console.info("no data for bun ".concat(v), err); + }); + }); + return [2 /*return*/]; + } }); }); }; @@ -527,7 +563,8 @@ define("app", ["require", "exports", "preact", "vega", "vega-lite"], function (r _this.setState({ sortBy: event.target.value }); }, value: this.state.sortBy }, (0, preact_1.h)("option", { value: "fastest" }, "Fastest"), - (0, preact_1.h)("option", { value: "alphabetically" }, "Alphabetically"))))), + (0, preact_1.h)("option", { value: "alphabetically" }, "Alphabetically"), + (0, preact_1.h)("option", { value: "popularity" }, "Popularity"))))), (0, preact_1.h)(Graph, { benchmarks: BENCHMARKS.filter(function (b) { return _this.state.selectedBenchmarks[b.name]; }), nodeJsVersions: Object.entries(this.state.selectedNodeJsVersions) .sort() .filter(function (entry) { return entry[1]; }) diff --git a/docs/packagesPopularity.json b/docs/packagesPopularity.json new file mode 100644 index 000000000..9133159d8 --- /dev/null +++ b/docs/packagesPopularity.json @@ -0,0 +1 @@ +[{"name":"aeria","weeklyDownloads":925},{"name":"ajv","weeklyDownloads":108193774},{"name":"arktype","weeklyDownloads":60942},{"name":"banditypes","weeklyDownloads":250},{"name":"bueno","weeklyDownloads":70},{"name":"caketype","weeklyDownloads":70},{"name":"class-transformer-validator-sync","weeklyDownloads":3167945},{"name":"computed-types","weeklyDownloads":1778},{"name":"decoders","weeklyDownloads":13809},{"name":"io-ts","weeklyDownloads":1333952},{"name":"jointz","weeklyDownloads":107},{"name":"json-decoder","weeklyDownloads":127},{"name":"$mol_data","weeklyDownloads":85},{"name":"@mojotech/json-type-validation","weeklyDownloads":15090},{"name":"mondrian-framework","weeklyDownloads":77},{"name":"myzod","weeklyDownloads":25398},{"name":"ok-computer","weeklyDownloads":70},{"name":"parse-dont-validate (chained function)","weeklyDownloads":195},{"name":"parse-dont-validate (named parameters)","weeklyDownloads":195},{"name":"purify-ts","weeklyDownloads":16508},{"name":"r-assign","weeklyDownloads":1684},{"name":"rescript-schema","weeklyDownloads":888},{"name":"rulr","weeklyDownloads":1227},{"name":"runtypes","weeklyDownloads":224592},{"name":"@sapphire/shapeshift","weeklyDownloads":241466},{"name":"simple-runtypes","weeklyDownloads":794},{"name":"@sinclair/typebox-(ahead-of-time)","weeklyDownloads":30679213},{"name":"@sinclair/typebox-(dynamic)","weeklyDownloads":30679213},{"name":"@sinclair/typebox-(just-in-time)","weeklyDownloads":30679213},{"name":"spectypes","weeklyDownloads":77},{"name":"succulent","weeklyDownloads":87},{"name":"superstruct","weeklyDownloads":1626205},{"name":"suretype","weeklyDownloads":30307},{"name":"tiny-schema-validator","weeklyDownloads":15},{"name":"to-typed","weeklyDownloads":74},{"name":"toi","weeklyDownloads":695},{"name":"ts-interface-checker","weeklyDownloads":9449654},{"name":"ts-json-validator","weeklyDownloads":32701},{"name":"ts-runtime-checks","weeklyDownloads":567},{"name":"ts-utils","weeklyDownloads":71},{"name":"tson","weeklyDownloads":70},{"name":"@typeofweb/schema","weeklyDownloads":152},{"name":"typia","weeklyDownloads":152527},{"name":"unknownutil","weeklyDownloads":882},{"name":"valibot","weeklyDownloads":412391},{"name":"valita","weeklyDownloads":13451},{"name":"vality","weeklyDownloads":122},{"name":"yup","weeklyDownloads":6542575},{"name":"zod","weeklyDownloads":12787921},{"name":"deepkit","weeklyDownloads":8114},{"name":"effect-schema","weeklyDownloads":811506},{"name":"ts-auto-guard","weeklyDownloads":7029}] \ No newline at end of file diff --git a/download-packages-popularity.ts b/download-packages-popularity.ts new file mode 100644 index 000000000..71de17ad6 --- /dev/null +++ b/download-packages-popularity.ts @@ -0,0 +1,265 @@ +import fs from 'node:fs'; +import { request } from 'undici'; + +export const packages = [ + { + name: 'aeria', + packageName: '@aeriajs/validation', + }, + { + name: 'ajv', + packageName: 'ajv', + }, + { + name: 'arktype', + packageName: 'arktype', + }, + { + name: 'banditypes', + packageName: 'banditypes', + }, + { + name: 'bueno', + packageName: 'bueno', + }, + { + name: 'caketype', + packageName: 'caketype', + }, + { + name: 'class-transformer-validator-sync', + packageName: 'class-validator', + }, + { + name: 'computed-types', + packageName: 'computed-types', + }, + { + name: 'decoders', + packageName: 'decoders', + }, + { + name: 'io-ts', + packageName: 'io-ts', + }, + { + name: 'jointz', + packageName: 'jointz', + }, + { + name: 'json-decoder', + packageName: 'json-decoder', + }, + { + name: '$mol_data', + packageName: 'mol_data_all', + }, + { + name: '@mojotech/json-type-validation', + packageName: '@mojotech/json-type-validation', + }, + { + name: 'mondrian-framework', + packageName: '@mondrian-framework/model', + }, + { + name: 'myzod', + packageName: 'myzod', + }, + { + name: 'ok-computer', + packageName: 'ok-computer', + }, + { + name: 'parse-dont-validate (chained function)', + packageName: 'parse-dont-validate', + }, + { + name: 'parse-dont-validate (named parameters)', + packageName: 'parse-dont-validate', + }, + { + name: 'purify-ts', + packageName: 'purify-ts', + }, + { + name: 'r-assign', + packageName: 'r-assign', + }, + { + name: 'rescript-schema', + packageName: 'rescript-schema', + }, + { + name: 'rulr', + packageName: 'rulr', + }, + { + name: 'runtypes', + packageName: 'runtypes', + }, + { + name: '@sapphire/shapeshift', + packageName: '@sapphire/shapeshift', + }, + { + name: 'simple-runtypes', + packageName: 'simple-runtypes', + }, + { + name: '@sinclair/typebox-(ahead-of-time)', + packageName: '@sinclair/typebox', + }, + { + name: '@sinclair/typebox-(dynamic)', + packageName: '@sinclair/typebox', + }, + { + name: '@sinclair/typebox-(just-in-time)', + packageName: '@sinclair/typebox', + }, + { + name: 'spectypes', + packageName: 'spectypes', + }, + { + name: 'succulent', + packageName: 'succulent', + }, + { + name: 'superstruct', + packageName: 'superstruct', + }, + { + name: 'suretype', + packageName: 'suretype', + }, + { + name: 'tiny-schema-validator', + packageName: 'tiny-schema-validator', + }, + { + name: 'to-typed', + packageName: 'to-typed', + }, + { + name: 'toi', + packageName: '@toi/toi', + }, + { + name: 'ts-interface-checker', + packageName: 'ts-interface-checker', + }, + { + name: 'ts-json-validator', + packageName: 'ts-json-validator', + }, + { + name: 'ts-runtime-checks', + packageName: 'ts-runtime-checks', + }, + { + name: 'ts-utils', + packageName: '@ailabs/ts-utils', + }, + { + name: 'tson', + packageName: '@skarab/tson', + }, + { + name: '@typeofweb/schema', + packageName: '@typeofweb/schema', + }, + { + name: 'typia', + packageName: 'typia', + }, + { + name: 'unknownutil', + packageName: 'unknownutil', + }, + { + name: 'valibot', + packageName: 'valibot', + }, + { + name: 'valita', + packageName: '@badrap/valita', + }, + { + name: 'vality', + packageName: 'vality', + }, + { + name: 'yup', + packageName: 'yup', + }, + { + name: 'zod', + packageName: 'zod', + }, + { + name: 'deepkit', + packageName: '@deepkit/core', + }, + { + name: 'effect-schema', + packageName: '@effect/schema', + }, + { + name: 'ts-auto-guard', + packageName: 'ts-auto-guard', + }, +] as const; + +interface BodyWeeklyDownloads { + downloads: number; + start: Date; + end: Date; + package: string; +} + +async function getWeeklyDownloads(packageName: string) { + try { + const response = await request( + `https://api.npmjs.org/downloads/point/last-week/${packageName}`, + ).then(response => response.body.json() as Promise); + + return response.downloads; + } catch (error) { + console.error('Error fetching download data:', error); + } +} + +const packagesData: { + name: string; + weeklyDownloads: number; +}[] = []; + +async function main() { + for (const { name, packageName } of packages) { + console.log(`Downloading ${name}`); + + const weeklyDownloads = await getWeeklyDownloads(packageName); + + if (typeof weeklyDownloads !== 'number') { + console.error(`No weekly downloads found for ${packageName}`); + + continue; + } + + packagesData.push({ + name, + weeklyDownloads, + }); + } + + fs.writeFileSync( + './docs/packagesPopularity.json', + JSON.stringify(packagesData), + ); +} + +main().catch(error => { + console.error('Error:', error); +}); diff --git a/package-lock.json b/package-lock.json index 17d7c877f..223b981e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "ts-runtime-checks": "0.6.2", "typescript": "5.6.3", "typia": "6.12.0", + "undici": "6.20.1", "unknownutil": "3.18.1", "valibot": "0.42.1", "vality": "6.3.4", @@ -10642,6 +10643,15 @@ "node": ">=12.17" } }, + "node_modules/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -19016,6 +19026,11 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz", "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==" }, + "undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==" + }, "undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/package.json b/package.json index 471178863..bb36a332c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "compile:typebox": "ts-node cases/typebox/index.ts cases/typebox/build", "compile:typia": "rimraf cases/typia/build && tsc -p cases/typia/tsconfig.json", "compile:ts-auto-guard": "rimraf cases/ts-auto-guard/build && ts-auto-guard --project cases/ts-auto-guard/tsconfig.json && tsc -p cases/ts-auto-guard/tsconfig.json", - "prepare": "ts-patch install" + "prepare": "ts-patch install", + "download-packages-popularity": "ts-node download-packages-popularity.ts" }, "dependencies": { "@aeriajs/validation": "0.0.131", @@ -86,6 +87,7 @@ "ts-runtime-checks": "0.6.2", "typescript": "5.6.3", "typia": "6.12.0", + "undici": "6.20.1", "unknownutil": "3.18.1", "valibot": "0.42.1", "vality": "6.3.4",