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

@jaac/fetch.proxy

v0.0.4

Published

Cliente HTTP elegante/minimalista para gestionar múltiples servicios API

Readme

🚀 @jaac/fetch.proxy

Cliente HTTP elegante/minimalista para gestionar múltiples servicios API

@jaac/fetch.proxy es una biblioteca minimalista que proporciona una forma elegante de interactuar con múltiples endpoints API REST sin repetir configuraciones o código boilerplate. Cuenta con un peso de ~4.0kb.

         ┌──────────┐       ┌──────────┐
         │  API 1   │       │  API 2   │
         └────┬─────┘       └────┬─────┘
              │                  │
              │                  │
         ┌────▼──────────────────▼─────┐
         │                             │
         │      @jaac/fetch.proxy      │
         │                             │
         └───────────────┬─────────────┘
                         │
                         │
                    ┌────▼────┐
                    │   App   │
                    └─────────┘

📦 Instalación

# Usando yarn
yarn add @jaac/fetch.proxy
# Usando pnpm
pnpm add @jaac/fetch.proxy
# Usando npm
npm install @jaac/fetch.proxy

🔍 Características

  • TypeScript
  • ✅ Soporte para todos los métodos HTTP (GET, POST, PUT, PATCH, DELETE)
  • ✅ Registro de múltiples servicios API con diferente URL base
  • ✅ Manejo automático de headers y opciones por servicio
  • ✅ Interceptores para request y response
  • ✅ Capacidad para abortar peticiones
  • ✅ API chainable con sintaxis limpia
  • ✅ Gestión de errores HTTP integrada
  • ✅ Patrón singleton para reutilización eficiente

🚦 Ejemplos

Configuración en tu proyecto

// En un archivo infrastructure/fetch.proxy.ts
import {fetchProxy} from '@jaac/fetch.proxy';
import {STRAPI_URL, CHAT_URL} from '@/infrastructure/utility/endpoints.utility';

// Registrar servicios
fetchProxy.register('strapi', STRAPI_URL, {
  'Authorization': 'Bearer YOUR_API_TOKEN'
});

fetchProxy.register('chat', CHAT_URL);

// Exportar para uso en toda la aplicación
export const api = fetchProxy;

Realizar Peticiones

// GET request
const posts = await api.strapi.get('/posts').json();

// POST request con datos
const newPost = await api.strapi.post('/posts', {
  title: 'Teletubbies',
  content: 'La hora de los teletubbies'
}).json();

// PUT request para actualizar
await api.strapi.put(`/posts/${postId}`, {
  title: 'Enjambre de abejas'
});

// DELETE request
await api.strapi.delete(`/posts/${postId}`);

// Petición al servicio de chat
await api.chat.post('/messages', {
  text: 'Hola Mundo'
});

Usando Interceptores

Los interceptores te permiten modificar las solicitudes antes de enviarlas y transformar las respuestas antes de que sean procesadas.

Interceptores de Solicitud (Request)

// Añadir headers personalizados a todas las solicitudes
api.strapi.request.use(async (request: RequestInit) => {
  const headers = new Headers(request.headers);
  headers.append("x-ray-id", "1234567890");
  headers.append("x-code-id", "987654321");
  return {
    ...request,
    headers
  };
});

Interceptores de Respuesta (Response)

// Transformar todas las respuestas para un formato estándar
api.strapi.response.use(async (response: Response) => {
  const data = await response.json();
  if (response.ok) {
    return {
      message: "Respuesta correcta",
      code: "A0001",
      data: {...data},
    };
  }
  return {
    message: "Respuesta incorrecta",
    code: "A0002",
    data: {},
  };
});

Ejemplo Completo con Interceptores

import {fetchProxy} from '@jaac/fetch.proxy';

fetchProxy.register("demo", "https://httpbin.org", {
  "x-master-id": "1234567890",
});
const api = fetchProxy;

// Interceptor de solicitud
api.demo.request.use(async (request: RequestInit) => {
  const headers = new Headers(request.headers);
  headers.append("x-ray-id", "1234567890");
  headers.append("x-code-id", "987654321");
  return {
    ...request,
    headers
  };
});

// Interceptor de respuesta
api.demo.response.use(async (response: Response) => {
  const data = await response.json();
  if (response.ok)
    return {
      message: "Respuesta correcta",
      code: "A0001",
      data: {...data},
    };
  return {
    message: "Respuesta incorrecta",
    code: "A0002",
    data: {},
  };
});

async function main() {
  const responseNotFound = await api.demo.get("/headers-x");
  const responseData = await responseNotFound.json();
  console.log("Respuesta incorrecta:", responseData);
  
  const responseFound = await api.demo.get("/headers");
  const responseDataFound = await responseFound.json();
  console.log("Respuesta correcta:", responseDataFound);
}

main().catch(console.error);

Abortar Peticiones

// Iniciar una petición que podría tardar
const request = api.strapi.get('/large-dataset');

// En algún otro lugar (por ejemplo, cuando el usuario navega a otra página)
setTimeout(() => {
  request.abort();
}, 3000); // Abortará después de 3 segundos

try {
  const data = await request.json();
  console.log('Datos recibidos:', data);
} catch (error) {
  console.log('La petición fue abortada o falló:', error);
}

Hook useDebounced

  • La biblioteca es ideal para crear hooks personalizados que optimicen las llamadas a la API.
  • A continuación, un hook useDebounced que funciona con cualquier servicio registrado:
import {useEffect, useRef, useState} from 'react';
import {ApiPromise, ApiService} from '@jaac/fetch.proxy';

type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';

/**
 * Hook para crear peticiones HTTP con debounce
 * @param service - El servicio API a utilizar (api.users, api.strapi, etc.)
 * @param method - Verbo HTTP a usar ('get', 'post', etc.)
 * @param delay - Tiempo de debounce en milisegundos
 */
