npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@23blocks/transport-http

v3.1.0

Published

HTTP transport layer for 23blocks SDK with fetch-based implementation

Readme

@23blocks/transport-http

HTTP transport implementation for the 23blocks SDK.

npm version License: MIT

Installation

npm install @23blocks/transport-http

Overview

This package provides the HTTP transport layer for the 23blocks SDK. It handles:

  • HTTP requests - GET, POST, PUT, PATCH, DELETE operations
  • Error handling - Automatic conversion to BlockErrorException
  • Timeouts - Configurable request timeouts with AbortController
  • Dynamic headers - Static headers or async header providers
  • Query parameters - Automatic serialization including arrays
  • Debug logging - Built-in request/response logging for development
  • Request tracing - Automatic request IDs for debugging and support
  • Automatic retries - Exponential backoff with jitter for transient failures
  • Interceptors - Hook into request/response lifecycle

Usage

Basic Configuration

import { createHttpTransport } from '@23blocks/transport-http';

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
});

// Make requests
const data = await transport.get('/api/users');
const user = await transport.post('/api/users', { name: 'John' });

With Authentication Headers

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  headers: {
    'x-api-key': 'your-api-key',
  },
});

With Dynamic Headers

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  headers: () => {
    const token = localStorage.getItem('access_token');
    return token ? { Authorization: `Bearer ${token}` } : {};
  },
});

With Async Headers

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  headers: async () => {
    const token = await getTokenFromSecureStorage();
    return { Authorization: `Bearer ${token}` };
  },
});

Debug Logging

Enable debug mode to log all requests and responses to the console. This is invaluable for development and troubleshooting.

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  debug: true, // Enable debug logging
});

Console Output

When debug mode is enabled, you'll see output like this:

[23blocks] POST /auth/sign_in [req_m5abc_xyz123]
[23blocks] → Headers: { "content-type": "application/json", "x-api-key": "***" }
[23blocks] → Body: { "email": "[email protected]", "password": "***" }
[23blocks] ← 200 OK (145ms) [req_m5abc_xyz123]
[23blocks] ← Body: { "data": { "type": "user", "id": "123", ... } }

Sensitive data like passwords, tokens, and API keys are automatically masked in logs.

Custom Logger

You can provide a custom logger implementation:

import { createHttpTransport } from '@23blocks/transport-http';
import type { Logger } from '@23blocks/contracts';

const customLogger: Logger = {
  debug: (msg, meta) => myLoggingService.log('debug', msg, meta),
  info: (msg, meta) => myLoggingService.log('info', msg, meta),
  warn: (msg, meta) => myLoggingService.log('warn', msg, meta),
  error: (msg, meta) => myLoggingService.log('error', msg, meta),
};

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  logger: customLogger,
  debug: true,
});

Request Tracing

Every request automatically includes a unique X-Request-ID header. This ID is:

  • Included in all error responses for easy debugging
  • Passed to your backend for end-to-end tracing
  • Logged in debug mode for correlation
try {
  await transport.get('/api/users/123');
} catch (error) {
  if (isBlockErrorException(error)) {
    console.log('Request ID:', error.requestId); // "req_m5abc_xyz123"
    console.log('Duration:', error.duration);     // 145 (ms)

    // Send to support: "Please check request req_m5abc_xyz123"
  }
}

Custom Request ID Generator

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  generateRequestId: () => `myapp-${Date.now()}-${Math.random().toString(36).slice(2)}`,
});

Automatic Retries

Configure automatic retries with exponential backoff for transient failures:

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  retry: {
    maxRetries: 3,                        // Retry up to 3 times
    initialDelay: 1000,                   // Start with 1 second delay
    maxDelay: 10000,                      // Cap at 10 seconds
    backoffMultiplier: 2,                 // Double delay each retry
    retryableStatuses: [429, 502, 503, 504], // Which status codes to retry
  },
});

Retry Behavior

  • Retries only trigger for configured status codes (default: 429, 502, 503, 504)
  • Uses exponential backoff with jitter to prevent thundering herd
  • Network errors are also retried
  • Debug mode logs each retry attempt
