37 lines · 1.4 KB
import { DurableObject } from "cloudflare:workers";
import { processAutoMerges } from "./merge-automation";
import { processQueue } from "./merge-queue";

const TARGET_KEY = "target";
const DIRTY_KEY = "dirty";
const RETRY_ON_ERROR_MS = 60_000;

type Target = { repoId: string; baseRef: string };

/**
 * One instance per (repository, base branch). Alarms are delivered one at a time, so every auto-merge
 * and merge-queue landing into a branch is serialized here; D1 remains the source of truth.
 */
export class MergeQueue extends DurableObject<Env> {
  async kick(repoId: string, baseRef: string): Promise<void> {
    await this.ctx.storage.put({ [TARGET_KEY]: { repoId, baseRef }, [DIRTY_KEY]: true });
    await this.ctx.storage.setAlarm(Date.now());
  }

  override async alarm(): Promise<void> {
    const target = await this.ctx.storage.get<Target>(TARGET_KEY);
    if (!target) return;
    await this.ctx.storage.put(DIRTY_KEY, false);
    try {
      await processAutoMerges(target.repoId, target.baseRef);
      const delay = await processQueue(target.repoId, target.baseRef);
      const rekicked = (await this.ctx.storage.get<boolean>(DIRTY_KEY)) === true;
      if (rekicked) await this.ctx.storage.setAlarm(Date.now());
      else if (delay !== null) await this.ctx.storage.setAlarm(Date.now() + delay);
    } catch (error) {
      console.error("Merge queue pass failed", error);
      await this.ctx.storage.setAlarm(Date.now() + RETRY_ON_ERROR_MS);
    }
  }
}