import { env } from "cloudflare:workers"; /** * Artifacts rate-limits control-plane calls per namespace, so repositories can be spread over several * namespaces, one binding each: shard 0 is `ARTIFACTS`, shard k is `ARTIFACTS_`. The shard is encoded * in the repository name (`r-` for shard 0, `r-` otherwise), so existing repositories keep * resolving no matter how many shards are configured. `ARTIFACTS_SHARDS` only decides where new * repositories go; never remove a binding that still holds repositories. */ const NAME_PATTERN = /^r(\d*)-(.+)$/; const FNV_OFFSET = 0x811c9dc5; const FNV_PRIME = 0x01000193; export function shardOfName(artifactsName: string): number { const match = NAME_PATTERN.exec(artifactsName); if (!match) return 0; return match[1] ? Number(match[1]) : 0; } export function nameInShard(repoId: string, shard: number): string { return shard === 0 ? `r-${repoId}` : `r${shard}-${repoId}`; } export function bindingNameForShard(shard: number): string { return shard === 0 ? "ARTIFACTS" : `ARTIFACTS_${shard}`; } /** A stable shard for a new repository id, spread over `count` shards. */ export function pickShard(repoId: string, count: number): number { if (count <= 1) return 0; let hash = FNV_OFFSET; for (let index = 0; index < repoId.length; index++) { hash ^= repoId.charCodeAt(index); hash = Math.imul(hash, FNV_PRIME) >>> 0; } return hash % count; } export function configuredShardCount(raw: string | undefined): number { const count = Number(raw ?? "1"); return Number.isInteger(count) && count >= 1 ? count : 1; } function isArtifactsBinding(value: unknown): value is Artifacts { return typeof value === "object" && value !== null && "get" in value && typeof value.get === "function"; } export function bindingForShard(shard: number): Artifacts { const name = bindingNameForShard(shard); const binding: unknown = Reflect.get(env, name); if (!isArtifactsBinding(binding)) throw new Error(`Artifacts binding ${name} is not configured`); return binding; } /** The binding (namespace) that holds `artifactsName`. */ export function artifactsBinding(artifactsName: string): Artifacts { return bindingForShard(shardOfName(artifactsName)); } /** Every configured shard binding, for sweeps that walk all namespaces. */ export function allShards(): Array<{ shard: number; binding: Artifacts }> { const out: Array<{ shard: number; binding: Artifacts }> = []; for (let shard = 0; shard < configuredShardCount(env.ARTIFACTS_SHARDS); shard++) { out.push({ shard, binding: bindingForShard(shard) }); } return out; } export function artifactsNameFor(repoId: string): string { return nameInShard(repoId, pickShard(repoId, configuredShardCount(env.ARTIFACTS_SHARDS))); }