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

@bereasoftware/nexa

v1.7.0

Published

Nexa is a TypeScript HTTP client library that combines the power of fetch with the convenience of axios, while adhering to SOLID principles. It provides a flexible and extensible API for making HTTP requests, handling retries, caching, and more.

Readme

📚 Documentación disponible en otros idiomas:

  • 🇪🇸 Español (este archivo - README.md)
  • 🇬🇧 English (README.en.md)

📖 Documentación adicional:


Posicionamiento

Nexa no busca ser solo otro wrapper de fetch.

La librería está diseñada para combinar tres cosas en un solo paquete:

  • una API HTTP type-safe y sin dependencias runtime
  • extensibilidad real con retries, caché, middlewares y plugins
  • observabilidad integrada para desarrollo con Dev Overlay

Eso la hace útil no solo como cliente HTTP, sino también como base para SDKs, tooling interno y flujos donde la experiencia de depuración importa.


¿Por qué Nexa?

| Característica | fetch | axios | Nexa | | ------------------------------------------ | :-----: | :-----: | :------: | | Cero dependencias | ✅ | ❌ | ✅ | | Errores type-safe (Result monad) | ❌ | ❌ | ✅ | | Serialización automática del body | ❌ | ✅ | ✅ | | Interpolación de parámetros en ruta | ❌ | ❌ | ✅ | | Estrategias de reintentos (pluggable) | ❌ | ❌ | ✅ | | Caché integrado | ❌ | ❌ | ✅ | | Deduplicación de peticiones | ❌ | ❌ | ✅ | | Progreso de descarga | ❌ | ✅ | ✅ | | Hooks de ciclo de vida | ❌ | ❌ | ✅ | | Limitación de peticiones concurrentes | ❌ | ❌ | ✅ | | Auto-paginación | ❌ | ❌ | ✅ | | Polling inteligente | ❌ | ❌ | ✅ | | Extensión de cliente (.extend()) | ❌ | ✅ | ✅ | | Disposal de interceptores | ❌ | ❌ | ✅ | | Pipeline de middleware | ❌ | ❌ | ✅ | | Sistema de plugins | ❌ | ❌ | ✅ | | Validadores y transformadores | ❌ | ❌ | ✅ | | Tracking de duración de respuesta | ❌ | ❌ | ✅ | | Detección inteligente de tipo de respuesta | ❌ | ✅ | ✅ | | WebSocket/SSE con reconexión automática | ❌ | ❌ | ✅ | | Rate limiting integrado | ❌ | ❌ | ✅ | | Circuit breaker para fallos en cascada | ❌ | ❌ | ✅ | | Tree-shakeable | ✅ | ❌ | ✅ | | Dev Overlay (herramientas de desarrollo) | ❌ | ❌ | ✅ |


Tabla de Contenidos


Instalación

npm install @bereasoftware/nexa
yarn add @bereasoftware/nexa
pnpm add @bereasoftware/nexa

Inicio Rápido

import { createHttpClient } from "@bereasoftware/nexa";

const client = createHttpClient({
  baseURL: "https://api.example.com",
});

// Type-safe, sin necesidad de try/catch
const result = await client.get<User>("/users/1");

if (result.ok) {
  console.log(result.value.data); // User
  console.log(result.value.status); // 200
  console.log(result.value.duration); // 42 (ms)
} else {
  console.log(result.error.message); // "Request failed with status 404"
  console.log(result.error.code); // "HTTP_ERROR"
}

Conceptos Fundamentales

Result Monad

Nexa retorna un tipo Result<T, E> en lugar de lanzar excepciones. Esto elimina la necesidad de bloques try/catch y te da seguridad de tipos completa tanto en el camino de éxito como en el de error.

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

También puedes construir resultados manualmente:

import { Ok, Err } from "@bereasoftware/nexa";

const exito = Ok({ name: "John" }); // { ok: true, value: { name: 'John' } }
const fallo = Err({ message: "No encontrado", code: "HTTP_ERROR" });

Cada método del cliente retorna Promise<Result<HttpResponse<T>, HttpErrorDetails>>:

const result = await client.get<User[]>('/users');

if (result.ok) {
  // result.value es HttpResponse<User[]>
  const users: User[] = result.value.data;
  const status: number = result.value.status;
  const duration: number = result.value.duration;
  const headers: Headers = result.value.headers;
} else {
  // result.error es HttpErrorDetails
  const message: string = result.error.message;
  const code: string = result.error.code;       // 'HTTP_ERROR' | 'TIMEOUT' | 'NETWORK_ERROR' | 'ABORTED' | ...
  const status?: number = result.error.status;
}

Creando un Cliente

import { createHttpClient } from "@bereasoftware/nexa";

const client = createHttpClient({
  baseURL: "https://api.example.com",
  defaultHeaders: { Authorization: "Bearer token123" },
  defaultTimeout: 10000, // 10s (por defecto: 30s)
  validateStatus: (status) => status < 400, // Validación de status personalizada
  maxConcurrent: 5, // Máximo 5 peticiones simultáneas
  defaultResponseType: "json", // 'json' | 'text' | 'blob' | 'auto' | ...
  defaultHooks: {
    onStart: (req) => console.log("Iniciando:", req.url),
    onFinally: () => console.log("Listo"),
  },
});

Opciones completas de HttpClientConfig:

| Opción | Tipo | Por defecto | Descripción | | --------------------- | ----------------------------- | ---------------------------------------- | ------------------------------------------------------------ | | baseURL | string | '' | URL base que se antepone a todas las peticiones | | defaultHeaders | Record<string, string> | { 'Content-Type': 'application/json' } | Headers por defecto para cada petición | | defaultTimeout | number | 30000 | Timeout por defecto en ms | | validateStatus | (status: number) => boolean | status >= 200 && status < 300 | Qué códigos HTTP se consideran exitosos | | cacheStrategy | CacheStrategy | MemoryCache | Implementación de caché personalizada | | maxConcurrent | number | 0 (ilimitado) | Máximo de peticiones concurrentes | | defaultResponseType | ResponseType | 'auto' | Estrategia de parseo de respuesta por defecto | | defaultHooks | RequestHooks | {} | Hooks de ciclo de vida por defecto para todas las peticiones | | debug | boolean \| 'verbose' | undefined | Habilita logging de debug para requests/responses. true para logs básicos, 'verbose' para logs detallados. | | logger | (message: string, data?: unknown) => void | undefined | Función de logging personalizada. Si se proporciona, reemplaza el console.log por defecto con logging personalizado. |

Nota: Las opciones avanzadas transformRequest, credentials, withCredentials, debug, logger, transport, nodeOptions y autoFormData ya están tipadas, pero no todas están completamente integradas en el pipeline principal de HttpClient en esta versión. Úsalas con validación manual hasta que se cierre esa integración.


Métodos HTTP

// GET
const result = await client.get<User>("/users/1");

