Skip to content

Commit

Permalink
migrate Pages api routes to Astro endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
aantipov committed Aug 7, 2024
1 parent b20f583 commit e5cfcd3
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 68 deletions.
8 changes: 6 additions & 2 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}),
});
9 changes: 0 additions & 9 deletions functions/tsconfig.json

This file was deleted.

9 changes: 0 additions & 9 deletions functions/worker-configuration.d.ts

This file was deleted.

6 changes: 6 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ interface ImportMetaEnv {
interface ImportMeta {
readonly env: ImportMetaEnv;
}

type Runtime = import('@astrojs/cloudflare').Runtime<Env>;

declare namespace App {
interface Locals extends Runtime {}
}
Original file line number Diff line number Diff line change
@@ -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<Env, 'packages', Record<string, unknown>>;
type Runtime = import('@astrojs/cloudflare').Runtime<Env>;
const errorPrefix = 'API-NPM-COMPARE';

export const onRequest: PagesFunction<Env, 'packages'> = 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);
Expand All @@ -17,8 +21,11 @@ export const onRequest: PagesFunction<Env, 'packages'> = async (ctx) => {
}
};

async function handleRequest(ctx: CTX): Promise<Response> {
const packagesStr = decodeURIComponent(ctx.params.packages as string);
async function handleRequest(
packagesStr: string,
ctx: Runtime['runtime']['ctx'],
env: Runtime['runtime']['env'],
): Promise<Response> {
const packages = packagesStr.split('_vs_');
if (
packages.length !== 2 ||
Expand All @@ -33,7 +40,7 @@ async function handleRequest(ctx: CTX): Promise<Response> {
}

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
Expand All @@ -42,15 +49,15 @@ async function handleRequest(ctx: CTX): Promise<Response> {
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), {
headers: { 'content-type': 'application/json;charset=UTF-8' },
});
}

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, {
Expand All @@ -62,9 +69,9 @@ async function handleRequest(ctx: CTX): Promise<Response> {
async function getKvValue(
pkgName1: string,
pkgName2: string,
ctx: CTX
env: Runtime['runtime']['env'],
): Promise<KvAiCompareT> {
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<KvAiCompareT>(kvKey, {
type: 'json',
Expand All @@ -84,9 +91,9 @@ async function updateKvValue(
pkgName1: string,
pkgName2: string,
kvValue: KvAiCompareT,
ctx: CTX
env: Runtime['runtime']['env'],
): Promise<void> {
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(
Expand All @@ -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);
Expand All @@ -105,14 +112,14 @@ async function updateKvValue(
async function setKvValue(
pkgName1: string,
pkgName2: string,
ctx: CTX
env: Runtime['runtime']['env'],
): Promise<void> {
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);
Expand Down
71 changes: 41 additions & 30 deletions functions/npm-info/[pkg].ts → src/pages/npm-info/[pkg].ts
Original file line number Diff line number Diff line change
@@ -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<Env>;

type CTX = EventContext<Env, 'pkg', Record<string, unknown>>;
const cacheTtl = 3600 * 24 * 1; // 1 day in seconds
const errorPrefix = 'API-NPM-INFO';

export const onRequest: PagesFunction<Env, 'pkg'> = 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
Expand All @@ -25,9 +28,11 @@ export const onRequest: PagesFunction<Env, 'pkg'> = async (ctx) => {
}
};

async function handleRequest(ctx: CTX): Promise<Response> {
const pkg = ctx.params.pkg as string;

async function handleRequest(
pkg: string | undefined,
ctx: Runtime['runtime']['ctx'],
env: Runtime['runtime']['env'],
): Promise<Response> {
if (!pkg) {
return new Response(null, {
status: 404,
Expand All @@ -36,7 +41,7 @@ async function handleRequest(ctx: CTX): Promise<Response> {
}

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 (
Expand All @@ -53,7 +58,7 @@ async function handleRequest(ctx: CTX): Promise<Response> {
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), {
Expand All @@ -64,7 +69,7 @@ async function handleRequest(ctx: CTX): Promise<Response> {
});
}

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) {
Expand All @@ -74,7 +79,7 @@ async function handleRequest(ctx: CTX): Promise<Response> {
});
}

ctx.waitUntil(updateCache(pkgName, res, ctx));
ctx.waitUntil(updateCache(pkgName, res, env));

return new Response(JSON.stringify(res), {
headers: {
Expand All @@ -86,9 +91,9 @@ async function handleRequest(ctx: CTX): Promise<Response> {

async function getCachedValue(
pkgName: string,
ctx: CTX,
env: Runtime['runtime']['env'],
): Promise<KvNpmInfoT | null> {
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<KvNpmInfoT>(KV_CACHE_KEY, {
type: 'json',
Expand All @@ -99,10 +104,10 @@ async function getCachedValue(
async function updateCache(
pkgName: string,
res: NpmInfoApiResponseT,
ctx: CTX,
env: Runtime['runtime']['env'],
): Promise<void> {
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,
Expand All @@ -113,20 +118,20 @@ 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;
}

async function fetchDataAndUpdateCache(
pkgName: string,
cachedValue: KvNpmInfoT | null,
ctx: CTX,
env: Runtime['runtime']['env'],
): Promise<null> {
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);
Expand All @@ -138,9 +143,9 @@ async function fetchDataAndUpdateCache(
async function fetchData(
pkgName: string,
cachedValue: KvNpmInfoT | null,
ctx: CTX,
env: Runtime['runtime']['env'],
): Promise<NpmInfoApiResponseT | null> {
const kvAiBinding = ctx.env.aiPkgDescription;
const kvAiBinding = env.aiPkgDescription;
const aiPromise = kvAiBinding.get<KvAiT>(pkgName, { type: 'json' });
const npm = await fetchPkgInfo(pkgName, cachedValue?.data?.npm);

Expand All @@ -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 };
Expand All @@ -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 },
},
);
Expand All @@ -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 },
},
);
Expand Down Expand Up @@ -285,11 +292,11 @@ async function fetchPkgPublishedAt(

async function fetchRepoInfo(
repoId: string,
ctx: CTX,
env: Runtime['runtime']['env'],
): Promise<NpmInfoApiResponseT['repo']> {
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;
Expand All @@ -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
Expand All @@ -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 },
Expand Down

0 comments on commit e5cfcd3

Please sign in to comment.