npm package: @revas-hq/kit-fetch
Description: 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 you
Latest version: 0.0.16

# 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

```sh
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 fetch` that wraps a fetch implementation.
- **ContextFetchMiddleware**: A factory `(context: Context) => FetchMiddleware` that creates context-specific FetchMiddleware.
- **ContextFetchFunction**: A factory `(context: Context) => typeof fetch` that 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

```ts
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 `/`.

```ts
import { createBaseUrlMiddleware } from '@revas-hq/kit-fetch';

const addApiBaseUrl = createBaseUrlMiddleware("https://api.example.com/v1/");
// `addApiBaseUrl` is now a ContextFetchMiddleware factory
```

---

### `createContextFetch(...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 `ContextFetchMiddleware` factories (like `addApiBaseUrl` above, or custom ones).

```ts
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 fetch
```

---

## Usage 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).

```ts
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`)

```ts
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.