Plugins
Plugins extend endpoint-fetcher with reusable cross-cutting functionality.
Using Plugins
Install an official plugin and add it to the plugins array in your client config:
import { createApiClient, get } from 'endpoint-fetcher';
import { cache } from '@endpoint-fetcher/cache';
import { retryPlugin } from '@endpoint-fetcher/retry';
const api = createApiClient({
getUser: get<{ id: string }, User>((input) => `/users/${input.id}`),
}, {
baseUrl: 'https://api.example.com',
plugins: [
retryPlugin({ maxRetries: 3, strategy: 'exponential' }),
cache({ ttl: 300 }),
],
});Plugins run in the order they are listed. Plugin hooks execute before global/group/endpoint hooks.
Creating a Plugin
Use createPlugin(name, factory):
import { createPlugin } from 'endpoint-fetcher';
export const loggingPlugin = createPlugin('logging', () => ({
hooks: {
beforeRequest: async (url, init) => {
console.log(`→ ${init.method} ${url}`);
return { url, init };
},
afterResponse: async (response, url, init) => {
console.log(`← ${response.status} ${init.method} ${url}`);
return response;
},
},
}));
// Usage
plugins: [loggingPlugin()]Accept a config object in the factory:
export const authPlugin = createPlugin('auth', (config: { token: string }) => ({
hooks: {
beforeRequest: async (url, init) => ({
url,
init: { ...init, headers: { ...init.headers, Authorization: `Bearer ${config.token}` } },
}),
},
}));
// Usage
plugins: [authPlugin({ token: 'my-token' })]Plugin Structure
A plugin factory returns an object with any combination of:
| Property | Description |
|---|---|
hooks | beforeRequest, afterResponse, onError — same interface as client hooks |
handlerWrapper | Wraps each endpoint’s handler function (useful for retry, caching) |
methods | Functions exposed on client.plugins.<pluginName> |
handlerWrapper
Wraps the endpoint handler, giving full control over execution (retries, short-circuit caching, etc.):
export const retryPlugin = createPlugin('retry', (config: { maxRetries: number }) => ({
handlerWrapper: (originalHandler) => async (input, context) => {
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await originalHandler(input, context);
} catch (err) {
const error = err as { status?: number };
if (error.status && error.status < 500) throw err; // don't retry 4xx
if (attempt === config.maxRetries) throw err;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
},
}));methods
Expose functions on client.plugins.<pluginName> (fully typed):
export const metricsPlugin = createPlugin('metrics', () => {
let requestCount = 0;
let errorCount = 0;
return {
hooks: {
beforeRequest: async (url, init) => { requestCount++; return { url, init }; },
onError: async () => { errorCount++; },
},
methods: {
getStats: () => ({ requestCount, errorCount }),
reset: () => { requestCount = 0; errorCount = 0; },
},
};
});
// Access
const stats = api.plugins.metrics.getStats(); // fully typed
api.plugins.metrics.reset();When multiple plugins expose methods, each is namespaced under its plugin name:
api.plugins.metrics.getStats();
api.plugins.cache.clear();Plugin names must be unique. Registering two plugins with the same name throws at client creation time.
Official Plugins
- Cache Plugin — Response caching with TTL, LRU eviction, and custom storage
- Retry Plugin — Automatic retries with fixed, linear, or exponential backoff
- Auth Plugin — JWT, OAuth2, API Key, Basic, Bearer, HMAC, Digest, and Custom authentication strategies