Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ lib/

worker.capnp
.storage/
packages/vercel/api/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ To build the image by yourself, use `pnpm build:image` script.

See [docker-compose.yml](./docker-compose.yml) for an example.

### Deploy on Vercel

An Edge Function version is available under [`packages/vercel`](./packages/vercel/README.md). Set the project root to that folder and use `pnpm --filter vercel-edge build` as the build command to produce the `api/[[...path]].js` entrypoint for Vercel.

## Usage

Simply copy the code below, paste it into your `README.md`, and change the path to your leetcode username (case-insensitive).
Expand Down
12 changes: 12 additions & 0 deletions packages/vercel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# LeetCode Stats Card - Vercel Edge

This package provides a Vercel Edge Function version of the Cloudflare Worker.

## Deploying to Vercel

1. Set the project root in Vercel to `packages/vercel`.
2. Use the build command `pnpm --filter vercel-edge build` to bundle the edge function (it emits `api/[[...path]].js`).
3. Keep the output directory as the project root so the generated `api` folder is deployed.
4. After deployment, the endpoint will be available at `/api` (e.g. `/api/<username>?theme=unicorn`).

All query options and behaviors match the Cloudflare Worker variant, including the demo page when no username is provided.
9 changes: 9 additions & 0 deletions packages/vercel/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare module "*.html" {
const content: string;
export default content;
}

declare module "*.html?raw" {
const content: string;
export default content;
}
18 changes: 18 additions & 0 deletions packages/vercel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"private": true,
"name": "vercel-edge",
"version": "0.0.0",
"scripts": {
"build": "esbuild src/index.ts --outfile=\"api/[[...path]].js\" --bundle --format=esm --target=es2021 --loader:.html=text",
"test": "vitest"
},
"dependencies": {
"hono": "^4.10.4",
"leetcode-card": "workspace:*"
},
"devDependencies": {
"esbuild": "^0.25.11",
"typescript": "^5.9.3",
"vitest": "^3.2.4"
}
}
56 changes: 56 additions & 0 deletions packages/vercel/src/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
function normalizeKey(request: RequestInfo | URL): string {
if (typeof request === "string") return request;
if (request instanceof Request) return request.url;
return request.toString();
}

class MemoryCache implements Cache {
private store = new Map<string, Response>();

async match(request: RequestInfo | URL): Promise<Response | undefined> {
const res = this.store.get(normalizeKey(request));
return res?.clone();
}

async matchAll(request?: RequestInfo | URL): Promise<readonly Response[]> {
const res = request ? await this.match(request) : undefined;
return res ? [res] : [];
}

async add(request: RequestInfo | URL): Promise<void> {
const res = await fetch(request);
await this.put(request, res);
}

async addAll(requests: RequestInfo[]): Promise<void> {
await Promise.all(requests.map((req) => this.add(req)));
}

async put(request: RequestInfo | URL, response: Response): Promise<void> {
this.store.set(normalizeKey(request), response.clone());
}

async delete(request: RequestInfo | URL): Promise<boolean> {
return this.store.delete(normalizeKey(request));
}

async keys(request?: RequestInfo | URL): Promise<readonly Request[]> {
if (!request) {
return Array.from(this.store.keys()).map((key) => new Request(key));
}
const key = normalizeKey(request);
return this.store.has(key) ? [new Request(key)] : [];
}
}

export async function createCache(): Promise<Cache> {
if (typeof caches !== "undefined") {
try {
return await caches.open("leetcode");
} catch (err) {
console.error("Failed to open edge cache", err);
}
}

return new MemoryCache();
}
Loading