// POST
const result = await client.post<User>("/users", {
  name: "John",
  email: "[email protected]",
});

// PUT
const result = await client.put<User>("/users/1", { name: "John Actualizado" });

// PATCH
const result = await client.patch<User>("/users/1", {
  email: "[email protected]",
});

// DELETE
const result = await client.delete<void>("/users/1");

// HEAD (verificar existencia de recurso)
const result = await client.head("/users/1");

// OPTIONS (preflight CORS, métodos disponibles)
const result = await client.options("/users");

Todos los métodos aceptan un objeto de configuración opcional como último parámetro:

const result = await client.get<User>("/users/1", {
  timeout: 5000,
  headers: { "X-Custom": "valor" },
  cache: { enabled: true, ttlMs: 60000 },
  retry: { maxAttempts: 3, backoffMs: 1000 },
});

Configuración de Peticiones

Parámetros de Ruta

Nexa soporta interpolación de rutas estilo :param con codificación URI automática:

const result = await client.get<User>("/users/:id/posts/:postId", {
  params: { id: 42, postId: "hola mundo" },
});
// → GET /users/42/posts/hola%20mundo

Parámetros de Query

const result = await client.get<User[]>("/users", {
  query: { page: 1, limit: 20, active: true },
});
// → GET /users?page=1&limit=20&active=true

Serialización Automática del Body

Nexa detecta y serializa automáticamente el cuerpo de la petición:

| Tipo de Body | Serialización | Content-Type | | ------------------ | ------------------ | ----------------------------------- | | object / array | JSON.stringify() | application/json | | string | Se envía tal cual | text/plain | | FormData | Se envía tal cual | Auto (boundary multipart) | | URLSearchParams | Se envía tal cual | application/x-www-form-urlencoded | | Blob | Se envía tal cual | Tipo del Blob | | ArrayBuffer | Se envía tal cual | application/octet-stream | | ReadableStream | Se envía tal cual | application/octet-stream |

Cuando autoFormData está habilitado (por defecto), los objetos que contienen instancias de File o Blob se convierten automáticamente a FormData. Esto es útil para subir archivos sin crear manualmente instancias de FormData.

// JSON (automático)
await client.post("/users", { name: "John" });

// FormData (content-type automático con boundary)
const form = new FormData();
form.append("file", fileBlob);
await client.post("/upload", form);

// URL-encoded
await client.post(
  "/login",
  new URLSearchParams({ user: "john", pass: "secreto" }),
);

Transformación de Requests (transformRequest)

Nexa soporta transformación de requests similar a transformRequest de axios. Puedes proporcionar una función o un array de funciones que transformen el body de la request antes de la serialización. La transformación se aplica después de los interceptores y antes de la serialización.

// Configuración global
const client = createHttpClient({
  transformRequest: [(data, headers) => {
    // Agregar timestamp a todas las requests
    if (data && typeof data === 'object') {
      return { ...data, timestamp: Date.now() };
    }
    return data;
  }]
});

// Configuración por request
await client.post('/api', { foo: 'bar' }, {
  transformRequest: [(data) => ({ ...data, extra: 'value' })]
});

Política de Credenciales

Nexa soporta la opción estándar fetch credentials así como el booleano compatible con axios withCredentials. Esto controla si las cookies y otras credenciales se envían con requests cross-origin.

// Usando credentials estilo fetch
await client.get('/api', { credentials: 'include' });

// Usando withCredentials estilo axios (true = 'include', false = 'same-origin')
await client.post('/api', data, { withCredentials: true });

// Configuración global
const client = createHttpClient({
  credentials: 'same-origin',
  // o
  withCredentials: true, // Equivalente a credentials: 'include'
});

Prioridad: credentials anula withCredentials si ambos se especifican.

Patrón Adapter

Nexa soporta adapters personalizados para mocking, testing, o integración con diferentes entornos (Cloudflare Workers, Node.js, etc.). El adapter tiene la misma firma que la función global fetch.

// Mock adapter para testing
const mockAdapter = async (input: RequestInfo, init?: RequestInit) => {
  return new Response(JSON.stringify({ mock: true }), { status: 200 });
};

// Adapter global
const client = createHttpClient({
  adapter: mockAdapter,
});

// Adapter por request (anula el global)
await client.get('/api', { adapter: mockAdapter });

Los adapters pueden usarse para interceptar requests antes de que lleguen a la red, facilitando testing sin llamadas HTTP reales.

Utilidades de Mocking (estilo axios-mock-adapter)

Para escenarios de testing más sofisticados, Nexa proporciona una utilidad de mocking similar a axios-mock-adapter. Esto te permite configurar respuestas mock para rutas y métodos HTTP específicos.

import { createHttpClient } from '@bereasoftware/nexa';
import { createMockClient } from '@bereasoftware/nexa/testing';

const client = createHttpClient({ baseURL: 'https://api.example.com' });
const mockClient = createMockClient(client);

// Configurar respuestas mock
mockClient.onGet('/users').reply(200, [{ id: 1, name: 'John' }]);
mockClient.onPost('/users').reply(201, { id: 2, name: 'Jane' });
mockClient.onPut('/users/1').reply(200, { id: 1, name: 'Updated' });
mockClient.onDelete('/users/1').reply(204);

// Simulación de error de red
mockClient.onGet('/error').networkError('Network failure');

// Usar el cliente mock-enabled para requests
const result = await mockClient.client.get('/users');
if (result.ok) {
  console.log(result.value.data); // [{ id: 1, name: 'John' }]
}

// Avanzado: responder una vez, luego dejar pasar
mockClient.onGet('/once').replyOnce(200, { data: 'first' });
// Segunda llamada a la misma ruta no coincidirá (a menos que passthrough esté habilitado)

// Avanzado: respuestas basadas en funciones
mockClient.onGet('/dynamic').reply((config) => ({
  status: 200,
  data: { url: config.url, query: config.query },
}));

// Reiniciar rutas mock entre tests
mockClient.reset();

Características principales:

  • API fluida: onGet(url).reply(status, data) similar a axios-mock-adapter
  • Patrones de URL: Soporta coincidencia exacta de string o RegExp
  • Funciones de respuesta: Respuestas dinámicas basadas en configuración de request
  • Límites de llamadas: replyOnce() para mock de una sola vez
  • Errores de red: Simula fallos de red con networkError()
  • Simulación de timeout: Método timeout() para testing de timeouts
  • Modo passthrough: Opcionalmente enviar requests no coincidentes al adapter real
  • Soporte de base URL: Automáticamente elimina baseURL al hacer matching

Opciones para createMockClient:

  • passthrough: Enviar requests no coincidentes al adapter original (predeterminado: false)
  • baseURL: Base URL a eliminar al hacer matching de rutas
  • delay: Delay predeterminado para todas las respuestas (ms)

Configuración de Transporte

