{"record":{"id":"c8302611b3555186","repo":"tursodatabase/turso","slug":"retryfetch-attempts-must-be-a-finite-integer-1","errorCode":null,"errorMessage":"retryFetch: attempts must be a finite integer >= 1, got ${attempts}","messagePattern":"retryFetch: attempts must be a finite integer >= 1, got (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"bindings/javascript/sync/packages/common/run.ts","lineNumber":159,"sourceCode":" * ```ts\n * import { connect } from '@tursodatabase/sync';\n * import { retryFetch } from '@tursodatabase/sync-common';\n *\n * const db = await connect({\n *   path: 'local.db',\n *   url: 'libsql://...',\n *   fetch: retryFetch(),                              // defaults\n *   // fetch: retryFetch({ attempts: 5, delayMs: 1000 }),\n * });\n * ```\n */\nexport function retryFetch(opts: RetryFetchOpts = {}): typeof fetch {\n    const attempts = opts.attempts ?? 3;\n    const baseDelay = opts.delayMs ?? 500;\n    const backoff = opts.backoff ?? 2;\n    const underlying: typeof fetch = opts.fetch ?? ((input, init) => fetch(input, init));\n    if (!Number.isFinite(attempts) || attempts < 1) {\n        throw new Error(`retryFetch: attempts must be a finite integer >= 1, got ${attempts}`);\n    }\n    return async (input: RequestInfo | URL, init?: RequestInit) => {\n        let lastError: unknown = null;\n        let lastResponse: Response | null = null;\n        let delay = baseDelay;\n        for (let i = 0; i < attempts; i++) {\n            try {\n                const response = await underlying(input, init);\n                if (response.status < 500 && response.status !== 429) {\n                    return response;\n                }\n                lastResponse = response;\n                lastError = null;\n            } catch (error) {\n                lastError = error;\n                lastResponse = null;\n            }\n            if (i + 1 < attempts) {","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/bindings/javascript/sync/packages/common/run.ts#L141-L177","documentation":"retryFetch (run.ts:159) validates its configuration eagerly: attempts (default 3) must be Number.isFinite and >= 1, otherwise it throws immediately when retryFetch() is called - not per request. It guards the retry loop bounds before any network traffic; note the check enforces finiteness and minimum, so NaN, 0, negative values and Infinity all throw, while fractional values like 2.5 pass despite the 'integer' wording.","triggerScenarios":"retryFetch({ attempts: 0 }) from a 'disable retries' config; attempts: NaN via a typo'd or unset env variable parsed with parseInt (NaN); attempts: Infinity meant as 'retry forever' (unsupported); negative attempts from arithmetic like userProvided - 1; constructing the fetch at module load, so the throw happens at import time.","commonSituations":"Config-driven retry counts (env vars, YAML) flowing in unparsed or empty (Number('') === 0); test setups passing attempts: 0 expecting no retries; spreading user options into retryFetch without validating; CI config where a missing variable yields NaN.","solutions":["Pass a finite value >= 1 (use 1 to disable retries: retryFetch({ attempts: 1 })).","Sanitize config before calling: const attempts = Number.isFinite(n) && n >= 1 ? Math.floor(n) : 3.","Default missing env/config values explicitly (parseInt(x ?? '3', 10) with a NaN fallback).","Remember Infinity is not supported - pick a bounded number for 'many' retries."],"exampleFix":"// before\nconst attempts = Number(process.env.SYNC_RETRIES); // '' -> 0, undefined -> NaN\nconst fetchImpl = retryFetch({ attempts }); // throws\n\n// after\nconst parsed = Number(process.env.SYNC_RETRIES);\nconst attempts = Number.isFinite(parsed) && parsed >= 1 ? Math.floor(parsed) : 3;\nconst fetchImpl = retryFetch({ attempts });","handlingStrategy":"validation","validationCode":"const raw = Number(process.env.SYNC_RETRIES);\nconst attempts = Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 3;\nconst fetchImpl = retryFetch({ attempts });","typeGuard":"const isValidRetryAttempts = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isFinite(v) && v >= 1;","tryCatchPattern":null,"preventionTips":["Use attempts: 1 to disable retries; 0/Infinity/NaN are rejected.","Sanitize config-derived counts (env vars, YAML) with a finite/integer check and default.","Construct retryFetch lazily (not at module load) so a bad config fails with a clear stack.","Floor fractional values before passing them."],"tags":["retry","validation","fetch","config","sync"],"backgroundTag":"invalid-argument-value","analyzedSha":"6c7252267988c76e632af00a671e4b9788dfae13","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}