diff --git a/README.md b/README.md index d851b26..16ba654 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,21 @@ ffetch can wrap any fetch-compatible implementation (native fetch, node-fetch, undici, or framework-provided fetch), making it flexible for SSR, edge, and custom environments. +ffetch uses a plugin architecture for optional features, so you only include what you need. + **Key Features:** - **Timeouts** – per-request or global - **Retries** – exponential backoff + jitter -- **Circuit breaker** – automatic failure protection -- **Deduplication** – automatic deduping of in-flight identical requests +- **Plugin architecture** – extensible lifecycle-based plugins for optional behavior - **Hooks** – logging, auth, metrics, request/response transformation - **Pending requests** – real-time monitoring of active requests - **Per-request overrides** – customize behavior on a per-request basis - **Universal** – Node.js, Browser, Cloudflare Workers, React Native - **Zero runtime deps** – ships as dual ESM/CJS - **Configurable error handling** – custom error types and `throwOnHttpError` flag to throw on HTTP errors +- **Circuit breaker plugin (optional, prebuilt)** – automatic failure protection +- **Deduplication plugin (optional, prebuilt)** – automatic deduping of in-flight identical requests ## Quick Start @@ -39,13 +42,14 @@ npm install @fetchkit/ffetch ### Basic Usage ```typescript -import createClient from '@fetchkit/ffetch' +import { createClient } from '@fetchkit/ffetch' +import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe' -// Create a client with timeout, retries, and deduplication +// Create a client with timeout, retries, and deduplication plugin const api = createClient({ timeout: 5000, retries: 3, - dedupe: true, // Enable deduplication globally + plugins: [dedupePlugin()], retryDelay: ({ attempt }) => 2 ** attempt * 100 + Math.random() * 100, }) @@ -64,7 +68,7 @@ const [r1, r2] = await Promise.all([p1, p2]) ```typescript // Example: SvelteKit, Next.js, Nuxt, or node-fetch -import createClient from '@fetchkit/ffetch' +import { createClient } from '@fetchkit/ffetch' // Pass your framework's fetch implementation const api = createClient({ @@ -84,19 +88,31 @@ const response = await api('/api/data') ```typescript // Production-ready client with error handling and monitoring +import { createClient } from '@fetchkit/ffetch' +import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe' +import { circuitPlugin } from '@fetchkit/ffetch/plugins/circuit' + const client = createClient({ timeout: 10000, retries: 2, - dedupe: true, - dedupeHashFn: (params) => `${params.method}|${params.url}|${params.body}`, - circuit: { threshold: 5, reset: 30000 }, fetchHandler: fetch, // Use custom fetch if needed + plugins: [ + dedupePlugin({ + hashFn: (params) => `${params.method}|${params.url}|${params.body}`, + ttl: 30_000, + sweepInterval: 5_000, + }), + circuitPlugin({ + threshold: 5, + reset: 30_000, + onCircuitOpen: (req) => console.warn('Circuit opened due to:', req.url), + onCircuitClose: (req) => console.info('Circuit closed after:', req.url), + }), + ], hooks: { before: async (req) => console.log('→', req.url), after: async (req, res) => console.log('←', res.status), onError: async (req, err) => console.error('Error:', err.message), - onCircuitOpen: (req) => console.warn('Circuit opened due to:', req.url), - onCircuitClose: (req) => console.info('Circuit closed after:', req.url), }, }) @@ -130,6 +146,7 @@ Native `fetch`'s controversial behavior of not throwing errors for HTTP error st | --------------------------------------------- | ------------------------------------------------------------------------- | | **[Complete Documentation](./docs/index.md)** | **Start here** - Documentation index and overview | | **[API Reference](./docs/api.md)** | Complete API documentation and configuration options | +| **[Plugin Architecture](./docs/plugins.md)** | Plugin lifecycle, custom plugin authoring, and integration patterns | | **[Deduplication](./docs/deduplication.md)** | How deduplication works, hash config, optional TTL cleanup, limitations | | **[Error Handling](./docs/errorhandling.md)** | Strategies for managing errors, including `throwOnHttpError` | | **[Advanced Features](./docs/advanced.md)** | Per-request overrides, pending requests, circuit breakers, custom errors | @@ -139,12 +156,12 @@ Native `fetch`'s controversial behavior of not throwing errors for HTTP error st ## Environment Requirements -`ffetch` requires modern AbortSignal APIs: +`ffetch` works best with native `AbortSignal.any` support: -- **Node.js 20.6+** (for AbortSignal.any) -- **Modern browsers** (Chrome 117+, Firefox 117+, Safari 17+, Edge 117+) +- **Node.js 20.6+** (native `AbortSignal.any`) +- **Modern browsers with `AbortSignal.any`** (for example: Chrome 117+, Firefox 117+, Safari 17+, Edge 117+) -If your environment does not support `AbortSignal.any` (Node.js < 20.6, older browsers), you **must install a polyfill** before using ffetch. See the [compatibility guide](./docs/compatibility.md) for instructions. +If your environment does not support `AbortSignal.any` (Node.js < 20.6, older browsers), you can still use ffetch by installing an `AbortSignal.any` polyfill. `AbortSignal.timeout` is optional because ffetch includes an internal timeout fallback. See the [compatibility guide](./docs/compatibility.md) for instructions. **Custom fetch support:** You 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. @@ -161,7 +178,7 @@ npm install abort-controller-x ```html ``` @@ -118,8 +129,8 @@ self.addEventListener('fetch', async (event) => { #### Web Workers ```javascript -// Works in web workers -importScripts('https://unpkg.com/@fetchkit/ffetch/dist/index.min.js') +// Use a module worker and import the ESM build +import { createClient } from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js' const client = createClient() self.postMessage(await client('/api/data').then((r) => r.json())) @@ -202,10 +213,6 @@ function checkCompatibility() { throw new Error('AbortSignal not supported. Please add a polyfill.') } - if (typeof AbortSignal.timeout !== 'function') { - throw new Error('AbortSignal.timeout not supported. Please add a polyfill.') - } - if (typeof AbortSignal.any !== 'function') { throw new Error( 'AbortSignal.any is required for combining multiple signals. Please install a polyfill.' @@ -234,22 +241,16 @@ const client = createClient({ ```html ``` -### UMD (Legacy) +### UMD -```html - - -``` +`ffetch` does not currently publish a UMD build. Use the ESM build shown above (or import from the package in a bundler/runtime environment). ## Framework Integration @@ -257,7 +258,7 @@ const client = createClient({ ```jsx import { useEffect, useState } from 'react' -import createClient from '@fetchkit/ffetch' +import { createClient } from '@fetchkit/ffetch' const client = createClient({ timeout: 5000 }) @@ -292,7 +293,7 @@ function DataComponent() {