mandrel 1.77.0 → 1.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,62 +0,0 @@
1
- /**
2
- * transient-retry — shared retry-with-backoff for GitHub provider calls.
3
- *
4
- * Resilience for flaky connections (e.g. cell hotspots): retry ONLY on
5
- * connectivity blips, never on auth / scope / not-found / already-exists /
6
- * validation errors. Works for both surfaces the provider uses:
7
- * - the gh CLI path — errors carry the Go HTTP error on `err.stderr`
8
- * (e.g. `dial tcp ...: i/o timeout`); and
9
- * - the direct `fetch` path — errors are a `TypeError: fetch failed` with
10
- * the real reason on `err.cause` (e.g. `ETIMEDOUT`, `ENOTFOUND`).
11
- *
12
- * Retrying a non-idempotent create is acceptable for the dominant hotspot
13
- * failure (`dial tcp ... i/o timeout` means the connection never opened, so
14
- * the request never reached GitHub). Callers still gate retry per call so
15
- * the genuinely non-idempotent project-create can opt out.
16
- */
17
-
18
- const TRANSIENT_RE =
19
- /i\/o timeout|dial tcp|TLS handshake timeout|connection reset|connection refused|temporary failure|could not resolve host|no such host|network is unreachable|socket hang up|fetch failed|ConnectTimeoutError|UND_ERR_CONNECT_TIMEOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|\b50[234]\b/i;
20
-
21
- /** True when an error looks like a retryable network/connectivity blip. */
22
- export function isTransientNetworkError(err) {
23
- const hay = [
24
- err?.stderr,
25
- err?.message,
26
- err?.code,
27
- err?.cause?.message,
28
- err?.cause?.code,
29
- ]
30
- .filter(Boolean)
31
- .join(' ');
32
- return TRANSIENT_RE.test(hay);
33
- }
34
-
35
- /**
36
- * Run `fn`, retrying with exponential backoff ONLY on transient network
37
- * errors (1s, 2s, 4s by default). Non-transient errors throw immediately so
38
- * real failures stay loud. `sleep` is injectable for tests.
39
- *
40
- * @template T
41
- * @param {() => Promise<T>} fn
42
- * @param {{ retries?: number, baseDelayMs?: number,
43
- * sleep?: (ms: number) => Promise<void> }} [opts]
44
- * @returns {Promise<T>}
45
- */
46
- export async function withTransientRetry(
47
- fn,
48
- {
49
- retries = 3,
50
- baseDelayMs = 1000,
51
- sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
52
- } = {},
53
- ) {
54
- for (let attempt = 0; ; attempt++) {
55
- try {
56
- return await fn();
57
- } catch (err) {
58
- if (attempt >= retries || !isTransientNetworkError(err)) throw err;
59
- await sleep(baseDelayMs * 2 ** attempt);
60
- }
61
- }
62
- }