|
| 1 | +import type { Env } from '../config' |
| 2 | +import { REF_TTL_MS } from './getConfiguredRefs' |
| 3 | + |
| 4 | +// R2 key prefixes for the per-version preview artifacts. Must match `metaKey` / |
| 5 | +// `tarballKey` in ../cache/r2Cache. NOT `meta-index/` or `refs/`: those are the |
| 6 | +// live indexes (self-pruned on write), not per-version orphans. |
| 7 | +const ARTIFACT_PREFIXES = ['meta/', 'tarball/'] |
| 8 | + |
| 9 | +/** |
| 10 | + * Delete per-version preview artifacts (meta + tarball) whose ref TTL has lapsed, |
| 11 | + * so R2 storage stays bounded to the active-ref window rather than growing with |
| 12 | + * every ref ever published. |
| 13 | + * |
| 14 | + * A ref's objects are (re-)uploaded on each publish, so an object's `uploaded` |
| 15 | + * age equals its ref's age, and the ref TTL maps exactly to |
| 16 | + * `uploaded < now - REF_TTL_MS`. An active ref (published within the window) is |
| 17 | + * therefore never touched; an expired ref's objects, which are no longer served |
| 18 | + * (the packument skips expired refs), are the orphans this removes. `now` is |
| 19 | + * injectable for tests. |
| 20 | + */ |
| 21 | +export async function cleanupExpiredArtifacts( |
| 22 | + env: Pick<Env, 'STORAGE'>, |
| 23 | + now: number = Date.now(), |
| 24 | +): Promise<{ deleted: number }> { |
| 25 | + const cutoff = now - REF_TTL_MS |
| 26 | + let deleted = 0 |
| 27 | + for (const prefix of ARTIFACT_PREFIXES) { |
| 28 | + let cursor: string | undefined |
| 29 | + do { |
| 30 | + const listing = await env.STORAGE.list({ prefix, cursor }) |
| 31 | + const expired = listing.objects |
| 32 | + .filter((o) => o.uploaded.getTime() < cutoff) |
| 33 | + .map((o) => o.key) |
| 34 | + if (expired.length > 0) { |
| 35 | + // R2 bulk delete takes up to 1000 keys; a list page is <= 1000. |
| 36 | + await env.STORAGE.delete(expired) |
| 37 | + deleted += expired.length |
| 38 | + } |
| 39 | + cursor = listing.truncated ? listing.cursor : undefined |
| 40 | + } while (cursor) |
| 41 | + } |
| 42 | + return { deleted } |
| 43 | +} |
0 commit comments