@bereasoftware/nexa 1.0.5 → 1.1.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.en.md +296 -10
- package/README.md +378 -10
- package/dist/bereasoftware-nexa-1.1.0.tgz +0 -0
- package/dist/nexa.cjs.js +2 -1
- package/dist/nexa.cjs.js.map +1 -0
- package/dist/nexa.es.js +1246 -223
- package/dist/nexa.es.js.map +1 -0
- package/dist/nexa.iife.js +2 -1
- package/dist/nexa.iife.js.map +1 -0
- package/dist/nexa.umd.js +2 -1
- package/dist/nexa.umd.js.map +1 -0
- package/dist/types/http-client/http-client.d.ts +22 -10
- package/dist/types/http-client/index.d.ts +1 -1
- package/dist/types/http-client/node-http-adapter.d.ts +46 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/realtime/index.d.ts +9 -0
- package/dist/types/realtime/plugin.d.ts +12 -0
- package/dist/types/realtime/sse-client.d.ts +5 -0
- package/dist/types/realtime/websocket-client.d.ts +5 -0
- package/dist/types/testing/index.d.ts +5 -0
- package/dist/types/testing/mock-client.d.ts +152 -0
- package/dist/types/types/index.d.ts +348 -7
- package/dist/types/utils/index.d.ts +68 -0
- package/package.json +17 -8
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-
|
|
10
|
-
<a href="#test-coverage"><img src="https://img.shields.io/badge/Coverage-
|
|
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
|
-
**
|
|
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:
|
|
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 | **
|
|
1609
|
+
| HTTP Client | **66.7%** | 67.3% ramas, 67.0% funciones |
|
|
1251
1610
|
| Types | **100%** | Cobertura perfecta de tipos |
|
|
1252
|
-
|
|
|
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:
|
|
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
|
|
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)
|
|
Binary file
|
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
|