Nexa soporta múltiples capas de transporte para diferentes entornos. Por defecto, utiliza la API global fetch (disponible en navegadores y Node.js 18+). Para escenarios avanzados en Node.js, puedes usar los módulos nativos HTTP/1.1 o HTTP/2 con keep-alive, pooling de conexiones y otras optimizaciones específicas de Node.js.

import { createHttpClient } from '@bereasoftware/nexa';

// Usar Node.js HTTP/1.1 con keep-alive y pooling de conexiones
const client = createHttpClient({
  transport: 'node', // 'fetch' (predeterminado), 'node', o 'http2'
  nodeOptions: {
    keepAlive: true,
    maxSockets: 50,
    maxFreeSockets: 10,
    maxRequestsPerSocket: 0, // ilimitado
    timeout: 60000,
  },
});

// Usar HTTP/2 para mejor rendimiento con multiplexación
const http2Client = createHttpClient({
  transport: 'http2',
  nodeOptions: {
    http2Settings: {
      enablePush: false,
    },
  },
});

// Anular transporte por request
await client.get('/api', {
  transport: 'http2',
  nodeOptions: { keepAlive: true },
});

Nota: Los transportes de Node ('node' y 'http2') solo están disponibles en entornos Node.js. En navegadores, automáticamente se usará 'fetch'.

Opciones disponibles en nodeOptions:

  • keepAlive (boolean): Habilitar conexiones keep-alive (predeterminado: true)
  • maxSockets (number): Máximo de sockets por host (predeterminado: 50)
  • maxFreeSockets (number): Máximo de sockets libres a mantener abiertos (predeterminado: 10)
  • maxRequestsPerSocket (number): Máximo de requests por socket (predeterminado: 0 = ilimitado)
  • timeout (number): Timeout del socket en milisegundos (predeterminado: 60000)
  • http2 (boolean): Obsoleto - usar transport: 'http2' en su lugar
  • http2Settings (Record<string, unknown>): Configuración específica de HTTP/2 (solo para transport: 'http2')

Tipos de Respuesta

Controla cómo se parsea el cuerpo de la respuesta:

// Auto-detección basada en el header Content-Type (por defecto)
const result = await client.get("/data", { responseType: "auto" });

// Forzar parseo JSON
const result = await client.get<User>("/user", { responseType: "json" });

// Obtener texto crudo
const result = await client.get<string>("/page", { responseType: "text" });

// Descargar como Blob
const result = await client.get<Blob>("/file.pdf", { responseType: "blob" });

// Obtener ArrayBuffer
const result = await client.get<ArrayBuffer>("/binary", {
  responseType: "arrayBuffer",
});

// Obtener FormData
const result = await client.get<FormData>("/form", {
  responseType: "formData",
});

// Obtener ReadableStream (para streaming manual)
const result = await client.get<ReadableStream>("/stream", {
  responseType: "stream",
});

