run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
62 lines • 2.28 kB
JavaScript
/**
* `withRetry(fn, opts?)` — exponential-backoff retry helper for SDK
* operations. Defaults to the canonical {@link isRetryableRun402Error}
* policy. `safeToRetry` alone does not trigger a retry; it only says a
* repeated mutation should not duplicate/corrupt state.
*
* Caller-controlled by design: the helper is policy-only. It does NOT own
* idempotency keys. Pair with the SDK method's own `idempotencyKey`
* option by baking it into the closure:
*
* @example
* const release = await withRetry(
* () => r.project(id).apply(spec, { idempotencyKey: "my-deploy-2026-05-01" }),
* { attempts: 3, onRetry: (e, n, ms) => console.warn(`retry ${n} in ${ms}ms`) },
* );
*
* The closure carries the same `idempotencyKey` on every retry, so the
* gateway dedups duplicates server-side and a retried mutation does not
* double-apply.
*/
import { isRetryableRun402Error } from "./errors.js";
/**
* Run `fn` with exponential backoff, retrying retryable {@link Run402Error}
* subclasses up to `attempts` times. After exhausting attempts (or when
* `retryIf` returns false), throws the LAST observed error — preserving the
* structured envelope so the caller's catch handler can branch on `kind`.
*/
export async function withRetry(fn, opts = {}) {
const attempts = opts.attempts ?? 3;
const base = opts.baseDelayMs ?? 250;
const max = opts.maxDelayMs ?? 5_000;
const retryIf = opts.retryIf ?? defaultRetryIf;
let last;
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
return await fn();
}
catch (e) {
last = e;
if (attempt === attempts)
break;
if (!retryIf(e, attempt))
break;
const delay = Math.min(max, base * 2 ** (attempt - 1));
try {
opts.onRetry?.(e, attempt, delay);
}
catch {
/* swallow — buggy onRetry must not abort the retry chain */
}
await sleep(delay);
}
}
throw last;
}
function defaultRetryIf(error, _attempt) {
return isRetryableRun402Error(error);
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
//# sourceMappingURL=retry.js.map