{"id":33180264,"url":"https://github.com/fetch-kit/ffetch","last_synced_at":"2025-11-20T21:03:34.276Z","repository":{"id":309252400,"uuid":"1035623898","full_name":"fetch-kit/ffetch","owner":"fetch-kit","description":"TypeScript-first fetch wrapper with configurable timeouts, retries, and circuit-breaker baked in.","archived":false,"fork":false,"pushed_at":"2025-10-25T01:17:38.000Z","size":225,"stargazers_count":158,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-25T03:11:07.166Z","etag":null,"topics":["backoff","fetch","http-client","javascript","production-ready","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fetch-kit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["gkoos"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-08-10T19:40:40.000Z","updated_at":"2025-10-25T01:16:49.000Z","dependencies_parsed_at":"2025-09-06T14:17:47.541Z","dependency_job_id":"657665e1-5273-4bde-aaf4-e97477f87dc1","html_url":"https://github.com/fetch-kit/ffetch","commit_stats":null,"previous_names":["gkoos/ffetch","fetch-kit/ffetch"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/fetch-kit/ffetch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fetch-kit%2Fffetch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fetch-kit%2Fffetch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fetch-kit%2Fffetch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fetch-kit%2Fffetch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fetch-kit","download_url":"https://codeload.github.com/fetch-kit/ffetch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fetch-kit%2Fffetch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285511793,"owners_count":27184240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-20T02:00:05.334Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backoff","fetch","http-client","javascript","production-ready","typescript"],"created_at":"2025-11-16T03:00:41.705Z","updated_at":"2025-11-20T21:03:34.271Z","avatar_url":"https://github.com/fetch-kit.png","language":"TypeScript","readme":"![npm](https://img.shields.io/npm/v/@fetchkit/ffetch)\n![Downloads](https://img.shields.io/npm/dm/@fetchkit/ffetch)\n![GitHub stars](https://img.shields.io/github/stars/fetch-kit/ffetch?style=social)\n\n![Build](https://github.com/fetch-kit/ffetch/actions/workflows/ci.yml/badge.svg)\n![codecov](https://codecov.io/gh/fetch-kit/ffetch/branch/main/graph/badge.svg)\n\n![MIT](https://img.shields.io/npm/l/@fetchkit/ffetch)\n![bundlephobia](https://badgen.net/bundlephobia/minzip/@fetchkit/ffetch)\n![Types](https://img.shields.io/npm/types/@fetchkit/ffetch)\n\n# @fetchkit/ffetch\n\n**A production-ready TypeScript-first drop-in replacement for native fetch, or any fetch-compatible implementation.**\n\nffetch can wrap any fetch-compatible implementation (native fetch, node-fetch, undici, or framework-provided fetch), making it flexible for SSR, edge, and custom environments.\n\n**Key Features:**\n\n- **Timeouts** – per-request or global\n- **Retries** – exponential backoff + jitter\n- **Circuit breaker** – automatic failure protection\n- **Deduplication** – automatic deduping of in-flight identical requests\n- **Hooks** – logging, auth, metrics, request/response transformation\n- **Pending requests** – real-time monitoring of active requests\n- **Per-request overrides** – customize behavior on a per-request basis\n- **Universal** – Node.js, Browser, Cloudflare Workers, React Native\n- **Zero runtime deps** – ships as dual ESM/CJS\n- **Configurable error handling** – custom error types and `throwOnHttpError` flag to throw on HTTP errors\n\n## Quick Start\n\n### Install\n\n```bash\nnpm install @fetchkit/ffetch\n```\n\n### Basic Usage\n\n```typescript\nimport createClient from '@fetchkit/ffetch'\n\n// Create a client with timeout, retries, and deduplication\nconst api = createClient({\n  timeout: 5000,\n  retries: 3,\n  dedupe: true, // Enable deduplication globally\n  retryDelay: ({ attempt }) =\u003e 2 ** attempt * 100 + Math.random() * 100,\n})\n\n// Make requests\nconst response = await api('https://api.example.com/users')\nconst data = await response.json()\n\n// Deduplication example: these two requests will be deduped\nconst p1 = api('https://api.example.com/data')\nconst p2 = api('https://api.example.com/data')\nconst [r1, r2] = await Promise.all([p1, p2])\n// Only one fetch will occur; both promises resolve to the same response\n```\n\n### Using a Custom fetchHandler (SSR, metaframeworks, or polyfills)\n\n```typescript\n// Example: SvelteKit, Next.js, Nuxt, or node-fetch\nimport createClient from '@fetchkit/ffetch'\n\n// Pass your framework's fetch implementation\nconst api = createClient({\n  fetchHandler: fetch, // SvelteKit/Next.js/Nuxt provide their own fetch\n  timeout: 5000,\n})\n\n// Or use node-fetch/undici in Node.js\nimport nodeFetch from 'node-fetch'\nconst apiNode = createClient({ fetchHandler: nodeFetch })\n\n// All ffetch features work identically\nconst response = await api('/api/data')\n```\n\n### Advanced Example\n\n```typescript\n// Production-ready client with error handling and monitoring\nconst client = createClient({\n  timeout: 10000,\n  retries: 2,\n  dedupe: true,\n  dedupeHashFn: (params) =\u003e `${params.method}|${params.url}|${params.body}`,\n  circuit: { threshold: 5, reset: 30000 },\n  fetchHandler: fetch, // Use custom fetch if needed\n  hooks: {\n    before: async (req) =\u003e console.log('→', req.url),\n    after: async (req, res) =\u003e console.log('←', res.status),\n    onError: async (req, err) =\u003e console.error('Error:', err.message),\n    onCircuitOpen: (req) =\u003e console.warn('Circuit opened due to:', req.url),\n    onCircuitClose: (req) =\u003e console.info('Circuit closed after:', req.url),\n  },\n})\n\ntry {\n  const response = await client('/api/data')\n\n  // Check HTTP status manually (like native fetch)\n  if (!response.ok) {\n    console.log('HTTP error:', response.status)\n    return\n  }\n\n  const data = await response.json()\n  console.log('Active requests:', client.pendingRequests.length)\n} catch (err) {\n  if (err instanceof TimeoutError) {\n    console.log('Request timed out')\n  } else if (err instanceof RetryLimitError) {\n    console.log('Request failed after retries')\n  }\n}\n```\n\n### Custom Error Handling with `throwOnHttpError`\n\nNative `fetch`'s controversial behavior of not throwing errors for HTTP error status codes (4xx, 5xx) can lead to overlooked errors in applications. By default, `ffetch` follows this same pattern, returning a `Response` object regardless of the HTTP status code. However, with the `throwOnHttpError` flag, developers can configure `ffetch` to throw an `HttpError` for HTTP error responses, making error handling more explicit and robust. Note that this behavior is affected by retries and the circuit breaker - full details are explained in the [Error Handling documentation](./docs/errorhandling.md).\n\n## Documentation\n\n| Topic                                         | Description                                                               |\n| --------------------------------------------- | ------------------------------------------------------------------------- |\n| **[Complete Documentation](./docs/index.md)** | **Start here** - Documentation index and overview                         |\n| **[API Reference](./docs/api.md)**            | Complete API documentation and configuration options                      |\n| **[Deduplication](./docs/deduplication.md)**  | How deduplication works, config, custom hash, limitations                 |\n| **[Error Handling](./docs/errorhandling.md)** | Strategies for managing errors, including `throwOnHttpError`              |\n| **[Advanced Features](./docs/advanced.md)**   | Per-request overrides, pending requests, circuit breakers, custom errors  |\n| **[Hooks \u0026 Transformation](./docs/hooks.md)** | Lifecycle hooks, authentication, logging, request/response transformation |\n| **[Usage Examples](./docs/examples.md)**      | Real-world patterns: REST clients, GraphQL, file uploads, microservices   |\n| **[Compatibility](./docs/compatibility.md)**  | Browser/Node.js support, polyfills, framework integration                 |\n\n## Environment Requirements\n\n`ffetch` requires modern AbortSignal APIs:\n\n- **Node.js 20.6+** (for AbortSignal.any)\n- **Modern browsers** (Chrome 117+, Firefox 117+, Safari 17+, Edge 117+)\n\nIf your environment does not support `AbortSignal.any` (Node.js \u003c 20.6, older browsers), you **must install a polyfill** before using ffetch. See the [compatibility guide](./docs/compatibility.md) for instructions.\n\n**Custom fetch support:**\nYou can pass any fetch-compatible implementation (native fetch, node-fetch, undici, SvelteKit, Next.js, Nuxt, or a polyfill) via the `fetchHandler` option. This makes ffetch fully compatible with SSR, edge, metaframework environments, custom backends, and test runners.\n\n#### \"AbortSignal.any is not a function\"\n\nSolution: Install a polyfill for `AbortSignal.any`\n\n```bash\nnpm install abort-controller-x\n```\n\n## CDN Usage\n\n```html\n\u003cscript type=\"module\"\u003e\n  import createClient from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'\n\n  const api = createClient({ timeout: 5000 })\n  const data = await api('/api/data').then((r) =\u003e r.json())\n\u003c/script\u003e\n```\n\n## Deduplication Limitations\n\n- Deduplication is **off** by default. Enable it via the `dedupe` option.\n- The default hash function is `dedupeRequestHash`, which handles common body types and skips deduplication for streams and FormData.\n- **Stream bodies** (`ReadableStream`, `FormData`): Deduplication is skipped for requests with these body types, as they cannot be reliably hashed or replayed.\n- **Non-idempotent requests**: Use deduplication with caution for non-idempotent methods (e.g., POST), as it may suppress multiple intended requests.\n- **Custom hash function**: Ensure your hash function uniquely identifies requests to avoid accidental deduplication.\n\nSee [deduplication.md](./docs/deduplication.md) for full details.\n\n## Fetch vs. Axios vs. `ffetch`\n\n| Feature              | Native Fetch              | Axios                | ffetch                                                                                 |\n| -------------------- | ------------------------- | -------------------- | -------------------------------------------------------------------------------------- |\n| Timeouts             | ❌ Manual AbortController | ✅ Built-in          | ✅ Built-in with fallbacks                                                             |\n| Retries              | ❌ Manual implementation  | ❌ Manual or plugins | ✅ Smart exponential backoff                                                           |\n| Circuit Breaker      | ❌ Not available          | ❌ Manual or plugins | ✅ Automatic failure protection                                                        |\n| Deduplication        | ❌ Not available          | ❌ Not available     | ✅ Automatic deduplication of in-flight identical requests                             |\n| Request Monitoring   | ❌ Manual tracking        | ❌ Manual tracking   | ✅ Built-in pending requests                                                           |\n| Error Types          | ❌ Generic errors         | ⚠️ HTTP errors only  | ✅ Specific error classes                                                              |\n| TypeScript           | ⚠️ Basic types            | ⚠️ Basic types       | ✅ Full type safety                                                                    |\n| Hooks/Middleware     | ❌ Not available          | ✅ Interceptors      | ✅ Comprehensive lifecycle hooks                                                       |\n| Bundle Size          | ✅ Native (0kb)           | ❌ ~13kb minified    | ✅ ~3kb minified                                                                       |\n| Modern APIs          | ✅ Web standards          | ❌ XMLHttpRequest    | ✅ Fetch + modern features                                                             |\n| Custom Fetch Support | ❌ No (global only)       | ❌ No                | ✅ Yes (wrap any fetch-compatible implementation, including framework or custom fetch) |\n\n## Join the Community\n\nGot questions, want to discuss features, or share examples? Join the **Fetch-Kit Discord server**:\n\n[![Discord](https://img.shields.io/badge/Discord-Join_Fetch--Kit-7289DA?logo=discord\u0026logoColor=white)](https://discord.gg/sdyPBPCDUg)\n\n## Contributing\n\n- **Issues**: [GitHub Issues](https://github.com/fetch-kit/ffetch/issues)\n- **Pull Requests**: [GitHub PRs](https://github.com/fetch-kit/ffetch/pulls)\n- **Documentation**: Found in `./docs/` - PRs welcome!\n\n## License\n\nMIT © 2025 gkoos\n","funding_links":["https://github.com/sponsors/gkoos"],"categories":["Built with TypeScript"],"sub_categories":["Libraries"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffetch-kit%2Fffetch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffetch-kit%2Fffetch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffetch-kit%2Fffetch/lists"}