@gzl10/baserow 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +435 -0
- package/README.md +847 -0
- package/dist/index.d.ts +8749 -0
- package/dist/index.js +11167 -0
- package/dist/index.js.map +1 -0
- package/package.json +91 -0
- package/src/BaserowClient.ts +501 -0
- package/src/ClientWithCreds.ts +545 -0
- package/src/ClientWithCredsWs.ts +852 -0
- package/src/ClientWithToken.ts +171 -0
- package/src/contexts/DatabaseClientContext.ts +114 -0
- package/src/contexts/DatabaseContext.ts +870 -0
- package/src/contexts/DatabaseTokenContext.ts +331 -0
- package/src/contexts/FieldContext.ts +399 -0
- package/src/contexts/RowContext.ts +99 -0
- package/src/contexts/TableClientContext.ts +291 -0
- package/src/contexts/TableContext.ts +1247 -0
- package/src/contexts/TableOnlyContext.ts +74 -0
- package/src/contexts/WorkspaceContext.ts +490 -0
- package/src/express/errors.ts +260 -0
- package/src/express/index.ts +69 -0
- package/src/express/middleware.ts +225 -0
- package/src/express/serializers.ts +314 -0
- package/src/index.ts +247 -0
- package/src/presets/performance.ts +262 -0
- package/src/services/AuthService.ts +472 -0
- package/src/services/DatabaseService.ts +246 -0
- package/src/services/DatabaseTokenService.ts +186 -0
- package/src/services/FieldService.ts +1543 -0
- package/src/services/RowService.ts +982 -0
- package/src/services/SchemaControlService.ts +420 -0
- package/src/services/TableService.ts +781 -0
- package/src/services/WorkspaceService.ts +113 -0
- package/src/services/core/BaseAuthClient.ts +111 -0
- package/src/services/core/BaseClient.ts +107 -0
- package/src/services/core/BaseService.ts +71 -0
- package/src/services/core/HttpService.ts +115 -0
- package/src/services/core/ValidationService.ts +149 -0
- package/src/types/auth.ts +177 -0
- package/src/types/core.ts +91 -0
- package/src/types/errors.ts +105 -0
- package/src/types/fields.ts +456 -0
- package/src/types/index.ts +222 -0
- package/src/types/requests.ts +333 -0
- package/src/types/responses.ts +50 -0
- package/src/types/schema.ts +446 -0
- package/src/types/tokens.ts +36 -0
- package/src/types.ts +11 -0
- package/src/utils/auth.ts +174 -0
- package/src/utils/axios.ts +647 -0
- package/src/utils/field-cache.ts +164 -0
- package/src/utils/httpFactory.ts +66 -0
- package/src/utils/jwt-decoder.ts +188 -0
- package/src/utils/jwtTokens.ts +50 -0
- package/src/utils/performance.ts +105 -0
- package/src/utils/prisma-mapper.ts +961 -0
- package/src/utils/validation.ts +463 -0
- package/src/validators/schema.ts +419 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cliente HTTP simplificado con plugins oficiales de Axios
|
|
3
|
+
*
|
|
4
|
+
* Cliente HTTP optimizado para la API de Baserow con funcionalidades avanzadas:
|
|
5
|
+
* - Retry automático con backoff exponencial
|
|
6
|
+
* - Rate limiting configurable
|
|
7
|
+
* - Manejo robusto de errores con tipos específicos
|
|
8
|
+
* - Logging estructurado de requests y respuestas
|
|
9
|
+
* - Soporte para JWT y Database Tokens
|
|
10
|
+
* - Connection pooling automático (Node.js 20+)
|
|
11
|
+
*
|
|
12
|
+
* **Características principales:**
|
|
13
|
+
* - **Retry Logic**: 3 intentos con backoff exponencial usando axios-retry oficial
|
|
14
|
+
* - **Rate Limiting**: Control de requests/segundo con axios-rate-limit oficial
|
|
15
|
+
* - **Error Handling**: Transformación automática a errores tipados de Baserow
|
|
16
|
+
* - **Auto-detection**: Detecta formato de token (JWT vs Database Token)
|
|
17
|
+
* - **Optimizado**: 36% menos código que versiones anteriores
|
|
18
|
+
* - **Probado**: Plugins mantenidos por millones de desarrolladores
|
|
19
|
+
*
|
|
20
|
+
* **Configuración recomendada:**
|
|
21
|
+
* - Producción: maxRequestsPerSecond = 10 (default)
|
|
22
|
+
* - Testing: maxRequestsPerSecond = 1 (usar HttpClient.forTesting())
|
|
23
|
+
* - Retries: 3 (default) para alta disponibilidad
|
|
24
|
+
* - Timeout: 30s (default) para operaciones complejas
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Cliente estándar
|
|
29
|
+
* const client = new HttpClient({
|
|
30
|
+
* baseURL: 'https://baserow.example.com',
|
|
31
|
+
* token: 'your-token-here',
|
|
32
|
+
* maxRequestsPerSecond: 10,
|
|
33
|
+
* retries: 3,
|
|
34
|
+
* logger: console
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* // Cliente para testing (más conservador)
|
|
38
|
+
* const testClient = HttpClient.forTesting({
|
|
39
|
+
* baseURL: 'https://test.baserow.com',
|
|
40
|
+
* token: 'test-token'
|
|
41
|
+
* })
|
|
42
|
+
*
|
|
43
|
+
* // Uso básico
|
|
44
|
+
* const data = await client.get('/api/endpoint')
|
|
45
|
+
* const result = await client.post('/api/create', { name: 'Test' })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @since 1.0.0 - Versión simplificada con plugins oficiales
|
|
49
|
+
* @since 1.1.0 - Migración de agentkeepalive a Node.js 20+ nativo
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios'
|
|
53
|
+
import axiosRetry from 'axios-retry'
|
|
54
|
+
import rateLimit from 'axios-rate-limit'
|
|
55
|
+
import { PERFORMANCE_DEFAULTS } from './performance'
|
|
56
|
+
import {
|
|
57
|
+
BaserowError,
|
|
58
|
+
BaserowRateLimitError,
|
|
59
|
+
BaserowValidationError,
|
|
60
|
+
BaserowNotFoundError,
|
|
61
|
+
BaserowNetworkError,
|
|
62
|
+
BaserowTimeoutError,
|
|
63
|
+
Logger
|
|
64
|
+
} from '../types'
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Configuración del cliente HTTP
|
|
68
|
+
*
|
|
69
|
+
* Define todas las opciones disponibles para personalizar el comportamiento
|
|
70
|
+
* del cliente HTTP, incluyendo autenticación, retry logic y rate limiting.
|
|
71
|
+
*
|
|
72
|
+
* @since 1.0.0
|
|
73
|
+
*/
|
|
74
|
+
export interface HttpClientConfig {
|
|
75
|
+
baseURL: string
|
|
76
|
+
token: string
|
|
77
|
+
timeout?: number
|
|
78
|
+
retries?: number
|
|
79
|
+
logger?: Logger
|
|
80
|
+
// Rate limiting options simplificadas
|
|
81
|
+
maxRequestsPerSecond?: number // Máximo requests por segundo (default: 10)
|
|
82
|
+
enableRateLimiting?: boolean // Habilitar rate limiting (default: true)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Cliente HTTP avanzado para la API de Baserow
|
|
87
|
+
*
|
|
88
|
+
* Implementa un cliente HTTP robusto y optimizado con características
|
|
89
|
+
* empresariales como retry automático, rate limiting y manejo de errores.
|
|
90
|
+
* Utiliza plugins oficiales de Axios para máxima compatibilidad.
|
|
91
|
+
*
|
|
92
|
+
* **Arquitectura simplificada:**
|
|
93
|
+
* - Axios core para requests HTTP
|
|
94
|
+
* - axios-retry para lógica de reintentos
|
|
95
|
+
* - axios-rate-limit para control de velocidad
|
|
96
|
+
* - Node.js 20+ keepAlive nativo para connection pooling
|
|
97
|
+
*
|
|
98
|
+
* @since 1.0.0
|
|
99
|
+
*/
|
|
100
|
+
export class HttpClient {
|
|
101
|
+
private client: AxiosInstance
|
|
102
|
+
private config: HttpClientConfig
|
|
103
|
+
private logger?: Logger
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Crea una nueva instancia del cliente HTTP
|
|
107
|
+
*
|
|
108
|
+
* @param config - Configuración del cliente HTTP
|
|
109
|
+
* @param config.baseURL - URL base de la API de Baserow
|
|
110
|
+
* @param config.token - Token de autenticación (JWT o Database Token)
|
|
111
|
+
* @param config.timeout - Timeout en ms (default: 30000)
|
|
112
|
+
* @param config.retries - Número de reintentos (default: 3)
|
|
113
|
+
* @param config.maxRequestsPerSecond - Rate limit (default: 10)
|
|
114
|
+
* @param config.enableRateLimiting - Habilitar rate limiting (default: true)
|
|
115
|
+
* @param config.logger - Logger para debug y monitoreo
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const client = new HttpClient({
|
|
120
|
+
* baseURL: 'https://baserow.example.com',
|
|
121
|
+
* token: 'your-database-token-or-jwt',
|
|
122
|
+
* timeout: 30000,
|
|
123
|
+
* retries: 3,
|
|
124
|
+
* maxRequestsPerSecond: 10,
|
|
125
|
+
* logger: console
|
|
126
|
+
* })
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @since 1.0.0
|
|
130
|
+
*/
|
|
131
|
+
constructor(config: HttpClientConfig) {
|
|
132
|
+
this.config = {
|
|
133
|
+
...PERFORMANCE_DEFAULTS,
|
|
134
|
+
...config
|
|
135
|
+
}
|
|
136
|
+
this.logger = config.logger
|
|
137
|
+
|
|
138
|
+
// Crear instancia básica de Axios (Node.js 20+ maneja keepAlive automáticamente)
|
|
139
|
+
this.client = axios.create({
|
|
140
|
+
baseURL: config.baseURL,
|
|
141
|
+
timeout: this.config.timeout,
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
Accept: 'application/json'
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// Configurar token inicial
|
|
149
|
+
this.setAuthToken(config.token)
|
|
150
|
+
|
|
151
|
+
// Configurar interceptores básicos
|
|
152
|
+
this.setupRequestInterceptor()
|
|
153
|
+
this.setupResponseInterceptor()
|
|
154
|
+
|
|
155
|
+
// Configurar retry automático con plugin oficial
|
|
156
|
+
this.setupRetryPlugin()
|
|
157
|
+
|
|
158
|
+
// Configurar rate limiting con plugin oficial
|
|
159
|
+
if (this.config.enableRateLimiting) {
|
|
160
|
+
this.setupRateLimitingPlugin()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private getAuthHeader(token: string): string {
|
|
165
|
+
// Detectar formato del token automáticamente
|
|
166
|
+
const isJWT = token.includes('.') && token.split('.').length === 3
|
|
167
|
+
return isJWT ? `JWT ${token}` : `Token ${token}`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private extractResourceFromUrl(url: string): { resource: string; id: string } {
|
|
171
|
+
// Extraer información del contexto desde la URL para mejores mensajes de error
|
|
172
|
+
const patterns = [
|
|
173
|
+
{ pattern: /\/database\/tables\/(\d+)\//, resource: 'Table', idIndex: 1 },
|
|
174
|
+
{ pattern: /\/database\/fields\/(\d+)\//, resource: 'Field', idIndex: 1 },
|
|
175
|
+
{ pattern: /\/database\/rows\/(\d+)\//, resource: 'Row', idIndex: 1 },
|
|
176
|
+
{ pattern: /\/applications\/(\d+)\//, resource: 'Database', idIndex: 1 },
|
|
177
|
+
{ pattern: /\/workspaces\/(\d+)\/applications\//, resource: 'Workspace', idIndex: 1 },
|
|
178
|
+
{ pattern: /\/workspaces\/(\d+)\//, resource: 'Workspace', idIndex: 1 }
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
for (const { pattern, resource, idIndex } of patterns) {
|
|
182
|
+
const match = url.match(pattern)
|
|
183
|
+
if (match) {
|
|
184
|
+
return { resource, id: match[idIndex] }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Si no encontramos patrón específico, intentar extraer último número como ID
|
|
189
|
+
const genericMatch = url.match(/\/(\d+)\/?$/)
|
|
190
|
+
if (genericMatch) {
|
|
191
|
+
return { resource: 'Resource', id: genericMatch[1] }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { resource: 'Resource', id: 'unknown' }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private setupRequestInterceptor(): void {
|
|
198
|
+
this.client.interceptors.request.use(config => {
|
|
199
|
+
// Agregar timestamp para calcular duración
|
|
200
|
+
;(config as any).startTime = Date.now()
|
|
201
|
+
|
|
202
|
+
// Log de requests salientes (solo en debug)
|
|
203
|
+
if (this.logger?.debug) {
|
|
204
|
+
this.logger.debug('HTTP Request starting', {
|
|
205
|
+
method: config.method?.toUpperCase(),
|
|
206
|
+
url: config.url,
|
|
207
|
+
baseURL: config.baseURL
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return config
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private setupResponseInterceptor(): void {
|
|
216
|
+
this.client.interceptors.response.use(
|
|
217
|
+
(response: AxiosResponse) => {
|
|
218
|
+
// Log de respuestas exitosas (solo en modo debug si es necesario)
|
|
219
|
+
if (this.logger?.debug) {
|
|
220
|
+
this.logger.debug('HTTP Request successful', {
|
|
221
|
+
method: response.config.method?.toUpperCase(),
|
|
222
|
+
url: response.config.url,
|
|
223
|
+
status: response.status,
|
|
224
|
+
duration: Date.now() - (response.config as any).startTime
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
return response
|
|
228
|
+
},
|
|
229
|
+
async (error: AxiosError) => {
|
|
230
|
+
const requestInfo = {
|
|
231
|
+
method: error.config?.method?.toUpperCase(),
|
|
232
|
+
url: error.config?.url,
|
|
233
|
+
startTime: (error.config as any)?.startTime
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Transformar errores de Axios a errores de Baserow
|
|
237
|
+
if (error.response) {
|
|
238
|
+
const { status, data } = error.response
|
|
239
|
+
const errorData = data as any
|
|
240
|
+
const message = errorData?.detail || errorData?.message || `HTTP ${status}: ${error.message}`
|
|
241
|
+
|
|
242
|
+
// Log estructurado del error
|
|
243
|
+
this.logger?.error('HTTP Request failed', {
|
|
244
|
+
...requestInfo,
|
|
245
|
+
status,
|
|
246
|
+
message,
|
|
247
|
+
errorData: errorData ? JSON.stringify(errorData, null, 2) : null,
|
|
248
|
+
fullErrorData: errorData,
|
|
249
|
+
duration: requestInfo.startTime ? Date.now() - requestInfo.startTime : null
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
switch (status) {
|
|
253
|
+
case 400:
|
|
254
|
+
if (errorData?.error && typeof errorData.error === 'object') {
|
|
255
|
+
throw new BaserowValidationError(message, errorData.error)
|
|
256
|
+
}
|
|
257
|
+
// Mejorar mensaje de error para debugging
|
|
258
|
+
const detailedMessage = `${message} | Full error: ${JSON.stringify(errorData, null, 2)}`
|
|
259
|
+
throw new BaserowError(detailedMessage, 400, 'BAD_REQUEST', errorData)
|
|
260
|
+
|
|
261
|
+
case 404:
|
|
262
|
+
// Intentar extraer contexto del URL para mejor mensaje de error
|
|
263
|
+
const resourceContext = this.extractResourceFromUrl(requestInfo.url || '')
|
|
264
|
+
throw new BaserowNotFoundError(resourceContext.resource, resourceContext.id)
|
|
265
|
+
|
|
266
|
+
case 429:
|
|
267
|
+
const retryAfter = error.response.headers['retry-after']
|
|
268
|
+
throw new BaserowRateLimitError(retryAfter ? parseInt(retryAfter) : undefined)
|
|
269
|
+
|
|
270
|
+
case 401:
|
|
271
|
+
throw new BaserowError('Unauthorized - check your token', 401, 'UNAUTHORIZED', errorData, error.config)
|
|
272
|
+
|
|
273
|
+
case 403:
|
|
274
|
+
throw new BaserowError('Forbidden - insufficient permissions', 403, 'FORBIDDEN', errorData)
|
|
275
|
+
|
|
276
|
+
case 408:
|
|
277
|
+
throw new BaserowTimeoutError(this.config.timeout || 30000)
|
|
278
|
+
|
|
279
|
+
case 500:
|
|
280
|
+
case 502:
|
|
281
|
+
case 503:
|
|
282
|
+
case 504:
|
|
283
|
+
// Errores de servidor - potencialmente transitorios
|
|
284
|
+
throw new BaserowError(
|
|
285
|
+
`Servidor Baserow no disponible (HTTP ${status}). El servidor puede estar sobrecargado o en mantenimiento. Intenta de nuevo más tarde.`,
|
|
286
|
+
status,
|
|
287
|
+
'SERVER_ERROR',
|
|
288
|
+
errorData
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
default:
|
|
292
|
+
throw new BaserowError(message, status, 'HTTP_ERROR', errorData)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Error de red, timeout o conexión
|
|
297
|
+
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
|
|
298
|
+
this.logger?.error('Request timeout', {
|
|
299
|
+
...requestInfo,
|
|
300
|
+
timeout: this.config.timeout,
|
|
301
|
+
duration: requestInfo.startTime ? Date.now() - requestInfo.startTime : null
|
|
302
|
+
})
|
|
303
|
+
throw new BaserowTimeoutError(this.config.timeout || 30000)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Error de red
|
|
307
|
+
this.logger?.error('Network error', {
|
|
308
|
+
...requestInfo,
|
|
309
|
+
errorCode: error.code,
|
|
310
|
+
errorMessage: error.message,
|
|
311
|
+
duration: requestInfo.startTime ? Date.now() - requestInfo.startTime : null
|
|
312
|
+
})
|
|
313
|
+
throw new BaserowNetworkError(error.message, error)
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private setupRetryPlugin(): void {
|
|
319
|
+
axiosRetry(this.client, {
|
|
320
|
+
retries: this.config.retries!,
|
|
321
|
+
retryDelay: axiosRetry.exponentialDelay,
|
|
322
|
+
retryCondition: error => {
|
|
323
|
+
// Retry en errores de red o métodos idempotentes con 5xx
|
|
324
|
+
if (axiosRetry.isNetworkOrIdempotentRequestError(error)) {
|
|
325
|
+
return true
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// También retry en 429 (rate limiting)
|
|
329
|
+
if (error.response?.status === 429) {
|
|
330
|
+
return true
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return false
|
|
334
|
+
},
|
|
335
|
+
onRetry: (retryCount, error, requestConfig) => {
|
|
336
|
+
this.logger?.warn('Retrying request', {
|
|
337
|
+
method: requestConfig.method?.toUpperCase(),
|
|
338
|
+
url: requestConfig.url,
|
|
339
|
+
attempt: retryCount,
|
|
340
|
+
maxAttempts: this.config.retries,
|
|
341
|
+
errorType: error.name,
|
|
342
|
+
errorMessage: error.message
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private setupRateLimitingPlugin(): void {
|
|
349
|
+
const maxRequests = this.config.maxRequestsPerSecond!
|
|
350
|
+
this.client = rateLimit(this.client, {
|
|
351
|
+
maxRequests: maxRequests,
|
|
352
|
+
perMilliseconds: 1000,
|
|
353
|
+
maxRPS: maxRequests
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
this.logger?.debug?.('Rate limiting configured', {
|
|
357
|
+
maxRequestsPerSecond: maxRequests
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Actualizar el token de autenticación
|
|
363
|
+
*
|
|
364
|
+
* Permite cambiar el token de autenticación dinámicamente sin crear
|
|
365
|
+
* una nueva instancia del cliente. Detecta automáticamente el formato
|
|
366
|
+
* del token (JWT vs Database Token).
|
|
367
|
+
*
|
|
368
|
+
* @param token - Nuevo token de autenticación
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```typescript
|
|
372
|
+
* // Cambiar a Database Token
|
|
373
|
+
* client.setAuthToken('new-database-token-123')
|
|
374
|
+
*
|
|
375
|
+
* // Cambiar a JWT
|
|
376
|
+
* client.setAuthToken('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...')
|
|
377
|
+
* ```
|
|
378
|
+
*
|
|
379
|
+
* @since 1.0.0
|
|
380
|
+
*/
|
|
381
|
+
setAuthToken(token: string): void {
|
|
382
|
+
if (token) {
|
|
383
|
+
this.client.defaults.headers.common['Authorization'] = this.getAuthHeader(token)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Limpiar el token de autenticación
|
|
389
|
+
*
|
|
390
|
+
* Remueve el header de autenticación del cliente.
|
|
391
|
+
* Útil para testing o cuando se necesita acceso anónimo.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* client.clearAuthToken()
|
|
396
|
+
* // Ahora las requests no incluirán Authorization header
|
|
397
|
+
* ```
|
|
398
|
+
*
|
|
399
|
+
* @since 1.0.0
|
|
400
|
+
*/
|
|
401
|
+
clearAuthToken(): void {
|
|
402
|
+
delete this.client.defaults.headers.common['Authorization']
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Agregar interceptor de respuesta personalizado
|
|
407
|
+
*
|
|
408
|
+
* Permite agregar lógica personalizada para procesar respuestas
|
|
409
|
+
* o manejar errores específicos. Se ejecuta después de los
|
|
410
|
+
* interceptors internos del cliente.
|
|
411
|
+
*
|
|
412
|
+
* @param onFulfilled - Función para procesar respuestas exitosas
|
|
413
|
+
* @param onRejected - Función para manejar errores
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```typescript
|
|
417
|
+
* client.addResponseInterceptor(
|
|
418
|
+
* (response) => {
|
|
419
|
+
* console.log('Response received:', response.status)
|
|
420
|
+
* return response
|
|
421
|
+
* },
|
|
422
|
+
* (error) => {
|
|
423
|
+
* console.error('Request failed:', error.message)
|
|
424
|
+
* return Promise.reject(error)
|
|
425
|
+
* }
|
|
426
|
+
* )
|
|
427
|
+
* ```
|
|
428
|
+
*
|
|
429
|
+
* @since 1.0.0
|
|
430
|
+
*/
|
|
431
|
+
addResponseInterceptor(
|
|
432
|
+
onFulfilled: (response: AxiosResponse) => AxiosResponse,
|
|
433
|
+
onRejected: (error: any) => any
|
|
434
|
+
): void {
|
|
435
|
+
this.client.interceptors.response.use(onFulfilled, onRejected)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Realizar petición GET
|
|
440
|
+
*
|
|
441
|
+
* @template T - Tipo de datos esperado en la respuesta
|
|
442
|
+
* @param endpoint - Endpoint relativo a la baseURL
|
|
443
|
+
* @param params - Parámetros de query string
|
|
444
|
+
* @returns Promise con los datos de la respuesta
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* const tables = await client.get<Table[]>('/database/tables/database/123/')
|
|
449
|
+
* const rows = await client.get('/database/rows/table/456/', { page: 1, size: 100 })
|
|
450
|
+
* ```
|
|
451
|
+
*
|
|
452
|
+
* @since 1.0.0
|
|
453
|
+
*/
|
|
454
|
+
async get<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {
|
|
455
|
+
const response = await this.client.get(endpoint, { params })
|
|
456
|
+
return response.data
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Realizar petición POST
|
|
461
|
+
*
|
|
462
|
+
* @template T - Tipo de datos esperado en la respuesta
|
|
463
|
+
* @param endpoint - Endpoint relativo a la baseURL
|
|
464
|
+
* @param data - Datos a enviar en el body
|
|
465
|
+
* @param params - Parámetros de query string
|
|
466
|
+
* @returns Promise con los datos de la respuesta
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```typescript
|
|
470
|
+
* const newTable = await client.post<Table>('/database/tables/database/123/', {
|
|
471
|
+
* name: 'Nueva Tabla'
|
|
472
|
+
* })
|
|
473
|
+
* ```
|
|
474
|
+
*
|
|
475
|
+
* @since 1.0.0
|
|
476
|
+
*/
|
|
477
|
+
async post<T = any>(endpoint: string, data?: any, params?: Record<string, any>): Promise<T> {
|
|
478
|
+
const response = await this.client.post(endpoint, data, { params })
|
|
479
|
+
return response.data
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Realizar petición PUT
|
|
484
|
+
*
|
|
485
|
+
* @template T - Tipo de datos esperado en la respuesta
|
|
486
|
+
* @param endpoint - Endpoint relativo a la baseURL
|
|
487
|
+
* @param data - Datos a enviar en el body
|
|
488
|
+
* @param params - Parámetros de query string
|
|
489
|
+
* @returns Promise con los datos de la respuesta
|
|
490
|
+
*
|
|
491
|
+
* @since 1.0.0
|
|
492
|
+
*/
|
|
493
|
+
async put<T = any>(endpoint: string, data?: any, params?: Record<string, any>): Promise<T> {
|
|
494
|
+
const response = await this.client.put(endpoint, data, { params })
|
|
495
|
+
return response.data
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Realizar petición PATCH
|
|
500
|
+
*
|
|
501
|
+
* @template T - Tipo de datos esperado en la respuesta
|
|
502
|
+
* @param endpoint - Endpoint relativo a la baseURL
|
|
503
|
+
* @param data - Datos a enviar en el body
|
|
504
|
+
* @param params - Parámetros de query string
|
|
505
|
+
* @returns Promise con los datos de la respuesta
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
* ```typescript
|
|
509
|
+
* const updated = await client.patch<Table>('/database/tables/456/', {
|
|
510
|
+
* name: 'Nuevo Nombre'
|
|
511
|
+
* })
|
|
512
|
+
* ```
|
|
513
|
+
*
|
|
514
|
+
* @since 1.0.0
|
|
515
|
+
*/
|
|
516
|
+
async patch<T = any>(endpoint: string, data?: any, params?: Record<string, any>): Promise<T> {
|
|
517
|
+
const response = await this.client.patch(endpoint, data, { params })
|
|
518
|
+
return response.data
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Realizar petición DELETE
|
|
523
|
+
*
|
|
524
|
+
* @template T - Tipo de datos esperado en la respuesta
|
|
525
|
+
* @param endpoint - Endpoint relativo a la baseURL
|
|
526
|
+
* @param params - Parámetros de query string
|
|
527
|
+
* @returns Promise con los datos de la respuesta
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```typescript
|
|
531
|
+
* await client.delete('/database/tables/456/')
|
|
532
|
+
* ```
|
|
533
|
+
*
|
|
534
|
+
* @since 1.0.0
|
|
535
|
+
*/
|
|
536
|
+
async delete<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {
|
|
537
|
+
const response = await this.client.delete(endpoint, { params })
|
|
538
|
+
return response.data
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Crear instancia HttpClient optimizada para testing
|
|
543
|
+
*
|
|
544
|
+
* Crea un cliente con configuración conservadora optimizada para tests:
|
|
545
|
+
* - Rate limiting muy bajo (1 req/s) para no saturar el servidor
|
|
546
|
+
* - Timeout alto (60s) para operaciones lentas en CI/CD
|
|
547
|
+
* - Menos reintentos (2) para tests más rápidos
|
|
548
|
+
*
|
|
549
|
+
* @param config - Configuración base (sin maxRequestsPerSecond)
|
|
550
|
+
* @returns Instancia optimizada para testing
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* ```typescript
|
|
554
|
+
* const testClient = HttpClient.forTesting({
|
|
555
|
+
* baseURL: process.env.TEST_BASEROW_URL,
|
|
556
|
+
* token: process.env.TEST_TOKEN
|
|
557
|
+
* })
|
|
558
|
+
* // Rate limiting: 1 req/s, timeout: 60s, retries: 2
|
|
559
|
+
* ```
|
|
560
|
+
*
|
|
561
|
+
* @since 1.0.0
|
|
562
|
+
*/
|
|
563
|
+
static forTesting(config: Omit<HttpClientConfig, 'maxRequestsPerSecond'>): HttpClient {
|
|
564
|
+
return new HttpClient({
|
|
565
|
+
...config,
|
|
566
|
+
maxRequestsPerSecond: 1, // Máximo 1 request por segundo en tests (más conservador)
|
|
567
|
+
enableRateLimiting: true,
|
|
568
|
+
retries: 2, // Menos reintentos en tests
|
|
569
|
+
timeout: 60000 // Timeout más alto para tests (60s)
|
|
570
|
+
})
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Acceso directo a la instancia de Axios para casos avanzados
|
|
575
|
+
*
|
|
576
|
+
* Proporciona acceso a la instancia configurada de Axios para
|
|
577
|
+
* casos que requieren funcionalidad no cubierta por los métodos
|
|
578
|
+
* simplificados del cliente.
|
|
579
|
+
*
|
|
580
|
+
* @returns Instancia configurada de Axios
|
|
581
|
+
*
|
|
582
|
+
* @example
|
|
583
|
+
* ```typescript
|
|
584
|
+
* // Usar funcionalidades avanzadas de Axios
|
|
585
|
+
* const response = await client.axios({
|
|
586
|
+
* method: 'post',
|
|
587
|
+
* url: '/custom-endpoint',
|
|
588
|
+
* headers: { 'Custom-Header': 'value' }
|
|
589
|
+
* })
|
|
590
|
+
* ```
|
|
591
|
+
*
|
|
592
|
+
* @since 1.0.0
|
|
593
|
+
*/
|
|
594
|
+
get axios(): AxiosInstance {
|
|
595
|
+
return this.client
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Realizar petición personalizada usando configuración de Axios
|
|
600
|
+
*
|
|
601
|
+
* Permite realizar peticiones con configuración avanzada de Axios
|
|
602
|
+
* manteniendo todos los beneficios del cliente (retry, rate limiting, etc.).
|
|
603
|
+
*
|
|
604
|
+
* @template T - Tipo de datos esperado en la respuesta
|
|
605
|
+
* @param config - Configuración completa de Axios
|
|
606
|
+
* @returns Promise con los datos de la respuesta
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* ```typescript
|
|
610
|
+
* const result = await client.request<CustomType>({
|
|
611
|
+
* method: 'POST',
|
|
612
|
+
* url: '/special-endpoint',
|
|
613
|
+
* data: complexData,
|
|
614
|
+
* headers: { 'Special-Header': 'value' },
|
|
615
|
+
* timeout: 60000
|
|
616
|
+
* })
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* @since 1.0.0
|
|
620
|
+
*/
|
|
621
|
+
async request<T = any>(config: AxiosRequestConfig): Promise<T> {
|
|
622
|
+
const response = await this.client.request(config)
|
|
623
|
+
return response.data
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Cerrar las conexiones HTTP y limpiar recursos
|
|
628
|
+
*
|
|
629
|
+
* Limpia los recursos del cliente HTTP. En Node.js 20+ con keepAlive
|
|
630
|
+
* automático, no requiere gestión manual de connection pools.
|
|
631
|
+
* Principalmente útil para testing y cleanup de recursos.
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```typescript
|
|
635
|
+
* // En tests o al cerrar la aplicación
|
|
636
|
+
* client.destroy()
|
|
637
|
+
* ```
|
|
638
|
+
*
|
|
639
|
+
* @since 1.0.0
|
|
640
|
+
* @since 1.1.0 - Simplificado para Node.js 20+ keepAlive nativo
|
|
641
|
+
*/
|
|
642
|
+
destroy(): void {
|
|
643
|
+
this.logger?.debug?.('Destroying HTTP client')
|
|
644
|
+
// En Node.js 20+ con keepAlive automático, no necesitamos gestión manual de agentes
|
|
645
|
+
this.logger?.debug?.('HTTP client destroyed successfully')
|
|
646
|
+
}
|
|
647
|
+
}
|