From f13d945258eeeca9352a54ddc08e1b3c875ad310 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:33:05 +0000 Subject: [PATCH 1/2] fix(typescript): handle undici timeout errors and retry network failures - Detect Node.js undici timeout errors (UND_ERR_HEADERS_TIMEOUT, UND_ERR_BODY_TIMEOUT, ETIMEDOUT) and classify them as reason: timeout instead of reason: unknown - Include full error cause chain in unknown error messages so underlying error codes surface to users instead of just 'fetch failed' - Retry on network-level failures (connection resets, socket errors) in addition to retryable HTTP status codes (408, 429, 5xx) Co-Authored-By: rishabh --- .../fix-undici-timeout-handling.yml | 17 +++++++ .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../accept-header/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../alias-extends/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- seed/ts-sdk/alias/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../allof-inline/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../basic-auth/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../bytes-upload/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../content-type/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde-layer/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../empty-clients/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../enum/serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../union-utils/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../ts-sdk/errors/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../bigint/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../local-files/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../node-fetch/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../output-src-only/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../test-packagePath/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde-layer/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-jest/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../extends/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../inline/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-jest/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../wrapper/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../folders/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../header-auth/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../http-head/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 39 ++++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../license/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../literal/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../no-retries/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../null-type/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../nullable/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../ts-sdk/object/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../optional/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../package-yml/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../plain-text/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../public-object/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../bundle/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../jsr/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../naming/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../no-scripts/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../oidc-token/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-oxc/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-oxfmt/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-oxlint/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-prettier/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../use-yarn/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../simple-fhir/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde-layer/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../wrapper/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../exhaustive/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../trace/serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../inline/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../no-inline/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde-layer/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../validation/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../variables/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../version/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../websockets/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../websockets/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../no-serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../serde/src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- .../src/core/fetcher/Fetcher.ts | 49 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 37 +++++++++++--- 425 files changed, 16342 insertions(+), 1909 deletions(-) create mode 100644 generators/typescript/sdk/changes/unreleased/fix-undici-timeout-handling.yml diff --git a/generators/typescript/sdk/changes/unreleased/fix-undici-timeout-handling.yml b/generators/typescript/sdk/changes/unreleased/fix-undici-timeout-handling.yml new file mode 100644 index 000000000000..a5a9713ceeef --- /dev/null +++ b/generators/typescript/sdk/changes/unreleased/fix-undici-timeout-handling.yml @@ -0,0 +1,17 @@ +- summary: | + Detect Node.js undici timeout errors (UND_ERR_HEADERS_TIMEOUT, UND_ERR_BODY_TIMEOUT) + and classify them as `reason: "timeout"` instead of `reason: "unknown"`. This fixes + an issue where Node's built-in fetch has a hard 300s headersTimeout that fires before + the SDK's timeoutInSeconds, causing generic "fetch failed" errors. + type: fix + +- summary: | + Include the full error cause chain in unknown error messages so underlying error codes + (e.g., undici error codes, ECONNRESET) surface to users instead of just "fetch failed". + type: fix + +- summary: | + Retry on network-level failures (e.g., connection resets, socket errors) in addition + to retryable HTTP status codes (408, 429, 5xx). Previously, thrown errors from the + fetch call bypassed retry logic entirely. + type: feat diff --git a/generators/typescript/utils/core-utilities/src/core/fetcher/Fetcher.ts b/generators/typescript/utils/core-utilities/src/core/fetcher/Fetcher.ts index f59409ce0d3c..b3ff95a7b710 100644 --- a/generators/typescript/utils/core-utilities/src/core/fetcher/Fetcher.ts +++ b/generators/typescript/utils/core-utilities/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/accept-header/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/accept-header/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/accept-header/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/accept-header/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/alias-extends/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/alias-extends/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/alias-extends/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/alias-extends/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/alias/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/alias/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/alias/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/alias/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/allof/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/api-wide-base-path/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/api-wide-base-path/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/api-wide-base-path/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/api-wide-base-path/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/basic-auth/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/basic-auth/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/basic-auth/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/basic-auth/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/bytes-download/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/bytes-download/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/bytes-download/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/bytes-download/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/bytes-upload/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/bytes-upload/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/bytes-upload/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/bytes-upload/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/circular-references-advanced/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/circular-references-advanced/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/circular-references-advanced/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/circular-references-advanced/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/circular-references-extends/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/circular-references-extends/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/circular-references-extends/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/circular-references-extends/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/circular-references/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/circular-references/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/circular-references/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/circular-references/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/client-side-params/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/client-side-params/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/client-side-params/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/client-side-params/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/content-type/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/content-type/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/content-type/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/content-type/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/dollar-string-examples/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/dollar-string-examples/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/dollar-string-examples/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/dollar-string-examples/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/empty-clients/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/empty-clients/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/empty-clients/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/empty-clients/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/enum/serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/enum/serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/enum/serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/enum/serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/error-property/union-utils/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/error-property/union-utils/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/error-property/union-utils/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/error-property/union-utils/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/errors/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/errors/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/errors/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/errors/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/local-files/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/local-files/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/local-files/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/local-files/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/extends/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/extends/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/extends/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/extends/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/extra-properties/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/extra-properties/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/extra-properties/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/extra-properties/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload-openapi/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload-openapi/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload-openapi/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload-openapi/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload/inline/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload/inline/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload/inline/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload/inline/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload/serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload/serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload/serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload/serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/folders/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/folders/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/folders/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/folders/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/header-auth/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/header-auth/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/header-auth/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/header-auth/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/http-head/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/http-head/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/http-head/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/http-head/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/idempotency-headers/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/idempotency-headers/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/idempotency-headers/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/idempotency-headers/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/license/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/license/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/license/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/license/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/literal/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/literal/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/literal/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/literal/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/literals-unions/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/literals-unions/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/literals-unions/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/literals-unions/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/mixed-file-directory/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/mixed-file-directory/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/mixed-file-directory/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/mixed-file-directory/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/multi-line-docs/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/multi-line-docs/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/multi-line-docs/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/multi-line-docs/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/multi-url-environment/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/multi-url-environment/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/multi-url-environment/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/multi-url-environment/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/no-content-response/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/no-content-response/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/no-content-response/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/no-content-response/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/no-environment/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/no-environment/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/no-environment/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/no-environment/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/no-retries/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/no-retries/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/no-retries/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/no-retries/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/null-type/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/null-type/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/null-type/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/null-type/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/nullable-optional/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/nullable-optional/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/nullable-optional/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/nullable-optional/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/nullable-request-body/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/nullable-request-body/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/nullable-request-body/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/nullable-request-body/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/nullable/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/nullable/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/nullable/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/nullable/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/object/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/object/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/object/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/object/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/objects-with-imports/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/objects-with-imports/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/objects-with-imports/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/objects-with-imports/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/optional/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/optional/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/optional/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/optional/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/package-yml/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/package-yml/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/package-yml/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/package-yml/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/pagination-custom/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/pagination-custom/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/pagination-custom/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/pagination-custom/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/pagination-uri-path/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/pagination-uri-path/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/pagination-uri-path/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/pagination-uri-path/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/plain-text/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/plain-text/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/plain-text/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/plain-text/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/public-object/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/public-object/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/public-object/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/public-object/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/query-parameters/serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/query-parameters/serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/query-parameters/serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/query-parameters/serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/required-nullable/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/required-nullable/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/required-nullable/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/required-nullable/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/reserved-keywords/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/reserved-keywords/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/reserved-keywords/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/reserved-keywords/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/response-property/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/response-property/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/response-property/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/response-property/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/server-sent-events/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/server-sent-events/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/server-sent-events/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/server-sent-events/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/server-url-templating/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/server-url-templating/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/server-url-templating/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/server-url-templating/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/bundle/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/bundle/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/bundle/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/bundle/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/jsr/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/jsr/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/jsr/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/jsr/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/Fetcher.ts index f59409ce0d3c..b3ff95a7b710 100644 --- a/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/naming/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/naming/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/naming/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/naming/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/simple-fhir/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/simple-fhir/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/simple-fhir/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/simple-fhir/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/single-url-environment-default/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/single-url-environment-default/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/single-url-environment-default/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/single-url-environment-default/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/streaming-parameter/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/streaming-parameter/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/streaming-parameter/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/streaming-parameter/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/streaming/wrapper/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/streaming/wrapper/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/streaming/wrapper/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/streaming/wrapper/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/trace/exhaustive/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/trace/exhaustive/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/trace/exhaustive/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/trace/exhaustive/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/trace/serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/trace/serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/trace/serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/trace/serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/ts-express-casing/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/ts-express-casing/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/ts-express-casing/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/ts-express-casing/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/ts-extra-properties/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/ts-extra-properties/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/ts-extra-properties/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/ts-extra-properties/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/unions-with-local-date/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/unions-with-local-date/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/unions-with-local-date/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/unions-with-local-date/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/unions/serde-layer/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/unions/serde-layer/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/unions/serde-layer/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/unions/serde-layer/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/url-form-encoded/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/url-form-encoded/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/url-form-encoded/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/url-form-encoded/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/validation/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/validation/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/validation/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/validation/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/variables/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/variables/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/variables/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/variables/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/version-no-default/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/version-no-default/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/version-no-default/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/version-no-default/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/version/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/version/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/version/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/version/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/webhook-audience/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/webhook-audience/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/webhook-audience/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/webhook-audience/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/websocket-multi-url/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/websocket-multi-url/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/websocket-multi-url/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/websocket-multi-url/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/websocket/no-serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/websocket/no-serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/websocket/no-serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/websocket/no-serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/websocket/serde/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/websocket/serde/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/websocket/serde/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/websocket/serde/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } diff --git a/seed/ts-sdk/x-fern-default/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/x-fern-default/src/core/fetcher/Fetcher.ts index 943ec1bfe9c9..95c2411e995c 100644 --- a/seed/ts-sdk/x-fern-default/src/core/fetcher/Fetcher.ts +++ b/seed/ts-sdk/x-fern-default/src/core/fetcher/Fetcher.ts @@ -254,6 +254,33 @@ async function getHeaders(args: Fetcher.Args): Promise { return newHeaders; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isTimeoutError(error: Error): boolean { + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return true; + } + } + return false; +} + +function getErrorMessageWithCause(error: Error): string { + let message = error.message; + let current: unknown = "cause" in error ? error.cause : undefined; + while (current instanceof Error) { + const suffix = "code" in current && typeof (current as Error & { code: unknown }).code === "string" + ? ` [${(current as Error & { code: string }).code}]` + : ""; + if (current.message) { + message += ` -> ${current.message}${suffix}`; + } + current = "cause" in current ? current.cause : undefined; + } + return message; +} + export async function fetcherImpl(args: Fetcher.Args): Promise> { let url = args.url; if (args.queryString != null && args.queryString.length > 0) { @@ -370,12 +397,30 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise Promise, maxRetries: number = DEFAULT_MAX_RETRIES, ): Promise { - let response: Response = await requestFn(); + let lastError: unknown; + let lastResponse: Response | undefined; + + for (let i = 0; i <= maxRetries; i++) { + try { + const response = await requestFn(); + lastResponse = response; + lastError = undefined; + + if (!([408, 429].includes(response.status) || response.status >= 500)) { + return response; + } + + if (i === maxRetries) { + return response; + } - for (let i = 0; i < maxRetries; ++i) { - if ([408, 429].includes(response.status) || response.status >= 500) { const delay = getRetryDelayFromHeaders(response, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + } catch (error) { + lastError = error; + lastResponse = undefined; + + if (i === maxRetries) { + throw error; + } + const delay = addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** i, MAX_RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, delay)); - response = await requestFn(); - } else { - break; } } - return response!; + + if (lastResponse != null) { + return lastResponse; + } + throw lastError; } From 886ee35a71a747855657a3883c0b4eb2c2427784 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:39:10 +0000 Subject: [PATCH 2/2] fix(typescript): don't retry AbortError or undici timeout errors Timeout and abort errors should propagate immediately without retries. Only transient network errors (ECONNRESET, ECONNREFUSED, etc.) are retried. Co-Authored-By: rishabh --- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- .../src/core/fetcher/requestWithRetries.ts | 20 ++++++++++++++++++- 212 files changed, 4028 insertions(+), 212 deletions(-) diff --git a/generators/typescript/utils/core-utilities/src/core/fetcher/requestWithRetries.ts b/generators/typescript/utils/core-utilities/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/generators/typescript/utils/core-utilities/src/core/fetcher/requestWithRetries.ts +++ b/generators/typescript/utils/core-utilities/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/accept-header/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/accept-header/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/accept-header/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/accept-header/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/alias-extends/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/alias-extends/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/alias-extends/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/alias-extends/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/alias/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/alias/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/alias/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/alias/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/allof-inline/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/allof/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/any-auth/generate-endpoint-metadata/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/any-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/api-wide-base-path/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/api-wide-base-path/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/api-wide-base-path/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/api-wide-base-path/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/audiences/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/audiences/with-partner-audience/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/basic-auth-environment-variables/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/basic-auth-pw-omitted/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/basic-auth/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/basic-auth/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/basic-auth/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/basic-auth/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/bearer-token-environment-variable/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/bytes-download/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/bytes-download/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/bytes-download/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/bytes-download/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/bytes-upload/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/bytes-upload/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/bytes-upload/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/bytes-upload/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/circular-references-advanced/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/circular-references-advanced/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/circular-references-advanced/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/circular-references-advanced/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/circular-references-extends/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/circular-references-extends/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/circular-references-extends/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/circular-references-extends/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/circular-references/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/circular-references/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/circular-references/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/circular-references/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/client-side-params/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/client-side-params/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/client-side-params/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/client-side-params/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/content-type/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/content-type/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/content-type/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/content-type/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/cross-package-type-names/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/cross-package-type-names/serde-layer/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/dollar-string-examples/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/dollar-string-examples/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/dollar-string-examples/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/dollar-string-examples/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/empty-clients/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/empty-clients/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/empty-clients/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/empty-clients/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/endpoint-security-auth/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/enum/forward-compatible-enums-with-serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/enum/forward-compatible-enums/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/enum/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/enum/serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/enum/serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/enum/serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/enum/serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/error-property/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/error-property/union-utils/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/error-property/union-utils/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/error-property/union-utils/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/error-property/union-utils/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/errors/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/errors/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/errors/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/errors/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/bigint-serde-layer/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/bigint/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/consolidate-type-files/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/export-all-requests-at-root/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/local-files/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/local-files/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/local-files/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/local-files/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/multiple-exports/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/never-throw-errors/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/node-fetch/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/output-src-only/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/package-path/src/test-packagePath/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/serde-layer/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/use-jest/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/web-stream-wrapper/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/exhaustive/with-audiences/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/extends/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/extends/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/extends/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/extends/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/extra-properties/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/extra-properties/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/extra-properties/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/extra-properties/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-download/file-download-response-headers/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-download/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-download/stream-wrapper/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload-openapi/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload-openapi/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload-openapi/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload-openapi/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload/form-data-node16/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload/inline/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload/inline/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload/inline/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload/inline/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload/serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload/serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload/serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload/serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload/use-jest/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/file-upload/wrapper/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/folders/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/folders/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/folders/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/folders/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/header-auth-environment-variable/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/header-auth/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/header-auth/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/header-auth/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/header-auth/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/http-head/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/http-head/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/http-head/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/http-head/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/idempotency-headers/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/idempotency-headers/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/idempotency-headers/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/idempotency-headers/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/imdb/branded-string-aliases/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/imdb/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/imdb/omit-undefined/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/inferred-auth-explicit/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/inferred-auth-implicit-api-key/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/inferred-auth-implicit-no-expiry/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/inferred-auth-implicit-reference/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/inferred-auth-implicit/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/license/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/license/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/license/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/license/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/literal/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/literal/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/literal/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/literal/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/literals-unions/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/literals-unions/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/literals-unions/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/literals-unions/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/mixed-case/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/mixed-case/retain-original-casing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/mixed-file-directory/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/mixed-file-directory/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/mixed-file-directory/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/mixed-file-directory/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/multi-line-docs/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/multi-line-docs/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/multi-line-docs/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/multi-line-docs/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/multi-url-environment-no-default/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/multi-url-environment-reference/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/multi-url-environment/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/multi-url-environment/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/multi-url-environment/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/multi-url-environment/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/multiple-request-bodies/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/no-content-response/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/no-content-response/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/no-content-response/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/no-content-response/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/no-environment/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/no-environment/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/no-environment/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/no-environment/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/no-retries/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/no-retries/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/no-retries/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/no-retries/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/null-type/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/null-type/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/null-type/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/null-type/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/nullable-allof-extends/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/nullable-optional/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/nullable-optional/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/nullable-optional/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/nullable-optional/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/nullable-request-body/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/nullable-request-body/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/nullable-request-body/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/nullable-request-body/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/nullable/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/nullable/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/nullable/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/nullable/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-custom/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-default/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-environment-variables/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-openapi/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-reference/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials-with-variables/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/oauth-client-credentials/serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/object/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/object/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/object/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/object/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/objects-with-imports/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/objects-with-imports/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/objects-with-imports/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/objects-with-imports/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/openapi-request-body-ref/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/optional/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/optional/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/optional/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/optional/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/package-yml/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/package-yml/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/package-yml/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/package-yml/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/pagination-custom/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/pagination-custom/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/pagination-custom/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/pagination-custom/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/pagination-uri-path/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/pagination-uri-path/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/pagination-uri-path/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/pagination-uri-path/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/pagination/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/pagination/page-index-semantics/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/no-inline-path-parameters-retain-original-casing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/no-inline-path-parameters-serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/no-inline-path-parameters/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/path-parameters/retain-original-casing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/plain-text/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/plain-text/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/plain-text/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/plain-text/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/property-access/generate-read-write-only-types/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/property-access/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/public-object/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/public-object/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/public-object/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/public-object/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-param-name-conflict/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters-openapi-as-objects/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters-openapi/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-camel-case/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-original-name/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-snake-case/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters/parameter-naming-wire-value/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/query-parameters/serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/query-parameters/serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/query-parameters/serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/query-parameters/serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/request-parameters/flatten-request-parameters/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/request-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/request-parameters/use-big-int-and-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/request-parameters/use-default-request-parameter-values/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/required-nullable/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/required-nullable/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/required-nullable/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/required-nullable/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/reserved-keywords/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/reserved-keywords/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/reserved-keywords/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/reserved-keywords/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/response-property/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/response-property/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/response-property/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/response-property/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/schemaless-request-body-examples/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/server-sent-event-examples/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/server-sent-events-openapi/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/server-sent-events/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/server-sent-events/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/server-sent-events/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/server-sent-events/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/server-url-templating/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/server-url-templating/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/server-url-templating/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/server-url-templating/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/allow-custom-fetcher/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/allow-extra-fields/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/bundle/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/bundle/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/bundle/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/bundle/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/custom-package-json/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/jsr/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/jsr/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/jsr/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/jsr/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/legacy-exports/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/naming-shorthand/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/naming/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/naming/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/naming/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/naming/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/no-linter-and-formatter/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/no-scripts/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/oidc-token/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/omit-fern-headers/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/use-oxlint/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/use-prettier-no-linter/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/use-prettier/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-api/use-yarn/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/simple-fhir/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/simple-fhir/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/simple-fhir/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/simple-fhir/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/single-url-environment-default/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/single-url-environment-default/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/single-url-environment-default/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/single-url-environment-default/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/single-url-environment-no-default/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/streaming-parameter/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/streaming-parameter/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/streaming-parameter/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/streaming-parameter/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/streaming/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/streaming/serde-layer/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/streaming/wrapper/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/streaming/wrapper/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/streaming/wrapper/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/streaming/wrapper/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/trace/exhaustive/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/trace/exhaustive/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/trace/exhaustive/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/trace/exhaustive/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/trace/serde-no-throwing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/trace/serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/trace/serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/trace/serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/trace/serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/ts-express-casing/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/ts-express-casing/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/ts-express-casing/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/ts-express-casing/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/ts-extra-properties/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/ts-extra-properties/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/ts-extra-properties/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/ts-extra-properties/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/ts-inline-types/inline/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/ts-inline-types/no-inline/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/undiscriminated-union-with-response-property/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/union-query-parameters/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/unions-with-local-date/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/unions-with-local-date/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/unions-with-local-date/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/unions-with-local-date/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/unions/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/unions/serde-layer/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/unions/serde-layer/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/unions/serde-layer/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/unions/serde-layer/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/unknown/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/unknown/unknown-as-any/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/url-form-encoded/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/url-form-encoded/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/url-form-encoded/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/url-form-encoded/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/validation/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/validation/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/validation/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/validation/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/variables/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/variables/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/variables/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/variables/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/version-no-default/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/version-no-default/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/version-no-default/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/version-no-default/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/version/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/version/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/version/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/version/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/webhook-audience/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/webhook-audience/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/webhook-audience/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/webhook-audience/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/webhooks/no-custom-config/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/websocket-inferred-auth/websockets/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/websocket-multi-url/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/websocket-multi-url/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/websocket-multi-url/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/websocket-multi-url/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/websocket/no-serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/websocket/no-serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/websocket/no-serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/websocket/no-serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/websocket/no-websocket-clients/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/websocket/serde/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/websocket/serde/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/websocket/serde/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/websocket/serde/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; } diff --git a/seed/ts-sdk/x-fern-default/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/x-fern-default/src/core/fetcher/requestWithRetries.ts index f4f23527c0c3..1a840ea11f0d 100644 --- a/seed/ts-sdk/x-fern-default/src/core/fetcher/requestWithRetries.ts +++ b/seed/ts-sdk/x-fern-default/src/core/fetcher/requestWithRetries.ts @@ -13,6 +13,24 @@ function addSymmetricJitter(delay: number): number { return delay * jitterMultiplier; } +const TIMEOUT_ERROR_CODES = new Set(["UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT", "ETIMEDOUT"]); + +function isRetryableError(error: unknown): boolean { + if (!(error instanceof Error)) { + return true; + } + if (error.name === "AbortError") { + return false; + } + if ("cause" in error && error.cause instanceof Error && "code" in error.cause) { + const { code } = error.cause as Error & { code: unknown }; + if (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code)) { + return false; + } + } + return true; +} + function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { @@ -71,7 +89,7 @@ export async function requestWithRetries( lastError = error; lastResponse = undefined; - if (i === maxRetries) { + if (i === maxRetries || !isRetryableError(error)) { throw error; }