@revas-hq/kit-fetch v0.0.14
Revas Kit: Fetch (@revas-hq/kit-fetch)
The @revas-hq/kit-fetch package provides utilities for creating enhanced, context-aware fetch functions using composable middleware. This allows you to centralize logic like adding base URLs, injecting authentication headers, logging, or tracing for your outgoing HTTP requests.
It integrates with @revas-hq/kit-context to allow middleware behaviour to depend on request-scoped context values.
Installation
npm install @revas-hq/kit-fetch @revas-hq/kit-context
# or
yarn add @revas-hq/kit-fetch @revas-hq/kit-context(Requires
@revas-hq/kit-context)
Core Concepts
- FetchMiddleware: A higher-order function
(next: typeof fetch) => typeof fetchthat wraps a fetch implementation. - ContextFetchMiddleware: A factory
(context: Context) => FetchMiddlewarethat creates context-specific FetchMiddleware. - ContextFetchFunction: A factory
(context: Context) => typeof fetchthat returns a fully configured fetch function for a given context. - Composition: Middleware is applied sequentially, allowing multiple enhancements (e.g., base URL + auth header).
Key Types
import type { Context } from '@revas-hq/kit-context';
/** Middleware that wraps a fetch function. */
export type FetchMiddleware = (next: typeof fetch) => typeof fetch;
/** A factory that creates FetchMiddleware based on context. */
export type ContextFetchMiddleware = (context: Context) => FetchMiddleware;
/** A factory that returns a configured fetch function based on context. */
export type ContextFetchFunction = (context: Context) => typeof fetch;Provided Utilities
createBaseUrlMiddleware(baseUrl: string)
Creates a ContextFetchMiddleware factory that ensures relative string URLs or Request object URLs passed to fetch are resolved against the provided baseUrl. Absolute URLs or URL objects are passed through unchanged.
- baseUrl: The base URL string (e.g.,
https://api.example.com/v1/). Must end with/.
import { createBaseUrlMiddleware } from '@revas-hq/kit-fetch';
const addApiBaseUrl = createBaseUrlMiddleware("https://api.example.com/v1/");
// `addApiBaseUrl` is now a ContextFetchMiddleware factorycreateContextFetch(...factories: ContextFetchMiddleware[])
Takes one or more ContextFetchMiddleware factories and composes them to create a final ContextFetchFunction. The middleware factories are applied in order: the first factory provides the innermost wrapper, the last factory provides the outermost wrapper.
- factories: A list of
ContextFetchMiddlewarefactories (likeaddApiBaseUrlabove, or custom ones).
import { createContextFetch } from '@revas-hq/kit-fetch';
// Assuming addApiBaseUrl and addAuthHeader are ContextFetchMiddleware factories
// Create a function that will generate a fetch instance with both middlewares applied
const createApiFetch = createContextFetch(
addApiBaseUrl,
addAuthHeader // Example: Auth middleware factory from @revas-hq/kit-auth
);
// `createApiFetch` is now a ContextFetchFunction: (context: Context) => typeof fetchUsage Example
Combine middleware factories using createContextFetch, then use the resulting function to get a context-specific fetch implementation when needed (e.g., inside an endpoint or service).
import { createContext, Context, withValue } from '@revas-hq/kit-context';
import { createBaseUrlMiddleware, createContextFetch } from '@revas-hq/kit-fetch';
// Assuming auth middleware from @revas-hq/kit-auth
import { createAuthorizedFetchMiddleware, createAuthorizationHeaderTokenSource } from '@revas-hq/kit-auth';
// --- 1. Setup Middleware Factories ---
// Base URL middleware factory
const addApiBaseUrl = createBaseUrlMiddleware("https://api.example.com/v1/");
// Auth middleware factory (example using context token source)
const tokenSourceFactory = createAuthorizationHeaderTokenSource();
const addAuthHeader = createAuthorizedFetchMiddleware(tokenSourceFactory);
// --- 2. Create the Context-to-Fetch Factory ---
// Middleware order matters: auth might need the final URL from base URL resolver.
// Let's apply base URL first (innermost), then auth (outermost).
const createApiFetch = createContextFetch(
addApiBaseUrl, // Innermost
addAuthHeader // Outermost
);
// --- 3. Usage (e.g., inside an endpoint or service method) ---
async function fetchUserData(userId: string, requestContext: Context) {
// Create the specific fetch instance for *this* request's context
const apiFetch = createApiFetch(requestContext);
try {
// Use the configured fetch instance. URLs are relative to the base URL.
// Authorization header will be added automatically based on context.
const response = await apiFetch(`/users/${userId}`); // Resolves to https://api.example.com/v1/users/userId
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const userData = await response.json();
console.log("User data:", userData);
return userData;
} catch (error) {
console.error("Failed to fetch user data:", error);
// Handle or re-throw error
throw error;
}
}
// --- Example Context Setup (usually done by framework adapter/middleware) ---
const rootCtx = createContext();
// Assume 'captureAuthorizationHeader' middleware ran and put token in context
const ctxWithToken = withValue(rootCtx, 'authToken', 'example-bearer-token');
// Call the function with the context
// await fetchUserData('user-42', ctxWithToken);Custom Middleware
You can create your own ContextFetchMiddleware factories for tasks like:
- Adding specific headers (API keys, Content-Type)
- Logging requests/responses
- Implementing retries or circuit breakers
- Modifying request/response bodies
- Adding tracing spans (as shown in
createAuthorizedFetchMiddleware)
import type { ContextFetchMiddleware, FetchMiddleware } from '@revas-hq/kit-fetch';
import type { Context } from '@revas-hq/kit-context';
const addJsonHeaders: ContextFetchMiddleware = (context: Context) => {
return (next: typeof fetch): typeof fetch => {
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
const headers = new Headers(init?.headers);
if (!headers.has('Accept')) {
headers.set('Accept', 'application/json');
}
// Only set Content-Type if there's likely a body
const method = (input instanceof Request ? input.method : init?.method)?.toUpperCase();
if (method !== 'GET' && method !== 'HEAD' && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
const updatedInit = { ...init, headers };
return next(input, updatedInit);
};
};
};
// Then use it: createContextFetch(addApiBaseUrl, addJsonHeaders, addAuthHeader)License
This project is licensed under the MIT License. See the LICENSE file for details.