export type RateLimitVerdict = { success: boolean; resetAt: number }; /** Shard count for the limiter namespace; one fixed window per (limiter, key) lives in exactly one shard. */ export const RATE_LIMIT_SHARDS = 256; /** sha256(limiter + key), hex; the storage key, so raw IPs and user ids never land in DO storage. */ export async function rateLimitDigest(limiter: string, key: string): Promise { const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${limiter}\0${key}`))); return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); } export function rateLimitShard(digest: string): string { return `rl:${parseInt(digest.slice(0, 4), 16) % RATE_LIMIT_SHARDS}`; }