[23blocks] POST /api/data [req_m5abc_xyz123]
[23blocks] ✗ 503 Service Unavailable (89ms) [req_m5abc_xyz123]
[23blocks] Retrying in 1250ms (attempt 1/3) [req_m5abc_xyz123]
[23blocks] ✗ 503 Service Unavailable (92ms) [req_m5abc_xyz123]
[23blocks] Retrying in 2480ms (attempt 2/3) [req_m5abc_xyz123]
[23blocks] ← 200 OK (134ms) [req_m5abc_xyz123]

Interceptors

Hook into the request/response lifecycle for cross-cutting concerns:

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  interceptors: {
    // Called before each request
    onRequest: async ({ method, path, headers, requestId }) => {
      console.log(`Starting ${method} ${path}`);
      // You could add analytics tracking here
    },

    // Called after each successful response
    onResponse: async (response, { method, path, duration, requestId }) => {
      trackMetric('api_latency', duration, { path });
      return response; // Must return response (can transform it)
    },

    // Called when an error occurs
    onError: async (error, { method, path, duration, requestId }) => {
      // Report to error tracking service
      Sentry.captureException(error, {
        extra: { requestId, path, duration },
      });
      throw error; // Must re-throw or handle
    },
  },
});

Common Interceptor Patterns

Token Refresh

interceptors: {
  onError: async (error, context) => {
    if (error instanceof BlockErrorException && error.code === 'token_expired') {
      await refreshAuthToken();
      // Retry logic would go here
    }
    throw error;
  },
}

Performance Monitoring

interceptors: {
  onResponse: async (response, { path, duration }) => {
    if (duration > 1000) {
      console.warn(`Slow request: ${path} took ${duration}ms`);
    }
    return response;
  },
}

Timeout Configuration

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  timeout: 60000, // 60 seconds (default is 30 seconds)
});

Credentials (Cookies)

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  credentials: 'include', // Include cookies in cross-origin requests
});

Per-Request Options

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
});

// With query parameters
const users = await transport.get('/api/users', {
  params: {
    limit: 20,
    offset: 0,
    status: 'active',
    roles: ['admin', 'user'], // Arrays become ?roles[]=admin&roles[]=user
  },
});

// With custom headers for specific request
const data = await transport.get('/api/sensitive', {
  headers: {
    'X-Custom-Header': 'value',
  },
});

// With custom timeout
const data = await transport.post('/api/long-operation', payload, {
  timeout: 120000, // 2 minutes
});

// With abort signal
const controller = new AbortController();
const data = await transport.get('/api/data', {
  signal: controller.signal,
});

// Cancel the request
controller.abort();

API Reference

createHttpTransport(config)

Creates a new HTTP transport instance.

function createHttpTransport(config: TransportConfig): Transport;

TransportConfig

| Property | Type | Default | Description | |----------|------|---------|-------------| | baseUrl | string | required | Base URL for all requests | | headers | Record<string, string> | HeadersProvider | {} | Static or dynamic headers | | timeout | number | 30000 | Default timeout in milliseconds | | credentials | RequestCredentials | undefined | Fetch credentials option | | debug | boolean | false | Enable debug logging | | logger | Logger | consoleLogger | Custom logger implementation | | generateRequestId | () => string | built-in | Custom request ID generator | | retry | RetryConfig | undefined | Retry configuration | | interceptors | Interceptors | undefined | Request/response interceptors |

RetryConfig

| Property | Type | Default | Description | |----------|------|---------|-------------| | maxRetries | number | required | Maximum number of retry attempts | | initialDelay | number | required | Initial delay in milliseconds | | maxDelay | number | required | Maximum delay in milliseconds | | backoffMultiplier | number | required | Multiplier for exponential backoff | | retryableStatuses | number[] | [429, 502, 503, 504] | HTTP status codes to retry |

Interceptors

