File
Blob: src/worker/queues/blob-gc.ts
| 1 | import type { QueueHandleResult } from "@/worker/queues/types"; |
| 2 | |
| 3 | export interface BlobGcMessage { |
| 4 | type: "r2_blob_gc"; |
| 5 | subject_id: string; |
| 6 | storage_id: string; |
| 7 | blob_id: string; |
| 8 | blob_key: string; |
| 9 | not_before_ms: number; |
| 10 | } |
| 11 | |
| 12 | export function blobGcMessage(input: { |
| 13 | subjectId: string; |
| 14 | storageId: string; |
| 15 | blobId: string; |
| 16 | blobKey: string; |
| 17 | notBeforeMs: number; |
| 18 | }): BlobGcMessage { |
| 19 | return { |
| 20 | type: "r2_blob_gc", |
| 21 | subject_id: input.subjectId, |
| 22 | storage_id: input.storageId, |
| 23 | blob_id: input.blobId, |
| 24 | blob_key: input.blobKey, |
| 25 | not_before_ms: input.notBeforeMs, |
| 26 | }; |
| 27 | } |
| 28 | |
| 29 | export function isBlobGcMessage(value: unknown): value is BlobGcMessage { |
| 30 | if (!value || typeof value !== "object") return false; |
| 31 | const record = value as Record<string, unknown>; |
| 32 | return ( |
| 33 | record.type === "r2_blob_gc" && |
| 34 | typeof record.subject_id === "string" && |
| 35 | record.subject_id.length > 0 && |
| 36 | typeof record.storage_id === "string" && |
| 37 | record.storage_id.length > 0 && |
| 38 | typeof record.blob_id === "string" && |
| 39 | record.blob_id.length > 0 && |
| 40 | typeof record.blob_key === "string" && |
| 41 | record.blob_key.length > 0 && |
| 42 | typeof record.not_before_ms === "number" && |
| 43 | Number.isFinite(record.not_before_ms) |
| 44 | ); |
| 45 | } |
| 46 | |
| 47 | export function retryUntilReady(message: BlobGcMessage, nowMs: number): QueueHandleResult | null { |
| 48 | if (message.not_before_ms <= nowMs) return null; |
| 49 | const delaySeconds = Math.max(1, Math.min(900, Math.ceil((message.not_before_ms - nowMs) / 1000))); |
| 50 | return { action: "retry", delaySeconds }; |
| 51 | } |