@bereasoftware/nexa 1.0.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,8 +6,8 @@
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <a href="#tests"><img src="https://img.shields.io/badge/Tests-157_pasando-brightgreen?style=for-the-badge" alt="Tests" /></a>
10
- <a href="#test-coverage"><img src="https://img.shields.io/badge/Coverage-75.73%25-orange?style=for-the-badge" alt="Coverage" /></a>
9
+ <a href="#tests"><img src="https://img.shields.io/badge/Tests-233_pasando-brightgreen?style=for-the-badge" alt="Tests" /></a>
10
+ <a href="#test-coverage"><img src="https://img.shields.io/badge/Coverage-72%25-orange?style=for-the-badge" alt="Coverage" /></a>
11
11
  <a href="https://www.npmjs.com/package/@bereasoftware/nexa"><img src="https://img.shields.io/npm/v/@bereasoftware/nexa?style=for-the-badge" alt="NPM Version" /></a>
12
12
  <a href="https://bundlephobia.com/package/@bereasoftware/nexa"><img src="https://img.shields.io/bundlephobia/minzip/@bereasoftware/nexa?label=Bundle&style=for-the-badge" alt="Bundle Size" /></a>
13
13
  <a href="https://www.npmjs.com/package/@bereasoftware/nexa"><img src="https://img.shields.io/npm/dm/@bereasoftware/nexa?style=for-the-badge" alt="NPM Downloads" /></a>
@@ -48,6 +48,17 @@
48
48
  | Validadores y transformadores | ❌ | ❌ | ✅ |
49
49
  | Tracking de duración de respuesta | ❌ | ❌ | ✅ |
50
50
  | Detección inteligente de tipo de respuesta | ❌ | ✅ | ✅ |
51
+ | Transformación de requests (transformRequest) | ❌ | ✅ | ✅ |
52
+ | Política de credenciales | ✅ | ✅ | ✅ |
53
+ | Patrón adapter (mocking/testing) | ❌ | ✅ | ✅ |
54
+ | Conversión automática a FormData | ❌ | ❌ | ✅ |
55
+ | Contexto de error enriquecido (request/response/config) | ❌ | ✅ | ✅ |
56
+ | Timeouts diferenciados (conexión vs respuesta) | ❌ | ❌ | ✅ |
57
+ | Logging de debug (modo desarrollo) | ❌ | ❌ | ✅ |
58
+ | WebSocket/SSE con reconexión automática | ❌ | ❌ | ✅ |
59
+ | Rate limiting integrado | ❌ | ❌ | ✅ |
60
+ | Circuit breaker para fallos en cascada | ❌ | ❌ | ✅ |
61
+ | Adaptadores multi-entorno (Node, Deno, Bun, Cloudflare) | ❌ | ❌ | ✅ |
51
62
  | Tree-shakeable | ✅ | ❌ | ✅ |
52
63
 
53
64
  ---
