148 lines · 6.1 KB
import { env } from "cloudflare:workers";
import { all } from "../db";
import { isArtifactsError } from "./artifacts";
import { artifactsCall } from "./artifacts-call";
import { allShards, shardOfName } from "./artifacts-shards";
import { isLoopbackUrl } from "../deployment";

/** Artifacts repos that `createRepository` creates before inserting their row can be younger than this. */
const GRACE_MS = 24 * 60 * 60 * 1000;
const PAGE_SIZE = 200;
const PAGES_PER_RUN = 2;
const CURSOR_TTL_SECONDS = 7 * 24 * 60 * 60;
/** D1 binds at most 100 parameters per statement. */
const D1_MAX_PARAMS = 90;
const OWN_NAME = /^r\d*-[A-Za-z0-9_-]+$/;

/** Delete mode stops for a human once a run finds more than this many orphans in total... */
const MAX_DELETES_PER_RUN = 20;
/** ...or once a page's orphans exceed this share of its own-looking repositories (GIT-P3). */
const MAX_ORPHAN_SHARE = 0.05;
/** Below this many orphans a page never trips the share rule, so a sparse shard's few failed creates still clear. */
const MIN_ORPHANS_FOR_SHARE_RULE = 3;

type SweepMode = "off" | "report" | "delete";

/**
 * `delete` also needs ARTIFACTS_NAMESPACE_EXCLUSIVE=true and a non-local PUBLIC_URL: a namespace shared with
 * another deployment (or a local dev database) would make every repository of the other side look orphaned.
 */
export function sweepMode(vars: { mode?: string; exclusive?: string; publicUrl?: string; local?: string }): SweepMode {
  if (vars.mode === "off") return "off";
  if (vars.mode !== "delete") return "report";
  if (vars.exclusive !== "true" || isLoopbackUrl(vars.publicUrl) || vars.local === "true") {
    console.error(
      "ARTIFACTS_ORPHAN_SWEEP=delete ignored: it requires ARTIFACTS_NAMESPACE_EXCLUSIVE=true and a non-local PUBLIC_URL; reporting only",
    );
    return "report";
  }
  return "delete";
}