From ed26ce29daaf9633717dd6f269be4cac45677dc2 Mon Sep 17 00:00:00 2001 From: Jacob M-G Evans Date: Fri, 1 Sep 2023 10:24:36 -0500 Subject: [PATCH] Scheduled cache busting can be built into the R2 Objects with R2 rules --- .gitignore | 3 ++- src/autoCacheBust.ts | 49 ++++++++------------------------------------ src/index.ts | 4 ---- src/routes.ts | 24 ++++++++++++++-------- wrangler.json | 11 ++-------- 5 files changed, 27 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index b68d563..e0d942e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .env dist .wrangler -.turbo \ No newline at end of file +.turbo +.dev.vars diff --git a/src/autoCacheBust.ts b/src/autoCacheBust.ts index cbfefcd..411b576 100644 --- a/src/autoCacheBust.ts +++ b/src/autoCacheBust.ts @@ -1,50 +1,17 @@ -export function isOlderThan(date: Date, hours: number | string) { - const now = new Date(); +// For scheduled R2 cache busting, the R2 lifecycle rules can be used to delete an object after a certain amount of time. - const diffInMilliseconds = now.getTime() - date.getTime(); - - const diffInHours = diffInMilliseconds / 1000 / 60 / 60; - - return diffInHours >= Number(hours); -} - -const RECORDS_BATCH_SIZE = 500; - -/** - * Creates a cache object with two methods: add and getKeys. - * */ -function r2CacheCollector() { - const keys: string[] = []; - return { - add: function (key: string) { - keys.push(key); - }, - getKeys: function () { - return keys; - }, - }; -} - -async function deleteKeys(env: Env, cacheDeletion: ReturnType) { - if (cacheDeletion.getKeys().length > 0) { - await env.R2_ARTIFACT_ARCHIVE.delete(cacheDeletion.getKeys()); - } -} - -async function processList(list: R2Objects, env: Env) { - const r2CacheToDelete = r2CacheCollector(); +async function bustEntireCache(list: R2Objects, env: Env) { for (const object of list.objects) { - if (isOlderThan(object.uploaded, env.EXPIRATION_HOURS)) { - r2CacheToDelete.add(object.key); - } + await env.R2_ARTIFACT_ARCHIVE.delete(object.key); } - - await deleteKeys(env, r2CacheToDelete); } export async function bustOldCache(env: Env, cursor?: string) { - const list = await env.R2_ARTIFACT_ARCHIVE.list({ limit: RECORDS_BATCH_SIZE, cursor }); - await processList(list, env); + const list = await env.R2_ARTIFACT_ARCHIVE.list({ + limit: 500, + cursor, + }); + await bustEntireCache(list, env); if (list.truncated) { await bustOldCache(env, list.cursor); diff --git a/src/index.ts b/src/index.ts index 4df3825..8a3eeee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,7 @@ -import { bustOldCache } from './autoCacheBust'; import { router } from './routes'; export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { return router.fetch(request, env, ctx); }, - async scheduled(_event: ScheduledEvent, env: Env, _ctx: ExecutionContext) { - await bustOldCache(env); - }, }; diff --git a/src/routes.ts b/src/routes.ts index 47394c4..9fc7d7c 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,15 +1,21 @@ import { zValidator } from '@hono/zod-validator'; import { Hono } from 'hono'; -import { HTTPException } from 'hono/http-exception'; import { bearerAuth } from 'hono/bearer-auth'; import { cors } from 'hono/cors'; +import { HTTPException } from 'hono/http-exception'; import { z } from 'zod'; import { bustOldCache } from './autoCacheBust'; export const router = new Hono<{ Bindings: Env }>(); -const paramValidator = z.object({ artifactID: z.string(), teamID: z.string().optional() }); -const queryValidator = z.object({ teamId: z.string().optional(), slug: z.string().optional() }); +const paramValidator = z.object({ + artifactID: z.string(), + teamID: z.string().optional(), +}); +const queryValidator = z.object({ + teamId: z.string().optional(), + slug: z.string().optional(), +}); router.onError((error, c) => { if (error instanceof HTTPException) { @@ -21,19 +27,19 @@ router.onError((error, c) => { router.use('*', cors()); router.use('*', async (c, next) => { - const middleware = bearerAuth({ token: 'SECRET' }); + const middleware = bearerAuth({ token: c.env.TURBO_TOKEN }); await middleware(c, next); }); -router.post('/artifacts/manual-cache-bust', zValidator('json', z.object({ expireInHours: z.number().optional() })), async (c) => { - const { expireInHours } = c.req.valid('json'); +router.post('/artifacts/manual-cache-bust', async (c) => { /** - * manual cache busting, if no expiration hours are provided, it will bust the entire cache. + * manual cache busting, it will bust the entire cache. */ await bustOldCache({ ...c.env, - EXPIRATION_HOURS: expireInHours ?? 0, }); + + // maybe this could return the keys that were busted? return c.json({ success: true }); }); @@ -73,7 +79,7 @@ router.get('/v8/artifacts/:artifactID', zValidator('param', paramValidator), zVa if (artifactID === 'list') { const list = await c.env.R2_ARTIFACT_ARCHIVE.list(); - return c.json(list.objects.map((object) => object)); + return c.json(list.objects); } const r2Object = await c.env.R2_ARTIFACT_ARCHIVE.get(`${teamID}/${artifactID}`); diff --git a/wrangler.json b/wrangler.json index f8f7662..4fd26eb 100644 --- a/wrangler.json +++ b/wrangler.json @@ -1,19 +1,12 @@ { "main": "src/index.ts", "compatibility_date": "2023-08-14", - "name": "r2-archive", - "vars": { - "EXPIRATION_HOURS": 144 - }, - + "name": "turbo-r2-archive", "r2_buckets": [ { "binding": "R2_ARTIFACT_ARCHIVE", "bucket_name": "turbo-cache", "preview_bucket_name": "turbo-cache-preview" } - ], - "triggers": { - "crons": ["0 17 * * sun"] - } + ] }