Lógica de auto-detección: application/json → JSON, text/* → texto, multipart/form-data → FormData, application/octet-stream / image/* / audio/* / video/* → Blob, fallback → intenta JSON luego texto.

Timeout

// Timeout por petición
const result = await client.get("/endpoint-lento", { timeout: 5000 });

// El timeout produce un código de error específico
if (!result.ok && result.error.code === "TIMEOUT") {
  console.log("La petición expiró");

Nexa también soporta timeouts diferenciados para las fases de conexión y respuesta, yendo más allá del timeout único de axios/fetch. Puedes especificar timeouts como un objeto:

// Timeouts diferenciados
await client.get('/api', {
  timeout: {
    connection: 3000,  // 3 segundos para establecer conexión
    response: 10000,   // 10 segundos para recibir respuesta completa
    total: 15000       // Timeout total opcional (anula connection/response)
  }
});

// Compatibilidad hacia atrás: número aún funciona (timeout total)
await client.get('/api', { timeout: 5000 });

Códigos de error: TIMEOUT para timeouts de conexión/total, RESPONSE_TIMEOUT para timeouts de fase de respuesta.

Logging de Debug

Nexa provee logging de debug similar a axios para desarrollo. Habilítalo globalmente o por petición:

// Logging de debug global
const client = createHttpClient({
  debug: true, // logs básicos
  // debug: 'verbose', // logs detallados
});

// Override por petición
await client.get('/api', { debug: 'verbose' });

Los logs incluyen método/URL de la petición, status de respuesta, duración, errores, reintentos y cache hits. Con modo verbose, también ves requests transformados, salidas de interceptores y datos de respuesta.

Logger Personalizado

Puedes proporcionar una función de logger personalizada para redirigir logs a tu sistema de logging preferido:

const client = createHttpClient({
  debug: true,
  logger: (message, data) => {
    // Enviar a tu servicio de logging
    myLogger.info(message, data);
  },
});

// Override de logger a nivel de petición
await client.post('/api', data, { 
  debug: true,
  logger: (msg, data) => console.warn(msg, data)
});

El logger recibe dos argumentos: el mensaje de log (con prefijo [Nexa HTTP]) y un objeto de datos opcional.


Estrategias de Reintentos

Configuración Inline

Reintento simple con backoff exponencial:

const result = await client.get("/api-inestable", {
  retry: { maxAttempts: 3, backoffMs: 1000 },
});
// Reintenta hasta 3 veces con backoff exponencial + jitter

AggressiveRetry

Reintenta todos los errores hasta el máximo de intentos con delay mínimo:

import { AggressiveRetry } from "@bereasoftware/nexa";

const result = await client.get("/api", {
  retry: new AggressiveRetry(5), // 5 intentos, delay de 50ms * intento
});

ConservativeRetry

Solo reintenta en códigos HTTP específicos (408, 429, 500, 502, 503, 504) y timeouts:

import { ConservativeRetry } from "@bereasoftware/nexa";

const result = await client.get("/api", {
  retry: new ConservativeRetry(3), // 3 intentos, backoff exponencial con tope de 10s
});

CircuitBreakerRetry

Patrón fail-fast — deja de reintentar después de un umbral de fallos:

import { CircuitBreakerRetry } from "@bereasoftware/nexa";

const breaker = new CircuitBreakerRetry(
  3, // maxAttempts por petición
  5, // failureThreshold antes de abrir el circuito
  60000, // resetTimeMs — el circuito se resetea después de 60s
);

const result = await client.get("/api", { retry: breaker });

// Resetear el circuito manualmente
breaker.reset();

Estrategia Personalizada

Implementa la interfaz RetryStrategy:

import type { RetryStrategy, HttpErrorDetails } from "@bereasoftware/nexa";

const reintentoCustom: RetryStrategy = {
  shouldRetry(attempt: number, error: HttpErrorDetails): boolean {
    // Solo reintentar errores de red y 503
    return (
      (error.code === "NETWORK_ERROR" || error.status === 503) && attempt < 5
    );
  },
  delayMs(attempt: number): number {
    // Backoff lineal: 500ms, 1000ms, 1500ms...
    return attempt * 500;
  },
};

const result = await client.get("/api", { retry: reintentoCustom });

Interceptores

Interceptores de Petición

Modifica las peticiones antes de que se envíen:

client.addRequestInterceptor({
  onRequest(request) {
    // Agregar token de auth a cada petición
    return {
      ...request,
      headers: {
        ...request.headers,
        Authorization: `Bearer ${getToken()}`,
      },
    };
  },
});

Interceptores de Respuesta

Transforma respuestas o maneja errores globalmente:

client.addResponseInterceptor({
  onResponse(response) {
    // Loguear todas las respuestas exitosas
    console.log(
      `[${response.status}] ${response.request.url} (${response.duration}ms)`,
    );
    return response;
  },
  onError(error) {
    // Manejar 401 globalmente
    if (error.status === 401) {
      redirigirAlLogin();
    }
    return error;
  },
});

Disposal de Interceptores

Tanto addRequestInterceptor como addResponseInterceptor retornan una función disposer para remover el interceptor:

const dispose = client.addRequestInterceptor({
  onRequest(request) {
    return { ...request, headers: { ...request.headers, "X-Temp": "valor" } };
  },
});

// Después: remover el interceptor
dispose();

// O limpiar todos los interceptores
client.clearInterceptors();

Caché

Caché en memoria integrado con soporte TTL. Solo cachea peticiones GET:

const result = await client.get<User>("/users/1", {
  cache: { enabled: true, ttlMs: 60000 }, // Cachear por 1 minuto
});

// La segunda llamada retorna la respuesta cacheada instantáneamente
const cached = await client.get<User>("/users/1", {
  cache: { enabled: true, ttlMs: 60000 },
});

Implementación de caché personalizada:

import type { CacheStrategy } from "@bereasoftware/nexa";

const redisCache: CacheStrategy = {
  get(key: string) {
    return redis.get(key);
  },
  set(key: string, value: unknown, ttlMs?: number) {
    redis.set(key, value, "PX", ttlMs);
  },
  has(key: string) {
    return redis.exists(key);
  },
  clear() {
    redis.flushdb();
  },
};

const client = createHttpClient({ cacheStrategy: redisCache });

Hooks de Ciclo de Vida

Monitorea el ciclo de vida completo de la petición:

const result = await client.get<User>("/users/1", {
  hooks: {
    onStart(request) {
      console.log("Iniciando petición a:", request.url);
    },
    onSuccess(response) {
      console.log("Éxito:", response.status, `(${response.duration}ms)`);
    },
    onError(error) {
      console.error("Falló:", error.message, error.code);
    },
    onRetry(attempt, error) {
      console.warn(`Reintento #${attempt}:`, error.message);
    },
    onFinally() {
      console.log("Petición completada (éxito o fallo)");
    },
  },
});

Los hooks por defecto se pueden configurar a nivel de cliente:

const client = createHttpClient({
  defaultHooks: {
    onError: (error) => reportarASentry(error),
    onFinally: () => ocultarSpinnerDeCarga(),
  },
});

Progreso de Descarga

Trackea el progreso de descarga con un callback:

const result = await client.get<Blob>("/archivo-grande.zip", {
  responseType: "blob",
  onDownloadProgress(event) {
    console.log(
      `Descargado: ${event.percent}% (${event.loaded}/${event.total} bytes)`,
    );
    actualizarBarraDeProgreso(event.percent);
  },
});

La interfaz ProgressEvent:

interface ProgressEvent {
  loaded: number; // Bytes descargados hasta ahora
  total: number; // Total de bytes (del header Content-Length)
  percent: number; // 0-100
}

Limitación de Peticiones Concurrentes

Limita el número de peticiones simultáneas para no sobrecargar el servidor:

const client = createHttpClient({
  baseURL: "https://api.example.com",
  maxConcurrent: 3, // Solo 3 peticiones a la vez
});

// Lanza 10 peticiones — solo 3 corren simultáneamente, el resto se encola automáticamente
const results = await Promise.all(urls.map((url) => client.get(url)));

Consultar el estado de la cola:

console.log(client.queueStats);
// { active: 3, pending: 7 }

console.log(client.activeRequests);
// 3

Extensión de Cliente

Crea clientes hijo que heredan configuración e interceptores:

const clienteBase = createHttpClient({
  baseURL: "https://api.example.com",
  defaultHeaders: { "X-App": "MiApp" },
});

clienteBase.addRequestInterceptor({
  onRequest(req) {
    return {
      ...req,
      headers: { ...req.headers, Authorization: "Bearer token" },
    };
  },
});

// El hijo hereda baseURL, headers, interceptores — y agrega header de versión
const clienteV2 = clienteBase.extend({
  defaultHeaders: { "X-API-Version": "2" },
});

// clienteV2 tiene headers: { 'X-App': 'MiApp', 'X-API-Version': '2' }
// clienteV2 también tiene el interceptor de auth del clienteBase

Auto-Paginación

Itera a través de APIs paginadas con generadores asíncronos:

interface PageResponse {
  items: User[];
  nextCursor: string | null;
}

for await (const users of client.paginate<PageResponse>("/users", {
  getItems: (data) => data.items,
  getNextPage: (data, config) =>
    data.nextCursor
      ? {
          ...config,
          query: { ...(config.query as any), cursor: data.nextCursor },
        }
      : null,
})) {
  console.log("Página con", users.length, "usuarios");
  // Procesar cada página de usuarios
}

La paginación se detiene automáticamente cuando getNextPage retorna null o una petición falla.


Polling Inteligente

Consulta un endpoint repetidamente hasta que se cumpla una condición:

interface Job {
  id: string;
  status: "pending" | "running" | "completed" | "failed";
  result?: string;
}

const result = await client.poll<Job>("/jobs/abc123", {
  intervalMs: 2000, // Consultar cada 2 segundos
  maxAttempts: 30, // Rendirse después de 30 intentos (0 = ilimitado)
  until: (job) => job.status === "completed" || job.status === "failed",
  onPoll: (job, attempt) => {
    console.log(`Intento ${attempt}: ${job.status}`);
  },
});

if (result.ok) {
  console.log("Job terminado:", result.value.data.result);
} else if (result.error.code === "POLL_EXHAUSTED") {
  console.log("El polling expiró");
}

Cancelación de Peticiones

Cancela todas las peticiones pendientes:

// Iniciar varias peticiones
const promise1 = client.get("/lento-1");
const promise2 = client.get("/lento-2");

// Cancelar todo
client.cancelAll();

// O usar AbortSignal para peticiones individuales
const controller = new AbortController();
const result = client.get("/data", { signal: controller.signal });
controller.abort();

Validadores

Valida los datos de respuesta antes de que lleguen a tu código:

import {
  createSchemaValidator,
  createRequiredFieldsValidator,
  validatorIsArray,
  validatorIsObject,
} from "@bereasoftware/nexa";

// Validador de esquema
const userValidator = createSchemaValidator<User>({
  id: (v) => typeof v === "number",
  name: (v) => typeof v === "string" && (v as string).length > 0,
  email: (v) => typeof v === "string" && (v as string).includes("@"),
});

const result = await client.get<User>("/users/1", {
  validate: userValidator,
});
// Si la validación falla: result.error.code === 'VALIDATION_ERROR'

// Validador de campos requeridos
const result = await client.get("/api/data", {
  validate: createRequiredFieldsValidator(["id", "name", "createdAt"]),
});

// Validador de array
const result = await client.get("/users", {
  validate: validatorIsArray,
});

// Validador de objeto
const result = await client.get("/user/1", {
  validate: validatorIsObject,
});

Transformadores

Transforma los datos de respuesta después del parseo:

import {
  transformSnakeToCamel,
  transformCamelToSnake,
  transformFlatten,
  createProjectionTransformer,
  createWrapperTransformer,
} from "@bereasoftware/nexa";

// Convertir respuestas snake_case de la API a camelCase
const result = await client.get("/users/1", {
  transform: transformSnakeToCamel,
});
// { first_name: 'John' } → { firstName: 'John' }

// Convertir camelCase a snake_case (para enviar datos)
const result = await client.get("/data", {
  transform: transformCamelToSnake,
});

// Aplanar objetos anidados
const result = await client.get("/nested", {
  transform: transformFlatten,
});
// { user: { name: 'John' } } → { 'user.name': 'John' }

// Seleccionar campos específicos
const result = await client.get("/users/1", {
  transform: createProjectionTransformer(["id", "name"]),
});
// Solo mantiene { id, name } de la respuesta

// Envolver datos en un contenedor
const result = await client.get("/items", {
  transform: createWrapperTransformer("data"),
});
// [1, 2, 3] → { data: [1, 2, 3] }

Pipeline de Middleware

Pipeline de middleware estilo Express/Koa para procesamiento avanzado de peticiones:

import {
  createPipeline,
  createCacheMiddleware,
  createDedupeMiddleware,
  createStreamingMiddleware,
  type HttpContext,
  type Middleware,
} from "@bereasoftware/nexa";

// Crear middleware personalizado
const loggingMiddleware: Middleware<HttpContext> = async (ctx, next) => {
  console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
  const start = Date.now();
  await next();
  console.log(`← ${ctx.response.status} (${Date.now() - start}ms)`);
};

const authMiddleware: Middleware<HttpContext> = async (ctx, next) => {
  ctx.request.headers["Authorization"] = `Bearer ${getToken()}`;
  await next();
};

// Construir y ejecutar pipeline
const pipeline = createPipeline([
  loggingMiddleware,
  authMiddleware,
  createCacheMiddleware({ ttlMs: 30000 }),
  createDedupeMiddleware(),
]);

const ctx: HttpContext = {
  request: { method: "GET", url: "/users", headers: {} },
  response: { status: 0, headers: {} },
  state: {},
};

await pipeline(ctx);

Middleware pre-construidos:

| Middleware | Descripción | | ------------------------------------- | ---------------------------------------------- | | createCacheMiddleware(options?) | Cachea respuestas GET con TTL | | cacheMiddleware | Caché pre-configurado (60s TTL) | | createDedupeMiddleware(options?) | Deduplica peticiones concurrentes idénticas | | dedupeMiddleware | Deduplicación pre-configurada para GET | | createStreamingMiddleware(options?) | Maneja respuestas streaming con progreso | | streamingMiddleware | Streaming pre-configurado con salida a consola |


Comunicación en Tiempo Real

Nexa incluye clientes WebSocket y SSE (Server-Sent Events) con reconexión automática, heartbeat y soporte de plugins:

import { createWebSocketClient, createSSEClient } from "@bereasoftware/nexa";

// WebSocket con reconexión automática y heartbeat
const wsClient = createWebSocketClient("wss://echo.websocket.org", {
  reconnect: { enabled: true, maxAttempts: 10 },
  heartbeat: { interval: 30000 },
});

await wsClient.connect();
wsClient.sendJson({ type: "message", data: "Hello" });
wsClient.onMessage((event) => console.log("Mensaje:", event.data));

// SSE para streams de eventos
const sseClient = createSSEClient("https://stream.example.com/events");
await sseClient.connect();
sseClient.onEvent("update", (data) => console.log("Actualización:", data));

Características:

  • ✅ Reconexión automática con backoff exponencial
  • ✅ Heartbeat/ping-pong para mantener conexiones activas
  • ✅ Soporte para JSON y mensajes binarios
  • ✅ Estadísticas de conexión y métricas
  • ✅ Integración con sistema de plugins de Nexa

Adaptadores Multi-Entorno

Nexa soporta múltiples entornos de ejecución con adaptadores optimizados:

import { createHttpClient } from "@bereasoftware/nexa";

// Usa fetch global (navegador, Node.js 18+, Deno, Bun, Cloudflare Workers)
const client = createHttpClient({ transport: "fetch" });

// Usa módulos nativos de Node.js (HTTP/1.1 con connection pooling)
const nodeClient = createHttpClient({ 
  transport: "node",
  nodeOptions: { keepAlive: true }
});

// Usa HTTP/2 de Node.js con session pooling
const http2Client = createHttpClient({ transport: "http2" });

// Entornos específicos (detección automática)
const denoClient = createHttpClient({ transport: "deno" });
const bunClient = createHttpClient({ transport: "bun" });
const cloudflareClient = createHttpClient({ transport: "cloudflare" });

Entornos soportados:

  • Navegador: fetch API global
  • Node.js: http/https (HTTP/1.1), http2 (HTTP/2 con pooling)
  • Deno: fetch nativo de Deno
  • Bun: fetch optimizado de Bun
  • Cloudflare Workers: fetch con bindings de Cloudflare

Plugins Avanzados

Nexa incluye plugins listos para producción:

import { 
  RateLimitPlugin, 
  CircuitBreakerPlugin,
  LoggerPlugin,
  MetricsPlugin 
} from "@bereasoftware/nexa";

const manager = new PluginManager();
manager
  .register(new LoggerPlugin())
  .register(new MetricsPlugin())
  .register(new RateLimitPlugin({ maxRequests: 100, windowMs: 60000 }))
  .register(new CircuitBreakerPlugin({ 
    failureThreshold: 5, 
    resetTimeout: 30000 
  }));

Plugins disponibles:

  • RateLimitPlugin: Limita peticiones por ventana de tiempo
  • CircuitBreakerPlugin: Previene fallos en cascada
  • LoggerPlugin: Logs detallados de peticiones/respuestas
  • MetricsPlugin: Métricas de rendimiento
  • CachePlugin: Cache automático de respuestas
  • DedupePlugin: Deduplicación de peticiones concurrentes

Sistema de Plugins

Extiende Nexa con una arquitectura de plugins:

import {
  PluginManager,
  LoggerPlugin,
  MetricsPlugin,
  CachePlugin,
  DedupePlugin,
} from "@bereasoftware/nexa";

const manager = new PluginManager();

// Registrar plugins
manager
  .register(LoggerPlugin)
  .register(new MetricsPlugin())
  .register(new CachePlugin(30000)) // 30s TTL
  .register(new DedupePlugin());

// Escuchar eventos
manager.on("request:start", (url) => console.log("Petición a:", url));
manager.on("request:success", (url, status) =>
  console.log("Éxito:", url, status),
);

// Obtener métricas
const metrics = (
  manager.getPlugins().find((p) => p.name === "metrics") as MetricsPlugin
).getMetrics();
console.log(metrics); // { requests: 10, errors: 1, totalTime: 4200, avgTime: 420 }

Crear plugins personalizados:

import type { Plugin } from "@bereasoftware/nexa";

const rateLimitPlugin: Plugin = {
  name: "rate-limit",
  setup(client) {
    // Agregar middleware de rate limiting, event listeners, etc.
    const manager = client as PluginManager;
    manager.on("request:start", () => {
      // Lógica de rate limiting personalizada
    });
  },
};

manager.register(rateLimitPlugin);

Dev Overlay

Herramienta visual de desarrollo integrada para depurar y monitorear requests HTTP en tiempo real.

Estado de API: disponible públicamente y pensada para flujos de desarrollo en navegador. No está orientada a entornos SSR o Node sin DOM.

import { createHttpClient, createDevOverlay } from "@bereasoftware/nexa";

// Crear el Dev Overlay - aparece automáticamente
const { tracker, ui } = createDevOverlay({
  position: "bottom-right", // top-right, top-left, bottom-left
  theme: "dark",
  maxHistory: 200,
});

// Pasar el tracker al cliente HTTP
const client = createHttpClient({
  baseURL: "https://api.example.com",
  devTracker: tracker,
});

// ¡Listo! Todas las requests aparecerán en el overlay
const result = await client.get("/users");

Características

  • Toggle con teclado — Presiona Ctrl+Shift+N o Cmd+Shift+N para mostrar/ocultar
  • Lista de requests — Muestra método, status, URL, duración y badges (cache, retries)
  • Métricas en tiempo real — Total requests, promedio, throughput, éxitos y fallos
  • Búsqueda y filtro — Filtra por URL, método o status (Ctrl+F)
  • Detalle de request — Click en cualquier request para ver headers, body y timing

Persistencia de configuración

El Dev Overlay persiste la configuración del usuario (posición, tema, tamaño del botón flotante, etc.) en localStorage bajo la clave nexa.devOverlay.config.

Al llamar a createDevOverlay() la configuración que se aplica sigue el orden de preferencia (los valores persistidos en localStorage tienen prioridad por diseño):

  • Valores por defecto ← (defaultDevOverlayConfig)
  • Opciones pasadas a createDevOverlay() (argumento config)
  • Valores persistidos en localStorage (sobrescriben los anteriores)

En entornos sin DOM (por ejemplo, durante tests en Node) el componente visual no se monta, pero el tracker sigue inicializándose y respeta la carga y persistencia de la configuración.

  • Retry rápido — Reejecuta la URL seleccionada con fetch para una verificación rápida
  • Keyboard shortcutsCtrl+Shift+N / Cmd+Shift+N (toggle), Escape (cerrar), Ctrl+F / Cmd+F (buscar)

API

createDevOverlay(config?: DevOverlayConfig): { tracker: RequestTracker; ui: DevOverlayUI; config: Required<DevOverlayConfig> }

Opciones de configuración:

| Opción | Tipo | Por defecto | Descripción | |--------|------|-------------|-------------| | enabled | boolean | true | Flag de configuración almacenado por el tracker para habilitar o deshabilitar el overlay desde tu flujo | | position | 'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' | 'bottom-right' | Posición del overlay | | theme | 'dark' \| 'light' | 'dark' | Tema visual tipado; la UI actual está optimizada para el tema oscuro | | maxHistory | number | 500 |Máximo de requests en historial | | keyboardShortcut | string | 'ctrl+shift+n' | Atajo de teclado | | devOnly | boolean | true | Cuando es true, la UI solo se monta en entornos de desarrollo (NODE_ENV=development o localhost) | | floatingButtonSize | number | 64 | Tamaño (ancho & alto) del botón flotante en píxeles | | floatingButtonOffset | number | 24 | Offset desde los bordes de la ventana en píxeles | | floatingButtonTheme | 'inherit' \| 'dark' \| 'light' | 'inherit' | Tema del botón flotante; 'inherit' usa el theme del overlay |

Notas de comportamiento:

  • createDevOverlay() es singleton. Si lo llamas varias veces, reutiliza la misma instancia.
  • El overlay solo monta UI cuando hay DOM disponible. En SSR o Node puedes importar el módulo sin romper el proceso, pero no se renderiza panel.
  • theme está tipado como 'dark' | 'light', pero la UI actual está diseñada principalmente para tema oscuro.
  • El botón de retry del panel usa fetch() directo para revalidación rápida; no reaplica automáticamente interceptores ni configuración avanzada del HttpClient.
  • destroyDevOverlay() limpia la instancia singleton, desmonta el panel y remueve listeners globales registrados por el overlay.

Métodos del UI:

ui.show()   // Mostrar overlay
ui.hide()   // Ocultar overlay
ui.toggle() // Alternar visibilidad
ui.destroy() // Desmontar el panel del DOM

tracker.clear() // Limpiar historial
tracker.getHistory() // Obtener todos los requests
tracker.getMetrics() // Obtener métricas
tracker.getConfig() // Obtener configuración resuelta
tracker.onChange((request) => {}) // Escuchar nuevos requests

Métodos del módulo:

getDevOverlay() // Obtener { tracker, ui } actuales o null si no existen
destroyDevOverlay() // Destruir la instancia singleton actual

Cuándo usar cada export:

  • createDevOverlay() crea o reutiliza el overlay y devuelve el tracker que debes pasar a createHttpClient({ devTracker }).
  • getDevOverlay() te permite consultar la instancia actual sin crear una nueva.
  • destroyDevOverlay() sirve para cerrar completamente la herramienta en hot reloads, tests o limpieza manual.
  • RequestTracker puede usarse por separado si quieres recolectar historial y métricas sin montar la UI.

Estructuras expuestas:

type TrackedRequest = {
  id: string;
  method: string;
  url: string;
  status?: number;
  duration: number;
  timestamp: number;
  cached: boolean;
  ok: boolean;
  code?: string;
  headers: Record<string, string>;
  body?: unknown;
  responseHeaders?: Record<string, string>;
  retryCount: number;
};

type DevMetrics = {
  totalRequests: number;
  successfulRequests: number;
  failedRequests: number;
  cachedRequests: number;
  avgDuration: number;
  maxDuration: number;
  minDuration: number;
  requestsPerSecond: number;
  slowestRequests: TrackedRequest[];
};

Streaming

Maneja archivos grandes y respuestas streaming:

import { handleStream, streamToFile } from "@bereasoftware/nexa";

// Procesamiento de stream manual
const response = await fetch("https://example.com/archivo-grande");
const data = await handleStream(response, {
  onChunk(chunk) {
    console.log("Chunk recibido:", chunk.length, "bytes");
  },
  onProgress(loaded, total) {
    console.log(`Progreso: ${Math.round((loaded / total) * 100)}%`);
  },
});

// Descargar stream a archivo
const response = await fetch("https://example.com/datos.csv");
await streamToFile(response, "salida.csv");
// Funciona tanto en Node.js (fs.writeFile) como en navegador (descarga Blob)

Generics Tipados

Utilidades avanzadas type-safe para diseño de clientes API:

Cliente API Tipado

import { createTypedApiClient, type ApiEndpoint } from "@bereasoftware/nexa";

// Define tu esquema API con tipos completos
interface UserApi {
  getUser: ApiEndpoint<void, User>;
  createUser: ApiEndpoint<CreateUserDto, User>;
  listUsers: ApiEndpoint<void, User[]>;
}

const api = createTypedApiClient<UserApi>({
  getUser: { method: "GET", path: "/users/1", response: {} as User },
  createUser: { method: "POST", path: "/users", response: {} as User },
  listUsers: { method: "GET", path: "/users", response: [] as User[] },
});

// Petición totalmente tipada — conoce los tipos de entrada y salida
const user = await api.request(client, "getUser");
const newUser = await api.request(client, "createUser", {
  name: "Ella",
  email: "[email protected]",
});

Tipos Branded

import {
  createUrl,
  createApiUrl,
  type Url,
  type ApiUrl,
  type FileUrl,
} from "@bereasoftware/nexa";

// Las URLs branded previenen mezclar diferentes tipos de URL en tiempo de compilación
const apiUrl: ApiUrl = createApiUrl("/users/1");
const genericUrl: Url = createUrl("https://example.com");

Type Guards

import { createTypeGuard } from "@bereasoftware/nexa";

interface User {
  id: number;
  name: string;
}

const asegurarUser = createTypeGuard<User>(
  (value): value is User =>
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value,
);

const user = asegurarUser(datosDesconocidos); // lanza TypeError si es inválido

Patrón Observable

import { TypedObservable } from "@bereasoftware/nexa";

const stream = new TypedObservable<User>();

const sub = stream.subscribe(
  (user) => console.log("Usuario:", user.name),
  (err) => console.error("Error:", err),
  () => console.log("Completado"),
);

// Operadores encadenables
const nombres = stream.filter((user) => user.active).map((user) => user.name);

stream.next({ id: 1, name: "John", active: true });
stream.complete();

sub.unsubscribe();

Defer (Promesa Lazy)

import { Defer } from "@bereasoftware/nexa";

const deferred = new Defer<string>();

// Pasar la promesa a los consumidores
algunConsumidor(deferred.promise_());

// Resolver después
deferred.resolve("listo");
// O rechazar
deferred.reject(new Error("falló"));

Manejo de Errores

Códigos de Error

| Código | Descripción | | ------------------ | ----------------------------------------------------------------- | | HTTP_ERROR | Status HTTP no-2xx (configurable vía validateStatus) | | TIMEOUT | La petición excedió la duración del timeout | | NETWORK_ERROR | Fallo de red (DNS, conexión rechazada, etc.) | | ABORTED | La petición fue cancelada manualmente | | VALIDATION_ERROR | Los datos de respuesta no pasaron la validación | | POLL_EXHAUSTED | El polling alcanzó el máximo de intentos sin cumplir la condición | | MAX_RETRIES | Todos los intentos de reintento agotados | | UNKNOWN_ERROR | Error no clasificado |

Contexto de Error Enriquecido

HttpErrorDetails incluye campos opcionales para request, response y config, pero en esta versión no todos los caminos de error los rellenan de forma consistente. Úsalos como metadatos opcionales, no como garantía fuerte.

const result = await client.get('/api');
if (!result.ok) {
  const { request, response, config } = result.error;
  console.log('URL de request fallida:', request?.url);
  console.log('Status de respuesta:', response?.status);
  console.log('Timeout de configuración:', config?.timeout);
}

Si necesitas ese contexto en todos los errores, conviene complementarlo temporalmente con hooks o interceptores propios.

Clase HttpError

import { HttpError, isHttpError } from "@bereasoftware/nexa";

// Verificar si un error es un HttpError
if (isHttpError(error)) {
  console.log(error.status); // Código de status HTTP
  console.log(error.code); // Cadena de código de error
}

Patrón: Manejar Diferentes Tipos de Error

const result = await client.get<User>("/users/1");

if (!result.ok) {
  switch (result.error.code) {
    case "TIMEOUT":
      mostrarNotificacion("La petición expiró, intenta de nuevo");
      break;
    case "NETWORK_ERROR":
      mostrarNotificacion("Sin conexión a internet");
      break;
    case "HTTP_ERROR":
      if (result.error.status === 404)
        mostrarNotificacion("Usuario no encontrado");
      else if (result.error.status === 403) redirigirAlLogin();
      break;
    case "VALIDATION_ERROR":
      reportarBug("La API retornó un formato de datos inesperado");
      break;
    default:
      reportarError(result.error);
  }
}

Referencia de API

createHttpClient(config?: HttpClientConfig): HttpClient

Función factory para crear una nueva instancia del cliente HTTP.

Métodos de HttpClient

| Método | Firma | Descripción | | ------------------------ | ------------------------------------------------------------------------------------- | ---------------------------------------- | | request | <T>(config: HttpRequestConfig) → Promise<Result<HttpResponse<T>, HttpErrorDetails>> | Método core de petición | | get | <T>(url, config?) → Promise<Result<...>> | Petición GET | | post | <T>(url, body?, config?) → Promise<Result<...>> | Petición POST | | put | <T>(url, body?, config?) → Promise<Result<...>> | Petición PUT | | patch | <T>(url, body?, config?) → Promise<Result<...>> | Petición PATCH | | delete | <T>(url, config?) → Promise<Result<...>> | Petición DELETE | | head | (url, config?) → Promise<Result<...>> | Petición HEAD | | options | (url, config?) → Promise<Result<...>> | Petición OPTIONS | | extend | (overrides?: HttpClientConfig) → HttpClient | Crear cliente hijo | | paginate | <T>(url, options, config?) → AsyncGenerator<T[]> | Auto-paginación | | poll | <T>(url, options, config?) → Promise<Result<...>> | Polling inteligente | | addRequestInterceptor | (interceptor) → Disposer | Agregar interceptor de petición | | addResponseInterceptor | (interceptor) → Disposer | Agregar interceptor de respuesta | | clearInterceptors | () → void | Remover todos los interceptores | | cancelAll | () → void | Cancelar todas las peticiones pendientes | | activeRequests | number (getter) | Número de peticiones en vuelo | | queueStats | { active, pending } (getter) | Estadísticas de la cola |

Tipos

| Tipo | Descripción | | --------------------- | --------------------------------------------------------------------------------- | | Result<T, E> | Unión discriminada éxito/fallo | | HttpRequest | Configuración de petición (url, method, headers, body, query, params) | | HttpResponse<T> | Respuesta con data, status, headers, duration | | HttpErrorDetails | Error con message, code, status, originalError | | HttpRequestConfig | Config completa de petición (extiende HttpRequest + retry, cache, hooks, etc.) | | HttpClientConfig | Configuración a nivel de cliente | | RequestInterceptor | Interceptar peticiones salientes | | ResponseInterceptor | Interceptar respuestas entrantes | | RetryStrategy | Interfaz de lógica de reintento personalizada | | CacheStrategy | Interfaz de implementación de caché personalizada | | Validator | Interfaz de validación de respuesta | | Transformer | Interfaz de transformación de respuesta | | PaginateOptions<T> | Configuración de paginación | | PollOptions<T> | Configuración de polling | | RequestHooks<T> | Callbacks de hooks de ciclo de vida | | ProgressEvent | Datos de progreso de descarga | | ResponseType | 'json' \| 'text' \| 'blob' \| 'arrayBuffer' \| 'formData' \| 'stream' \| 'auto' | | Disposer | Función que remueve un interceptor |


Formatos de Build

Nexa se distribuye en múltiples formatos de módulo:

| Formato | Archivo | Caso de Uso | | --------- | ----------------------- | ----------------------------------------- | | ESM | dist/nexa.es.js | Bundlers modernos (Vite, Rollup, esbuild) | | CJS | dist/nexa.cjs.js | Node.js require() | | UMD | dist/nexa.umd.js | Universal (AMD, CJS, global) | | IIFE | dist/nexa.iife.js | Tags de script (<script>) | | Types | dist/types/index.d.ts | Declaraciones de tipos TypeScript |


Desarrollo

Pruebas

205 tests en total: 88 tests de HTTP Client + 69 tests de utilities + 24 tests de mocking + 4 tests de adaptadores Node.js + 20 tests adicionales

# Ejecutar todos los tests
npm test

# Watch mode
npm run test:watch

# Test coverage
npm run test:coverage

# UI de tests
npm run test:ui

Los tests usan Vitest (globals mode) con BDD style (describe/it/expect).

Linting y Formato

# Verificar tipo
npm run lint

# Corregir errores de estilo
npm run lint:fix

# Formatear código
npm run format

# Verificar formato
npm run format:check

Build

# Generar distribución
npm run build

Configuración de build:

  • Formatos: ES, CommonJS, UMD, IIFE
  • Minificación: OXC (ultra-rápido)
  • Type Definitions: Bundled en /dist/types
  • Tree-shakeable: Solo importa lo que usas
  • Externas: fs (Node.js only para streamToFile)

Output:

dist/
  ├── nexa.es.js        (24.9 KB, gzip: 7.53 KB)
  ├── nexa.cjs.js       (19.9 KB, gzip: 6.68 KB)
  ├── nexa.umd.js       (19.8 KB, gzip: 6.75 KB)
  ├── nexa.iife.js      (19.6 KB, gzip: 6.68 KB)
  └── types/            (Tipo definitivo .d.ts)

Benchmark

# Ejecutar benchmark de performance
npm run benchmark

Ejemplos

Ver la carpeta examples/ para ejemplos de uso con diferentes frameworks:

  • react-example.ts — Uso con React
  • vue-example.ts — Uso con Vue
  • svelte-example.ts — Uso con Svelte
  • node-example.ts — Uso con Node.js

Cobertura de Tests

Cobertura General: 71.4% — sólida cobertura de tests unitarios con mocking HTTP

| Componente | Cobertura | Detalles | | ----------- | ---------- | --------------------------------- | | HTTP Client | 66.7% | 67.3% ramas, 67.0% funciones | | Types | 100% | Cobertura perfecta de tipos | | Testing | 93.7% | 85.1% ramas, 95.8% funciones | | Utils | 71.8% | 66.7% ramas, 81.1% funciones |

HTTP Client (test/http-client.test.ts) — 88 tests:

  • ✓ Métodos core: create, GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS (7 tests)
  • ✓ Estrategias de reintentos & timeouts (3 tests)
  • ✓ Interceptores & disposal (5 tests)
  • ✓ Caché & validación (4 tests)
  • ✓ Type safety & extensiones (3 tests)
  • ✓ Paginación & polling (5 tests)
  • ✓ Manejo de tipos de respuesta: 8+ tipos + auto-detección (13 tests)
  • ✓ Detección de content-type binario: image/, audio/, video/*, octet-stream (5 tests)
  • ✓ Serialización de body: JSON, null, strings, Blob, URLSearchParams, ArrayBuffer, TypedArray, FormData, ReadableStream (7 tests)
  • ✓ Normalización de errores: TimeoutError, AbortError, TypeError, unknown, NETWORK_ERROR (5+ tests)
  • ✓ Gestión de peticiones: activeRequests, cancelAll, clearCache (2 tests)
  • ✓ Verificación de exports: todas las 8 categorías de exports (8 tests)
  • ✓ Integración de plugins: LoggerPlugin, MetricsPlugin event handlers (7 tests)
  • ✓ Configuración avanzada: null body, direct Blob, abort messages (5+ tests)

Utilities (test/utils.test.ts) — 69 tests:

  • ✓ Validadores: schema, required fields, type checks (4 tests)
  • ✓ Transformadores: snake↔camel case, flatten, projection, wrapper (5 tests)
  • ✓ Estrategias de Reintentos: Aggressive, Conservative, Circuit Breaker (10 tests)
  • ✓ Timeout & Retry: withTimeout, retry function (6 tests)
  • ✓ Caché: CacheStore CRUD, TTL expiry (5 tests)
  • ✓ Deduplicación: RequestDeduplicator sharing, cleanup (3 tests)
  • ✓ Pipeline de Middleware: ordering, next() guard, legacy pipeline (3 tests)
  • ✓ Cache Middleware: GET caching, POST bypass (2 tests)
  • ✓ Dedup Middleware: GET dedup, POST bypass (2 tests)
  • ✓ Generics Tipados: TypedResponse, TypedObservable (map/filter), Defer, type guards, branded types (9 tests)
  • ✓ Plugins: PluginManager, LoggerPlugin, MetricsPlugin, CachePlugin, DedupePlugin (5 tests)

Limitaciones de Cobertura & Techo Realista

La cobertura de tests unitarios se estabiliza alrededor del 75-80% debido a limitaciones inherentes del mocking:

¿Por qué no 95%?

  • **Características de stream