@jysperu/helper-fetcher
v1.1.2
Published
Librería moderna TypeScript para peticiones HTTP con autenticación (Bearer/Token), headers personalizados, timeout configurable y detección automática de URL base
Downloads
54
Maintainers
Readme
@jysperu/helper-fetcher
Librería moderna TypeScript para peticiones HTTP con autenticación (Bearer/Token), headers personalizados, timeout configurable y detección automática de URL base.
🚀 Características
- 🔧 TypeScript completamente tipado con genéricos
- 🔑 Autenticación integrada - Bearer Token y headers personalizados
- ⚡ API moderna basada en fetch con timeout
- 🛡️ Manejo de errores robusto con soporte para respuestas no-JSON
- 🎯 Métodos HTTP completos: GET, POST, PUT, DELETE, PATCH
- 🆕 Métodos directos -
fetcher.get(),fetcher.post(), etc. (v1.0.2) - 📤 Upload/Download con progreso -
fetcher.upload(),fetcher.download()(v1.1.0) - 📦 Mínimas dependencias - Solo
form-datapara Node.js - 🔄 Cache control granular y personalizable
- 🌐 Detección automática del tag
<base>HTML o fallback inteligente - ⚡ Singleton pattern con instancia global
- 🔍 Respuestas flexibles - Maneja JSON, texto plano y HTML (v1.0.5)
- 🌍 Multiplataforma - Node.js y navegador con mismo código
📦 Instalación
npm install @jysperu/helper-fetcher⚡ Inicio Rápido
import { fetcher } from "@jysperu/helper-fetcher";
// Configuración global con autenticación
fetcher.configure({
base: "https://api.ejemplo.com",
timeout: 5000,
bearerToken: "tu-jwt-token", // Automáticamente añade Authorization: Bearer
headers: {
"X-Custom-Header": "valor-personalizado",
},
});
import { api, api_get, api_post } from "@jysperu/helper-fetcher";
// GET request con tipado
interface User {
id: number;
name: string;
email: string;
}
const usuarios = await api_get<User[]>("/usuarios");
if (usuarios.success) {
console.log("Usuarios:", usuarios.data);
} else {
console.error("Error:", usuarios.log);
}
// POST request
const nuevoUsuario = await api_post<User>("/usuarios", {
name: "Juan Pérez",
email: "[email protected]",
});
// Usando la función principal con cache control
const result = await api<User[]>("/usuarios", {}, false, "GET"); // Sin cache
// 🆕 Nuevos métodos HTTP directos (v1.0.2)
const users = await fetcher.get<User[]>("/usuarios"); // GET
const newUser = await fetcher.post<User>("/usuarios", { name: "Ana" }); // POST
const updated = await fetcher.put<User>("/usuarios/1", { name: "Ana García" }); // PUT
await fetcher.delete("/usuarios/1"); // DELETE
const patched = await fetcher.patch<User>("/usuarios/1", { email: "[email protected]" }); // PATCH
// Método genérico con detección automática
const data = await fetcher.request<User[]>("/usuarios", "GET", { active: true });📚 API Reference
🏭 Clase Fetcher
La clase principal que maneja todas las peticiones HTTP.
class Fetcher {
static instance: Fetcher; // Instancia singleton
constructor(config?: Partial<RequestConfig>);
configure(config: Partial<RequestConfig>): void;
buildUrl(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null): string;
fetch<T>(url: string, options: RequestInit, timeout?: number): Promise<ApiResponse<T>>;
fetchWithParams<T>(
endpoint: string,
method?: string,
params?: ApiParams,
cachecode?: string | number | boolean | null,
timeout?: number
): Promise<ApiResponse<T>>;
fetchWithBody<T>(endpoint: string, method?: string, body?: ApiBody, timeout?: number): Promise<ApiResponse<T>>;
// Métodos HTTP directos (nuevos en v1.0.2)
request<T>(
endpoint: string,
method?: HttpMethod,
data?: ApiParams | ApiBody,
cachecode?: string | number | boolean | null
): Promise<ApiResponse<T>>;
get<T>(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null): Promise<ApiResponse<T>>;
post<T>(endpoint: string, body?: ApiBody): Promise<ApiResponse<T>>;
put<T>(endpoint: string, body?: ApiBody): Promise<ApiResponse<T>>;
delete<T>(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null): Promise<ApiResponse<T>>;
patch<T>(endpoint: string, body?: ApiBody): Promise<ApiResponse<T>>;
}🌐 detectBaseUrl
Detecta automáticamente la URL base desde el tag <base> del documento con fallback inteligente.
function detectBaseUrl(): string;Comportamiento avanzado:
- ✅ Detecta tag
<base href="...">en el documento - ✅ Fallback a
window.location.origin - ✅ Compatible con SSR (retorna cadena vacía)
- ✅ Normalización automática (elimina barras finales)
- ✅ Validación de URLs válidas
⚙️ fetcher.configure
Configura los ajustes globales con soporte para autenticación.
interface RequestConfig {
timeout?: number; // Timeout en ms (debe ser entero positivo)
base?: string; // URL base para peticiones
headers?: Record<string, string>; // Headers HTTP por defecto
bearerToken?: string; // Token Bearer para Authorization header
headerToken?: string; // Token personalizado para header Token
}
fetcher.configure(config: Partial<RequestConfig>): void;Ejemplos de configuración:
// Autenticación Bearer
fetcher.configure({
bearerToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
base: "https://api.miapp.com",
});
// Automáticamente añade: Authorization: Bearer <token>
// Headers personalizados
fetcher.configure({
headers: {
"X-API-Key": "mi-api-key",
"Content-Type": "application/json",
},
headerToken: "custom-token-value",
});
// Añade: Token: custom-token-valueValidaciones automáticas:
- ✅ Timeout debe ser entero positivo
- ✅ Headers debe ser objeto válido
- ✅ Tokens deben ser strings
🆕 Métodos HTTP Directos (Nuevo en v1.0.2)
La clase Fetcher ahora incluye métodos HTTP directos para un código más limpio y expresivo:
fetcher.get() - Peticiones GET
function fetcher.get<T>(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null, timeout?: number): Promise<ApiResponse<T>>;// GET básico
const users = await fetcher.get<User[]>("/users");
// GET con parámetros
const activeUsers = await fetcher.get<User[]>("/users", { active: true, limit: 10 });
// GET con cache
const cachedData = await fetcher.get("/config", {}, true);
// GET con timeout personalizado (5 segundos)
const quickData = await fetcher.get<Data>("/quick-data", {}, false, 5000);fetcher.post() - Peticiones POST
function fetcher.post<T>(endpoint: string, body?: ApiBody, timeout?: number): Promise<ApiResponse<T>>;// Crear nuevo usuario
const newUser = await fetcher.post<User>("/users", {
name: "Juan Pérez",
email: "[email protected]",
});
// POST con timeout personalizado (10 segundos para operaciones pesadas)
const uploadResult = await fetcher.post<Result>("/upload", largeFileData, 10000);fetcher.put() - Peticiones PUT
function fetcher.put<T>(endpoint: string, body?: ApiBody, timeout?: number): Promise<ApiResponse<T>>;// Actualizar usuario completo
const updatedUser = await fetcher.put<User>("/users/123", {
name: "Juan Carlos Pérez",
email: "[email protected]",
});
// PUT con timeout largo para operaciones de sincronización (30 segundos)
const syncResult = await fetcher.put<SyncStatus>("/sync-data", bulkData, 30000);fetcher.delete() - Peticiones DELETE
function fetcher.delete<T>(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null, timeout?: number): Promise<ApiResponse<T>>;// Eliminar usuario por ID
await fetcher.delete("/users/123");
// Eliminar con parámetros
await fetcher.delete("/users", { inactive: true });
// DELETE con timeout personalizado para limpieza de datos (20 segundos)
await fetcher.delete("/cleanup", {}, null, 20000);fetcher.patch() - Peticiones PATCH
function fetcher.patch<T>(endpoint: string, body?: ApiBody, timeout?: number): Promise<ApiResponse<T>>;// Actualizar parcialmente
const patched = await fetcher.patch<User>("/users/123", {
email: "[email protected]",
});
// PATCH con timeout corto para actualizaciones críticas (2 segundos)
const urgentUpdate = await fetcher.patch<Status>("/status", { active: false }, 2000);fetcher.request() - Método Genérico
function fetcher.request<T>(endpoint: string, method?: HttpMethod, data?: ApiParams | ApiBody, cachecode?: string | number | boolean | null, timeout?: number): Promise<ApiResponse<T>>;// Detección automática: GET usa parámetros, POST usa cuerpo
const users = await fetcher.request<User[]>("/users", "GET", { active: true });
const newUser = await fetcher.request<User>("/users", "POST", { name: "Ana" });
// Con timeout personalizado según el tipo de operación
const quickSearch = await fetcher.request<SearchResult>("/search", "GET", { q: "test" }, null, 3000);
const dataImport = await fetcher.request<ImportResult>("/import", "POST", csvData, null, 60000);🌟 Ventajas de los Métodos Directos
- ✅ Más expresivo:
fetcher.get()vsfetchWithParams() - ✅ Menos imports: Todo en la instancia principal
- ✅ Mejor autocompletado: IntelliSense mejorado en IDEs
- ✅ Compatibilidad total: Los métodos anteriores siguen funcionando
- ✅ Timeout granular: Control individual del timeout por petición
⏱️ Control de Timeout Personalizado (v1.0.4)
Todos los métodos HTTP ahora soportan un parámetro timeout opcional para control granular:
// Timeouts según el tipo de operación
const quickData = await fetcher.get("/live-data", {}, false, 2000); // 2s - datos en vivo
const searchResults = await fetcher.get("/search", { q: "query" }, false, 5000); // 5s - búsqueda
const fileUpload = await fetcher.post("/upload", fileData, 30000); // 30s - subida de archivos
const bulkOperation = await fetcher.put("/bulk-update", data, 60000); // 60s - operaciones masivas
const cleanup = await fetcher.delete("/cleanup", {}, null, 45000); // 45s - limpieza de datos
const statusUpdate = await fetcher.patch("/status", { active: true }, 1000); // 1s - actualización crítica
// Si no se especifica timeout, usa la configuración global
const defaultTimeout = await fetcher.get("/data"); // Usa fetcher timeout configuradoCaracterísticas del timeout:
- ✅ Fallback inteligente: Usa configuración global si no se especifica
- ✅ Validación automática: Debe ser un número entero positivo
- ✅ Control granular: Cada petición puede tener su propio timeout
- ✅ AbortController: Cancelación nativa de peticiones
- ✅ Sin afectar global: No modifica la configuración general
🔄 Migración Fácil
// Antes
const users = await fetchWithParams("/users", "GET", { active: true });
const newUser = await fetchWithBody("/users", "POST", { name: "Juan" });
// Ahora (más limpio)
const users = await fetcher.get("/users", { active: true });
const newUser = await fetcher.post("/users", { name: "Juan" });📤 Upload y Download con Progreso (v1.1.0)
fetcher.upload() - Subir archivos
function fetcher.upload<T>(
endpoint: string,
file: string | File | Blob,
fileparam?: string,
onprogress?: (event: ProgressEvent) => void,
body?: ApiBody,
timeout?: number,
method?: string
): Promise<ApiResponse<T>>;Navegador:
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const result = await fetcher.upload(
"/api/upload",
file,
"avatar",
(e) => {
console.log(`Progreso: ${e.percent.toFixed(2)}%`);
console.log(`${e.loaded} / ${e.total} bytes`);
},
{ userId: 123, category: "profile" }
);Node.js:
const result = await fetcher.upload(
"/api/upload",
"./documents/invoice.pdf", // Ruta del archivo
"document",
(e) => console.log(`${e.percent.toFixed(2)}%`),
{ documentType: "invoice" }
);fetcher.download() - Descargar archivos
function fetcher.download<T>(
endpoint: string,
filename?: string,
onprogress?: (event: ProgressEvent) => void,
params?: ApiParams,
cachecode?: string | number | boolean | null,
timeout?: number,
method?: string
): Promise<ApiResponse<T>>;Navegador (descarga automática):
const result = await fetcher.download("/api/files/report.pdf", "informe-mensual.pdf", (e) => {
const percent = e.percent.toFixed(2);
console.log(`Descargando: ${percent}%`);
});
// El archivo se descarga automáticamenteNode.js (guarda en disco):
const result = await fetcher.download("/api/files/backup.zip", "./downloads/backup.zip", (e) => console.log(`${e.loaded} / ${e.total} bytes`));
// Archivo guardado en ./downloads/backup.zipappendObjectToForm() - Helper para FormData
import { appendObjectToForm } from "@jysperu/helper-fetcher";
const form = new FormData();
appendObjectToForm(form, {
name: "Juan Pérez",
age: 30,
avatar: fileInput.files[0], // File
metadata: { role: "admin", active: true }, // Se serializa a JSON
optional: undefined, // Se omite
});
await fetcher.post("/api/profile", form);📚 API Reference Clásica
Todos los métodos anteriores siguen siendo completamente compatibles:
🌐 Funciones de Petición
api_get - GET con parámetros
function api_get<T = unknown>(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null): Promise<ApiResponse<T>>;api_post - POST con cuerpo
function api_post<T = unknown>(endpoint: string, body?: ApiBody): Promise<ApiResponse<T>>;api_request - Método genérico
function api_request<T = unknown>(
endpoint: string,
method?: HttpMethod,
data?: ApiParams | ApiBody,
cacheCode?: string | number | boolean | null
): Promise<ApiResponse<T>>;api - Función principal
function api<T = unknown>(
endpoint: string,
bodyOrParams?: ApiParams | ApiBody,
cacheCode?: string | number | boolean | null,
method?: HttpMethod
): Promise<ApiResponse<T>>;🔧 Funciones de utilidad
buildUrl - Constructor de URLs
fetcher.buildUrl(endpoint: string, params?: ApiParams, cachecode?: string | number | boolean | null): string;Maneja múltiples tipos de endpoints:
// URL completa - se usa directamente
fetcher.buildUrl("https://otra-api.com/datos");
// Ruta absoluta - concatena con base
fetcher.buildUrl("/api/usuarios"); // → https://mibase.com/api/usuarios
// Ruta relativa - agrega / automáticamente
fetcher.buildUrl("usuarios"); // → https://mibase.com/usuariosfetch - Fetch con timeout
function fetch<T = unknown>(url: string, options: RequestInit, timeout?: number): Promise<ApiResponse<T>>;processFetchJsonResponse - Normalizador
function processFetchJsonResponse<T = unknown>(json: any): ApiResponse<T>;Características:
- ✅ Convierte
status: "success"→success: true - ✅ Normaliza mensajes de error y éxito
- ✅ Limpia propiedades redundantes
- ✅ Validación de JSON inválido
- ✅ Manejo robusto de tipos
📋 Tipos TypeScript
ApiResponse
interface ApiResponse<T = unknown> {
success: boolean; // true si la operación fue exitosa
log: string; // Mensaje de log/error
data?: T; // Datos de respuesta (opcional)
response?: string; // Texto original si la respuesta no es JSON (v1.0.5)
[key: string]: unknown;
}Manejo flexible de respuestas (v1.0.5):
// Respuesta JSON estándar
const result = await api_get<User[]>("/users");
// { success: true, log: "Operación exitosa", data: [...] }
// Respuesta de texto plano
const textResult = await api_get("/health-check");
// { success: true, log: "OK", response: "OK" }
// Error HTTP con JSON
const error = await api_post("/invalid-endpoint", {});
// { success: false, log: "Error message from server", data: {...} }
// Error HTTP con texto plano
const htmlError = await api_get("/not-found");
// { success: false, log: "HTTP 404: Not Found", response: "<!DOCTYPE html>..." }Características del manejo de respuestas:
- ✅ Detecta automáticamente JSON vs texto plano
- ✅ Preserva contenido original en
responsesi no es JSON - ✅
successsiempre reflejaresponse.ok(códigos HTTP 2xx) - ✅ Maneja errores con y sin cuerpo JSON
- ✅ Compatible con APIs que retornan HTML o texto
ApiParams
interface ApiParams {
[key: string]: string | number | boolean;
}ApiBody
interface ApiBody {
[key: string]: unknown;
}HttpMethod
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";🌐 Detección Automática de Base URL
La librería detecta automáticamente la URL base de tu aplicación:
Tag Base HTML
<!DOCTYPE html>
<html>
<head>
<!-- La librería detecta automáticamente este tag -->
<base href="https://api.miapp.com/v2/" />
</head>
<body>
<!-- Tu aplicación -->
</body>
</html>import { detectBaseUrl, api_get } from "@jysperu/helper-fetcher";
// Detecta automáticamente: "https://api.miapp.com/v2"
console.log(detectBaseUrl());
// Todas las peticiones usan esta base automáticamente
const datos = await api_get("/usuarios"); // → https://api.miapp.com/v2/usuariosSin Tag Base
Si no hay tag <base>, usa window.location.origin:
// En https://miapp.com/dashboard
console.log(detectBaseUrl()); // "https://miapp.com"
const datos = await api_get("/api/datos"); // → https://miapp.com/api/datos🌐 Compatibilidad
- ✅ Todos los navegadores modernos (con soporte para fetch)
- ✅ Compatible con frameworks (React, Vue, Angular, Svelte)
- ✅ SSR friendly
- ✅ TypeScript nativo
- ✅ ES Modules y CommonJS
🔗 Enlaces y Recursos
- 📘 Repositorio: GitLab - helper-fetcher
- 🐛 Issues: Reportar problemas
- 📦 npm: @jysperu/helper-fetcher
- 🏢 JYS Perú: www.jys.pe
📄 Licencia
MIT License - Consulta el archivo LICENSE para detalles completos.
MIT License - Copyright (c) 2025 JYS PerúDesarrollado con ❤️ por JYS Perú
