@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +435 -0
  2. package/README.md +847 -0
  3. package/dist/index.d.ts +8749 -0
  4. package/dist/index.js +11167 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +91 -0
  7. package/src/BaserowClient.ts +501 -0
  8. package/src/ClientWithCreds.ts +545 -0
  9. package/src/ClientWithCredsWs.ts +852 -0
  10. package/src/ClientWithToken.ts +171 -0
  11. package/src/contexts/DatabaseClientContext.ts +114 -0
  12. package/src/contexts/DatabaseContext.ts +870 -0
  13. package/src/contexts/DatabaseTokenContext.ts +331 -0
  14. package/src/contexts/FieldContext.ts +399 -0
  15. package/src/contexts/RowContext.ts +99 -0
  16. package/src/contexts/TableClientContext.ts +291 -0
  17. package/src/contexts/TableContext.ts +1247 -0
  18. package/src/contexts/TableOnlyContext.ts +74 -0
  19. package/src/contexts/WorkspaceContext.ts +490 -0
  20. package/src/express/errors.ts +260 -0
  21. package/src/express/index.ts +69 -0
  22. package/src/express/middleware.ts +225 -0
  23. package/src/express/serializers.ts +314 -0
  24. package/src/index.ts +247 -0
  25. package/src/presets/performance.ts +262 -0
  26. package/src/services/AuthService.ts +472 -0
  27. package/src/services/DatabaseService.ts +246 -0
  28. package/src/services/DatabaseTokenService.ts +186 -0
  29. package/src/services/FieldService.ts +1543 -0
  30. package/src/services/RowService.ts +982 -0
  31. package/src/services/SchemaControlService.ts +420 -0
  32. package/src/services/TableService.ts +781 -0
  33. package/src/services/WorkspaceService.ts +113 -0
  34. package/src/services/core/BaseAuthClient.ts +111 -0
  35. package/src/services/core/BaseClient.ts +107 -0
  36. package/src/services/core/BaseService.ts +71 -0
  37. package/src/services/core/HttpService.ts +115 -0
  38. package/src/services/core/ValidationService.ts +149 -0
  39. package/src/types/auth.ts +177 -0
  40. package/src/types/core.ts +91 -0
  41. package/src/types/errors.ts +105 -0
  42. package/src/types/fields.ts +456 -0
  43. package/src/types/index.ts +222 -0
  44. package/src/types/requests.ts +333 -0
  45. package/src/types/responses.ts +50 -0
  46. package/src/types/schema.ts +446 -0
  47. package/src/types/tokens.ts +36 -0
  48. package/src/types.ts +11 -0
  49. package/src/utils/auth.ts +174 -0
  50. package/src/utils/axios.ts +647 -0
  51. package/src/utils/field-cache.ts +164 -0
  52. package/src/utils/httpFactory.ts +66 -0
  53. package/src/utils/jwt-decoder.ts +188 -0
  54. package/src/utils/jwtTokens.ts +50 -0
  55. package/src/utils/performance.ts +105 -0
  56. package/src/utils/prisma-mapper.ts +961 -0
  57. package/src/utils/validation.ts +463 -0
  58. package/src/validators/schema.ts +419 -0
