diff --git a/astro.config.mjs b/astro.config.mjs index 2e42156d..ecf5d976 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -12,8 +12,12 @@ export default defineConfig({ vite: { // build: { minify: false }, // resolve: { alias: { '@': '/src' } }, - ssr: { noExternal: ['path-to-regexp'] }, + ssr: { external: ['node:buffer'], noExternal: ['path-to-regexp'] }, }, output: 'hybrid', - adapter: cloudflare({ mode: 'directory' }), + adapter: cloudflare({ + platformProxy: { + enabled: true, + }, + }), }); diff --git a/functions/tsconfig.json b/functions/tsconfig.json deleted file mode 100644 index 52a05201..00000000 --- a/functions/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "target": "esnext", - "module": "esnext", - "lib": ["esnext"], - "types": ["@cloudflare/workers-types"] - } -} diff --git a/functions/worker-configuration.d.ts b/functions/worker-configuration.d.ts deleted file mode 100644 index 3dd2227d..00000000 --- a/functions/worker-configuration.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// worker-configuration.d.ts -interface Env { - aiPkgDescription: KVNamespace; - CACHE_KV: KVNamespace; - AI_COMPARE_KV: KVNamespace; - OPENAI_API_KEY: string; - GITHUB_TOKEN: string; - GITHUB_USER_AGENT: string; -} diff --git a/src/env.d.ts b/src/env.d.ts index 772368e1..6549116b 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -10,3 +10,9 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +type Runtime = import('@astrojs/cloudflare').Runtime; + +declare namespace App { + interface Locals extends Runtime {} +} diff --git a/functions/npm-compare/[packages].ts b/src/pages/npm-compare/[packages].ts similarity index 68% rename from functions/npm-compare/[packages].ts rename to src/pages/npm-compare/[packages].ts index 5faf3f10..fbcd813e 100644 --- a/functions/npm-compare/[packages].ts +++ b/src/pages/npm-compare/[packages].ts @@ -1,12 +1,16 @@ -import { setAICompareInfo } from '../../functions-helpers/setAiCompareInfo'; -import type { KvAiCompareT } from '../../src/shared-types'; +import type { APIRoute } from 'astro'; +import { setAICompareInfo } from '../../../functions-helpers/setAiCompareInfo'; +import type { KvAiCompareT } from '../../shared-types'; -type CTX = EventContext>; +type Runtime = import('@astrojs/cloudflare').Runtime; const errorPrefix = 'API-NPM-COMPARE'; -export const onRequest: PagesFunction = async (ctx) => { +export const prerender = false; + +export const GET: APIRoute = ({ params, locals }) => { try { - return await handleRequest(ctx); + const packagesStr = decodeURIComponent(params.packages as string); + return handleRequest(packagesStr, locals.runtime.ctx, locals.runtime.env); } catch (error: any) { const msg = errorPrefix + ': ' + (error?.message || 'An error occurred!'); console.error(msg); @@ -17,8 +21,11 @@ export const onRequest: PagesFunction = async (ctx) => { } }; -async function handleRequest(ctx: CTX): Promise { - const packagesStr = decodeURIComponent(ctx.params.packages as string); +async function handleRequest( + packagesStr: string, + ctx: Runtime['runtime']['ctx'], + env: Runtime['runtime']['env'], +): Promise { const packages = packagesStr.split('_vs_'); if ( packages.length !== 2 || @@ -33,7 +40,7 @@ async function handleRequest(ctx: CTX): Promise { } const [pkgName1, pkgName2] = packages.sort(); - const kvValue = await getKvValue(pkgName1, pkgName2, ctx); + const kvValue = await getKvValue(pkgName1, pkgName2, env); if (kvValue) { // check the creation date and update the value if it's older than 30 days @@ -42,7 +49,7 @@ async function handleRequest(ctx: CTX): Promise { const diffTime = now.getTime() - creationDate.getTime(); const diffDays = diffTime / (1000 * 60 * 60 * 24); if (diffDays > 30) { - ctx.waitUntil(updateKvValue(pkgName1, pkgName2, kvValue, ctx)); + ctx.waitUntil(updateKvValue(pkgName1, pkgName2, kvValue, env)); } return new Response(JSON.stringify(kvValue.data), { @@ -50,7 +57,7 @@ async function handleRequest(ctx: CTX): Promise { }); } - ctx.waitUntil(setKvValue(pkgName1, pkgName2, ctx)); + ctx.waitUntil(setKvValue(pkgName1, pkgName2, env)); // TODO: Cache in KV the error response for 1 hour. return new Response(null, { @@ -62,9 +69,9 @@ async function handleRequest(ctx: CTX): Promise { async function getKvValue( pkgName1: string, pkgName2: string, - ctx: CTX + env: Runtime['runtime']['env'], ): Promise { - const KV_AI_COMPARE = ctx.env.AI_COMPARE_KV; + const KV_AI_COMPARE = env.AI_COMPARE_KV; const kvKey = `${pkgName1}_vs_${pkgName2}`; const kvValue = await KV_AI_COMPARE.get(kvKey, { type: 'json', @@ -84,9 +91,9 @@ async function updateKvValue( pkgName1: string, pkgName2: string, kvValue: KvAiCompareT, - ctx: CTX + env: Runtime['runtime']['env'], ): Promise { - const KV_AI_COMPARE = ctx.env.AI_COMPARE_KV; + const KV_AI_COMPARE = env.AI_COMPARE_KV; const kvKey = `${pkgName1}_vs_${pkgName2}`; try { await KV_AI_COMPARE.put( @@ -95,7 +102,7 @@ async function updateKvValue( ...kvValue, createdAt: new Date().toISOString().slice(0, 10), }), - { expirationTtl: 60 * 60 * 24 * 30 * 2 } // 2 months + { expirationTtl: 60 * 60 * 24 * 30 * 2 }, // 2 months ); } catch (error) { console.error('Error updating KV Compare value', error); @@ -105,14 +112,14 @@ async function updateKvValue( async function setKvValue( pkgName1: string, pkgName2: string, - ctx: CTX + env: Runtime['runtime']['env'], ): Promise { try { await setAICompareInfo( pkgName1, pkgName2, - ctx.env.AI_COMPARE_KV, - ctx.env.OPENAI_API_KEY + env.AI_COMPARE_KV, + env.OPENAI_API_KEY, ); } catch (error) { console.error(`[${errorPrefix}]: Error setting KV value`, error); diff --git a/functions/npm-info/[pkg].ts b/src/pages/npm-info/[pkg].ts similarity index 86% rename from functions/npm-info/[pkg].ts rename to src/pages/npm-info/[pkg].ts index 57efb09c..2f3efc55 100644 --- a/functions/npm-info/[pkg].ts +++ b/src/pages/npm-info/[pkg].ts @@ -1,19 +1,22 @@ -import { AI_INFO_VERSION } from '../../functions-helpers/fetchPackageAIInfo'; -import { setPkgAIInfo } from '../../functions-helpers/setPackageAiInfo'; +import type { APIRoute } from 'astro'; +import { AI_INFO_VERSION } from '../../../functions-helpers/fetchPackageAIInfo'; +import { setPkgAIInfo } from '../../../functions-helpers/setPackageAiInfo'; import type { KvAiT, NpmJsResponseT, NpmInfoApiResponseT, KvNpmInfoT, -} from '../../src/shared-types'; +} from '../../shared-types'; +type Runtime = import('@astrojs/cloudflare').Runtime; -type CTX = EventContext>; const cacheTtl = 3600 * 24 * 1; // 1 day in seconds const errorPrefix = 'API-NPM-INFO'; -export const onRequest: PagesFunction = async (ctx) => { +export const prerender = false; + +export const GET: APIRoute = ({ params, locals }) => { try { - return await handleRequest(ctx); + return handleRequest(params.pkg, locals.runtime.ctx, locals.runtime.env); } catch (error: any) { const msg = errorPrefix + ': ' + (error?.message || 'An error occurred!'); // TODO: report to sentry @@ -25,9 +28,11 @@ export const onRequest: PagesFunction = async (ctx) => { } }; -async function handleRequest(ctx: CTX): Promise { - const pkg = ctx.params.pkg as string; - +async function handleRequest( + pkg: string | undefined, + ctx: Runtime['runtime']['ctx'], + env: Runtime['runtime']['env'], +): Promise { if (!pkg) { return new Response(null, { status: 404, @@ -36,7 +41,7 @@ async function handleRequest(ctx: CTX): Promise { } const pkgName = decodeURIComponent(pkg); - const cachedValue = await getCachedValue(pkgName, ctx); + const cachedValue = await getCachedValue(pkgName, env); // Replace `&& cachedValue.data.npm` with ZOD scheme validataion: if cache value doesn't comply with the scheme, then re-fetch if ( @@ -53,7 +58,7 @@ async function handleRequest(ctx: CTX): Promise { cacheAge > 3600 * 24 * 1000 || cachedValue.data.ai.version !== AI_INFO_VERSION ) { - ctx.waitUntil(fetchDataAndUpdateCache(pkgName, cachedValue, ctx)); + ctx.waitUntil(fetchDataAndUpdateCache(pkgName, cachedValue, env)); } return new Response(JSON.stringify(cachedValue.data), { @@ -64,7 +69,7 @@ async function handleRequest(ctx: CTX): Promise { }); } - const res = await fetchData(pkgName, cachedValue, ctx); + const res = await fetchData(pkgName, cachedValue, env); // TODO: Cache in KV the error response for 1 hour. if (!res) { @@ -74,7 +79,7 @@ async function handleRequest(ctx: CTX): Promise { }); } - ctx.waitUntil(updateCache(pkgName, res, ctx)); + ctx.waitUntil(updateCache(pkgName, res, env)); return new Response(JSON.stringify(res), { headers: { @@ -86,9 +91,9 @@ async function handleRequest(ctx: CTX): Promise { async function getCachedValue( pkgName: string, - ctx: CTX, + env: Runtime['runtime']['env'], ): Promise { - const KV_CACHE = ctx.env.CACHE_KV; + const KV_CACHE = env.CACHE_KV; const KV_CACHE_KEY = `npm-info-${pkgName}`; const cachedValue = await KV_CACHE.get(KV_CACHE_KEY, { type: 'json', @@ -99,10 +104,10 @@ async function getCachedValue( async function updateCache( pkgName: string, res: NpmInfoApiResponseT, - ctx: CTX, + env: Runtime['runtime']['env'], ): Promise { - const kvAiBinding = ctx.env.aiPkgDescription; - const kvCacheBinding = ctx.env.CACHE_KV; + const kvAiBinding = env.aiPkgDescription; + const kvCacheBinding = env.CACHE_KV; const kvCacheKey = `npm-info-${pkgName}`; const newKvCacheValue: KvNpmInfoT = { data: res, @@ -113,7 +118,7 @@ async function updateCache( kvCacheBinding.put(kvCacheKey, JSON.stringify(newKvCacheValue)), res.ai && res.ai.version == AI_INFO_VERSION ? null - : setPkgAIInfo(pkgName, kvAiBinding, ctx.env.OPENAI_API_KEY), + : setPkgAIInfo(pkgName, kvAiBinding, env.OPENAI_API_KEY), ]); return; } @@ -121,12 +126,12 @@ async function updateCache( async function fetchDataAndUpdateCache( pkgName: string, cachedValue: KvNpmInfoT | null, - ctx: CTX, + env: Runtime['runtime']['env'], ): Promise { try { - const res = await fetchData(pkgName, cachedValue, ctx); + const res = await fetchData(pkgName, cachedValue, env); if (res) { - await updateCache(pkgName, res, ctx); + await updateCache(pkgName, res, env); } } catch (error) { console.log('error fetch and update', error); @@ -138,9 +143,9 @@ async function fetchDataAndUpdateCache( async function fetchData( pkgName: string, cachedValue: KvNpmInfoT | null, - ctx: CTX, + env: Runtime['runtime']['env'], ): Promise { - const kvAiBinding = ctx.env.aiPkgDescription; + const kvAiBinding = env.aiPkgDescription; const aiPromise = kvAiBinding.get(pkgName, { type: 'json' }); const npm = await fetchPkgInfo(pkgName, cachedValue?.data?.npm); @@ -149,7 +154,7 @@ async function fetchData( return null; } - const repo = npm.repoId ? await fetchRepoInfo(npm.repoId, ctx) : null; + const repo = npm.repoId ? await fetchRepoInfo(npm.repoId, env) : null; const ai = await aiPromise; return { npm, ai, repo }; @@ -168,6 +173,7 @@ async function fetchPkgInfo( `https://registry.npmjs.org/${encodeURIComponent(typesPackage)}/latest`, { headers: { 'content-type': 'application/json;charset=UTF-8' }, + // @ts-ignore cf: { cacheEverything: true, cacheTtl }, }, ); @@ -176,6 +182,7 @@ async function fetchPkgInfo( `https://registry.npmjs.org/${encodeURIComponent(pkgName)}/latest`, { headers: { 'content-type': 'application/json;charset=UTF-8' }, + // @ts-ignore cf: { cacheEverything: true, cacheTtl }, }, ); @@ -285,11 +292,11 @@ async function fetchPkgPublishedAt( async function fetchRepoInfo( repoId: string, - ctx: CTX, + env: Runtime['runtime']['env'], ): Promise { const url = 'https://api.github.com/graphql'; const [owner, name] = repoId.split('/'); - const response = await fetch(url, getRepoFetchParams(name, owner, ctx)); + const response = await fetch(url, getRepoFetchParams(name, owner, env)); if (!response.ok) { throw response; @@ -316,7 +323,11 @@ async function fetchRepoInfo( }; } -function getRepoFetchParams(name: string, owner: string, ctx: CTX) { +function getRepoFetchParams( + name: string, + owner: string, + env: Runtime['runtime']['env'], +) { // 'Type: Bug' - React; 'triage: bug' - Svelte; 'type: bug/fix' - Angular; 'Bug-fix' - Moment; 'issue: bug' - Luxon // '☢️Bug' - Dayjs; '🐜 Bug fix' & '🐛 Bug' - date-fns; 'type: bug' - chart.js; 'P2-bug' - Playwright // 'type: bug :sob:' - nestjs/nest @@ -328,8 +339,8 @@ function getRepoFetchParams(name: string, owner: string, ctx: CTX) { return { headers: { 'content-type': 'application/json;charset=UTF-8', - 'User-Agent': ctx.env.GITHUB_USER_AGENT, - Authorization: `Bearer ${ctx.env.GITHUB_TOKEN}`, + 'User-Agent': env.GITHUB_USER_AGENT, + Authorization: `Bearer ${env.GITHUB_TOKEN}`, }, method: 'POST', cf: { cacheEverything: true, cacheTtl },