72 lines · 2.7 KB
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_<k>`. The shard is encoded
 * in the repository name (`r-<id>` for shard 0, `r<k>-<id>` 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");