diff options
| author | Adam <[email protected]> | 2025-12-20 04:57:39 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-20 04:57:39 -0600 |
| commit | 49567fe61aea76871866bb816f27743f57708af2 (patch) | |
| tree | ee42f7b3d33875a28668575d2a18c95a7cb9e436 /packages/util/src | |
| parent | e5b3f796e4c2e2ff12939dfc5e523ab9bd042f51 (diff) | |
| download | opencode-49567fe61aea76871866bb816f27743f57708af2.tar.gz opencode-49567fe61aea76871866bb816f27743f57708af2.zip | |
fix(desktop): add retries to init promises
Diffstat (limited to 'packages/util/src')
| -rw-r--r-- | packages/util/src/retry.ts | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/packages/util/src/retry.ts b/packages/util/src/retry.ts new file mode 100644 index 000000000..0014a604c --- /dev/null +++ b/packages/util/src/retry.ts @@ -0,0 +1,41 @@ +export interface RetryOptions { + attempts?: number + delay?: number + factor?: number + maxDelay?: number + retryIf?: (error: unknown) => boolean +} + +const TRANSIENT_MESSAGES = [ + "load failed", + "network connection was lost", + "network request failed", + "failed to fetch", + "econnreset", + "econnrefused", + "etimedout", + "socket hang up", +] + +function isTransientError(error: unknown): boolean { + if (!error) return false + const message = String(error instanceof Error ? error.message : error).toLowerCase() + return TRANSIENT_MESSAGES.some((m) => message.includes(m)) +} + +export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> { + const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options + + let lastError: unknown + for (let attempt = 0; attempt < attempts; attempt++) { + try { + return await fn() + } catch (error) { + lastError = error + if (attempt === attempts - 1 || !retryIf(error)) throw error + const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay) + await new Promise((resolve) => setTimeout(resolve, wait)) + } + } + throw lastError +} |
