0.0.14 • Published 6 months ago

@revas-hq/kit-fetch v0.0.14

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

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 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

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

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.

0.0.10

6 months ago

0.0.11

6 months ago

0.0.12

6 months ago

0.0.13

6 months ago

0.0.14

6 months ago

0.0.9

6 months ago

0.0.8

6 months ago

0.0.5

7 months ago

0.0.4

7 months ago

0.0.7

6 months ago

0.0.6

6 months ago

0.0.3

1 year ago

0.0.1

1 year ago