@@ -64,6 +75,10 @@
64
75
  - [Parámetros de Ruta](#parámetros-de-ruta)
65
76
  - [Parámetros de Query](#parámetros-de-query)
66
77
  - [Serialización Automática del Body](#serialización-automática-del-body)
78
+ - [Transformación de Requests (transformRequest)](#transformación-de-requests-transformrequest)
79
+ - [Política de Credenciales](#política-de-credenciales)
80
+ - [Patrón Adapter](#patrón-adapter)
81
+ - [Configuración de Transporte](#configuración-de-transporte)
67
82
  - [Tipos de Respuesta](#tipos-de-respuesta)
68
83
  - [Timeout](#timeout)
69
84
  - [Estrategias de Reintentos](#estrategias-de-reintentos)
@@ -91,9 +106,12 @@
91
106
  - [Streaming](#streaming)
92
107
  - [Generics Tipados](#generics-tipados)
93
108
  - [Manejo de Errores](#manejo-de-errores)
109
+ - [Contexto de Error Enriquecido](#contexto-de-error-enriquecido)
94
110
  - [Referencia de API](#referencia-de-api)
95
111
  - [Formatos de Build](#formatos-de-build)
96
112
  - [Desarrollo](#desarrollo)
113
+ - [Contribución](#contribución)
114
+ - [Seguridad](#seguridad)
97
115
  - [Licencia](#licencia)
98
116
 
99
117
  ---
@@ -207,6 +225,8 @@ const client = createHttpClient({
207
225
  | `maxConcurrent` | `number` | `0` (ilimitado) | Máximo de peticiones concurrentes |
208
226
  | `defaultResponseType` | `ResponseType` | `'auto'` | Estrategia de parseo de respuesta por defecto |
209
227
  | `defaultHooks` | `RequestHooks` | `{}` | Hooks de ciclo de vida por defecto para todas las peticiones |
228
+ | `debug` | `boolean \| 'verbose'` | `undefined` | Habilita logging de debug para requests/responses. `true` para logs básicos, `'verbose'` para logs detallados. |
229
+ | `logger` | `(message: string, data?: unknown) => void` | `undefined` | Función de logging personalizada. Si se proporciona, reemplaza el console.log por defecto con logging personalizado. |
210
230
 
211
231
  ---
212
232
 
@@ -289,6 +309,8 @@ Nexa detecta y serializa automáticamente el cuerpo de la petición:
289
309
  | `ArrayBuffer` | Se envía tal cual | `application/octet-stream` |
290
310
  | `ReadableStream` | Se envía tal cual | `application/octet-stream` |
291
311
 
312
+ 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`.
313
+
292
314
  ```typescript
293
315
  // JSON (automático)
294
316
  await client.post("/users", { name: "John" });
@@ -305,6 +327,172 @@ await client.post(
305
327
  );
306
328
  ```
307
329
 
330
+ ### Transformación de Requests (transformRequest)
331
+
332
+ 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.
333
+
334
+ ```typescript
335
+ // Configuración global
336
+ const client = createHttpClient({
337
+ transformRequest: [(data, headers) => {
338
+ // Agregar timestamp a todas las requests
339
+ if (data && typeof data === 'object') {
340
+ return { ...data, timestamp: Date.now() };
341
+ }
342
+ return data;
343
+ }]
344
+ });
345
+
346
+ // Configuración por request
347
+ await client.post('/api', { foo: 'bar' }, {
348
+ transformRequest: [(data) => ({ ...data, extra: 'value' })]
349
+ });
350
+ ```
351
+
352
+ ### Política de Credenciales
353
+
354
+ 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.
355
+
356
+ ```typescript
357
+ // Usando credentials estilo fetch
358
+ await client.get('/api', { credentials: 'include' });
359
+
360
+ // Usando withCredentials estilo axios (true = 'include', false = 'same-origin')
361
+ await client.post('/api', data, { withCredentials: true });
362
+
363
+ // Configuración global
364
+ const client = createHttpClient({
365
+ credentials: 'same-origin', // Por defecto: 'omit'
366
+ // o
367
+ withCredentials: true, // Equivalente a credentials: 'include'
368
+ });
369
+ ```
370
+
371
+ Prioridad: `credentials` anula `withCredentials` si ambos se especifican.
372
+
373
+ ### Patrón Adapter
374
+
375
+ 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`.
376
+
377
+ ```typescript
378
+ // Mock adapter para testing
379
+ const mockAdapter = async (input: RequestInfo, init?: RequestInit) => {
380
+ return new Response(JSON.stringify({ mock: true }), { status: 200 });
381
+ };
382
+
383
+ // Adapter global
384
+ const client = createHttpClient({
385
+ adapter: mockAdapter,
386
+ });
387
+
388
+ // Adapter por request (anula el global)
389
+ await client.get('/api', { adapter: mockAdapter });
390
+ ```
391
+
392
+ Los adapters pueden usarse para interceptar requests antes de que lleguen a la red, facilitando testing sin llamadas HTTP reales.
393
+
394
+ ### Utilidades de Mocking (estilo axios-mock-adapter)
395
+
396
+ 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.
397
+
398
+ ```typescript
399
+ import { createHttpClient } from '@bereasoftware/nexa';
400
+ import { createMockClient } from '@bereasoftware/nexa/testing';
401
+
402
+ const client = createHttpClient({ baseURL: 'https://api.example.com' });
403
+ const mockClient = createMockClient(client);
404
+
405
+ // Configurar respuestas mock
406
+ mockClient.onGet('/users').reply(200, [{ id: 1, name: 'John' }]);
407
+ mockClient.onPost('/users').reply(201, { id: 2, name: 'Jane' });
408
+ mockClient.onPut('/users/1').reply(200, { id: 1, name: 'Updated' });
409
+ mockClient.onDelete('/users/1').reply(204);
410
+
411
+ // Simulación de error de red
412
+ mockClient.onGet('/error').networkError('Network failure');
413
+
414
+ // Usar el cliente mock-enabled para requests
415
+ const result = await mockClient.client.get('/users');
416
+ if (result.ok) {
417
+ console.log(result.value.data); // [{ id: 1, name: 'John' }]
418
+ }
419
+
420
+ // Avanzado: responder una vez, luego dejar pasar
421
+ mockClient.onGet('/once').replyOnce(200, { data: 'first' });
422
+ // Segunda llamada a la misma ruta no coincidirá (a menos que passthrough esté habilitado)
423
+
424
+ // Avanzado: respuestas basadas en funciones
425
+ mockClient.onGet('/dynamic').reply((config) => ({
426
+ status: 200,
427
+ data: { url: config.url, query: config.query },
428
+ }));
429
+
430
+ // Reiniciar rutas mock entre tests
431
+ mockClient.reset();
432
+ ```
433
+
434
+ **Características principales:**
435
+ - **API fluida**: `onGet(url).reply(status, data)` similar a axios-mock-adapter
436
+ - **Patrones de URL**: Soporta coincidencia exacta de string o RegExp
437
+ - **Funciones de respuesta**: Respuestas dinámicas basadas en configuración de request
438
+ - **Límites de llamadas**: `replyOnce()` para mock de una sola vez
439
+ - **Errores de red**: Simula fallos de red con `networkError()`
440
+ - **Simulación de timeout**: Método `timeout()` para testing de timeouts
441
+ - **Modo passthrough**: Opcionalmente enviar requests no coincidentes al adapter real
442
+ - **Soporte de base URL**: Automáticamente elimina baseURL al hacer matching
443
+
444
+ **Opciones** para `createMockClient`:
445
+ - `passthrough`: Enviar requests no coincidentes al adapter original (predeterminado: `false`)
446
+ - `baseURL`: Base URL a eliminar al hacer matching de rutas
447
+ - `delay`: Delay predeterminado para todas las respuestas (ms)
448
+
449
+ ### Configuración de Transporte
450
+
451
+ 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.
452
+
453
+ ```typescript
454
+ import { createHttpClient } from '@bereasoftware/nexa';
455
+
456
+ // Usar Node.js HTTP/1.1 con keep-alive y pooling de conexiones
457
+ const client = createHttpClient({
458
+ transport: 'node', // 'fetch' (predeterminado), 'node', o 'http2'
459
+ nodeOptions: {
460
+ keepAlive: true,
461
+ maxSockets: 50,
462
+ maxFreeSockets: 10,
463
+ maxRequestsPerSocket: 0, // ilimitado
464
+ timeout: 60000,
465
+ },
466
+ });
467
+
468
+ // Usar HTTP/2 para mejor rendimiento con multiplexación
469
+ const http2Client = createHttpClient({
470
+ transport: 'http2',
471
+ nodeOptions: {
472
+ http2Settings: {
473
+ enablePush: false,
474
+ },
475
+ },
476
+ });
477
+
478
+ // Anular transporte por request
479
+ await client.get('/api', {
480
+ transport: 'http2',
481
+ nodeOptions: { keepAlive: true },
482
+ });
483
+ ```
484
+
485
+ **Nota:** Los transportes de Node (`'node'` y `'http2'`) solo están disponibles en entornos Node.js. En navegadores, automáticamente se usará `'fetch'`.
486
+
487
+ **Opciones disponibles en `nodeOptions`:**
488
+ - `keepAlive` (boolean): Habilitar conexiones keep-alive (predeterminado: `true`)
489
+ - `maxSockets` (number): Máximo de sockets por host (predeterminado: `50`)
490
+ - `maxFreeSockets` (number): Máximo de sockets libres a mantener abiertos (predeterminado: `10`)
491
+ - `maxRequestsPerSocket` (number): Máximo de requests por socket (predeterminado: `0` = ilimitado)
492
+ - `timeout` (number): Timeout del socket en milisegundos (predeterminado: `60000`)
493
+ - `http2` (boolean): Obsoleto - usar `transport: 'http2'` en su lugar
494
+ - `http2Settings` (Record<string, unknown>): Configuración específica de HTTP/2 (solo para `transport: 'http2'`)
495
+
308
496
  ### Tipos de Respuesta
309
497
 
310
498
  Controla cómo se parsea el cuerpo de la respuesta:
@@ -349,10 +537,66 @@ const result = await client.get("/endpoint-lento", { timeout: 5000 });
349
537
  // El timeout produce un código de error específico
350
538
  if (!result.ok && result.error.code === "TIMEOUT") {
351
539
  console.log("La petición expiró");
352
- }
353
540
  ```
354
541
 
355
- ---
542
+ 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:
543
+
544
+ ```typescript
545
+ // Timeouts diferenciados
546
+ await client.get('/api', {
547
+ timeout: {
548
+ connection: 3000, // 3 segundos para establecer conexión
549
+ response: 10000, // 10 segundos para recibir respuesta completa
550
+ total: 15000 // Timeout total opcional (anula connection/response)
551
+ }
552
+ });
553
+
554
+ // Compatibilidad hacia atrás: número aún funciona (timeout total)
555
+ await client.get('/api', { timeout: 5000 });
556
+ ```
557
+
558
+ Códigos de error: `TIMEOUT` para timeouts de conexión/total, `RESPONSE_TIMEOUT` para timeouts de fase de respuesta.
559
+
560
+ ### Logging de Debug
561
+
562
+ Nexa provee logging de debug similar a axios para desarrollo. Habilítalo globalmente o por petición:
563
+
564
+ ```typescript
565
+ // Logging de debug global
566
+ const client = createHttpClient({
567
+ debug: true, // logs básicos
568
+ // debug: 'verbose', // logs detallados
569
+ });
570
+
571
+ // Override por petición
572
+ await client.get('/api', { debug: 'verbose' });
573
+ ```
574
+
575
+ 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.
576
+
577
+ #### Logger Personalizado
578
+
579
+ Puedes proporcionar una función de logger personalizada para redirigir logs a tu sistema de logging preferido:
580
+
581
+ ```typescript
582
+ const client = createHttpClient({
583
+ debug: true,
584
+ logger: (message, data) => {
585
+ // Enviar a tu servicio de logging
586
+ myLogger.info(message, data);
587
+ },
588
+ });
589
+
590
+ // Override de logger a nivel de petición
591
+ await client.post('/api', data, {
592
+ debug: true,
593
+ logger: (msg, data) => console.warn(msg, data)
594
+ });
595
+ ```
596
+
597
+ El logger recibe dos argumentos: el mensaje de log (con prefijo `[Nexa HTTP]`) y un objeto de datos opcional.
598
+
599
+ ---
356
600
 
357
601
  ## Estrategias de Reintentos
358
602
 
@@ -884,6 +1128,105 @@ await pipeline(ctx);
884
1128
 
885
1129
  ---
886
1130
 
1131
+ ## Comunicación en Tiempo Real
1132
+
1133
+ Nexa incluye clientes WebSocket y SSE (Server-Sent Events) con reconexión automática, heartbeat y soporte de plugins:
1134
+
1135
+ ```typescript
1136
+ import { createWebSocketClient, createSSEClient } from "@bereasoftware/nexa";
1137
+
1138
+ // WebSocket con reconexión automática y heartbeat
1139
+ const wsClient = createWebSocketClient("wss://echo.websocket.org", {
1140
+ reconnect: { enabled: true, maxAttempts: 10 },
1141
+ heartbeat: { interval: 30000 },
1142
+ });
1143
+
1144
+ await wsClient.connect();
1145
+ wsClient.sendJson({ type: "message", data: "Hello" });
1146
+ wsClient.onMessage((event) => console.log("Mensaje:", event.data));
1147
+
1148
+ // SSE para streams de eventos
1149
+ const sseClient = createSSEClient("https://stream.example.com/events");
1150
+ await sseClient.connect();
1151
+ sseClient.onEvent("update", (data) => console.log("Actualización:", data));
1152
+ ```
1153
+
1154
+ **Características:**
1155
+ - ✅ Reconexión automática con backoff exponencial
1156
+ - ✅ Heartbeat/ping-pong para mantener conexiones activas
1157
+ - ✅ Soporte para JSON y mensajes binarios
1158
+ - ✅ Estadísticas de conexión y métricas
1159
+ - ✅ Integración con sistema de plugins de Nexa
1160
+
1161
+ ---
1162
+
1163
+ ## Adaptadores Multi-Entorno
1164
+
1165
+ Nexa soporta múltiples entornos de ejecución con adaptadores optimizados:
1166
+
1167
+ ```typescript
1168
+ import { createHttpClient } from "@bereasoftware/nexa";
1169
+
1170
+ // Usa fetch global (navegador, Node.js 18+, Deno, Bun, Cloudflare Workers)
1171
+ const client = createHttpClient({ transport: "fetch" });
1172
+
1173
+ // Usa módulos nativos de Node.js (HTTP/1.1 con connection pooling)
1174
+ const nodeClient = createHttpClient({
1175
+ transport: "node",
1176
+ nodeOptions: { keepAlive: true }
1177
+ });
1178
+
1179
+ // Usa HTTP/2 de Node.js con session pooling
1180
+ const http2Client = createHttpClient({ transport: "http2" });
1181
+
1182
+ // Entornos específicos (detección automática)
1183
+ const denoClient = createHttpClient({ transport: "deno" });
1184
+ const bunClient = createHttpClient({ transport: "bun" });
1185
+ const cloudflareClient = createHttpClient({ transport: "cloudflare" });
1186
+ ```
1187
+
1188
+ **Entornos soportados:**
1189
+ - ✅ **Navegador:** fetch API global
1190
+ - ✅ **Node.js:** http/https (HTTP/1.1), http2 (HTTP/2 con pooling)
1191
+ - ✅ **Deno:** fetch nativo de Deno
1192
+ - ✅ **Bun:** fetch optimizado de Bun
1193
+ - ✅ **Cloudflare Workers:** fetch con bindings de Cloudflare
1194
+
1195
+ ---
1196
+
1197
+ ## Plugins Avanzados
1198
+
1199
+ Nexa incluye plugins listos para producción:
1200
+
1201
+ ```typescript
1202
+ import {
1203
+ RateLimitPlugin,
1204
+ CircuitBreakerPlugin,
1205
+ LoggerPlugin,
1206
+ MetricsPlugin
1207
+ } from "@bereasoftware/nexa";
1208
+
1209
+ const manager = new PluginManager();
1210
+ manager
1211
+ .register(new LoggerPlugin())
1212
+ .register(new MetricsPlugin())
1213
+ .register(new RateLimitPlugin({ maxRequests: 100, windowMs: 60000 }))
1214
+ .register(new CircuitBreakerPlugin({
1215
+ failureThreshold: 5,
1216
+ resetTimeout: 30000
1217
+ }));
1218
+ ```
1219
+
1220
+ **Plugins disponibles:**
1221
+ - ✅ **RateLimitPlugin:** Limita peticiones por ventana de tiempo
1222
+ - ✅ **CircuitBreakerPlugin:** Previene fallos en cascada
1223
+ - ✅ **LoggerPlugin:** Logs detallados de peticiones/respuestas
1224
+ - ✅ **MetricsPlugin:** Métricas de rendimiento
1225
+ - ✅ **CachePlugin:** Cache automático de respuestas
1226
+ - ✅ **DedupePlugin:** Deduplicación de peticiones concurrentes
1227
+
1228
+ ---
1229
+
887
1230
  ## Sistema de Plugins
888
1231
 
889
1232
  Extiende Nexa con una arquitectura de plugins:
@@ -1088,6 +1431,22 @@ deferred.reject(new Error("falló"));
1088
1431
  | `MAX_RETRIES` | Todos los intentos de reintento agotados |
1089
1432
  | `UNKNOWN_ERROR` | Error no clasificado |
1090
1433
 
1434
+ ### Contexto de Error Enriquecido
1435
+
1436
+ Nexa proporciona contexto de error enriquecido similar a axios, incluyendo la request original, response y configuración en los objetos de error. Esto facilita el debugging.
1437
+
1438
+ ```typescript
1439
+ const result = await client.get('/api');
1440
+ if (!result.ok) {
1441
+ const { request, response, config } = result.error;
1442
+ console.log('URL de request fallida:', request?.url);
1443
+ console.log('Status de respuesta:', response?.status);
1444
+ console.log('Timeout de configuración:', config?.timeout);
1445
+ }
1446
+ ```
1447
+
1448
+ Todos los errores ahora incluyen propiedades `request`, `response` (si está disponible), y `config`.
1449
+
1091
1450
  ### Clase HttpError
1092
1451
 
1093
1452
  ```typescript
@@ -1200,7 +1559,7 @@ Nexa se distribuye en múltiples formatos de módulo:
1200
1559
 
1201
1560
  ### Pruebas
1202
1561
 
1203
- **157 tests en total**: 88 tests de HTTP Client + 69 tests de utilities
1562
+ **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
1204
1563
 
1205
1564
  ```bash
1206
1565
  # Ejecutar todos los tests
@@ -1243,13 +1602,14 @@ dist/
1243
1602
 
1244
1603
  ### Cobertura de Tests
1245
1604
 
1246
- **Cobertura General: 75.73%** — sólida cobertura de tests unitarios con mocking HTTP
1605
+ **Cobertura General: 71.4%** — sólida cobertura de tests unitarios con mocking HTTP
1247
1606
 
1248
1607
  | Componente | Cobertura | Detalles |
1249
1608
  | ----------- | ---------- | --------------------------------- |
1250
- | HTTP Client | **80.85%** | 81.25% ramas, 73.43% funciones |
1609
+ | HTTP Client | **66.7%** | 67.3% ramas, 67.0% funciones |
1251
1610
  | Types | **100%** | Cobertura perfecta de tipos |
1252
- | Utils | **71.79%** | 66.66% ramas, 81.14% funciones |
1611
+ | Testing | **93.7%** | 85.1% ramas, 95.8% funciones |
1612
+ | Utils | **71.8%** | 66.7% ramas, 81.1% funciones |
1253
1613
 
1254
1614
  **HTTP Client** (`test/http-client.test.ts`) — **88 tests**:
1255
1615
 
@@ -1292,13 +1652,21 @@ La cobertura de tests unitarios se estabiliza alrededor del **75-80%** debido a
1292
1652
  - **Archivos solo-export** (~2-3% gap): `http-client/index.ts` verificado vía validación de imports, no testeable por unidad
1293
1653
 
1294
1654
  **Máximos realistas:**
1295
- - Unit tests + mocks: **~80-85%** techo (actual: 75.73%)
1655
+ - Unit tests + mocks: **~80-85%** techo (actual: 71.4%)
1296
1656
  - Tests de integración requeridos: llegaría a 90%+ pero fuera del alcance del proyecto
1297
1657
 
1298
- El 75.73% de cobertura representa testing exhaustivo de todas las **rutas de código de producción** alcanzables vía mocks HTTP.
1658
+ El 71.4% de cobertura representa testing exhaustivo de todas las **rutas de código de producción** alcanzables vía mocks HTTP.
1299
1659
 
1300
1660
  ---
1301
1661
 
1662
+ ## Contribución
1663
+
1664
+ ¿Quieres contribuir a Nexa? Por favor lee nuestra [guía de contribución](CONTRIBUTING.es.md) y [código de conducta](CODE_OF_CONDUCT.md).
1665
+
1666
+ ## Seguridad
1667
+
1668
+ Para reportar vulnerabilidades de seguridad, consulta nuestra [política de seguridad](SECURITY.es.md).
1669
+
1302
1670
  ## Licencia
1303
1671
 
1304
1672
  MIT © [John Andrade](mailto:johnandrade@bereasoft.com) — [@bereasoftware](https://github.com/Berea-Soft)
package/dist/nexa.cjs.js CHANGED
@@ -1 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=e=>({ok:!0,value:e}),t=e=>({ok:!1,error:e});function n(n){return{validate(r){let i=r;for(let[e,r]of Object.entries(n))if(!r(i[e]))return t({message:`Validation failed: field "${e}" is invalid`,code:`VALIDATION_ERROR`});return e(r)}}}function r(n){return{validate(r){let i=r,a=n.filter(e=>!(e in i));return a.length>0?t({message:`Validation failed: missing fields: ${a.join(`, `)}`,code:`VALIDATION_ERROR`}):e(r)}}}var i={validate(n){return Array.isArray(n)?e(n):t({message:`Expected array response`,code:`VALIDATION_ERROR`})}},a={validate(n){return n&&typeof n==`object`&&!Array.isArray(n)?e(n):t({message:`Expected object response`,code:`VALIDATION_ERROR`})}},o={transform(e){return g(e,m)}},s={transform(e){return g(e,h)}},c={transform(e){return _(e)}};function l(e){return{transform(t){return Array.isArray(t)?t.map(t=>v(t,e)):v(t,e)}}}function u(e){return{transform(t){return{[e]:t}}}}var d=class{maxAttempts;constructor(e=5){this.maxAttempts=e}shouldRetry(e){return e<this.maxAttempts}delayMs(e){return e*50}},f=class{retryableStatuses=[408,429,500,502,503,504];maxAttempts;constructor(e=3){this.maxAttempts=e}shouldRetry(e,t){return e>=this.maxAttempts?!1:this.retryableStatuses.includes(t.status??0)||t.code===`TIMEOUT`}delayMs(e){return Math.min(1e3*2**(e-1),1e4)}},p=class{failureCount=0;lastFailureTime=0;maxAttempts;failureThreshold;resetTimeMs;constructor(e=3,t=5,n=6e4){this.maxAttempts=e,this.failureThreshold=t,this.resetTimeMs=n}shouldRetry(e){return e>=this.maxAttempts||(Date.now()-this.lastFailureTime>this.resetTimeMs&&(this.failureCount=0),this.failureCount>=this.failureThreshold)?!1:(this.failureCount++,this.lastFailureTime=Date.now(),!0)}delayMs(e){return 100*2**(e-1)}reset(){this.failureCount=0,this.lastFailureTime=0}};function m(e){return e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function h(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}function g(e,t){if(!e||typeof e!=`object`)return e;if(Array.isArray(e))return e.map(e=>g(e,t));let n={};for(let[r,i]of Object.entries(e))n[t(r)]=g(i,t);return n}function _(e,t=``){let n={};if(Array.isArray(e))e.forEach((e,r)=>{let i=t?`${t}[${r}]`:`[${r}]`;Object.assign(n,_(e,i))});else if(e&&typeof e==`object`)for(let[r,i]of Object.entries(e)){let e=t?`${t}.${r}`:r;i&&typeof i==`object`&&!Array.isArray(i)?Object.assign(n,_(i,e)):n[e]=i}return n}function v(e,t){if(!e||typeof e!=`object`)return{};let n=e,r={};for(let e of t)e in n&&(r[e]=n[e]);return r}function y(e){let t=new AbortController,n=setTimeout(()=>t.abort(),e);return t.signal.addEventListener(`abort`,()=>clearTimeout(n),{once:!0}),t}async function b(e,t=3){try{return await e()}catch(n){if(t<=0)throw n;return b(e,t-1)}}var x=class{cache=new Map;get(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),null):t.data:null}set(e,t,n=6e4){this.cache.set(e,{data:t,timestamp:Date.now(),ttlMs:n})}clear(){this.cache.clear()}has(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),!1):!0:!1}delete(e){this.cache.delete(e)}};function S(e={}){let t=e.cache||new x,n=e.ttlMs||6e4,r=e.cacheableStatuses||[200,304];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase(),o=a===`GET`,s=`${a}:${e.request.url}`;if(o&&t.has(s)){let n=t.get(s);if(n){e.response=n,e.state.cacheHit=!0;return}}await i(),o&&e.response&&r.includes(e.response.status)&&(t.set(s,e.response,n),e.state.cacheMiss=!0)}}var C=S(),w=class{pending=new Map;async execute(e,t){if(this.pending.has(e))return this.pending.get(e);let n=t().finally(()=>{this.pending.delete(e)});return this.pending.set(e,n),n}clear(){this.pending.clear()}};function T(e={}){let t=e.deduplicator||new w,n=e.includeBody??!1,r=e.methods||[`GET`];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase();if(!r.includes(a)){await i();return}let o=`${a}:${e.request.url}`;n&&e.request.body&&(o+=`:${JSON.stringify(e.request.body)}`);try{e.response=await t.execute(o,async()=>(await i(),e.response)),e.state.deduped=!0}catch(t){throw e.error=t,t}}}var E=T();function D(e){return async t=>{let n=-1;async function r(i){if(i<=n)throw Error(`next() called multiple times`);n=i;let a=e[i];a&&await a(t,()=>r(i+1))}await r(0)}}var O=class{middlewares=[];use(e){return this.middlewares.push(e),this}async execute(e){if(e&&typeof e==`object`&&`request`in e&&`response`in e){let t=e;return await D(this.middlewares.map(e=>typeof e==`function`&&e.length===2?e:async(t,n)=>{let r=e;t.response.body=await r(t.response.body),await n()}))(t),t.response.body}let t=e;for(let e of this.middlewares)t=await e(t);return t}clear(){this.middlewares=[]}};function k(e,t,n,r={}){return{ok:e>=200&&e<300,data:t,error:n,status:e,headers:r}}function A(e){return async(t,n)=>{let r=e.path,i;switch(e.method){case`GET`:i=await t.get(r);break;case`POST`:i=await t.post(r,n);break;case`PUT`:i=await t.put(r,n);break;case`PATCH`:i=await t.patch(r,n);break;case`DELETE`:i=await t.delete(r);break;default:throw Error(`Unsupported method: ${e.method}`)}return i}}function j(e){return{request:async(t,n,r)=>{let i=e[n];return await A(i)(t,r)}}}var M=class e{subscribers=[];errorSubscribers=[];completeSubscribers=[];subscribe(e,t,n){return e&&this.subscribers.push(e),t&&this.errorSubscribers.push(t),n&&this.completeSubscribers.push(n),{unsubscribe:()=>{this.subscribers=this.subscribers.filter(t=>t!==e),this.errorSubscribers=this.errorSubscribers.filter(e=>e!==t),this.completeSubscribers=this.completeSubscribers.filter(e=>e!==n)}}}next(e){this.subscribers.forEach(t=>t(e))}error(e){this.errorSubscribers.forEach(t=>t(e))}complete(){this.completeSubscribers.forEach(e=>e())}map(t){let n=new e;return this.subscribe(e=>n.next(t(e)),e=>n.error(e),()=>n.complete()),n}filter(t){let n=new e;return this.subscribe(e=>{t(e)&&n.next(e)},e=>n.error(e),()=>n.complete()),n}};function N(e){return t=>{if(!e(t))throw TypeError(`Value does not match expected type`);return t}}function P(e){return e}function F(e){return P(e)}var I=class{_promise;resolveFunc;rejectFunc;constructor(){this._promise=new Promise((e,t)=>{this.resolveFunc=e,this.rejectFunc=t})}resolve(e){this.resolveFunc(e)}reject(e){this.rejectFunc(e)}get promise(){return this._promise}promise_(){return this._promise}};async function L(e,t={}){let n=e.body?.getReader();if(!n)throw Error(`Response body is not readable`);let r=[],i=0,a=parseInt(e.headers.get(`content-length`)||`0`,10);for(;;){let{done:e,value:o}=await n.read();if(e)break;r.push(o),i+=o.length,t.onChunk&&await t.onChunk(o),t.onProgress&&a>0&&t.onProgress(i,a)}return R(r,i)}function R(e,t){let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}async function z(e,t){let n=await L(e);if(typeof window>`u`)await(await import(`fs`).then(e=>e.promises)).writeFile(t,n);else{let e=new Blob([n.buffer],{type:`application/octet-stream`}),r=URL.createObjectURL(e),i=document.createElement(`a`);i.href=r,i.download=t,i.click(),URL.revokeObjectURL(r)}}function B(e={}){return async(t,n)=>{if(await n(),t.response&&t.response.body&&typeof t.response.body==`object`&&`getReader`in t.response.body){let n=t.response.body.getReader(),r=[],i=0,a=parseInt(t.response.headers?.[`content-length`]||`0`,10);try{for(;;){let{done:o,value:s}=await n.read();if(o)break;r.push(s),i+=s.length,e.onChunk&&await e.onChunk(s),e.onProgress&&a>0&&e.onProgress(i,a),t.state.streamedChunks=t.state.streamedChunks||[],t.state.streamedChunks.push(s)}let o=R(r,i);t.response.body=o,t.state.streaming=!0,t.state.streamedBytes=i}finally{n.releaseLock()}}}}var V=B({onProgress:(e,t)=>{if(t>0){let n=Math.round(e/t*100);console.log(`⬇️ Streaming: ${n}% (${e}/${t} bytes)`)}}}),H=class{plugins=[];cache=new x;deduplicator=new w;middlewares=[];listeners=new Map;register(e){return this.plugins.push(e),e.setup(this),this.emit(`plugin:registered`,e.name),this}addMiddleware(e){return this.middlewares.push(e),this}getCache(){return this.cache}getDeduplicator(){return this.deduplicator}getPipeline(){return D(this.middlewares)}async executePipeline(e){await this.getPipeline()(e)}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e)?.add(t),this}emit(e,...t){let n=this.listeners.get(e);if(n)for(let e of n)e(...t)}off(e,t){return this.listeners.get(e)?.delete(t),this}getPlugins(){return[...this.plugins]}clear(){this.plugins=[],this.middlewares=[],this.listeners.clear(),this.cache.clear(),this.deduplicator.clear()}},U={name:`logger`,setup(e){e.on(`request:start`,(...e)=>{let t=e[0];console.log(`📤 Request started: ${t}`)}),e.on(`request:success`,(...e)=>{let t=e[0],n=e[1];console.log(`✅ Request succeeded: ${t} (${n})`)}),e.on(`request:error`,(...e)=>{let t=e[0],n=e[1];console.error(`❌ Request failed: ${t}`,n)})}},W=class{name=`metrics`;metrics={requests:0,errors:0,totalTime:0,avgTime:0};setup(e){e.on(`request:complete`,(...e)=>{let t=e[0],n=e[1];this.metrics.requests++,this.metrics.totalTime+=t,this.metrics.avgTime=this.metrics.totalTime/this.metrics.requests,n||this.metrics.errors++})}getMetrics(){return{...this.metrics}}},G=class{name=`cache`;ttlMs;constructor(e=6e4){this.ttlMs=e}setup(e){e.addMiddleware(S({ttlMs:this.ttlMs}))}},K=class{name=`dedupe`;setup(e){e.addMiddleware(T())}},q=class{store=new x;get(e){return this.store.get(e)}set(e,t,n=6e4){this.store.set(e,t,n)}has(e){return this.store.has(e)}clear(){this.store.clear()}},J=class{maxAttempts;baseDelayMs;constructor(e=3,t=100){this.maxAttempts=e,this.baseDelayMs=t}shouldRetry(e,t){let n=t.status!==void 0&&t.status>=500,r=t.code===`NETWORK_ERROR`;return e<this.maxAttempts&&(n||r||t.code===`TIMEOUT`)}delayMs(e){let t=this.baseDelayMs*2**(e-1),n=Math.random()*t*.1;return Math.min(t+n,3e4)}},Y=class{running=0;queue=[];maxConcurrent;constructor(e){this.maxConcurrent=e}async acquire(){if(this.running<this.maxConcurrent){this.running++;return}return new Promise(e=>{this.queue.push(()=>{this.running++,e()})})}release(){this.running--;let e=this.queue.shift();e&&e()}get pending(){return this.queue.length}get active(){return this.running}};function X(e){return e==null?{serialized:void 0,contentType:null}:typeof FormData<`u`&&e instanceof FormData?{serialized:e,contentType:null}:typeof URLSearchParams<`u`&&e instanceof URLSearchParams?{serialized:e,contentType:`application/x-www-form-urlencoded`}:typeof Blob<`u`&&e instanceof Blob?{serialized:e,contentType:e.type||`application/octet-stream`}:e instanceof ArrayBuffer||ArrayBuffer.isView(e)||typeof ReadableStream<`u`&&e instanceof ReadableStream?{serialized:e,contentType:`application/octet-stream`}:typeof e==`string`?{serialized:e,contentType:`text/plain`}:{serialized:JSON.stringify(e),contentType:`application/json`}}function Z(e,t){return t?e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(e,n)=>{let r=t[n];if(r===void 0)throw Error(`Missing path parameter: :${n}`);return encodeURIComponent(String(r))}):e}var Q=class n{requestInterceptors=[];responseInterceptors=[];cache;config;requestQueue;pendingRequests=new Map;constructor(e={}){this.config={baseURL:e.baseURL??``,defaultHeaders:e.defaultHeaders??{"Content-Type":`application/json`},defaultTimeout:e.defaultTimeout??3e4,validateStatus:e.validateStatus??(e=>e>=200&&e<300),cacheStrategy:e.cacheStrategy??new q,maxConcurrent:e.maxConcurrent??0,defaultResponseType:e.defaultResponseType??`auto`,defaultHooks:e.defaultHooks??{}},this.cache=this.config.cacheStrategy,this.requestQueue=this.config.maxConcurrent>0?new Y(this.config.maxConcurrent):null}async request(n){let r={...this.config.defaultHooks,...n.hooks},i=this.getMaxAttempts(n.retry),a=this.getRetryStrategy(n.retry),o=Symbol(`request`);r.onStart?.(this.buildRequest(n)),this.requestQueue&&await this.requestQueue.acquire();try{for(let s=1;s<=i;s++)try{if((n.method===`GET`||!n.method)&&n.cache?.enabled){let t=this.getCacheKey(n),i=this.cache.get(t);if(i){let t=i;return r.onSuccess?.(t),r.onFinally?.(),e(t)}}let t=this.buildRequest(n);for(let e of this.requestInterceptors)t=await e.onRequest(t);let i=new AbortController;this.pendingRequests.set(o,i),n.signal&&n.signal.addEventListener(`abort`,()=>i.abort(),{once:!0});let a=performance.now(),s=await this.fetchWithTimeout(t,n.timeout??this.config.defaultTimeout,i),c=performance.now()-a,l=s;n.onDownloadProgress&&s.body&&(l=this.trackDownloadProgress(s,n.onDownloadProgress));let u=n.responseType??this.config.defaultResponseType,d=await this.parseResponse(l,t,c,u);if(!this.config.validateStatus(d.status))throw{message:`Request failed with status ${d.status}`,status:d.status,statusText:d.statusText,code:`HTTP_ERROR`};if(n.validate){let e=n.validate.validate(d.data);if(!e.ok)return e}n.transform&&(d.data=n.transform.transform(d.data));let f=d;for(let e of this.responseInterceptors)f=await e.onResponse(f);if((n.method===`GET`||!n.method)&&n.cache?.enabled){let e=this.getCacheKey(n);this.cache.set(e,f,n.cache.ttlMs)}return r.onSuccess?.(f),this.pendingRequests.delete(o),e(f)}catch(e){let n=this.isHttpErrorDetails(e)?e:this.normalizeError(e);if(s<i&&a.shouldRetry(s,n)){r.onRetry?.(s,n);let e=a.delayMs(s);await this.delay(e);continue}let c=n;for(let e of this.responseInterceptors)e.onError&&(c=await e.onError(c));return r.onError?.(c),this.pendingRequests.delete(o),t(c)}let s={message:`Max retries exceeded`,code:`MAX_RETRIES`};return r.onError?.(s),t(s)}finally{r.onFinally?.(),this.requestQueue&&this.requestQueue.release()}}get(e,t){return this.request({...t,url:e,method:`GET`})}post(e,t,n){return this.request({...n,url:e,method:`POST`,body:t})}put(e,t,n){return this.request({...n,url:e,method:`PUT`,body:t})}patch(e,t,n){return this.request({...n,url:e,method:`PATCH`,body:t})}delete(e,t){return this.request({...t,url:e,method:`DELETE`})}head(e,t){return this.request({...t,url:e,method:`HEAD`})}options(e,t){return this.request({...t,url:e,method:`OPTIONS`})}addRequestInterceptor(e){return this.requestInterceptors.push(e),()=>{let t=this.requestInterceptors.indexOf(e);t!==-1&&this.requestInterceptors.splice(t,1)}}addResponseInterceptor(e){return this.responseInterceptors.push(e),()=>{let t=this.responseInterceptors.indexOf(e);t!==-1&&this.responseInterceptors.splice(t,1)}}clearInterceptors(){this.requestInterceptors=[],this.responseInterceptors=[]}clearCache(){this.cache.clear()}cancelAll(){for(let e of this.pendingRequests.values())e.abort();this.pendingRequests.clear()}get activeRequests(){return this.pendingRequests.size}get queueStats(){return{active:this.requestQueue?.active??this.pendingRequests.size,pending:this.requestQueue?.pending??0}}extend(e={}){let t=new n({baseURL:e.baseURL??this.config.baseURL,defaultHeaders:{...this.config.defaultHeaders,...e.defaultHeaders},defaultTimeout:e.defaultTimeout??this.config.defaultTimeout,validateStatus:e.validateStatus??this.config.validateStatus,cacheStrategy:e.cacheStrategy??this.cache,maxConcurrent:e.maxConcurrent??this.config.maxConcurrent,defaultResponseType:e.defaultResponseType??this.config.defaultResponseType,defaultHooks:{...this.config.defaultHooks,...e.defaultHooks}});for(let e of this.requestInterceptors)t.addRequestInterceptor(e);for(let e of this.responseInterceptors)t.addResponseInterceptor(e);return t}async*paginate(e,t,n={}){let r={...n};for(;;){let n=await this.get(e,r);if(!n.ok)break;yield t.getItems(n.value.data);let i=t.getNextPage(n.value.data,r);if(!i)break;r=i}}async poll(e,n,r={}){let i=n.maxAttempts??0;for(let t=1;i===0||t<=i;t++){let a=await this.get(e,r);if(!a.ok||(n.onPoll?.(a.value.data,t),n.until(a.value.data)))return a;if(i>0&&t>=i)break;await this.delay(n.intervalMs)}return t({message:`Polling exhausted after ${i} attempts`,code:`POLL_EXHAUSTED`})}buildRequest(e){let t=Z(e.url,e.params);return{url:this.buildUrl(t,e.query),method:e.method??`GET`,headers:{...this.config.defaultHeaders,...e.headers},body:e.body,params:e.params}}buildUrl(e,t){let n=this.config.baseURL+e;if(t&&Object.keys(t).length>0){let e=new URLSearchParams;Object.entries(t).forEach(([t,n])=>{e.append(t,String(n))}),n+=`?${e.toString()}`}return n}getCacheKey(e){let t=Z(e.url,e.params),n=e.query?JSON.stringify(e.query):``;return`${e.method??`GET`}:${t}${n?`:`+n:``}`}fetchWithTimeout(e,t,n){let{serialized:r,contentType:i}=X(e.body),a={...e.headers};return i?a[`Content-Type`]=i:i===null&&r instanceof FormData&&delete a[`Content-Type`],new Promise((i,o)=>{let s=setTimeout(()=>{n.abort();let e=Error(`Request timed out`);e.name=`TimeoutError`,o(e)},t);fetch(e.url,{method:e.method,headers:a,body:r,signal:n.signal}).then(e=>{clearTimeout(s),i(e)},e=>{clearTimeout(s),o(e)})})}trackDownloadProgress(e,t){let n=parseInt(e.headers.get(`content-length`)||`0`,10),r=e.body?.getReader();if(!r)return e;let i=0,a=new ReadableStream({async pull(e){let{done:a,value:o}=await r.read();if(a){e.close();return}i+=o.byteLength,t({loaded:i,total:n,percent:n>0?Math.round(i/n*100):0}),e.enqueue(o)}});return new Response(a,{headers:e.headers,status:e.status,statusText:e.statusText})}async parseResponse(e,t,n,r){let i=await this.parseBody(e,r);return{status:e.status,statusText:e.statusText,headers:e.headers,data:i,request:t,duration:n}}async parseBody(e,t){switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();case`stream`:return e.body;default:{let t=e.headers.get(`content-type`)??``;if(t.includes(`application/json`))return await e.json();if(t.includes(`text/`))return await e.text();if(t.includes(`multipart/form-data`))return await e.formData();if(t.includes(`application/octet-stream`)||t.includes(`image/`)||t.includes(`audio/`)||t.includes(`video/`))return await e.blob();try{return await e.json()}catch{return await e.text()}}}}normalizeError(e){return e instanceof Error&&e.name===`TimeoutError`?{message:`Request timed out`,code:`TIMEOUT`}:e instanceof DOMException&&e.name===`AbortError`?{message:`Request aborted`,code:`ABORTED`}:e instanceof Error?e.name===`AbortError`||e.message.includes(`abort`)?{message:`Request aborted`,code:`ABORTED`}:{message:e.message,code:e.name===`TypeError`?`NETWORK_ERROR`:`UNKNOWN_ERROR`,originalError:e}:{message:String(e),code:`UNKNOWN_ERROR`,originalError:e}}isHttpErrorDetails(e){return typeof e==`object`&&!!e&&`message`in e&&`code`in e}getMaxAttempts(e){return e?`maxAttempts`in e?e.maxAttempts:100:1}getRetryStrategy(e){return e?`shouldRetry`in e?e:new J(e.maxAttempts,e.backoffMs):{shouldRetry:()=>!1,delayMs:()=>0}}delay(e){return new Promise(t=>setTimeout(t,e))}},$=class extends Error{status;code;response;constructor(e,t,n,r){super(e),this.name=`HttpError`,this.status=t,this.code=n,this.response=r}};function ee(e){return e instanceof $}function te(e){return new Q(e)}exports.AggressiveRetry=d,exports.CachePlugin=G,exports.CacheStore=x,exports.CircuitBreakerRetry=p,exports.ConservativeRetry=f,exports.DedupePlugin=K,exports.Defer=I,exports.Err=t,exports.HttpClient=Q,exports.HttpError=$,exports.LoggerPlugin=U,exports.MetricsPlugin=W,exports.MiddlewarePipeline=O,exports.Ok=e,exports.PluginManager=H,exports.RequestDeduplicator=w,exports.TypedObservable=M,exports.cacheMiddleware=C,exports.createApiUrl=F,exports.createCacheMiddleware=S,exports.createDedupeMiddleware=T,exports.createHttpClient=te,exports.createPipeline=D,exports.createProjectionTransformer=l,exports.createRequiredFieldsValidator=r,exports.createSchemaValidator=n,exports.createStreamingMiddleware=B,exports.createTypeGuard=N,exports.createTypedApiClient=j,exports.createTypedRequest=A,exports.createTypedResponse=k,exports.createUrl=P,exports.createWrapperTransformer=u,exports.dedupeMiddleware=E,exports.handleStream=L,exports.isHttpError=ee,exports.retry=b,exports.streamToFile=z,exports.streamingMiddleware=V,exports.transformCamelToSnake=s,exports.transformFlatten=c,exports.transformSnakeToCamel=o,exports.validatorIsArray=i,exports.validatorIsObject=a,exports.withTimeout=y;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.defineProperty,t=(e,t)=>()=>(e&&(t=e(e=0)),t),n=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n||e(r,Symbol.toStringTag,{value:`Module`}),r},r=e=>({ok:!0,value:e}),i=e=>({ok:!1,error:e});function a(e){return{validate(t){let n=t;for(let[t,r]of Object.entries(e))if(!r(n[t]))return i({message:`Validation failed: field "${t}" is invalid`,code:`VALIDATION_ERROR`});return r(t)}}}function o(e){return{validate(t){let n=t,a=e.filter(e=>!(e in n));return a.length>0?i({message:`Validation failed: missing fields: ${a.join(`, `)}`,code:`VALIDATION_ERROR`}):r(t)}}}var s={validate(e){return Array.isArray(e)?r(e):i({message:`Expected array response`,code:`VALIDATION_ERROR`})}},c={validate(e){return e&&typeof e==`object`&&!Array.isArray(e)?r(e):i({message:`Expected object response`,code:`VALIDATION_ERROR`})}},l={transform(e){return v(e,_)}},u={transform(e){return v(e,ee)}},d={transform(e){return y(e)}};function f(e){return{transform(t){return Array.isArray(t)?t.map(t=>b(t,e)):b(t,e)}}}function p(e){return{transform(t){return{[e]:t}}}}var m=class{maxAttempts;constructor(e=5){this.maxAttempts=e}shouldRetry(e){return e<this.maxAttempts}delayMs(e){return e*50}},h=class{retryableStatuses=[408,429,500,502,503,504];maxAttempts;constructor(e=3){this.maxAttempts=e}shouldRetry(e,t){return e>=this.maxAttempts?!1:this.retryableStatuses.includes(t.status??0)||t.code===`TIMEOUT`}delayMs(e){return Math.min(1e3*2**(e-1),1e4)}},g=class{failureCount=0;lastFailureTime=0;maxAttempts;failureThreshold;resetTimeMs;constructor(e=3,t=5,n=6e4){this.maxAttempts=e,this.failureThreshold=t,this.resetTimeMs=n}shouldRetry(e){return e>=this.maxAttempts||(Date.now()-this.lastFailureTime>this.resetTimeMs&&(this.failureCount=0),this.failureCount>=this.failureThreshold)?!1:(this.failureCount++,this.lastFailureTime=Date.now(),!0)}delayMs(e){return 100*2**(e-1)}reset(){this.failureCount=0,this.lastFailureTime=0}};function _(e){return e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function ee(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}function v(e,t){if(!e||typeof e!=`object`)return e;if(Array.isArray(e))return e.map(e=>v(e,t));let n={};for(let[r,i]of Object.entries(e))n[t(r)]=v(i,t);return n}function y(e,t=``){let n={};if(Array.isArray(e))e.forEach((e,r)=>{let i=t?`${t}[${r}]`:`[${r}]`;Object.assign(n,y(e,i))});else if(e&&typeof e==`object`)for(let[r,i]of Object.entries(e)){let e=t?`${t}.${r}`:r;i&&typeof i==`object`&&!Array.isArray(i)?Object.assign(n,y(i,e)):n[e]=i}return n}function b(e,t){if(!e||typeof e!=`object`)return{};let n=e,r={};for(let e of t)e in n&&(r[e]=n[e]);return r}function te(e){let t=new AbortController,n=setTimeout(()=>t.abort(),e);return t.signal.addEventListener(`abort`,()=>clearTimeout(n),{once:!0}),t}async function x(e,t=3){try{return await e()}catch(n){if(t<=0)throw n;return x(e,t-1)}}var S=class{cache=new Map;get(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),null):t.data:null}set(e,t,n=6e4){this.cache.set(e,{data:t,timestamp:Date.now(),ttlMs:n})}clear(){this.cache.clear()}has(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),!1):!0:!1}delete(e){this.cache.delete(e)}};function C(e={}){let t=e.cache||new S,n=e.ttlMs||6e4,r=e.cacheableStatuses||[200,304];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase(),o=a===`GET`,s=`${a}:${e.request.url}`;if(o&&t.has(s)){let n=t.get(s);if(n){e.response=n,e.state.cacheHit=!0;return}}await i(),o&&e.response&&r.includes(e.response.status)&&(t.set(s,e.response,n),e.state.cacheMiss=!0)}}var ne=C(),w=class{pending=new Map;async execute(e,t){if(this.pending.has(e))return this.pending.get(e);let n=t().finally(()=>{this.pending.delete(e)});return this.pending.set(e,n),n}clear(){this.pending.clear()}};function T(e={}){let t=e.deduplicator||new w,n=e.includeBody??!1,r=e.methods||[`GET`];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase();if(!r.includes(a)){await i();return}let o=`${a}:${e.request.url}`;n&&e.request.body&&(o+=`:${JSON.stringify(e.request.body)}`);try{e.response=await t.execute(o,async()=>(await i(),e.response)),e.state.deduped=!0}catch(t){throw e.error=t,t}}}var re=T();function E(e={maxRequests:100,windowMs:6e4}){let t=e.windowMs??6e4,n=e.maxRequests,r=e.keyGenerator??(e=>`${e.request.method}:${e.request.url}`),i=new Map;return async(a,o)=>{let s=r(a),c=Date.now();if(i.size>1e3)for(let[e,t]of i.entries())c>t.resetTime&&i.delete(e);let l=i.get(s);if((!l||c>l.resetTime)&&(l={count:0,resetTime:c+t},i.set(s,l)),l.count>=n){a.response={status:e.errorResponse?.status??429,headers:{"Content-Type":`application/json`},body:e.errorResponse?.body??{error:`Too Many Requests`,message:`Rate limit exceeded`}},a.state.rateLimited=!0;return}l.count++,a.state.rateLimit={limit:n,remaining:n-l.count,reset:l.resetTime},await o()}}var ie=E();function D(e={}){let t=e.failureThreshold??5,n=e.resetTimeout??3e4,r=e.isFailure??(e=>e.response.status>=500),i=e.keyGenerator??(e=>`${e.request.method}:${e.request.url}`),a=new Map;return async(e,o)=>{let s=i(e),c=a.get(s);if(c||(c={state:`closed`,failures:0,lastFailure:0,successCount:0},a.set(s,c)),c.state===`open`)if(Date.now()-c.lastFailure>n)c.state=`half-open`,c.successCount=0;else{e.response={status:503,headers:{"Content-Type":`application/json`},body:{error:`Service Unavailable`,message:`Circuit breaker is open`}},e.state.circuitOpen=!0;return}try{await o(),r(e)?(c.failures++,c.lastFailure=Date.now(),(c.failures>=t||c.state===`half-open`)&&(c.state=`open`)):(c.failures=0,c.state===`half-open`&&(c.successCount++,c.successCount>=3&&(c.state=`closed`,c.successCount=0)))}catch(e){throw c.failures++,c.lastFailure=Date.now(),(c.failures>=t||c.state===`half-open`)&&(c.state=`open`),e}}}var ae=D();function O(e){return async t=>{let n=-1;async function r(i){if(i<=n)throw Error(`next() called multiple times`);n=i;let a=e[i];a&&await a(t,()=>r(i+1))}await r(0)}}var oe=class{middlewares=[];use(e){return this.middlewares.push(e),this}async execute(e){if(e&&typeof e==`object`&&`request`in e&&`response`in e){let t=e;return await O(this.middlewares.map(e=>typeof e==`function`&&e.length===2?e:async(t,n)=>{let r=e;t.response.body=await r(t.response.body),await n()}))(t),t.response.body}let t=e;for(let e of this.middlewares)t=await e(t);return t}clear(){this.middlewares=[]}};function se(e,t,n,r={}){return{ok:e>=200&&e<300,data:t,error:n,status:e,headers:r}}function k(e){return async(t,n)=>{let r=e.path,i;switch(e.method){case`GET`:i=await t.get(r);break;case`POST`:i=await t.post(r,n);break;case`PUT`:i=await t.put(r,n);break;case`PATCH`:i=await t.patch(r,n);break;case`DELETE`:i=await t.delete(r);break;default:throw Error(`Unsupported method: ${e.method}`)}return i}}function ce(e){return{request:async(t,n,r)=>{let i=e[n];return await k(i)(t,r)}}}var le=class e{subscribers=[];errorSubscribers=[];completeSubscribers=[];subscribe(e,t,n){return e&&this.subscribers.push(e),t&&this.errorSubscribers.push(t),n&&this.completeSubscribers.push(n),{unsubscribe:()=>{this.subscribers=this.subscribers.filter(t=>t!==e),this.errorSubscribers=this.errorSubscribers.filter(e=>e!==t),this.completeSubscribers=this.completeSubscribers.filter(e=>e!==n)}}}next(e){this.subscribers.forEach(t=>t(e))}error(e){this.errorSubscribers.forEach(t=>t(e))}complete(){this.completeSubscribers.forEach(e=>e())}map(t){let n=new e;return this.subscribe(e=>n.next(t(e)),e=>n.error(e),()=>n.complete()),n}filter(t){let n=new e;return this.subscribe(e=>{t(e)&&n.next(e)},e=>n.error(e),()=>n.complete()),n}};function ue(e){return t=>{if(!e(t))throw TypeError(`Value does not match expected type`);return t}}function A(e){return e}function de(e){return A(e)}var fe=class{_promise;resolveFunc;rejectFunc;constructor(){this._promise=new Promise((e,t)=>{this.resolveFunc=e,this.rejectFunc=t})}resolve(e){this.resolveFunc(e)}reject(e){this.rejectFunc(e)}get promise(){return this._promise}promise_(){return this._promise}};async function j(e,t={}){let n=e.body?.getReader();if(!n)throw Error(`Response body is not readable`);let r=[],i=0,a=parseInt(e.headers.get(`content-length`)||`0`,10);for(;;){let{done:e,value:o}=await n.read();if(e)break;r.push(o),i+=o.length,t.onChunk&&await t.onChunk(o),t.onProgress&&a>0&&t.onProgress(i,a)}return M(r,i)}function M(e,t){let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}async function pe(e,t){let n=await j(e);if(typeof window>`u`)await(await import(`fs`).then(e=>e.promises)).writeFile(t,n);else{let e=new Blob([n.buffer],{type:`application/octet-stream`}),r=URL.createObjectURL(e),i=document.createElement(`a`);i.href=r,i.download=t,i.click(),URL.revokeObjectURL(r)}}function N(e={}){return async(t,n)=>{if(await n(),t.response&&t.response.body&&typeof t.response.body==`object`&&`getReader`in t.response.body){let n=t.response.body.getReader(),r=[],i=0,a=parseInt(t.response.headers?.[`content-length`]||`0`,10);try{for(;;){let{done:o,value:s}=await n.read();if(o)break;r.push(s),i+=s.length,e.onChunk&&await e.onChunk(s),e.onProgress&&a>0&&e.onProgress(i,a),t.state.streamedChunks=t.state.streamedChunks||[],t.state.streamedChunks.push(s)}let o=M(r,i);t.response.body=o,t.state.streaming=!0,t.state.streamedBytes=i}finally{n.releaseLock()}}}}var me=N({onProgress:(e,t)=>{if(t>0){let n=Math.round(e/t*100);console.log(`⬇️ Streaming: ${n}% (${e}/${t} bytes)`)}}}),P=class{plugins=[];cache=new S;deduplicator=new w;middlewares=[];listeners=new Map;register(e){return this.plugins.push(e),e.setup(this),this.emit(`plugin:registered`,e.name),this}addMiddleware(e){return this.middlewares.push(e),this}getCache(){return this.cache}getDeduplicator(){return this.deduplicator}getPipeline(){return O(this.middlewares)}async executePipeline(e){await this.getPipeline()(e)}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e)?.add(t),this}emit(e,...t){let n=this.listeners.get(e);if(n)for(let e of n)e(...t)}off(e,t){return this.listeners.get(e)?.delete(t),this}getPlugins(){return[...this.plugins]}clear(){this.plugins=[],this.middlewares=[],this.listeners.clear(),this.cache.clear(),this.deduplicator.clear()}},he={name:`logger`,setup(e){e.on(`request:start`,(...e)=>{let t=e[0];console.log(`📤 Request started: ${t}`)}),e.on(`request:success`,(...e)=>{let t=e[0],n=e[1];console.log(`✅ Request succeeded: ${t} (${n})`)}),e.on(`request:error`,(...e)=>{let t=e[0],n=e[1];console.error(`❌ Request failed: ${t}`,n)})}},ge=class{name=`metrics`;metrics={requests:0,errors:0,totalTime:0,avgTime:0};setup(e){e.on(`request:complete`,(...e)=>{let t=e[0],n=e[1];this.metrics.requests++,this.metrics.totalTime+=t,this.metrics.avgTime=this.metrics.totalTime/this.metrics.requests,n||this.metrics.errors++})}getMetrics(){return{...this.metrics}}},_e=class{name=`cache`;ttlMs;constructor(e=6e4){this.ttlMs=e}setup(e){e.addMiddleware(C({ttlMs:this.ttlMs}))}},ve=class{name=`dedupe`;setup(e){e.addMiddleware(T())}},ye=class{name=`rate-limit`;options;constructor(e={}){this.options=e}setup(e){e.addMiddleware(E({maxRequests:this.options.maxRequests??100,windowMs:this.options.windowMs??6e4,keyGenerator:this.options.keyGenerator,errorResponse:this.options.errorResponse}))}},be=class{name=`circuit-breaker`;options;constructor(e={}){this.options=e}setup(e){e.addMiddleware(D({failureThreshold:this.options.failureThreshold??5,resetTimeout:this.options.resetTimeout??3e4,isFailure:this.options.isFailure,keyGenerator:this.options.keyGenerator}))}},F=n({Http2SessionPool:()=>B,closeHttp2SessionPool:()=>De,getHttp2SessionPoolStats:()=>Oe,nodeHttp2Adapter:()=>Ee,nodeHttpAdapter:()=>Te});async function xe(){return L||(L=await import(`http`),R=await import(`https`)),{http:L,https:R}}async function Se(){return z||=await import(`http2`),z}function I(e,t){let n,r,i,a,o;if(typeof e==`string`)n=e,r=t?.method||`GET`,i=t?.headers||{},a=t?.body,o=t?.signal===null?void 0:t?.signal;else{n=e.url,r=t?.method||e.method||`GET`;let s=new Headers(e.headers),c=new Headers(t?.headers),l=new Headers(s);c.forEach((e,t)=>l.set(t,e)),i=Object.fromEntries(l.entries()),a=t?.body??e.body??void 0,o=t?.signal===null?e.signal:t?.signal}return{url:n,method:r,headers:i,body:a,signal:o}}function Ce(e,t,n,r,i=!1){if(i)return null;let a=e.startsWith(`https:`)?n.Agent:t.Agent,o={keepAlive:r?.keepAlive??!0,maxSockets:r?.maxSockets??50,maxFreeSockets:r?.maxFreeSockets??10,timeout:r?.timeout??6e4};return r?.maxRequestsPerSocket!==void 0&&(o.maxRequestsPerSocket=r.maxRequestsPerSocket),new a(o)}async function we(e){let t=new Headers;for(let[n,r]of Object.entries(e.headers))Array.isArray(r)?r.forEach(e=>t.append(n,e)):r!==void 0&&t.set(n,String(r));let n=[];return e.on(`data`,e=>n.push(e)),new Promise(r=>{e.on(`end`,()=>{let i=Buffer.concat(n);r(new Response(i,{status:e.statusCode||200,statusText:e.statusMessage||`OK`,headers:t}))})})}async function Te(e,t,n){let{http:r,https:i}=await xe(),{url:a,method:o,headers:s,body:c,signal:l}=I(e,t),u=new URL(a),d=u.protocol===`https:`,f=d?i:r,p=Ce(a,r,i,n,!1);return new Promise((e,t)=>{let r=f.request({hostname:u.hostname,port:u.port||(d?443:80),path:u.pathname+u.search,method:o,headers:s,agent:p});if(l){if(l.aborted){r.destroy(),t(Error(`Request aborted`));return}let e=()=>{r.destroy(),t(Error(`Request aborted`))};l.addEventListener(`abort`,e);let n=()=>l.removeEventListener(`abort`,e);r.on(`close`,n),r.on(`error`,n)}r.setTimeout(n?.timeout??6e4,()=>{r.destroy(),t(Error(`Request timed out`))}),r.on(`response`,async n=>{try{e(await we(n))}catch(e){t(e)}}),r.on(`error`,t),c&&(typeof c==`string`?r.write(c):c instanceof Uint8Array?r.write(Buffer.from(c)):Buffer.isBuffer(c)?r.write(c):typeof c==`object`&&r.write(JSON.stringify(c))),r.end()})}async function Ee(e,t,n){let{url:r,method:i,headers:a,body:o,signal:s}=I(e,t),c=new URL(r),l=c.origin;return new Promise(async(e,t)=>{let r,u,d=!1,f=()=>{d||(d=!0,V.releaseSession(l))},p=e=>{f(),t(e)},m=t=>{f(),e(t)};try{if(r=await V.getSession(l,n),s?.aborted){p(Error(`Request aborted`));return}if(u=r.request({":path":c.pathname+c.search,":method":i,...a}),n?.timeout&&u.setTimeout(n.timeout,()=>{u.close(),p(Error(`Request timed out`))}),s){let e=()=>{u.close(),p(Error(`Request aborted`))};s.addEventListener(`abort`,e);let t=()=>s.removeEventListener(`abort`,e);u.on(`close`,t),u.on(`error`,t)}let e=[];u.on(`data`,t=>e.push(Buffer.isBuffer(t)?t:Buffer.from(t))),u.on(`response`,t=>{let n=Number(t[`:status`])||200,r=new Headers;for(let[e,n]of Object.entries(t))e.startsWith(`:`)||(Array.isArray(n)?n.forEach(t=>r.append(e,t)):n!==void 0&&r.set(e,String(n)));u.on(`end`,()=>{let t=Buffer.concat(e);m(new Response(t,{status:n,statusText:`OK`,headers:r}))})}),u.on(`error`,e=>{p(e)}),o&&(typeof o==`string`||o instanceof Uint8Array||Buffer.isBuffer(o)?u.write(o):typeof o==`object`&&u.write(JSON.stringify(o))),u.end()}catch(e){p(e instanceof Error?e:Error(String(e)))}})}function De(){V.closeAll()}function Oe(){let e=V.getStats();return{sessionCount:e.sessionCount,origins:e.origins}}var L,R,z,B,V,H=t((()=>{B=class{sessions=new Map;cleanupInterval=null;maxIdleTime=3e4;maxRequestsPerSession=1e3;constructor(){this.startCleanup()}startCleanup(){this.cleanupInterval||=setInterval(()=>this.cleanup(),1e4)}cleanup(){let e=Date.now();for(let t of this.sessions.values())!t.closing&&e-t.lastUsed>this.maxIdleTime&&this.closeSession(t,`idle timeout`),!t.closing&&t.requestCount>=this.maxRequestsPerSession&&this.closeSession(t,`max requests exceeded`)}closeSession(e,t){e.closing=!0,e.session.close(),this.sessions.delete(e.origin)}async getSession(e,t){let n=this.sessions.get(e);if(n&&!n.session.closed&&!n.session.destroyed)return n.lastUsed=Date.now(),n.requestCount++,n.session;let r=(await Se()).connect(e,{settings:t?.http2Settings});return r.on(`error`,t=>{this.sessions.delete(e)}),r.on(`close`,()=>{this.sessions.delete(e)}),n={session:r,lastUsed:Date.now(),requestCount:1,origin:e,closing:!1},this.sessions.set(e,n),r}releaseSession(e){let t=this.sessions.get(e);t&&(t.lastUsed=Date.now())}getStats(){return{sessionCount:this.sessions.size,origins:Array.from(this.sessions.keys()),sessions:Array.from(this.sessions.values()).map(e=>({origin:e.origin,requestCount:e.requestCount,lastUsed:e.lastUsed,closing:e.closing,sessionAlive:!e.session.closed&&!e.session.destroyed}))}}closeAll(){for(let e of this.sessions.values())e.closing||e.session.close();this.sessions.clear(),this.cleanupInterval&&=(clearInterval(this.cleanupInterval),null)}},V=new B}));function ke(){return typeof window>`u`&&typeof process<`u`&&process.versions?.node!==void 0}function Ae(){let e=globalThis;return typeof window>`u`&&e.Deno!==void 0&&e.Deno.version?.deno!==void 0}function je(){let e=globalThis;return e.Bun!==void 0&&e.Bun.version!==void 0}function Me(){let e=globalThis;return e.caches!==void 0&&e.WebSocketPair!==void 0}function Ne(e,t){if(!e||e===`fetch`)return fetch;if(e===`node`||e===`http2`){if(!ke())throw Error(`Transport '${e}' is only available in Node.js environment`);return(n,r)=>e===`http2`?Promise.resolve().then(()=>(H(),F)).then(e=>e.nodeHttp2Adapter(n,r,t)):Promise.resolve().then(()=>(H(),F)).then(e=>e.nodeHttpAdapter(n,r,t))}switch(e){case`deno`:if(!Ae())throw Error(`Transport 'deno' is only available in Deno environment`);return fetch;case`bun`:if(!je())throw Error(`Transport 'bun' is only available in Bun environment`);return fetch;case`cloudflare`:if(!Me())throw Error(`Transport 'cloudflare' is only available in Cloudflare Workers environment`);return fetch;default:return fetch}}var Pe=class{store=new S;get(e){return this.store.get(e)}set(e,t,n=6e4){this.store.set(e,t,n)}has(e){return this.store.has(e)}clear(){this.store.clear()}},Fe=class{maxAttempts;baseDelayMs;constructor(e=3,t=100){this.maxAttempts=e,this.baseDelayMs=t}shouldRetry(e,t){let n=t.status!==void 0&&t.status>=500,r=t.code===`NETWORK_ERROR`;return e<this.maxAttempts&&(n||r||t.code===`TIMEOUT`)}delayMs(e){let t=this.baseDelayMs*2**(e-1),n=Math.random()*t*.1;return Math.min(t+n,3e4)}},Ie=class{maxAttempts;baseDelayMs;condition;constructor(e=3,t=100,n){this.maxAttempts=e,this.baseDelayMs=t,this.condition=n}shouldRetry(e,t){if(e>=this.maxAttempts)return!1;if(this.condition)return this.condition(t,e);let n=t.status!==void 0&&t.status>=500,r=t.code===`NETWORK_ERROR`;return n||r||t.code===`TIMEOUT`}delayMs(e){let t=this.baseDelayMs*2**(e-1),n=Math.random()*t*.1;return Math.min(t+n,3e4)}},Le=class{running=0;queue=[];maxConcurrent;constructor(e){this.maxConcurrent=e}async acquire(){if(this.running<this.maxConcurrent){this.running++;return}return new Promise(e=>{this.queue.push(()=>{this.running++,e()})})}release(){this.running--;let e=this.queue.shift();e&&e()}get pending(){return this.queue.length}get active(){return this.running}};function U(e){return e==null?!1:typeof Blob<`u`&&e instanceof Blob||typeof File<`u`&&e instanceof File?!0:Array.isArray(e)?e.some(e=>U(e)):typeof e==`object`?Object.values(e).some(e=>U(e)):!1}function W(e,t,n){let r=t||new FormData;for(let[t,i]of Object.entries(e)){let e=n?`${n}[${t}]`:t;i!=null&&(typeof Blob<`u`&&i instanceof Blob?r.append(e,i):Array.isArray(i)?i.some(e=>U(e))?i.forEach(t=>{U(t)?r.append(e,t):r.append(e,JSON.stringify(t))}):r.append(e,JSON.stringify(i)):typeof i==`object`&&!(i instanceof Blob)?W(i,r,e):r.append(e,String(i)))}return r}function Re(e,t){return!t||e==null||typeof FormData<`u`&&e instanceof FormData||typeof Blob<`u`&&e instanceof Blob||typeof URLSearchParams<`u`&&e instanceof URLSearchParams||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||typeof ReadableStream<`u`&&e instanceof ReadableStream||typeof e==`string`?e:typeof e==`object`&&U(e)?W(e):e}function ze(e,t=!0){let n=Re(e,t);return n==null?{serialized:void 0,contentType:null}:typeof FormData<`u`&&n instanceof FormData?{serialized:n,contentType:null}:typeof URLSearchParams<`u`&&n instanceof URLSearchParams?{serialized:n,contentType:`application/x-www-form-urlencoded`}:typeof Blob<`u`&&n instanceof Blob?{serialized:n,contentType:n.type||`application/octet-stream`}:n instanceof ArrayBuffer||ArrayBuffer.isView(n)||typeof ReadableStream<`u`&&n instanceof ReadableStream?{serialized:n,contentType:`application/octet-stream`}:typeof n==`string`?{serialized:n,contentType:`text/plain`}:{serialized:JSON.stringify(n),contentType:`application/json`}}function G(e,t){return t?e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(e,n)=>{let r=t[n];if(r===void 0)throw Error(`Missing path parameter: :${n}`);return encodeURIComponent(String(r))}):e}function K(e,t){if(e!==void 0)return e;if(t!==void 0)return t?`include`:`same-origin`}function Be(e){return typeof e==`number`?{total:e}:typeof e==`object`&&e?{connection:e.connection,response:e.response,total:e.total}:{}}var q=class e{requestInterceptors=[];responseInterceptors=[];cache;config;requestQueue;pendingRequests=new Map;constructor(e={}){this.config={baseURL:e.baseURL??``,defaultHeaders:e.defaultHeaders??{"Content-Type":`application/json`},defaultTimeout:e.defaultTimeout??3e4,validateStatus:e.validateStatus??(e=>e>=200&&e<300),cacheStrategy:e.cacheStrategy??new Pe,maxConcurrent:e.maxConcurrent??0,defaultResponseType:e.defaultResponseType??`auto`,defaultHooks:e.defaultHooks??{},transformRequest:e.transformRequest,credentials:K(e.credentials,e.withCredentials),adapter:e.adapter,autoFormData:e.autoFormData??!0,debug:e.debug,logger:e.logger,transport:e.transport,nodeOptions:e.nodeOptions},this.cache=this.config.cacheStrategy,this.requestQueue=this.config.maxConcurrent>0?new Le(this.config.maxConcurrent):null}logDebug(e,t,n,r,i){let a=e??this.config.debug;if(!a||a===!0&&t===`verbose`)return;let o=`[Nexa HTTP] `+n,s=i??this.config.logger??console.log;r===void 0?s(o):s(o,r)}async request(e){let t={...this.config.defaultHooks,...e.hooks},n=e.debug??this.config.debug,a=e.logger??this.config.logger,o=this.getMaxAttempts(e.retry),s=this.getRetryStrategy(e.retry),c=Symbol(`request`);t.onStart?.(this.buildRequest(e)),this.requestQueue&&await this.requestQueue.acquire();try{for(let l=1;l<=o;l++){let u;try{if((e.method===`GET`||!e.method)&&e.cache?.enabled){let i=this.getCacheKey(e),o=this.cache.get(i);if(o){let i=o;return this.logDebug(n,`info`,`Cache hit for ${e.url}`,i,a),t.onSuccess?.(i),t.onFinally?.(),r(i)}}u=this.buildRequest(e),this.logDebug(n,`info`,`${u.method||`GET`} ${u.url}`,u,a);for(let e of this.requestInterceptors)u=await e.onRequest(u);this.logDebug(n,`verbose`,`Request after interceptors`,u,a),u=this.applyTransformRequestToRequest(u,e),this.logDebug(n,`verbose`,`Request after transformRequest`,u,a);let i=Be(e.timeout??this.config.defaultTimeout),s=new AbortController;this.pendingRequests.set(c,s),e.signal&&e.signal.addEventListener(`abort`,()=>s.abort(),{once:!0}),this.logDebug(n,`info`,`Fetching (attempt ${l}/${o})`,{url:u.url,method:u.method},a);let d=performance.now(),f=await this.fetchWithTimeout(u,i,s),p=performance.now()-d;this.logDebug(n,`info`,`Response ${f.status} ${f.statusText}`,{duration:p,status:f.status,attempt:l},a);let m=f;e.onDownloadProgress&&f.body&&(m=this.trackDownloadProgress(f,e.onDownloadProgress));let h=e.responseType??this.config.defaultResponseType,g=await this.parseResponse(m,u,p,h,i.response);if(!this.config.validateStatus(g.status))throw{message:`Request failed with status ${g.status}`,status:g.status,statusText:g.statusText,code:`HTTP_ERROR`,request:u,response:g,config:e};if(e.validate){let t=e.validate.validate(g.data);if(!t.ok)return t}e.transform&&(g.data=e.transform.transform(g.data));let _=g;for(let e of this.responseInterceptors)_=await e.onResponse(_);if((e.method===`GET`||!e.method)&&e.cache?.enabled){let t=this.getCacheKey(e);this.cache.set(t,_,e.cache.ttlMs)}return t.onSuccess?.(_),this.logDebug(n,`verbose`,`Response data`,_.data,a),this.pendingRequests.delete(c),r(_)}catch(r){let d=this.normalizeError(r,u,e);if(this.logDebug(n,`info`,`Error: ${d.message}`,{error:d,attempt:l},a),l<o&&s.shouldRetry(l,d)){t.onRetry?.(l,d);let e=s.delayMs(l);this.logDebug(n,`info`,`Retrying after ${e}ms (attempt ${l+1}/${o})`,{error:d},a),await this.delay(e);continue}let f=d;for(let e of this.responseInterceptors)e.onError&&(f=await e.onError(f));return t.onError?.(f),this.pendingRequests.delete(c),i(f)}}let l={message:`Max retries exceeded`,code:`MAX_RETRIES`};return this.logDebug(n,`info`,`Max retries exceeded`,l,a),t.onError?.(l),i(l)}finally{t.onFinally?.(),this.requestQueue&&this.requestQueue.release()}}get(e,t){return this.request({...t,url:e,method:`GET`})}post(e,t,n){return this.request({...n,url:e,method:`POST`,body:t})}put(e,t,n){return this.request({...n,url:e,method:`PUT`,body:t})}patch(e,t,n){return this.request({...n,url:e,method:`PATCH`,body:t})}delete(e,t){return this.request({...t,url:e,method:`DELETE`})}head(e,t){return this.request({...t,url:e,method:`HEAD`})}options(e,t){return this.request({...t,url:e,method:`OPTIONS`})}addRequestInterceptor(e){return this.requestInterceptors.push(e),()=>{let t=this.requestInterceptors.indexOf(e);t!==-1&&this.requestInterceptors.splice(t,1)}}addResponseInterceptor(e){return this.responseInterceptors.push(e),()=>{let t=this.responseInterceptors.indexOf(e);t!==-1&&this.responseInterceptors.splice(t,1)}}clearInterceptors(){this.requestInterceptors=[],this.responseInterceptors=[]}clearCache(){this.cache.clear()}cancelAll(){for(let e of this.pendingRequests.values())e.abort();this.pendingRequests.clear()}get activeRequests(){return this.pendingRequests.size}get queueStats(){return{active:this.requestQueue?.active??this.pendingRequests.size,pending:this.requestQueue?.pending??0}}extend(t={}){let n=new e({baseURL:t.baseURL??this.config.baseURL,defaultHeaders:{...this.config.defaultHeaders,...t.defaultHeaders},credentials:K(t.credentials,t.withCredentials)??this.config.credentials,defaultTimeout:t.defaultTimeout??this.config.defaultTimeout,validateStatus:t.validateStatus??this.config.validateStatus,cacheStrategy:t.cacheStrategy??this.cache,maxConcurrent:t.maxConcurrent??this.config.maxConcurrent,defaultResponseType:t.defaultResponseType??this.config.defaultResponseType,defaultHooks:{...this.config.defaultHooks,...t.defaultHooks},transformRequest:t.transformRequest??this.config.transformRequest,adapter:t.adapter??this.config.adapter,autoFormData:t.autoFormData??this.config.autoFormData});for(let e of this.requestInterceptors)n.addRequestInterceptor(e);for(let e of this.responseInterceptors)n.addResponseInterceptor(e);return n}async*paginate(e,t,n={}){let r={...n};for(;;){let n=await this.get(e,r);if(!n.ok)break;yield t.getItems(n.value.data);let i=t.getNextPage(n.value.data,r);if(!i)break;r=i}}async poll(e,t,n={}){let r=t.maxAttempts??0;for(let i=1;r===0||i<=r;i++){let a=await this.get(e,n);if(!a.ok||(t.onPoll?.(a.value.data,i),t.until(a.value.data)))return a;if(r>0&&i>=r)break;await this.delay(t.intervalMs)}return i({message:`Polling exhausted after ${r} attempts`,code:`POLL_EXHAUSTED`})}buildRequest(e){let t=G(e.url,e.params),n=this.buildUrl(t,e.query),r=K(e.credentials,e.withCredentials)??this.config.credentials,i=e.transport??this.config.transport,a=e.nodeOptions??this.config.nodeOptions;return{url:n,method:e.method??`GET`,headers:{...this.config.defaultHeaders,...e.headers},body:e.body,query:e.query,params:e.params,timeout:e.timeout,signal:e.signal,credentials:r,adapter:e.adapter,autoFormData:e.autoFormData??this.config.autoFormData,transport:i,nodeOptions:a}}buildUrl(e,t){let n=this.config.baseURL+e;if(t&&Object.keys(t).length>0){let e=new URLSearchParams;Object.entries(t).forEach(([t,n])=>{e.append(t,String(n))}),n+=`?${e.toString()}`}return n}applyTransformRequestToRequest(e,t){let n=this.config.transformRequest,r=t.transformRequest,i=[];if(n&&(Array.isArray(n)?i.push(...n):i.push(n)),r&&(Array.isArray(r)?i.push(...r):i.push(r)),i.length===0)return e;let a=e.body,o=e.headers??{};for(let e of i)a=e(a,o);return{...e,body:a,headers:o}}getCacheKey(e){let t=G(e.url,e.params),n=e.query?JSON.stringify(e.query):``;return`${e.method??`GET`}:${t}${n?`:`+n:``}`}fetchWithTimeout(e,t,n){let{serialized:r,contentType:i}=ze(e.body,e.autoFormData),a={...e.headers};i?a[`Content-Type`]=i:i===null&&r instanceof FormData&&delete a[`Content-Type`];let o=t.total??t.connection;return new Promise((t,i)=>{let s=null;o!==void 0&&(s=setTimeout(()=>{n.abort();let e=Error(`Request timed out`);e.name=`TimeoutError`,i(e)},o));let c={method:e.method,headers:a,body:r,signal:n.signal};e.credentials!==void 0&&(c.credentials=e.credentials);let l=e.transport??this.config.transport??`fetch`,u=e.nodeOptions??this.config.nodeOptions,d=e.adapter??this.config.adapter;d||=Ne(l,u),d(e.url,c).then(e=>{s&&clearTimeout(s),t(e)},e=>{s&&clearTimeout(s),i(e)})})}trackDownloadProgress(e,t){let n=parseInt(e.headers.get(`content-length`)||`0`,10),r=e.body?.getReader();if(!r)return e;let i=0,a=new ReadableStream({async pull(e){let{done:a,value:o}=await r.read();if(a){e.close();return}i+=o.byteLength,t({loaded:i,total:n,percent:n>0?Math.round(i/n*100):0}),e.enqueue(o)}});return new Response(a,{headers:e.headers,status:e.status,statusText:e.statusText})}withTimeout(e,t,n=`Operation timed out`){return t<=0?e:new Promise((r,i)=>{let a=setTimeout(()=>{let e=Error(n);e.name=`TimeoutError`,i(e)},t);e.then(e=>{clearTimeout(a),r(e)},e=>{clearTimeout(a),i(e)})})}async parseResponse(e,t,n,r,i){let a=await this.parseBody(e,r,i);return{status:e.status,statusText:e.statusText,headers:e.headers,data:a,request:t,duration:n}}async parseBody(e,t,n){let r=async e=>n!==void 0&&n>0?this.withTimeout(e,n,`Response timeout`):e;switch(t){case`json`:return await r(e.json());case`text`:return await r(e.text());case`blob`:return await r(e.blob());case`arrayBuffer`:return await r(e.arrayBuffer());case`formData`:return await r(e.formData());case`stream`:return e.body;default:{let t=e.headers.get(`content-type`)??``;if(t.includes(`application/json`))return await r(e.json());if(t.includes(`text/`))return await r(e.text());if(t.includes(`multipart/form-data`))return await r(e.formData());if(t.includes(`application/octet-stream`)||t.includes(`image/`)||t.includes(`audio/`)||t.includes(`video/`))return await r(e.blob());try{return await r(e.json())}catch{return await r(e.text())}}}}normalizeError(e,t,n){let r=(r,i)=>({message:i,code:r,originalError:e,request:t,config:n});return e instanceof Error&&e.name===`TimeoutError`?e.message.includes(`Response timeout`)?r(`RESPONSE_TIMEOUT`,e.message):r(`TIMEOUT`,`Request timed out`):e instanceof DOMException&&e.name===`AbortError`?r(`ABORTED`,`Request aborted`):e instanceof Error?e.name===`AbortError`||e.message.includes(`abort`)?r(`ABORTED`,`Request aborted`):{message:e.message,code:e.name===`TypeError`?`NETWORK_ERROR`:`UNKNOWN_ERROR`,originalError:e,request:t,config:n}:this.isHttpErrorDetails(e)?{...e,request:e.request??t,config:e.config??n}:{message:String(e),code:`UNKNOWN_ERROR`,originalError:e,request:t,config:n}}isHttpErrorDetails(e){return typeof e==`object`&&!!e&&`message`in e&&`code`in e}getMaxAttempts(e){return e?`shouldRetry`in e?100:e.maxAttempts??3:1}getRetryStrategy(e){if(!e)return{shouldRetry:()=>!1,delayMs:()=>0};if(`shouldRetry`in e)return e;let t=e;return t.on?new Ie(t.maxAttempts??3,t.backoffMs??100,t.on):new Fe(t.maxAttempts??3,t.backoffMs??100)}delay(e){return new Promise(t=>setTimeout(t,e))}},J=class extends Error{status;code;response;constructor(e,t,n,r){super(e),this.name=`HttpError`,this.status=t,this.code=n,this.response=r}};function Ve(e){return e instanceof J}function He(e){return new q(e)}function Y(){return typeof window>`u`&&typeof process<`u`&&process.versions?.node!==void 0}var Ue=class{url;options;pluginManager;status=`closed`;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;stats={messagesSent:0,messagesReceived:0,connectionTime:0,reconnectAttempts:0};connectionStartTime=0;listeners={open:new Set,close:new Set,error:new Set,message:new Set};constructor(e,t={}){this.url=e,this.options=t,this.pluginManager=new P}updateStatus(e){this.status=e,e===`open`?this.connectionStartTime=Date.now():e===`closed`&&this.connectionStartTime>0&&(this.stats.connectionTime+=Date.now()-this.connectionStartTime,this.connectionStartTime=0)}emitOpen(e){this.pluginManager.emit(`websocket:open`,this.url,e);for(let t of this.listeners.open)t(e)}emitClose(e){this.pluginManager.emit(`websocket:close`,this.url,e);for(let t of this.listeners.close)t(e)}emitError(e){let t;e instanceof Error?(t=new Event(`error`),t.error=e):t=e,this.pluginManager.emit(`websocket:error`,this.url,t);for(let e of this.listeners.error)e(t)}emitMessage(e){this.pluginManager.emit(`websocket:message`,this.url,e);for(let t of this.listeners.message)t(e)}onOpen(e){return this.listeners.open.add(e),()=>this.listeners.open.delete(e)}onClose(e){return this.listeners.close.add(e),()=>this.listeners.close.delete(e)}onError(e){return this.listeners.error.add(e),()=>this.listeners.error.delete(e)}onMessage(e){return this.listeners.message.add(e),()=>this.listeners.message.delete(e)}getStatus(){return this.status}getStats(){return{...this.stats,connectionTime:this.stats.connectionTime+(this.connectionStartTime>0?Date.now()-this.connectionStartTime:0)}}scheduleReconnect(){if(this.options.reconnect?.enabled===!1)return;let e=this.options.reconnect?.maxAttempts??1/0;if(this.reconnectAttempt>=e){this.pluginManager.emit(`websocket:reconnect:failed`,this.url,this.reconnectAttempt);return}let t=this.options.reconnect?.baseDelay??1e3,n=this.options.reconnect?.maxDelay??3e4,r=Math.min(n,t*2**this.reconnectAttempt);this.reconnectAttempt++,this.stats.reconnectAttempts=this.reconnectAttempt,this.options.reconnect?.onReconnecting?.(this.reconnectAttempt,r),this.pluginManager.emit(`websocket:reconnecting`,this.url,this.reconnectAttempt,r),this.reconnectTimer=setTimeout(()=>{this.connect().catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))})},r)}startHeartbeat(){if(!this.options.heartbeat)return;let e=this.options.heartbeat.interval??3e4,t=this.options.heartbeat.pingMessage??`ping`,n=this.options.heartbeat.pongMessage??`pong`,r=!0,i=()=>{if(!r){this.pluginManager.emit(`websocket:heartbeat:timeout`,this.url),this.disconnect();return}r=!1,this.send(t),this.heartbeatTimer=setTimeout(i,e)};this.onMessage(e=>{let t=e.raw;typeof t==`string`&&t===n&&(r=!0)}),this.heartbeatTimer=setTimeout(i,e)}stopHeartbeat(){this.heartbeatTimer&&=(clearTimeout(this.heartbeatTimer),null)}cleanup(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),null),this.stopHeartbeat()}},X=class extends Ue{socket=null;messageTypeListeners=new Map;constructor(e,t={}){super(e,t)}async connect(){if(!(this.status===`connecting`||this.status===`open`))return this.updateStatus(`connecting`),this.pluginManager.emit(`websocket:connect:start`,this.url),new Promise((e,t)=>{let n=this.options.timeout??1e4,r=setTimeout(()=>{this.updateStatus(`closed`),this.socket?.close(),this.socket=null;let e=Error(`WebSocket connection timeout after ${n}ms`);this.emitError(e),t(e)},n);try{this.socket=new WebSocket(this.url,this.options.protocols),this.socket.onopen=t=>{clearTimeout(r),this.updateStatus(`open`),this.reconnectAttempt=0,this.emitOpen(t),this.options.onOpen?.(t),this.startHeartbeat(),this.pluginManager.emit(`websocket:connect:success`,this.url),e()},this.socket.onclose=e=>{clearTimeout(r),this.updateStatus(`closed`),this.emitClose(e),this.options.onClose?.(e),this.stopHeartbeat(),this.pluginManager.emit(`websocket:disconnected`,this.url,e.code,e.reason),e.code!==1e3&&!e.wasClean&&this.scheduleReconnect()},this.socket.onerror=e=>{clearTimeout(r),this.updateStatus(`closed`),this.emitError(e),this.options.onError?.(e),this.pluginManager.emit(`websocket:connect:error`,this.url,e),t(e)},this.socket.onmessage=e=>{this.stats.messagesReceived++;let t={data:this.tryParseData(e.data),raw:e.data,type:`message`,timestamp:Date.now()};this.emitMessage(t),this.pluginManager.emit(`websocket:message:received`,this.url,t)}}catch(e){clearTimeout(r),this.updateStatus(`closed`),this.pluginManager.emit(`websocket:connect:error`,this.url,e),t(e)}})}disconnect(){this.cleanup(),this.socket&&(this.updateStatus(`closing`),this.socket.close(1e3,`Client disconnected`),this.socket=null,this.updateStatus(`closed`))}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)throw Error(`WebSocket is not connected`);this.socket.send(e),this.stats.messagesSent++,this.pluginManager.emit(`websocket:message:sent`,this.url,e)}sendJson(e){this.send(JSON.stringify(e))}onMessageType(e,t){return this.messageTypeListeners.has(e)||this.messageTypeListeners.set(e,new Set),this.messageTypeListeners.get(e).add(t),()=>{let n=this.messageTypeListeners.get(e);n&&(n.delete(t),n.size===0&&this.messageTypeListeners.delete(e))}}tryParseData(e){if(typeof e==`string`)try{return JSON.parse(e)}catch{return e}return e}},We=class extends X{async connect(){if(Y())try{let{default:e}=await import(`ws`);return super.connect()}catch{throw Error(`WebSocket client for Node.js requires the "ws" package. Please install it: npm install ws`)}return super.connect()}};function Ge(e,t={}){return Y()?new We(e,t):new X(e,t)}function Z(){return typeof window>`u`&&typeof process<`u`&&process.versions?.node!==void 0}var Ke=class{url;options;_source=null;pluginManager;status=`closed`;reconnectAttempt=0;reconnectTimer=null;stats={messagesSent:0,messagesReceived:0,connectionTime:0,reconnectAttempts:0};connectionStartTime=0;listeners={open:new Set,close:new Set,error:new Set,message:new Set,event:new Map};_lastEventId=null;constructor(e,t={}){this.url=e,this.options=t,this.pluginManager=new P}updateStatus(e){this.status=e,e===`open`?this.connectionStartTime=Date.now():e===`closed`&&this.connectionStartTime>0&&(this.stats.connectionTime+=Date.now()-this.connectionStartTime,this.connectionStartTime=0)}async connect(){if(!(this.status===`connecting`||this.status===`open`))return this.updateStatus(`connecting`),this.pluginManager.emit(`sse:connect:start`,this.url),new Promise((e,t)=>{let n=this.options.timeout??1e4,r=setTimeout(()=>{this.updateStatus(`closed`),this._source?.close(),this._source=null;let e=Error(`SSE connection timeout after ${n}ms`);this.emitError(e),t(e)},n);try{this._source=new EventSource(this.url),this._source.onopen=t=>{clearTimeout(r),this.updateStatus(`open`),this.reconnectAttempt=0,this.emitOpen(t),this.options.onOpen?.(t),this.pluginManager.emit(`sse:connect:success`,this.url),e()},this._source.onerror=e=>{clearTimeout(r),this.updateStatus(`closed`),this.emitError(e),this.options.onError?.(e),this.pluginManager.emit(`sse:connect:error`,this.url,e),this._source?.readyState===EventSource.CLOSED&&(this.emitClose(),this.options.onClose?.(),this.scheduleReconnect()),t(e)},this._source.onmessage=e=>{this.stats.messagesReceived++,this._lastEventId=e.lastEventId||this._lastEventId;let t={data:this.tryParseData(e.data),raw:e.data,type:e.type||`message`,timestamp:Date.now()};this.emitMessage(t),this.pluginManager.emit(`sse:message:received`,this.url,t);let n=e.type||`message`,r=this.listeners.event.get(n);if(r)for(let e of r)e(t.data)},this._source.addEventListener=this._source.addEventListener.bind(this._source)}catch(e){clearTimeout(r),this.updateStatus(`closed`),this.pluginManager.emit(`sse:connect:error`,this.url,e),t(e)}})}disconnect(){this.cleanup(),this._source&&(this.updateStatus(`closing`),this._source.close(),this._source=null,this.updateStatus(`closed`),this.emitClose(),this.options.onClose?.())}send(e){throw Error(`SSE is a receive-only protocol. Use HTTP requests to send data to server.`)}onMessage(e){return this.listeners.message.add(e),()=>this.listeners.message.delete(e)}onOpen(e){return this.listeners.open.add(e),()=>this.listeners.open.delete(e)}onClose(e){return this.listeners.close.add(e),()=>this.listeners.close.delete(e)}onError(e){return this.listeners.error.add(e),()=>this.listeners.error.delete(e)}onEvent(e,t){return this.listeners.event.has(e)||this.listeners.event.set(e,new Set),this.listeners.event.get(e).add(t),this._source&&!this._source[`on${e}`]&&this._source.addEventListener(e,n=>{t({data:this.tryParseData(n.data),raw:n.data,type:e,timestamp:Date.now()}.data)}),()=>{let n=this.listeners.event.get(e);n&&(n.delete(t),n.size===0&&this.listeners.event.delete(e))}}getStatus(){return this.status}getStats(){return{...this.stats,connectionTime:this.stats.connectionTime+(this.connectionStartTime>0?Date.now()-this.connectionStartTime:0)}}get lastEventId(){return this._lastEventId}get source(){return this._source}emitOpen(e){this.pluginManager.emit(`sse:open`,this.url,e);for(let t of this.listeners.open)t(e)}emitClose(){this.pluginManager.emit(`sse:close`,this.url);for(let e of this.listeners.close)e()}emitError(e){let t;e instanceof Error?(t=new Event(`error`),t.error=e):t=e,this.pluginManager.emit(`sse:error`,this.url,t);for(let e of this.listeners.error)e(t)}emitMessage(e){this.pluginManager.emit(`sse:message`,this.url,e);for(let t of this.listeners.message)t(e)}scheduleReconnect(){if(this.options.reconnect?.enabled===!1)return;let e=this.options.reconnect?.maxAttempts??1/0;if(this.reconnectAttempt>=e){this.pluginManager.emit(`sse:reconnect:failed`,this.url,this.reconnectAttempt);return}let t=this.options.reconnect?.baseDelay??1e3,n=this.options.reconnect?.maxDelay??3e4,r=Math.min(n,t*2**this.reconnectAttempt);this.reconnectAttempt++,this.stats.reconnectAttempts=this.reconnectAttempt,this.options.reconnect?.onReconnecting?.(this.reconnectAttempt,r),this.pluginManager.emit(`sse:reconnecting`,this.url,this.reconnectAttempt,r),this.reconnectTimer=setTimeout(()=>{this.connect().catch(e=>{this.emitError(e instanceof Error?e:Error(String(e)))})},r)}cleanup(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),null)}tryParseData(e){try{return JSON.parse(e)}catch{return e}}},qe=class extends Ke{async connect(){if(Z())throw Error(`SSE client for Node.js requires a polyfill or custom implementation. Consider using a library like "eventsource" or implement using fetch with streaming.`);return super.connect()}};function Je(e,t={}){return Z()?new qe(e,t):new Ke(e,t)}var Ye=class{name=`realtime`;setup(e){e.on(`websocket:connect:start`,(...t)=>{let n=t[0];e.emit(`realtime:connect:start`,`websocket`,n)}),e.on(`websocket:connect:success`,(...t)=>{let n=t[0];e.emit(`realtime:connect:success`,`websocket`,n)}),e.on(`websocket:message:received`,(...t)=>{let n=t[0],r=t[1];e.emit(`realtime:message`,`websocket`,n,r)}),e.on(`sse:connect:start`,(...t)=>{let n=t[0];e.emit(`realtime:connect:start`,`sse`,n)}),e.on(`sse:connect:success`,(...t)=>{let n=t[0];e.emit(`realtime:connect:success`,`sse`,n)}),e.on(`sse:message:received`,(...t)=>{let n=t[0],r=t[1];e.emit(`realtime:message`,`sse`,n,r)})}};function Xe(){return new Ye}var Q=class{adapter;method;urlPattern;constructor(e,t,n){this.adapter=e,this.method=t,this.urlPattern=n}reply(e,t,n){let r;return r=typeof e==`number`?{status:e,data:t,headers:n}:e,this.adapter.addRoute(this.method,this.urlPattern,r),this.adapter}replyOnce(e,t,n){let r;return r=typeof e==`number`?{status:e,data:t,headers:n}:e,this.adapter.addRoute(this.method,this.urlPattern,r,{times:1}),this.adapter}networkError(e=`Network Error`){return this.adapter.addRoute(this.method,this.urlPattern,{networkError:!0,errorMessage:e}),this.adapter}timeout(){return this.adapter.addRoute(this.method,this.urlPattern,{status:408,statusText:`Request Timeout`}),this.adapter}},$=class{routes=[];originalAdapter;mockClient;options;defaultResponse={status:200,statusText:`OK`,headers:{"content-type":`application/json`}};constructor(e,t={}){this.options=t;let n=this.createAdapter();if(typeof e.extend==`function`)this.mockClient=e.extend({adapter:n});else throw Error(`MockAdapter requires an HttpClient instance with extend() method`)}get client(){return this.mockClient}createAdapter(){return async(e,t)=>{let n=typeof e==`string`?e:e.url,r=t?.method||`GET`,i={url:n,method:r,headers:t?.headers,body:t?.body,signal:t?.signal??void 0},a=this.findMatchingRoute(r,n);if(!a)return this.options.passthrough?(this.originalAdapter||fetch)(e,t):new Response(JSON.stringify({error:`No mock route matched`}),{status:404,statusText:`Not Found`,headers:{"content-type":`application/json`}});a.timesCalled++;let o;if(o=typeof a.response==`function`?await a.response(i):a.response,o.networkError)throw TypeError(o.errorMessage||`Network Error`);let s=Object.fromEntries(Object.entries(o).filter(([e,t])=>t!==void 0)),c={...this.defaultResponse,...s};if(o.headers&&`content-type`in o.headers||(c.data instanceof Uint8Array||ArrayBuffer.isView(c.data)?c.headers={...c.headers,"content-type":`application/octet-stream`}:c.data&&typeof c.data==`object`&&(c.headers={...c.headers,"content-type":`application/json`})),c.delay||this.options.delay){let e=c.delay??this.options.delay;if(e&&e>0){let n=t?.signal;n?(n.throwIfAborted(),await new Promise((t,r)=>{let i=setTimeout(t,e),a=()=>{clearTimeout(i),r(new DOMException(`Aborted`,`AbortError`))};n.addEventListener(`abort`,a,{once:!0}),setTimeout(()=>{n.removeEventListener(`abort`,a)},e)})):await new Promise(t=>setTimeout(t,e))}}let l=new Headers(c.headers),u=c.data;(c.status===204||c.status===205)&&l.delete(`content-type`);let d;if(u==null)d=``;else if(typeof u==`string`)d=u;else if(u instanceof Uint8Array||ArrayBuffer.isView(u)){let e;if(u.buffer instanceof ArrayBuffer)e=u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength);else{let t=new Uint8Array(u.byteLength);t.set(new Uint8Array(u.buffer,u.byteOffset,u.byteLength)),e=t.buffer}return new Response(e,{status:c.status,statusText:c.statusText,headers:l})}else d=JSON.stringify(u);let f=d;return(c.status===204||c.status===205||f===``)&&(f=null),f===null&&l.delete(`content-type`),new Response(f,{status:c.status,statusText:c.statusText,headers:l})}}onGet(e){return new Q(this,`GET`,e)}onPost(e){return new Q(this,`POST`,e)}onPut(e){return new Q(this,`PUT`,e)}onPatch(e){return new Q(this,`PATCH`,e)}onDelete(e){return new Q(this,`DELETE`,e)}onAny(e){return new Q(this,`ANY`,e)}addRoute(e,t,n,r){return this.routes.push({method:e.toUpperCase(),urlPattern:t,response:n,timesCalled:0,times:r?.times}),this}reset(){this.routes=[]}restore(){}static reply(e,t,n){return{status:e,data:t,headers:n}}static networkError(e=`Network Error`){return{networkError:!0,errorMessage:e}}findMatchingRoute(e,t){let n=t;if(this.options.baseURL&&t.startsWith(this.options.baseURL))n=t.slice(this.options.baseURL.length);else try{if(n.includes(`://`)){let e=new URL(n);n=e.pathname+e.search}}catch{}for(let t of this.routes){if(t.times&&t.timesCalled>=t.times||t.method!==`ANY`&&t.method!==e.toUpperCase())continue;let r=!1;if(typeof t.urlPattern==`string`?r=n===t.urlPattern||n.startsWith(t.urlPattern):t.urlPattern instanceof RegExp&&(r=t.urlPattern.test(n)),r)return t}return null}};function Ze(e,t={}){return new $(e,t)}exports.AggressiveRetry=m,exports.CachePlugin=_e,exports.CacheStore=S,exports.CircuitBreakerPlugin=be,exports.CircuitBreakerRetry=g,exports.ConservativeRetry=h,exports.DedupePlugin=ve,exports.Defer=fe,exports.Err=i,exports.HttpClient=q,exports.HttpError=J,exports.LoggerPlugin=he,exports.MetricsPlugin=ge,exports.MiddlewarePipeline=oe,exports.MockAdapter=$,exports.Ok=r,exports.PluginManager=P,exports.RateLimitPlugin=ye,exports.RequestDeduplicator=w,exports.TypedObservable=le,exports.cacheMiddleware=ne,exports.circuitBreakerMiddleware=ae,exports.createApiUrl=de,exports.createCacheMiddleware=C,exports.createCircuitBreakerMiddleware=D,exports.createDedupeMiddleware=T,exports.createHttpClient=He,exports.createMockClient=Ze,exports.createPipeline=O,exports.createProjectionTransformer=f,exports.createRateLimitMiddleware=E,exports.createRealtimePlugin=Xe,exports.createRequiredFieldsValidator=o,exports.createSSEClient=Je,exports.createSchemaValidator=a,exports.createStreamingMiddleware=N,exports.createTypeGuard=ue,exports.createTypedApiClient=ce,exports.createTypedRequest=k,exports.createTypedResponse=se,exports.createUrl=A,exports.createWebSocketClient=Ge,exports.createWrapperTransformer=p,exports.dedupeMiddleware=re,exports.handleStream=j,exports.isHttpError=Ve,exports.rateLimitMiddleware=ie,exports.retry=x,exports.streamToFile=pe,exports.streamingMiddleware=me,exports.transformCamelToSnake=u,exports.transformFlatten=d,exports.transformSnakeToCamel=l,exports.validatorIsArray=s,exports.validatorIsObject=c,exports.withTimeout=te;
2
+ //# sourceMappingURL=nexa.cjs.js.map