@@ -30,54 +30,25 @@ const RETRYABLE_ERROR_NAMES: ReadonlySet<string> = new Set([
3030
3131export type RetryOptions = {
3232 /**
33- * Set to false for non-idempotent writes whose upstream lacks a request-key
34- * dedupe primitive (QBO create/update/void/delete endpoints). In that mode
35- * the classifier retries ONLY on 429 (explicit server-side reject without
36- * commit) and explicit `RetryableError.retry === true`. Everything else —
37- * 5xx, network errors, AbortSignal timeouts — is treated as permanent,
38- * because each of those can occur after the server has already committed
39- * and a replay would duplicate the write. Defaults to true (read-path /
40- * safe-to-replay behavior).
33+ * False for non-idempotent writes (QBO create/update/void/delete) whose
34+ * upstream has no request-key dedupe. In strict mode the classifier
35+ * retries only on 429 and explicit `RetryableError.retry === true`;
36+ * 5xx, network errors, and AbortSignal timeouts all bubble. Defaults
37+ * to true (safe-to-replay reads).
4138 */
4239 idempotent ?: boolean
4340}
4441
4542/**
46- * Centralized classifier for whether an error should trigger a retry.
47- * Exported so it can be unit-tested independent of pRetry's timer plumbing.
43+ * Classifies whether an error should trigger a retry. 429 and explicit
44+ * `RetryableError.retry === true` retry in both modes. Strict mode
45+ * (`options.idempotent === false`) short-circuits past 429: 5xx, network
46+ * codes, AbortSignal timeouts, and undici fetch-failed envelopes are all
47+ * treated as possibly-after-commit and never replayed.
4848 *
49- * Retry-set composition is gated by `options.idempotent` (default true):
50- * - `RetryableError` is honored in both modes (explicit caller signal).
51- * - `ALWAYS_RETRY_STATUSES` (429) fires in both modes.
52- * - In strict mode (`idempotent: false`) everything past the 429 check
53- * short-circuits to non-retryable: 5xx, network codes, AbortSignal
54- * timeouts, and undici fetch-failed envelopes are all treated as
55- * possibly-after-commit and never replayed.
56- *
57- * The error shapes inspected here come from different layers; a single
58- * unified type doesn't exist, which is why the input is `unknown` and
59- * each field is checked defensively:
60- *
61- * - `RetryableError` (ours, src/utils/error.ts) — explicit retry flag.
62- * - `HttpFetchError` (ours) and Copilot SDK's `StatusableError` —
63- * `status: number` set after a non-2xx response was received.
64- * - undici (Node fetch) network failures — thrown as
65- * `TypeError: fetch failed` with the underlying error on `.cause`
66- * (e.g. `{ code: 'ECONNRESET' }`). No HTTP response was ever built,
67- * so there is no status to inspect.
68- * - `AbortSignal.timeout()` rejects with a `DOMException` whose
69- * `name` is `'TimeoutError'` (retryable). `AbortController.abort()`
70- * produces `'AbortError'` and is NOT retried (deliberate cancellation).
71- * - Top-level `error.code` is checked defensively for legacy Node
72- * error paths; in current Node fetch the code lives under `.cause`.
73- *
74- * Retry-nesting hazard: do not call a `withRetry`-wrapped function from
75- * inside another `withRetry`-wrapped function. With the broadened retry
76- * set, worst-case wait is `outer × inner × per_call_timeout`, which can
77- * blow past the 300s webhook execution budget. Inside `IntuitAPI._*`
78- * methods that are themselves wrapped at the public level (see exports
79- * at the bottom of `src/utils/intuitAPI.ts`), call the unwrapped `_*`
80- * counterparts directly (e.g. `this._customQuery`, not `this.customQuery`).
49+ * Retry-nesting hazard: don't call a wrapped function from inside another.
50+ * Inside `IntuitAPI._*` methods call the unwrapped `_*` counterparts
51+ * (e.g. `this._customQuery`, not `this.customQuery`).
8152 */
8253export const isRetryableError = (
8354 error : unknown ,
@@ -100,8 +71,7 @@ export const isRetryableError = (
10071 if ( typeof err . status === 'number' && ALWAYS_RETRY_STATUSES . has ( err . status ) )
10172 return true
10273
103- // Non-idempotent mode: nothing past this point is safe — 5xx, network blips,
104- // and timeouts can all occur after the upstream has committed.
74+ // Strict mode: nothing past this point is post-commit-safe.
10575 if ( ! idempotent ) return false
10676
10777 if (
0 commit comments