Something went wrong. Try again.
Automations and webhooks for the AT Protocol airglow.run
automation webhook atproto atprotocol
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import { PdsHttpError, PdsSessionMissingError } from "../automations/errors.js";
/** How sync reacts to a failed PDS call (KTD-5). */export type SyncPdsFailure = /** The OAuth session is gone or rejected. Counts toward the pause streak. */ | { kind: "auth"; message: string } /** The account hit its PDS write budget. Park until `resetAt` (ms). */ | { kind: "rate-limited"; resetAt: number | null; message: string } /** Transient: 5xx, network, timeout. Retry with backoff. */ | { kind: "retryable"; message: string } /** Anything else. Fail the row and record it. */ | { kind: "terminal"; status: number; message: string };
function messageOf(err: unknown): string { return err instanceof Error ? err.message : String(err);}
export function classifyPdsError(err: unknown): SyncPdsFailure { const message = messageOf(err); if (err instanceof PdsSessionMissingError) return { kind: "auth", message }; if (err instanceof PdsHttpError) { if (err.status === 401) return { kind: "auth", message }; if (err.status === 429) { return { kind: "rate-limited", resetAt: err.rateLimitReset === null ? null : err.rateLimitReset * 1000, message, }; } if (err.status >= 500) return { kind: "retryable", message }; return { kind: "terminal", status: err.status, message }; } // Untyped errors carry no status: thrown fetches, timeouts, session-restore // deadlines. Same convention as parsePdsError, where status 0 is retryable. const status = message.match(/\((\d{3})\)/); if (status) { const code = Number(status[1]); if (code === 401) return { kind: "auth", message }; if (code === 429) return { kind: "rate-limited", resetAt: null, message }; if (code >= 500) return { kind: "retryable", message }; return { kind: "terminal", status: code, message }; } return { kind: "retryable", message };}
/** A create conflict for a record key that already exists means an earlier * attempt with the same pre-generated rkey landed: treat it as written. */export function isRecordAlreadyExists(err: unknown): boolean { if (!(err instanceof PdsHttpError)) return false; if (err.status !== 400 && err.status !== 409) return false; return /already exists|RecordAlreadyExists/i.test(err.body);}