@@ -0,0 +1,164 @@
1
+ import { FieldService } from '../services/FieldService'
2
+ import { Field, Logger } from '../types'
3
+
4
+ /**
5
+ * Metadata de campos con tipos y opciones select
6
+ */
7
+ export interface FieldMetadata {
8
+ /** Mapa de nombre de campo → tipo de campo */
9
+ types: Record<string, string>
10
+ /** Mapa de nombre de campo → {valor: id} para campos select */
11
+ selectOptions: Record<string, Record<string, number>>
12
+ }
13
+
14
+ /**
15
+ * Cache centralizado para metadata de campos de tablas
16
+ *
17
+ * Proporciona un sistema de cache eficiente para tipos de campo y opciones
18
+ * de campos select/multiselect, permitiendo resolver automáticamente
19
+ * valores de texto a IDs de opción para filtros de Baserow.
20
+ *
21
+ * **Características:**
22
+ * - Cache por tabla (tableId) para evitar conflictos
23
+ * - Mapeo automático value → ID para campos select
24
+ * - Invalidación granular por tabla o completa
25
+ * - Logging opcional para debugging
26
+ * - Performance optimizada con Map interna
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const cache = new FieldCache(logger)
31
+ * const metadata = await cache.getFieldMetadata(fieldService, 123)
32
+ *
33
+ * // Tipos de campo: metadata.types['categoria'] // 'single_select'
34
+ * // Opciones select: metadata.selectOptions['categoria']['Premium'] // 45
35
+ * ```
36
+ *
37
+ * @since 1.2.0
38
+ */
39
+ export class FieldCache {
40
+ private cache = new Map<number, FieldMetadata>()
41
+ private logger?: Logger
42
+
43
+ /**
44
+ * Crear nueva instancia de FieldCache
45
+ *
46
+ * @param logger - Logger opcional para debugging y trazabilidad
47
+ */
48
+ constructor(logger?: Logger) {
49
+ this.logger = logger
50
+ }
51
+
52
+ /**
53
+ * Obtener metadata completa de campos para una tabla
54
+ *
55
+ * Retorna tipos de campo y opciones de select con cache automático.
56
+ * Si los datos no están en cache, hace fetch automático y los almacena.
57
+ *
58
+ * @param fieldService - Servicio de campos para hacer fetch si necesario
59
+ * @param tableId - ID numérico de la tabla
60
+ * @returns Promise con metadata completa de campos
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const metadata = await cache.getFieldMetadata(fieldService, 123)
65
+ *
66
+ * // Usar tipos
67
+ * const fieldType = metadata.types['categoria'] // 'single_select'
68
+ *
69
+ * // Resolver valor a ID
70
+ * const optionId = metadata.selectOptions['categoria']['Premium'] // 45
71
+ * ```
72
+ */
73
+ async getFieldMetadata(fieldService: FieldService, tableId: number): Promise<FieldMetadata> {
74
+ if (!this.cache.has(tableId)) {
75
+ const fields = await fieldService.findMany(tableId)
76
+ const metadata = this.buildMetadata(fields, tableId)
77
+ this.cache.set(tableId, metadata)
78
+ }
79
+ return this.cache.get(tableId)!
80
+ }
81
+
82
+ /**
83
+ * Construir metadata a partir de array de campos
84
+ *
85
+ * @private
86
+ * @param fields - Array de campos de la tabla
87
+ * @param tableId - ID de la tabla (para logging)
88
+ * @returns Metadata construida
89
+ */
90
+ private buildMetadata(fields: Field[], tableId: number): FieldMetadata {
91
+ const types: Record<string, string> = {}
92
+ const selectOptions: Record<string, Record<string, number>> = {}
93
+
94
+ for (const field of fields) {
95
+ // Mapear nombre → tipo
96
+ types[field.name] = field.type
97
+
98
+ // Para campos select, construir mapeo value → id
99
+ if (['single_select', 'multiple_select'].includes(field.type) && field.select_options) {
100
+ selectOptions[field.name] = Object.fromEntries(
101
+ field.select_options.map(option => [option.value, option.id]).filter(([, id]) => id !== undefined)
102
+ )
103
+ }
104
+ }
105
+
106
+ // Logging detallado para debugging
107
+ this.logger?.debug?.(`Field metadata cached for table ${tableId}`, {
108
+ fieldCount: fields.length,
109
+ selectFieldCount: Object.keys(selectOptions).length,
110
+ typeDistribution: Object.values(types).reduce(
111
+ (acc, type) => {
112
+ acc[type] = (acc[type] || 0) + 1
113
+ return acc
114
+ },
115
+ {} as Record<string, number>
116
+ ),
117
+ selectFields: Object.keys(selectOptions)
118
+ })
119
+
120
+ return { types, selectOptions }
121
+ }
122
+
123
+ /**
124
+ * Invalidar cache para una tabla específica o completo
125
+ *
126
+ * Útil cuando se sabe que los campos de una tabla han cambiado
127
+ * (campos agregados, eliminados, o opciones select modificadas).
128
+ *
129
+ * @param tableId - ID de tabla específica a invalidar. Si no se proporciona, limpia todo el cache
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * // Invalidar tabla específica
134
+ * cache.clearCache(123)
135
+ *
136
+ * // Limpiar todo el cache
137
+ * cache.clearCache()
138
+ * ```
139
+ */
140
+ clearCache(tableId?: number): void {
141
+ if (tableId !== undefined) {
142
+ const wasDeleted = this.cache.delete(tableId)
143
+ this.logger?.debug?.(`Cache cleared for table ${tableId}`, { wasDeleted })
144
+ } else {
145
+ const cacheSize = this.cache.size
146
+ this.cache.clear()
147
+ this.logger?.debug?.(`Full cache cleared`, { clearedTables: cacheSize })
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Obtener estadísticas del cache
153
+ *
154
+ * Útil para debugging y monitoreo de performance.
155
+ *
156
+ * @returns Estadísticas del cache
157
+ */
158
+ getCacheStats(): { cachedTables: number; tableIds: number[] } {
159
+ return {
160
+ cachedTables: this.cache.size,
161
+ tableIds: Array.from(this.cache.keys())
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Factory centralizado para creación de HttpClient
3
+ *
4
+ * Elimina duplicación de código en la inicialización de HttpClient
5
+ * proporcionando una fuente única de verdad para la configuración.
6
+ *
7
+ * @internal
8
+ * @since 1.0.0
9
+ */
10
+
11
+ import { HttpClient } from './axios'
12
+ import { PerformanceManager } from './performance'
13
+ import type { Logger, BaserowPerformanceOptions } from '../types'
14
+
15
+ /**
16
+ * Configuración para crear HttpClient
17
+ */
18
+ export interface HttpClientFactoryConfig {
19
+ /** URL base de Baserow (ej: 'https://baserow.com') */
20
+ url: string
21
+ /** Token de autenticación (Database Token o JWT) */
22
+ token: string
23
+ /** Logger opcional para debug */
24
+ logger?: Logger
25
+ /** Configuración de performance opcional */
26
+ performance?: Partial<BaserowPerformanceOptions>
27
+ }
28
+
29
+ /**
30
+ * Crea una instancia de HttpClient con configuración estandarizada
31
+ *
32
+ * Centraliza la lógica de:
33
+ * - Sanitización de URL base
34
+ * - Configuración de performance (merge con defaults globales)
35
+ * - Configuración de token y logger
36
+ *
37
+ * @param config - Configuración del cliente HTTP
38
+ * @returns Instancia configurada de HttpClient
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // Para Database Token
43
+ * const client = createHttpClient({
44
+ * url: 'https://baserow.com',
45
+ * token: 'db_token_123',
46
+ * logger: console
47
+ * })
48
+ *
49
+ * // Para JWT (durante login)
50
+ * const tempClient = createHttpClient({
51
+ * url: 'https://baserow.com',
52
+ * token: '', // Sin token inicial
53
+ * logger: console,
54
+ * performance: { timeout: 15000 }
55
+ * })
56
+ * ```
57
+ */
58
+ export function createHttpClient(config: HttpClientFactoryConfig): HttpClient {
59
+ return new HttpClient({
60
+ baseURL: `${config.url.replace(/\/$/, '')}/api`,
61
+ token: config.token,
62
+ logger: config.logger,
63
+ // Combinar defaults globales con configuración específica de instancia
64
+ ...PerformanceManager.merge(config.performance)
65
+ })
66
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Utilidades para decodificar JWT tokens sin dependencias externas
3
+ *
4
+ * Proporciona funciones para extraer información de JWT tokens usando
5
+ * solo decodificación Base64 nativa de Node.js/Browser.
6
+ *
7
+ * **Características:**
8
+ * - Sin dependencias externas
9
+ * - Manejo robusto de errores
10
+ * - Compatible con Node.js y navegadores
11
+ * - Extrae claim `exp` (expiration) del payload
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const token = 'eyJhbGc...'
16
+ * const expiry = decodeJwtExpiry(token)
17
+ * if (expiry) {
18
+ * console.log('Token expira en:', expiry)
19
+ * }
20
+ * ```
21
+ *
22
+ * @since 1.0.0
23
+ */
24
+
25
+ import { Logger } from '../types'
26
+
27
+ /**
28
+ * Decodifica el claim `exp` (expiration) de un JWT token
29
+ *
30
+ * Lee el payload del JWT token y extrae el timestamp de expiración
31
+ * sin validar la firma (solo para leer metadatos del token).
32
+ *
33
+ * **JWT Structure:**
34
+ * - JWT = `header.payload.signature` (3 partes separadas por `.`)
35
+ * - `payload` está en Base64URL encoding
36
+ * - `exp` claim es Unix timestamp en **segundos** (no milisegundos)
37
+ *
38
+ * **Implementación:**
39
+ * 1. Split token por `.`
40
+ * 2. Decodificar parte [1] (payload) desde Base64
41
+ * 3. Parsear JSON del payload
42
+ * 4. Extraer `exp` y convertir a Date (multiplicar por 1000 para ms)
43
+ *
44
+ * @param token - JWT token completo en formato string
45
+ * @param logger - Logger opcional para debugging
46
+ * @returns Date de expiración o undefined si no se puede decodificar
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // Token válido
51
+ * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDkzOTY4MDB9.sig'
52
+ * const expiry = decodeJwtExpiry(token)
53
+ * // expiry = Date(2024-03-02T12:00:00.000Z)
54
+ *
55
+ * // Token sin exp claim
56
+ * const tokenNoExp = 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.sig'
57
+ * const expiry2 = decodeJwtExpiry(tokenNoExp)
58
+ * // expiry2 = undefined
59
+ *
60
+ * // Token malformado
61
+ * const badToken = 'invalid-token'
62
+ * const expiry3 = decodeJwtExpiry(badToken)
63
+ * // expiry3 = undefined
64
+ * ```
65
+ *
66
+ * @since 1.0.0
67
+ */
68
+ export function decodeJwtExpiry(token: string, logger?: Logger): Date | undefined {
69
+ try {
70
+ // Validar que el token tenga formato JWT (3 partes separadas por puntos)
71
+ const parts = token.split('.')
72
+ if (parts.length !== 3) {
73
+ logger?.warn?.(`❌ JWT malformado: esperaba 3 partes, recibió ${parts.length}`)
74
+ return undefined
75
+ }
76
+
77
+ // Decodificar payload (parte [1]) desde Base64
78
+ // atob() es global en navegadores y Node.js 16+
79
+ // Para Node.js < 16, usar Buffer.from(parts[1], 'base64').toString()
80
+ const payload = JSON.parse(
81
+ typeof atob !== 'undefined'
82
+ ? atob(parts[1]) // Browser / Node.js 16+
83
+ : Buffer.from(parts[1], 'base64').toString() // Node.js < 16
84
+ )
85
+
86
+ // Extraer claim 'exp' (expiration time)
87
+ // exp está en formato Unix timestamp (segundos desde epoch)
88
+ if (payload.exp === undefined || payload.exp === null || typeof payload.exp !== 'number') {
89
+ logger?.warn?.('❌ JWT sin claim exp válido')
90
+ return undefined
91
+ }
92
+
93
+ // Convertir Unix timestamp (segundos) a Date (milisegundos)
94
+ const expiryDate = new Date(payload.exp * 1000)
95
+
96
+ // Validar que la fecha sea válida
97
+ if (isNaN(expiryDate.getTime())) {
98
+ logger?.warn?.(`❌ Claim exp inválido: ${payload.exp}`)
99
+ return undefined
100
+ }
101
+
102
+ logger?.debug?.(`✅ JWT exp decodificado: ${expiryDate.toISOString()}`)
103
+ return expiryDate
104
+ } catch (error) {
105
+ logger?.warn?.('❌ Error decodificando JWT exp:', error)
106
+ return undefined
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Decodifica el payload completo de un JWT token
112
+ *
113
+ * Útil para debugging o para acceder a otros claims del token
114
+ * además de `exp`.
115
+ *
116
+ * @param token - JWT token completo en formato string
117
+ * @param logger - Logger opcional para debugging
118
+ * @returns Payload del JWT o undefined si no se puede decodificar
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const token = 'eyJhbGc...'
123
+ * const payload = decodeJwtPayload(token)
124
+ * console.log(payload)
125
+ * // { user_id: 123, exp: 1709396800, iat: 1709393200 }
126
+ * ```
127
+ *
128
+ * @since 1.0.0
129
+ */
130
+ export function decodeJwtPayload(token: string, logger?: Logger): Record<string, any> | undefined {
131
+ try {
132
+ const parts = token.split('.')
133
+ if (parts.length !== 3) {
134
+ logger?.warn?.(`❌ JWT malformado: esperaba 3 partes, recibió ${parts.length}`)
135
+ return undefined
136
+ }
137
+
138
+ const payload = JSON.parse(
139
+ typeof atob !== 'undefined' ? atob(parts[1]) : Buffer.from(parts[1], 'base64').toString()
140
+ )
141
+
142
+ return payload
143
+ } catch (error) {
144
+ logger?.warn?.('❌ Error decodificando JWT payload:', error)
145
+ return undefined
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Verifica si un JWT token está expirado
151
+ *
152
+ * Compara el claim `exp` del token con el tiempo actual.
153
+ * Útil para validaciones rápidas sin decodificar manualmente.
154
+ *
155
+ * @param token - JWT token completo en formato string
156
+ * @param bufferMs - Buffer de tiempo en ms para considerar "casi expirado" (default: 0)
157
+ * @param logger - Logger opcional para debugging
158
+ * @returns true si está expirado, false si es válido, undefined si no se puede decodificar
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const token = 'eyJhbGc...'
163
+ *
164
+ * // Verificar si está expirado ahora
165
+ * if (isJwtExpired(token)) {
166
+ * console.log('Token expirado')
167
+ * }
168
+ *
169
+ * // Verificar si expira en los próximos 5 minutos
170
+ * const fiveMinutes = 5 * 60 * 1000
171
+ * if (isJwtExpired(token, fiveMinutes)) {
172
+ * console.log('Token expira pronto, renovar')
173
+ * }
174
+ * ```
175
+ *
176
+ * @since 1.0.0
177
+ */
178
+ export function isJwtExpired(token: string, bufferMs: number = 0, logger?: Logger): boolean | undefined {
179
+ const expiry = decodeJwtExpiry(token, logger)
180
+ if (!expiry) {
181
+ return undefined // No se pudo decodificar
182
+ }
183
+
184
+ const now = Date.now()
185
+ const expiryWithBuffer = expiry.getTime() - bufferMs
186
+
187
+ return now >= expiryWithBuffer
188
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Utilidades centralizadas para manejo de JWT tokens
3
+ *
4
+ * Elimina duplicación de lógica de configuración de tokens JWT
5
+ * entre ClientWithCreds y ClientWithCredsWs.
6
+ *
7
+ * @internal
8
+ * @since 1.0.0
9
+ */
10
+
11
+ import type { LoginResponse } from '../types'
12
+ import type { AuthService } from '../services/AuthService'
13
+ import { decodeJwtExpiry } from './jwt-decoder'
14
+
15
+ /**
16
+ * Configura los tokens JWT en un AuthService existente
17
+ *
18
+ * Centraliza la lógica de transferencia de tokens desde LoginResponse
19
+ * al AuthService interno, eliminando duplicación entre clientes.
20
+ *
21
+ * **Importante:** La expiración se decodifica del claim `exp` del JWT,
22
+ * no se asume un TTL hardcoded.
23
+ *
24
+ * @param authService - Instancia de AuthService donde configurar tokens
25
+ * @param loginResponse - Respuesta del login con tokens JWT
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // En ClientWithCreds.create()
30
+ * const admin = new ClientWithCreds(config, loginResponse)
31
+ * setupJwtTokens(admin.auth, loginResponse)
32
+ * ```
33
+ */
34
+ export function setupJwtTokens(authService: AuthService, loginResponse: LoginResponse): void {
35
+ // Acceder a propiedades privadas del AuthService
36
+ // Esto es necesario para transferir tokens del login temporal al AuthService final
37
+ const authServiceAny = authService as any
38
+
39
+ authServiceAny['accessToken'] = loginResponse.access_token
40
+ authServiceAny['refreshToken'] = loginResponse.refresh_token
41
+
42
+ // Decodificar expiración desde el claim 'exp' del JWT (estándar)
43
+ const expiry = decodeJwtExpiry(loginResponse.access_token)
44
+ if (!expiry) {
45
+ // Fallback conservador si no se puede decodificar
46
+ authServiceAny['tokenExpiry'] = new Date(Date.now() + 10 * 60 * 1000) // 10 min
47
+ } else {
48
+ authServiceAny['tokenExpiry'] = expiry
49
+ }
50
+ }
@@ -0,0 +1,105 @@
1
+ import type { BaserowPerformanceOptions } from '../types'
2
+
3
+ /**
4
+ * Defaults centralizados de performance para toda la librería
5
+ *
6
+ * Fuente única de verdad para valores por defecto de configuración de performance.
7
+ * Usado por PerformanceManager y HttpClient para evitar duplicación.
8
+ */
9
+ export const PERFORMANCE_DEFAULTS: BaserowPerformanceOptions = {
10
+ timeout: 30000,
11
+ retries: 3,
12
+ maxRequestsPerSecond: 10,
13
+ enableRateLimiting: true
14
+ } as const
15
+
16
+ /**
17
+ * Gestor centralizado de configuración de performance para toda la librería Baserow
18
+ *
19
+ * Proporciona una API unificada para gestionar la configuración global de performance
20
+ * que se aplicará a todas las instancias de clientes Baserow.
21
+ *
22
+ * @internal
23
+ * @since 1.0.0
24
+ */
25
+ export class PerformanceManager {
26
+ /**
27
+ * Configuración global por defecto de performance
28
+ */
29
+ private static globalDefaults: BaserowPerformanceOptions = { ...PERFORMANCE_DEFAULTS }
30
+
31
+ /**
32
+ * Establece la configuración global de performance
33
+ *
34
+ * @param defaults - Configuración parcial para sobrescribir defaults
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * PerformanceManager.setGlobal({
39
+ * timeout: 60000,
40
+ * retries: 5
41
+ * })
42
+ * ```
43
+ */
44
+ static setGlobal(defaults: Partial<BaserowPerformanceOptions>): void {
45
+ PerformanceManager.globalDefaults = {
46
+ ...PerformanceManager.globalDefaults,
47
+ ...defaults
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Obtiene la configuración global actual de performance
53
+ *
54
+ * @returns Copia de la configuración global actual
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const current = PerformanceManager.getGlobal()
59
+ * console.log('Timeout global:', current.timeout)
60
+ * ```
61
+ */
62
+ static getGlobal(): BaserowPerformanceOptions {
63
+ return { ...PerformanceManager.globalDefaults }
64
+ }
65
+
66
+ /**
67
+ * Resetea la configuración global a los valores por defecto de la librería
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * PerformanceManager.reset()
72
+ * ```
73
+ */
74
+ static reset(): void {
75
+ PerformanceManager.globalDefaults = { ...PERFORMANCE_DEFAULTS }
76
+ }
77
+
78
+ /**
79
+ * Combina la configuración global con la configuración específica de instancia
80
+ *
81
+ * La configuración de instancia tiene prioridad sobre la global.
82
+ *
83
+ * @param instanceConfig - Configuración específica de la instancia (opcional)
84
+ * @returns Configuración final combinada
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // Solo configuración global
89
+ * const config1 = PerformanceManager.merge()
90
+ *
91
+ * // Configuración global + override de instancia
92
+ * const config2 = PerformanceManager.merge({
93
+ * retries: 1, // Override
94
+ * timeout: 5000 // Override
95
+ * // maxRequestsPerSecond y enableRateLimiting vienen de global
96
+ * })
97
+ * ```
98
+ */
99
+ static merge(instanceConfig?: Partial<BaserowPerformanceOptions>): BaserowPerformanceOptions {
100
+ return {
101
+ ...PerformanceManager.globalDefaults,
102
+ ...instanceConfig
103
+ }
104
+ }
105
+ }