117 lines · 4.6 KB
import { env } from "cloudflare:workers";
import { MAX_IN_LIST, all, chunks, first, placeholders, run } from "../db";
import { HttpError } from "../errors";
import { adjustStorage } from "./quota";
import type { RepoRow } from "~/lib/types";

/** Forks read LFS objects from their parents; bound the chain so lookups stay O(1) in D1 calls. */
const MAX_FORK_DEPTH = 3;
const PURGE_PAGES = 10;
const R2_LIST_LIMIT = 1000;

type ObjectRow = { repo_id: string; size: number };

export function objectKey(repoId: string, oid: string): string {
  return `lfs/${repoId}/${oid}`;
}

async function repoChain(repo: Pick<RepoRow, "id" | "fork_of_id">): Promise<string[]> {
  const chain = [repo.id];
  let parent = repo.fork_of_id;
  while (parent && chain.length < MAX_FORK_DEPTH) {
    chain.push(parent);
    const row = await first<{ fork_of_id: string | null }>("SELECT fork_of_id FROM repos WHERE id = ?", parent);
    parent = row?.fork_of_id ?? null;
  }
  return chain;
}

/** Finds which repo in the fork chain stores the object, preferring the repo itself. */
export async function findObject(repo: Pick<RepoRow, "id" | "fork_of_id">, oid: string): Promise<ObjectRow | null> {
  const own = await first<ObjectRow>(
    "SELECT repo_id, size FROM lfs_objects WHERE repo_id = ? AND oid = ?",
    repo.id,
    oid,
  );
  if (own || !repo.fork_of_id) return own;
  const chain = (await repoChain(repo)).slice(1);
  const rows = await all<ObjectRow>(
    `SELECT repo_id, size FROM lfs_objects WHERE oid = ? AND repo_id IN (${placeholders(chain.length)})`,
    oid,