export const useDebounced = <T = any>(
  service: ApiService,
  method: HttpMethod = 'get',
  delay: number = 300
) => {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<Error | null>(null);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
  const requestRef = useRef<ApiPromise | null>(null);

  // Limpiar recursos al desmontar
  useEffect(() => {
    return () => {
      if (timeoutRef.current) clearTimeout(timeoutRef.current);
      if (requestRef.current) requestRef.current.abort();
    };
  }, []);

  /**
   * Ejecuta la petición con debounce
   * @param endpoint - URL del endpoint
   * @param payload - Datos para enviar (solo para POST, PUT, PATCH)
   */
  const execute = async (endpoint: string, payload?: any) => {
    // Cancelar petición anterior si existe
    if (requestRef.current) {
      requestRef.current.abort();
      requestRef.current = null;
    }
    // Cancelar timeout anterior
    if (timeoutRef.current) clearTimeout(timeoutRef.current);
    setLoading(true);
    setError(null);
    return new Promise<T>((resolve, reject) => {
      timeoutRef.current = setTimeout(async () => {
        try {
          // Crear la petición apropiada según el verbo HTTP
          let request: ApiPromise;
          switch (method) {
            case 'post':
            case 'put':
            case 'patch':
              request = service[method](endpoint, payload);
              break;
            default:
              request = service[method](endpoint);
          }
          // Guardar referencia para posible cancelación
          requestRef.current = request;
          // Esperar y procesar respuesta
          const result = await request.json<T>();
          setData(result);
          setLoading(false);
          resolve(result);
        } catch (err) {
          // Ignorar errores de cancelación
          if ((err as Error).name !== 'AbortError') {
            setError(err as Error);
            setLoading(false);
            reject(err);
          }
        }
      }, delay);
    });
  };

  return {execute, data, loading, error};
};

Ejemplos de uso de useDebounced

1. Búsqueda simple con GET

export const SearchComponent = () => {
  const {execute, data, loading} = useDebounced(api.strapi);

  const handleSearch = (query: string) => {
    execute(`/products?search=${encodeURIComponent(query)}`);
  };
  // handleSearch se puede llamar directamente desde un onChange de un input o similar
};

2. Envío de formulario con POST

export const FormComponent = () => {
  const {execute, loading, error} = useDebounced(api.users, 'post', 500);

  const handleSubmit = (formData: any) => {
    execute('/register', formData);
  };
  // Previene múltiples envíos accidentales
};

3. Diferentes servicios en el mismo componente

export const DashboardComponent = () => {
  const productSearch = useDebounced(api.products);
  const userSearch = useDebounced(api.users);
  // Ambos con debounce independiente
};

Este hook de ejemplo proporciona:

  • Reutilización entre diferentes servicios API
  • Manejo automático de cancelaciones
  • Estado de carga y errores integrado
  • Tipado con TypeScript
  • Flexibilidad para los verbos HTTP (GET, POST, PUT, PATCH, DELETE)

🔧 API Reference

fetchProxy.register(name, baseUrl, headers?, options?)

Registra un nuevo servicio API.

  • name: Nombre del servicio para referenciar
  • baseUrl: URL base para todas las peticiones de este servicio
  • headers (opcional): Headers por defecto para todas las peticiones
  • options (opcional): Opciones de fetch por defecto

fetchProxy.unregister(name)

Elimina un servicio API registrado previamente.

  • name: Nombre del servicio a eliminar

Interceptores

Cada servicio registrado tiene acceso a los siguientes interceptores:

  • .request.use(interceptor): Añade un interceptor que modifica la solicitud antes de enviarla
  • .response.use(interceptor): Añade un interceptor que transforma la respuesta antes de procesarla

Métodos por servicio

Cada servicio registrado tiene los siguientes métodos con tipado añadido:

  • .get<T>(endpoint, options?): Realizar petición GET
  • .post<T>(endpoint, data?, options?): Realizar petición POST
  • .put<T>(endpoint, data?, options?): Realizar petición PUT
  • .patch<T>(endpoint, data?, options?): Realizar petición PATCH
  • .delete<T>(endpoint, options?): Realizar petición DELETE

Cada método devuelve una ApiPromise<T> con:

  • .abort(): Método para cancelar la petición
  • .json(): Método para obtener directamente los datos JSON con tipo T

💡 Comparación

Sin @jaac/fetch.proxy:                  Con @jaac/fetch.proxy:
┌─────────────────────────┐             ┌─────────────────────────┐
│ // En cada componente:  │             │ // Solo una vez:        │
│ const BASE_URL =        │             │ fetchProxy.register(    │
│   'https://api.co/v1';  │             │   'search',             │
│                         │             │   'https://api.com/v1', │
│ // Para cada petición:  │             │   { 'Auth': 'token' }   │
│ const res = await fetch(│             │ );                      │
│   `${BASE_URL}/users`,  │             │                         │
│   {                     │             │ // Para cada petición:  │
│     headers: {          │             │ const users =           │
│       'Auth': 'token'   │             │   await api             │
│     }                   │             │     .search             │
│   }                     │             │     .get('/users')      │
│ );                      │             │     .json();            │
│ const users =           │             └─────────────────────────┘
│   await res.json();     │             
└─────────────────────────┘             

📋 Instalación y Requisitos

Requisitos

  • TypeScript >= 4.5
  • ES6 o superior
  • Muchas ganas de seguir haciendo código más limpio y elegante

🤝 Contribución

  • Las contribuciones son bienvenidas.
  • Si encuentras un error o tienes una mejora, no dudes en abrir un issue o enviar un pull request en el repositorio GitHub.

📄 Licencia

MIT © jaac