diff --git a/action.Dockerfile b/action.Dockerfile index 40ee8c8..f673dce 100644 --- a/action.Dockerfile +++ b/action.Dockerfile @@ -12,6 +12,8 @@ RUN bun install --frozen-lockfile --production FROM base AS runner +WORKDIR /app + # Install curl for healthcheck RUN apk update && apk add curl --no-cache diff --git a/bun.lockb b/bun.lockb index 64d69fc..8d90386 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index b43070e..c0b3ff8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "typescript": "^5.7.2" }, "dependencies": { - "xxhash-wasm": "^1.1.0" + "xxhash-wasm": "^1.1.0", + "zod": "^3.23.8" } } \ No newline at end of file diff --git a/src/cache.ts b/src/cache.ts index 39fe9a5..1bc0c56 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -40,6 +40,10 @@ const setCachedQuery = db.prepare< VALUES ($key, $body, $status, $contentType, strftime('%s', 'now')) `); +const deleteCachedQuery = db.prepare( + `DELETE FROM cache WHERE key = ?` +); + export function getCachedResponse(cacheKey: bigint) { return getCachedQuery.get(cacheKey); } @@ -50,12 +54,24 @@ export function setCachedResponse( return setCachedQuery.run(cachedResponse); } -export function createCacheKey(request: Request) { +export function createCacheKey(params: { + method: string; + url: string; + authorization?: string; +}) { return hasher.h64( - `${request.method} ${request.url} ${request.headers.get("Authorization")}` + `${params.method}:${params.url}:${params.authorization ?? ""}` ); } +export function createCacheKeyFromRequest(request: Request) { + return createCacheKey({ + method: request.method, + url: request.url, + authorization: request.headers.get("Authorization") ?? undefined, + }); +} + export function createResponseFromCached( cached: Omit ) { @@ -66,3 +82,7 @@ export function createResponseFromCached( status: cached.status, }); } + +export function invalidateCache(cacheKey: bigint) { + return deleteCachedQuery.run(cacheKey); +} diff --git a/src/index.ts b/src/index.ts index 88b911f..b69b298 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { handleApiRequest } from "./routes/api"; +import { handleInvalidateRequest } from "./routes/invalidate"; const server = Bun.serve({ fetch(request) { @@ -8,6 +9,10 @@ const server = Bun.serve({ return handleApiRequest(request, url); } + if (url.pathname === "/invalidate" && request.method === "POST") { + return handleInvalidateRequest(request); + } + return new Response("Not found", { status: 404 }); }, }); diff --git a/src/routes/api.ts b/src/routes/api.ts index 5f8e8c4..34c5e0c 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,5 +1,6 @@ import { createCacheKey, + createCacheKeyFromRequest, createResponseFromCached, getCachedResponse, setCachedResponse, @@ -18,7 +19,7 @@ export async function handleApiRequest(request: Request, url: URL) { return fetchFromRequest(url, request); } - const cacheKey = createCacheKey(request); + const cacheKey = createCacheKeyFromRequest(request); const existingCache = getCachedResponse(cacheKey); diff --git a/src/routes/invalidate.ts b/src/routes/invalidate.ts new file mode 100644 index 0000000..8e37d65 --- /dev/null +++ b/src/routes/invalidate.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; +import { createCacheKey, invalidateCache } from "../cache"; + +const schema = z.object({ + method: z.string(), + url: z.string(), + authorization: z.string().optional(), +}); + +export async function handleInvalidateRequest(request: Request) { + const body = schema.parse(await request.json()); + + const cacheKey = createCacheKey(body); + + invalidateCache(cacheKey); + + return new Response(null, { + status: 204, + }); +}