40 lines · 1.6 KB
import { HttpError } from "../errors";
import { isArtifactsError } from "./artifacts";

const HTTP_BAD_GATEWAY = 502;
const REFERENCE = /reference\s*=\s*([a-z0-9]+)/i;

/** An Artifacts failure without a typed error code, e.g. the opaque "internal error; reference = …". */
export class ArtifactsUnavailableError extends HttpError {
  readonly operation: string;
  readonly reference: string | null;

  constructor(operation: string, target: string, cause: unknown) {
    const detail = cause instanceof Error ? cause.message : String(cause);
    const reference = REFERENCE.exec(detail)?.[1] ?? null;
    super(
      HTTP_BAD_GATEWAY,
      `Cloudflare Artifacts failed to ${operation} for ${target}${reference ? ` (reference ${reference})` : ""}. ` +
        "This is usually transient; try again shortly.",
    );
    this.name = "ArtifactsUnavailableError";
    this.operation = operation;
    this.reference = reference;
  }
}

/**
 * Runs an Artifacts binding call, keeping typed ArtifactsErrors intact (callers branch on their codes)
 * but turning untyped failures into a logged, user-readable error that names the operation and repo.
 */
export async function artifactsCall<T>(operation: string, target: string, call: () => Promise<T>): Promise<T> {
  try {
    return await call();
  } catch (error) {
    if (isArtifactsError(error) || error instanceof ArtifactsUnavailableError) throw error;
    const wrapped = new ArtifactsUnavailableError(operation, target, error);
    console.error(`Artifacts ${operation} failed`, { target, reference: wrapped.reference, error });
    throw wrapped;
  }
}