| Property | Type | Description | |----------|------|-------------| | onRequest | (config) => void \| Promise<void> | Called before each request | | onResponse | <T>(response, context) => T \| Promise<T> | Called after successful response | | onError | (error, context) => never | Called on error |

Transport Interface

interface Transport {
  get<T>(path: string, options?: RequestOptions): Promise<T>;
  post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
  put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
  patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
  delete<T>(path: string, options?: RequestOptions): Promise<T>;
}

RequestOptions

| Property | Type | Description | |----------|------|-------------| | params | Record<string, string \| number \| boolean \| string[]> | Query parameters | | headers | Record<string, string> | Additional headers (merged with config headers) | | timeout | number | Override default timeout | | signal | AbortSignal | Abort signal for cancellation |

Error Handling

The transport automatically converts HTTP errors to BlockErrorException with request context:

import { BlockErrorException, ErrorCodes, isBlockErrorException } from '@23blocks/contracts';

try {
  await transport.get('/api/users/123');
} catch (error) {
  if (isBlockErrorException(error)) {
    // Error details
    console.log('Code:', error.code);           // "not_found"
    console.log('Message:', error.message);     // "User not found"
    console.log('Status:', error.status);       // 404

    // Request context (NEW)
    console.log('Request ID:', error.requestId); // "req_m5abc_xyz123"
    console.log('Duration:', error.duration);    // 145 (ms)

    switch (error.code) {
      case ErrorCodes.UNAUTHORIZED: // 401
        // Redirect to login
        break;
      case ErrorCodes.FORBIDDEN: // 403
        // Show access denied
        break;
      case ErrorCodes.NOT_FOUND: // 404
        // Show not found
        break;
      case ErrorCodes.VALIDATION_ERROR: // 422
        // Show validation errors
        console.log(error.meta?.errors);
        break;
      case ErrorCodes.TIMEOUT:
        // Request timed out
        break;
      case ErrorCodes.NETWORK_ERROR:
        // Network error (offline, DNS, etc.)
        break;
    }
  }
}

JSON:API Error Support

The transport understands JSON:API error format and extracts detailed error information:

// Server returns:
// { "errors": [{ "code": "invalid_email", "detail": "Email is invalid", "source": { "pointer": "/data/attributes/email" } }] }

try {
  await transport.post('/api/users', { email: 'invalid' });
} catch (error) {
  if (isBlockErrorException(error)) {
    console.log(error.message);         // "Email is invalid"
    console.log(error.source);          // "/data/attributes/email"
    console.log(error.requestId);       // "req_m5abc_xyz123"
    console.log(error.meta?.errors);    // Full errors array
  }
}

Complete Example

import { createHttpTransport } from '@23blocks/transport-http';
import { isBlockErrorException } from '@23blocks/contracts';

// Production-ready configuration
const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',

  // Dynamic auth headers
  headers: async () => {
    const token = await getAuthToken();
    return {
      'Authorization': `Bearer ${token}`,
      'x-api-key': process.env.API_KEY,
    };
  },

  // Debug in development only
  debug: process.env.NODE_ENV === 'development',

  // Retry transient failures
  retry: {
    maxRetries: 3,
    initialDelay: 1000,
    maxDelay: 10000,
    backoffMultiplier: 2,
  },

  // Error tracking
  interceptors: {
    onError: async (error, { requestId, path, duration }) => {
      errorTracker.capture(error, { requestId, path, duration });
      throw error;
    },
  },
});

// Use it
try {
  const user = await transport.post('/auth/sign_in', {
    email: '[email protected]',
    password: 'password',
  });
  console.log('Signed in:', user);
} catch (error) {
  if (isBlockErrorException(error)) {
    console.error(`Request ${error.requestId} failed: ${error.message}`);
  }
}

TypeScript Support

This package is written in TypeScript and exports all types:

import {
  createHttpTransport,
  type Transport,
  type TransportConfig,
  type RequestOptions,
} from '@23blocks/transport-http';

Related Packages

License

MIT - Copyright (c) 2024 23blocks