@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,852 @@
1
+ import { BaserowAdminConfig, LoginResponse, Workspace, BaserowConfigError } from './types'
2
+ import { createHttpClient } from './utils/httpFactory'
3
+ import { PerformanceManager } from './utils/performance'
4
+ import { setupJwtTokens } from './utils/jwtTokens'
5
+ import { validateRequired, validateString, validateUrl } from './utils/validation'
6
+ import { AuthService } from './services/AuthService'
7
+ import { DatabaseService } from './services/DatabaseService'
8
+ import { DatabaseTokenService } from './services/DatabaseTokenService'
9
+ import { TableService } from './services/TableService'
10
+ import { FieldService } from './services/FieldService'
11
+ import { RowService } from './services/RowService'
12
+ import { WorkspaceService } from './services/WorkspaceService'
13
+ import { DatabaseContext } from './contexts/DatabaseContext'
14
+ import { DatabaseTokenContext } from './contexts/DatabaseTokenContext'
15
+ import { BaseAuthClient } from './services/core/BaseAuthClient'
16
+
17
+ /**
18
+ * Cliente administrativo restringido a workspace específico
19
+ *
20
+ * Proporciona acceso administrativo completo pero limitado a un workspace específico.
21
+ * API simplificada sin prefijo workspace, ideal para aplicaciones con scope limitado.
22
+ * Se autentica con JWT y auto-refresh, pero valida permisos solo para el workspace configurado.
23
+ *
24
+ * **Características:**
25
+ * - Autenticación JWT con auto-refresh automático
26
+ * - API simplificada: `admin.databases.create(name)` (workspace automático)
27
+ * - Acceso restringido al workspace especificado en configuración
28
+ * - Operaciones administrativas completas dentro del workspace
29
+ * - Resolución automática de nombres a IDs
30
+ * - Validación automática de permisos de workspace
31
+ *
32
+ * **API Simplificada:**
33
+ * - `admin.databases` → operaciones de databases del workspace
34
+ * - `admin.database(name)` → contexto de database específica
35
+ * - `admin.databaseToken` → gestión de Database Tokens del workspace
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const admin = await BaserowWithCredentialsWorkspace.create({
40
+ * url: 'https://baserow.example.com',
41
+ * credentials: {
42
+ * email: 'admin@example.com',
43
+ * password: 'password123'
44
+ * },
45
+ * workspace: 'My Workspace'
46
+ * })
47
+ *
48
+ * // API simplificada sin prefijo workspace
49
+ * const databases = await admin.databases.findMany()
50
+ * const database = await admin.databases.create('My Database')
51
+ *
52
+ * // Acceso directo a operaciones de database
53
+ * const table = await admin.database('My Database')
54
+ * .table('Users')
55
+ * .create({
56
+ * data: [['Name', 'Email'], ['John', 'john@example.com']],
57
+ * first_row_header: true
58
+ * })
59
+ *
60
+ * // Gestión de Database Tokens del workspace
61
+ * const token = await admin.databaseToken.createFullAccess('app-token')
62
+ * ```
63
+ *
64
+ * @since 1.0.0
65
+ */
66
+ export class ClientWithCredsWs extends BaseAuthClient<BaserowAdminConfig, 'credentials'> {
67
+ private defaultWorkspace: Workspace
68
+
69
+ // Servicios que tienen sentido a nivel workspace
70
+ public databases: DatabaseService
71
+
72
+ // Servicios internos (no públicos)
73
+ private workspaceService: WorkspaceService
74
+ private databaseTokenService: DatabaseTokenService
75
+
76
+ private constructor(config: BaserowAdminConfig, loginResponse: LoginResponse, workspace: Workspace) {
77
+ // Procesar performance con defaults antes de guardar
78
+ const processedConfig = {
79
+ ...config,
80
+ performance: PerformanceManager.merge(config.performance)
81
+ }
82
+
83
+ // Inicializar HttpClient con JWT token
84
+ const http = createHttpClient({
85
+ url: processedConfig.url,
86
+ token: loginResponse.access_token,
87
+ logger: processedConfig.logger,
88
+ performance: processedConfig.performance
89
+ })
90
+
91
+ // Inicializar AuthService
92
+ const auth = new AuthService(http, processedConfig.logger)
93
+
94
+ super(processedConfig, http, auth, processedConfig.logger)
95
+ this.defaultWorkspace = workspace
96
+
97
+ // Transferir tokens al AuthService ANTES de configurar auto-refresh
98
+ setupJwtTokens(this.auth, loginResponse)
99
+
100
+ // Configurar auto-refresh de tokens (ahora tiene los tokens almacenados)
101
+ this.auth.setupAutoRefresh()
102
+
103
+ // Iniciar keep-alive si está habilitado
104
+ if (processedConfig.keepAlive?.enabled) {
105
+ const intervalMinutes = processedConfig.keepAlive.intervalMinutes ?? 7200
106
+ this.auth.startKeepAlive(intervalMinutes)
107
+ }
108
+
109
+ // Inicializar servicios
110
+ this.workspaceService = new WorkspaceService(this.http, this.logger)
111
+ this.databaseTokenService = new DatabaseTokenService(this.http, this.logger)
112
+ this.databases = new DatabaseService(this.http, this.logger, workspace.id)
113
+
114
+ if (this.logger) {
115
+ this.logger.info(
116
+ `BaserowWithCredentialsWorkspace initialized with JWT authentication (workspace-restricted to "${workspace.name}")`
117
+ )
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Campos que deben excluirse por defecto para cliente con credenciales workspace
123
+ * Excluye credentials por seguridad
124
+ */
125
+ protected getDefaultExcludeKeys(): string[] {
126
+ return ['credentials']
127
+ }
128
+
129
+ /**
130
+ * Crear instancia de BaserowWithCredentialsWorkspace con auto-login y validación de workspace
131
+ *
132
+ * Factory method que maneja la autenticación automáticamente y valida/crea el workspace.
133
+ * Retorna una instancia lista para usar con JWT token válido y workspace configurado.
134
+ *
135
+ * **Keep-Alive opcional** para backends 24/7:
136
+ * - Habilitar con `keepAlive: { enabled: true }` para evitar expiración de refresh token
137
+ * - Default: re-login cada 5 días (7200 min), margen de seguridad de 2 días
138
+ * - ⚠️ Almacena credenciales en memoria (solo para backends confiables)
139
+ *
140
+ * @param config - Configuración del cliente administrativo
141
+ * @param config.url - URL del servidor Baserow
142
+ * @param config.credentials - Credenciales de usuario (email/password)
143
+ * @param config.workspace - Nombre del workspace a usar (se crea si no existe)
144
+ * @param config.keepAlive - Opcional: configuración keep-alive para backends 24/7
145
+ * @param config.logger - Logger opcional para debugging
146
+ * @returns Promise que resuelve a instancia autenticada de BaserowWithCredentialsWorkspace
147
+ *
148
+ * @throws {BaserowConfigError} Si la configuración es inválida o falta workspace
149
+ * @throws {BaserowAuthError} Si las credenciales son incorrectas
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * // Cliente básico sin keep-alive
154
+ * const admin = await BaserowWithCredentialsWorkspace.create({
155
+ * url: 'https://baserow.example.com',
156
+ * credentials: {
157
+ * email: 'admin@example.com',
158
+ * password: 'secure-password'
159
+ * },
160
+ * workspace: 'My Company Workspace'
161
+ * })
162
+ *
163
+ * // Cliente con keep-alive para backend 24/7 (re-login cada 5 días)
164
+ * const adminBackend = await BaserowWithCredentialsWorkspace.create({
165
+ * url: 'https://baserow.example.com',
166
+ * credentials: {
167
+ * email: 'admin@example.com',
168
+ * password: 'secure-password'
169
+ * },
170
+ * workspace: 'My Company Workspace',
171
+ * keepAlive: { enabled: true }
172
+ * })
173
+ *
174
+ * // Cliente listo para usar (restringido al workspace)
175
+ * const databases = await admin.databases.list()
176
+ * const stats = await admin.getUsageStats() // Solo del workspace configurado
177
+ * ```
178
+ *
179
+ * @since 1.0.0
180
+ */
181
+ static async create(config: BaserowAdminConfig): Promise<ClientWithCredsWs> {
182
+ validateRequired(config, 'config')
183
+ validateString(config.url, 'url')
184
+ validateRequired(config.credentials, 'credentials')
185
+ validateString(config.credentials.email, 'email')
186
+ validateString(config.credentials.password, 'password')
187
+
188
+ if (!config.workspace) {
189
+ throw new BaserowConfigError('workspace is required for BaserowWithCredentialsWorkspace', 'workspace')
190
+ }
191
+
192
+ if (!validateUrl(config.url)) {
193
+ throw new BaserowConfigError('Invalid URL format', 'url')
194
+ }
195
+
196
+ // Crear HttpClient temporal para login
197
+ const tempHttp = createHttpClient({
198
+ url: config.url,
199
+ token: '', // Sin token inicial
200
+ logger: config.logger,
201
+ performance: config.performance
202
+ })
203
+
204
+ // Crear AuthService temporal
205
+ const tempAuth = new AuthService(tempHttp, config.logger)
206
+
207
+ // Realizar login
208
+ const loginResponse = await tempAuth.login(config.credentials)
209
+
210
+ if (config.logger) {
211
+ config.logger.info(`BaserowWithCredentialsWorkspace: Logged in as ${loginResponse.user.username}`)
212
+ }
213
+
214
+ // Resolver workspace por nombre
215
+ const tempWorkspaceService = new WorkspaceService(tempHttp, config.logger)
216
+ let workspace = await tempWorkspaceService.findUnique(config.workspace)
217
+
218
+ // Si no existe, crearlo
219
+ if (!workspace) {
220
+ if (config.logger) {
221
+ config.logger.info(`BaserowWithCredentialsWorkspace: Creating workspace "${config.workspace}"`)
222
+ }
223
+ workspace = await tempWorkspaceService.create({ name: config.workspace })
224
+ }
225
+
226
+ // Crear instancia con token válido y workspace resuelto
227
+ // Los tokens son transferidos dentro del constructor
228
+ const admin = new ClientWithCredsWs(config, loginResponse, workspace)
229
+
230
+ return admin
231
+ }
232
+
233
+ /**
234
+ * Verificar estado de salud del servidor Baserow
235
+ *
236
+ * Realiza un health check del servidor sin requerir autenticación.
237
+ * Útil para verificar conectividad antes de realizar operaciones.
238
+ *
239
+ * @returns Promise que resuelve a `true` si el servidor está saludable, `false` en caso contrario
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const isHealthy = await admin.health()
244
+ * if (!isHealthy) {
245
+ * console.log('Servidor Baserow no disponible')
246
+ * return
247
+ * }
248
+ *
249
+ * // Proceder con operaciones del workspace
250
+ * const databases = await admin.databases.list()
251
+ * ```
252
+ *
253
+ * @since 1.0.0
254
+ */
255
+ async health(): Promise<boolean> {
256
+ try {
257
+ // Usar endpoint de salud oficial (no requiere autenticación)
258
+ const healthUrl = `${this.config.url.replace(/\/$/, '')}/api/_health/`
259
+ const response = await fetch(healthUrl, {
260
+ method: 'GET',
261
+ headers: { 'Content-Type': 'application/json' }
262
+ })
263
+
264
+ return response.ok
265
+ } catch (error) {
266
+ if (this.logger) {
267
+ this.logger.error('BaserowWithCredentialsWorkspace health check failed:', error)
268
+ }
269
+ return false
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Obtener la configuración actual del cliente (sin exponer credenciales)
275
+ *
276
+ * Retorna una copia de solo lectura de la configuración, excluyendo las credenciales
277
+ * por seguridad. Incluye el workspace configurado.
278
+ *
279
+ * @returns Configuración actual sin credenciales (solo lectura)
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * const config = admin.getConfig()
284
+ * console.log('URL servidor:', config.url)
285
+ * console.log('Workspace:', config.workspace)
286
+ * // Las credenciales no están incluidas por seguridad
287
+ * ```
288
+ *
289
+ * @since 1.0.0
290
+ */
291
+ getConfig(): Readonly<Omit<BaserowAdminConfig, 'credentials'>> {
292
+ return this.getConfigBase()
293
+ }
294
+
295
+ /**
296
+ * Verificar si el cliente está autenticado
297
+ *
298
+ * Comprueba si hay un token JWT válido y no expirado.
299
+ * Si el token está próximo a expirar, el auto-refresh se encargará de renovarlo.
300
+ *
301
+ * @returns `true` si está autenticado con token válido, `false` en caso contrario
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * if (admin.isAuthenticated()) {
306
+ * const databases = await admin.databases.list()
307
+ * } else {
308
+ * console.log('Sesión expirada, necesario re-login')
309
+ * }
310
+ * ```
311
+ *
312
+ * @since 1.0.0
313
+ */
314
+ isAuthenticated(): boolean {
315
+ return this.auth.isAuthenticated()
316
+ }
317
+
318
+ /**
319
+ * Obtener el token JWT actual
320
+ *
321
+ * Retorna el token de acceso actual si está disponible.
322
+ * Útil para debugging o integración con otros sistemas.
323
+ *
324
+ * @returns Token JWT actual o `undefined` si no está autenticado
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * const token = admin.getCurrentToken()
329
+ * if (token) {
330
+ * console.log('Token disponible:', token.substring(0, 20) + '...')
331
+ * }
332
+ * ```
333
+ *
334
+ * @since 1.0.0
335
+ */
336
+ getCurrentToken(): string | undefined {
337
+ return this.auth.getCurrentToken()
338
+ }
339
+
340
+ /**
341
+ * Renovar el token de acceso manualmente
342
+ *
343
+ * Fuerza la renovación del token JWT usando el refresh token.
344
+ * Normalmente no es necesario llamar esto manualmente ya que el auto-refresh
345
+ * se encarga automáticamente.
346
+ *
347
+ * @returns Promise que resuelve al nuevo token de acceso
348
+ *
349
+ * @throws {BaserowReLoginRequiredError} Si el refresh token ha expirado (~7 días)
350
+ * @throws {BaserowAuthTokenInvalidError} Si el refresh token es inválido
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * try {
355
+ * const newToken = await admin.refreshToken()
356
+ * console.log('Token renovado exitosamente')
357
+ * } catch (error) {
358
+ * if (error instanceof BaserowReLoginRequiredError) {
359
+ * console.log('Sesión expirada, necesario re-login con credenciales')
360
+ * // Redirigir a login o solicitar credenciales nuevamente
361
+ * }
362
+ * }
363
+ * ```
364
+ *
365
+ * @since 1.0.0
366
+ */
367
+ async refreshToken(): Promise<string> {
368
+ return await this.auth.refreshAccessToken()
369
+ }
370
+
371
+ /**
372
+ * Verificar si el cliente necesita re-autenticación
373
+ *
374
+ * Útil para detectar si los tokens se han perdido o el refresh token expiró,
375
+ * permitiendo manejar proactivamente la re-autenticación antes de hacer requests.
376
+ *
377
+ * @aiUsage
378
+ * **✅ Usar cuando:**
379
+ * - Antes de operaciones críticas (validación proactiva de sesión)
380
+ * - Después de períodos largos de inactividad (>6 horas)
381
+ * - Al iniciar aplicación (verificar si sesión previa sigue válida)
382
+ * - Después de errores 401 (distinguir si es recoverable con refresh)
383
+ * - En background jobs/cron tasks (sesiones de larga duración)
384
+ *
385
+ * **❌ NO usar cuando:**
386
+ * - Ya tienes try/catch con BaserowReLoginRequiredError (manejo reactivo suficiente)
387
+ * - Llamando cada request (overhead innecesario, auto-refresh ya maneja)
388
+ * - Solo para logging (usar `getAuthStatus()` que da más info)
389
+ *
390
+ * @aiPattern
391
+ * ## 🎯 Patrón: Wrapper con Auto-Relogin
392
+ *
393
+ * Crea una función wrapper que automáticamente reautentica si la sesión expiró:
394
+ *
395
+ * ```typescript
396
+ * async function withAutoRelogin<T>(
397
+ * admin: ClientWithCredsWs,
398
+ * credentials: { email: string; password: string },
399
+ * operation: (client: ClientWithCredsWs) => Promise<T>
400
+ * ): Promise<T> {
401
+ * // 1. Verificar proactivamente si necesita re-login
402
+ * if (admin.needsReLogin()) {
403
+ * console.log('🔄 Sesión expirada, reautenticando...')
404
+ *
405
+ * admin = await ClientWithCredsWs.create({
406
+ * url: admin.getConfig().url,
407
+ * credentials,
408
+ * workspace: admin.getConfig().workspace!
409
+ * })
410
+ *
411
+ * console.log('✅ Re-autenticación exitosa')
412
+ * }
413
+ *
414
+ * // 2. Ejecutar operación con manejo de errores
415
+ * try {
416
+ * return await operation(admin)
417
+ * } catch (error) {
418
+ * // 3. Si falla con re-login required, reintentar UNA vez
419
+ * if (error instanceof BaserowReLoginRequiredError) {
420
+ * console.log('🔄 Token refresh falló, reautenticando...')
421
+ *
422
+ * admin = await ClientWithCredsWs.create({
423
+ * url: admin.getConfig().url,
424
+ * credentials,
425
+ * workspace: admin.getConfig().workspace!
426
+ * })
427
+ *
428
+ * return await operation(admin)
429
+ * }
430
+ * throw error
431
+ * }
432
+ * }
433
+ *
434
+ * // Uso del wrapper
435
+ * const result = await withAutoRelogin(
436
+ * admin,
437
+ * { email: 'user@example.com', password: 'secret' },
438
+ * async (client) => {
439
+ * const databases = await client.databases.findMany()
440
+ * return databases
441
+ * }
442
+ * )
443
+ * ```
444
+ *
445
+ * @aiPattern
446
+ * ## 🔄 Patrón: Session Manager con Polling
447
+ *
448
+ * Para aplicaciones de larga duración, monitorea la sesión periódicamente:
449
+ *
450
+ * ```typescript
451
+ * class SessionManager {
452
+ * private checkInterval: NodeJS.Timeout | null = null
453
+ *
454
+ * constructor(
455
+ * private admin: ClientWithCredsWs,
456
+ * private credentials: { email: string; password: string }
457
+ * ) {}
458
+ *
459
+ * // Monitorear sesión cada N minutos
460
+ * startMonitoring(intervalMinutes: number = 30) {
461
+ * this.checkInterval = setInterval(async () => {
462
+ * if (this.admin.needsReLogin()) {
463
+ * console.warn('⚠️ Sesión expirada detectada, reautenticando...')
464
+ *
465
+ * try {
466
+ * this.admin = await ClientWithCredsWs.create({
467
+ * url: this.admin.getConfig().url,
468
+ * credentials: this.credentials,
469
+ * workspace: this.admin.getConfig().workspace!
470
+ * })
471
+ *
472
+ * console.log('✅ Sesión renovada automáticamente')
473
+ * } catch (error) {
474
+ * console.error('❌ Error renovando sesión:', error)
475
+ * // Notificar al usuario o detener el servicio
476
+ * }
477
+ * }
478
+ * }, intervalMinutes * 60 * 1000)
479
+ * }
480
+ *
481
+ * stopMonitoring() {
482
+ * if (this.checkInterval) {
483
+ * clearInterval(this.checkInterval)
484
+ * }
485
+ * }
486
+ *
487
+ * getClient(): ClientWithCredsWs {
488
+ * return this.admin
489
+ * }
490
+ * }
491
+ *
492
+ * // Uso
493
+ * const sessionManager = new SessionManager(admin, credentials)
494
+ * sessionManager.startMonitoring(30) // Check cada 30 min
495
+ *
496
+ * // Usar cliente desde manager
497
+ * const client = sessionManager.getClient()
498
+ * const data = await client.databases.findMany()
499
+ * ```
500
+ *
501
+ * @aiErrorHandling
502
+ * ## ⚠️ Casos de Re-Login Necesario
503
+ *
504
+ * **Caso 1: Tokens perdidos (reinicio de proceso)**
505
+ * ```typescript
506
+ * // Al iniciar aplicación, verificar sesión previa
507
+ * if (admin.needsReLogin()) {
508
+ * console.log('Tokens perdidos (reinicio), necesario login')
509
+ * // needsReLogin() = true porque no hay tokens en memoria
510
+ * }
511
+ * ```
512
+ *
513
+ * **Caso 2: Refresh token expirado (~7 días inactividad)**
514
+ * ```typescript
515
+ * // Después de 7+ días sin uso
516
+ * if (admin.needsReLogin()) {
517
+ * console.log('Refresh token expirado, necesario login')
518
+ * // Access token expirado + no refresh token disponible
519
+ * }
520
+ * ```
521
+ *
522
+ * **Caso 3: Sesión válida (NO necesita re-login)**
523
+ * ```typescript
524
+ * if (!admin.needsReLogin()) {
525
+ * // Access token válido O refresh token disponible para renovar
526
+ * const data = await admin.databases.findMany()
527
+ * // Auto-refresh manejará la renovación si access token expira
528
+ * }
529
+ * ```
530
+ *
531
+ * @aiWarning
532
+ * **⚠️ Limitaciones:**
533
+ * - No detecta refresh token inválido (solo detecta ausencia o access token expirado)
534
+ * - Si refresh token está corrupto, `needsReLogin()` retorna `false` pero el refresh fallará
535
+ * - Para validación completa, usar try/catch con `BaserowReLoginRequiredError`
536
+ *
537
+ * **⚠️ Credenciales en Memoria:**
538
+ * - El wrapper `withAutoRelogin()` requiere credenciales en memoria
539
+ * - Evalúa riesgos de seguridad vs conveniencia
540
+ * - Alternativa: solicitar credenciales al usuario cuando `needsReLogin()` = true
541
+ *
542
+ * @returns `true` si necesita re-login, `false` si la sesión es válida
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * // Validación proactiva simple
547
+ * if (admin.needsReLogin()) {
548
+ * console.log('Sesión expirada, solicitando credenciales...')
549
+ * // Mostrar formulario de login o solicitar credenciales
550
+ * const newAdmin = await ClientWithCredsWs.create({
551
+ * url: config.url,
552
+ * credentials: { email, password },
553
+ * workspace: config.workspace
554
+ * })
555
+ * } else {
556
+ * // Sesión válida, continuar normalmente
557
+ * const databases = await admin.databases.findMany()
558
+ * }
559
+ *
560
+ * // Con wrapper de auto-relogin
561
+ * const databases = await withAutoRelogin(
562
+ * admin,
563
+ * credentials,
564
+ * async (client) => client.databases.findMany()
565
+ * )
566
+ * ```
567
+ *
568
+ * @since 1.0.4
569
+ */
570
+ needsReLogin(): boolean {
571
+ // No hay tokens = necesita login
572
+ if (!this.auth.getCurrentToken() || !this.auth['refreshToken']) {
573
+ return true
574
+ }
575
+
576
+ // Si el access token está expirado y no tenemos refresh token, necesita login
577
+ if (this.auth.isTokenExpired() && !this.auth['refreshToken']) {
578
+ return true
579
+ }
580
+
581
+ return false
582
+ }
583
+
584
+ /**
585
+ * Obtener estado detallado de autenticación
586
+ *
587
+ * Proporciona información completa sobre el estado actual de la sesión,
588
+ * útil para debugging y monitoreo de sesiones.
589
+ *
590
+ * @returns Objeto con información detallada del estado de autenticación
591
+ *
592
+ * @example
593
+ * ```typescript
594
+ * const status = admin.getAuthStatus()
595
+ * console.log('Autenticado:', status.isAuthenticated)
596
+ * console.log('Token expira:', status.tokenExpiry)
597
+ * console.log('Tiene refresh token:', status.hasRefreshToken)
598
+ * console.log('Necesita re-login:', status.needsReLogin)
599
+ * ```
600
+ *
601
+ * @since 1.0.4
602
+ */
603
+ getAuthStatus(): {
604
+ isAuthenticated: boolean
605
+ hasAccessToken: boolean
606
+ hasRefreshToken: boolean
607
+ tokenExpiry?: Date
608
+ isTokenExpired: boolean
609
+ needsReLogin: boolean
610
+ } {
611
+ const hasAccessToken = !!this.auth.getCurrentToken()
612
+ const hasRefreshToken = !!this.auth['refreshToken']
613
+ const tokenExpiry = this.auth['tokenExpiry'] as Date | undefined
614
+ const isTokenExpired = this.auth.isTokenExpired()
615
+
616
+ return {
617
+ isAuthenticated: this.auth.isAuthenticated(),
618
+ hasAccessToken,
619
+ hasRefreshToken,
620
+ tokenExpiry,
621
+ isTokenExpired,
622
+ needsReLogin: this.needsReLogin()
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Cerrar sesión y limpiar tokens
628
+ *
629
+ * Invalida los tokens JWT, detiene el keep-alive y limpia el estado de autenticación.
630
+ * Después de llamar este método, el cliente necesitará volver a autenticarse.
631
+ *
632
+ * @example
633
+ * ```typescript
634
+ * // Al final de la aplicación o cambio de usuario
635
+ * admin.logout()
636
+ *
637
+ * // El cliente ya no está autenticado
638
+ * console.log(admin.isAuthenticated()) // false
639
+ * ```
640
+ *
641
+ * @since 1.0.0
642
+ */
643
+ logout(): void {
644
+ this.auth.logout() // Esto internamente llama stopKeepAlive()
645
+ if (this.logger) {
646
+ this.logger.info('BaserowWithCredentialsWorkspace: Logged out')
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Obtener el workspace configurado
652
+ *
653
+ * Retorna la información completa del workspace al que está restringido este cliente.
654
+ * Útil para obtener ID, nombre y metadatos del workspace actual.
655
+ *
656
+ * @returns Información completa del workspace configurado
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * const workspace = admin.getDefaultWorkspace()
661
+ * console.log(`Workspace actual: ${workspace.name} (ID: ${workspace.id})`)
662
+ * ```
663
+ *
664
+ * @since 1.0.0
665
+ */
666
+ getDefaultWorkspace(): Workspace {
667
+ return this.defaultWorkspace
668
+ }
669
+
670
+ /**
671
+ * Obtener ID del workspace configurado
672
+ *
673
+ * Retorna únicamente el ID numérico del workspace al que está restringido este cliente.
674
+ * Método de conveniencia para obtener solo el ID sin metadatos adicionales.
675
+ *
676
+ * @returns ID numérico del workspace configurado
677
+ *
678
+ * @example
679
+ * ```typescript
680
+ * const workspaceId = admin.getWorkspaceId()
681
+ * console.log(`Operando en workspace ID: ${workspaceId}`)
682
+ * ```
683
+ *
684
+ * @since 1.0.0
685
+ */
686
+ getWorkspaceId(): number {
687
+ return this.defaultWorkspace.id
688
+ }
689
+
690
+ // ============================================================================
691
+ // API DIRECTO - SIN WORKSPACE PREFIX
692
+ // ============================================================================
693
+
694
+ /**
695
+ * Acceder a una database específica del workspace configurado (API simplificada)
696
+ *
697
+ * Crea un contexto de database que permite operaciones en tablas y filas.
698
+ * Solo puede acceder a databases del workspace configurado.
699
+ * Acepta tanto nombres de database como IDs numéricos.
700
+ *
701
+ * @param databaseIdentifier - Nombre de la database o ID numérico
702
+ * @returns Contexto de database con acceso a tablas y operaciones
703
+ *
704
+ * @example
705
+ * ```typescript
706
+ * // Usando nombre de la database
707
+ * const tables = await admin.database('My Database').tables.findMany()
708
+ * const table = await admin.database('My Database').table('Users').create()
709
+ *
710
+ * // Usando ID numérico
711
+ * const rows = await admin.database(123).table('Users').rows.list()
712
+ *
713
+ * // API simplificada completa
714
+ * const newTable = await admin.database('CRM')
715
+ * .table('Customers')
716
+ * .create({
717
+ * data: [['Name', 'Email'], ['John', 'john@example.com']],
718
+ * first_row_header: true
719
+ * })
720
+ *
721
+ * // Gestión de campos
722
+ * const field = await admin.database('CRM')
723
+ * .table('Customers')
724
+ * .field.createText('Company', undefined, 'Nombre de la empresa')
725
+ * ```
726
+ *
727
+ * @since 1.0.0
728
+ */
729
+ database(databaseIdentifier: string | number): DatabaseContext {
730
+ // Crear instancias de servicios según se necesiten para el contexto
731
+ const tableService = new TableService(this.http, this.logger)
732
+ const fieldService = new FieldService(this.http, this.logger)
733
+ const rowService = new RowService(this.http, this.logger)
734
+
735
+ return new DatabaseContext(
736
+ this.workspaceService,
737
+ this.defaultWorkspace.id,
738
+ databaseIdentifier,
739
+ this.databases,
740
+ tableService,
741
+ fieldService,
742
+ rowService,
743
+ this.logger
744
+ )
745
+ }
746
+
747
+ /**
748
+ * Acceder a gestión de Database Tokens del workspace configurado
749
+ *
750
+ * Proporciona operaciones completas de Database Tokens limitadas al workspace actual.
751
+ * Los Database Tokens creados solo tendrán acceso a databases de este workspace.
752
+ *
753
+ * @returns Contexto de Database Token con operaciones CRUD
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * // Crear token con acceso completo al workspace
758
+ * const fullToken = await admin.databaseToken.createFullAccess('app-token')
759
+ *
760
+ * // Crear token con permisos específicos
761
+ * const readOnlyToken = await admin.databaseToken.create('readonly-token', {
762
+ * databases: [{ database_id: 123, permissions: 'read' }]
763
+ * })
764
+ *
765
+ * // Listar todos los tokens del workspace
766
+ * const tokens = await admin.databaseToken.list()
767
+ *
768
+ * // Gestionar token específico
769
+ * await admin.databaseToken.update(tokenId, { name: 'New Name' })
770
+ * await admin.databaseToken.delete(tokenId)
771
+ * ```
772
+ *
773
+ * @since 1.0.0
774
+ */
775
+ get databaseToken(): DatabaseTokenContext {
776
+ return new DatabaseTokenContext(
777
+ this.workspaceService,
778
+ this.defaultWorkspace.id,
779
+ this.databaseTokenService,
780
+ this.logger
781
+ )
782
+ }
783
+
784
+ /**
785
+ * Obtener estadísticas de uso del workspace configurado
786
+ *
787
+ * Recopila información sobre recursos y estado del workspace al que está
788
+ * restringido este cliente. Útil para dashboards y monitoreo.
789
+ *
790
+ * @returns Promise con estadísticas completas del workspace
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * const stats = await admin.getUsageStats()
795
+ * console.log(`Workspace: ${stats.workspace.name}`)
796
+ * console.log(`Databases: ${stats.totalDatabases}`)
797
+ * console.log(`Tables: ${stats.totalTables}`)
798
+ * console.log(`Autenticado: ${stats.isAuthenticated}`)
799
+ * console.log(`Token válido: ${stats.tokenValid}`)
800
+ * console.log(`Modo: ${stats.mode}`) // 'workspace-restricted'
801
+ * ```
802
+ *
803
+ * @since 1.0.0
804
+ */
805
+ async getUsageStats(): Promise<{
806
+ totalDatabases: number
807
+ totalTables: number
808
+ isAuthenticated: boolean
809
+ tokenValid: boolean
810
+ workspace: { id: number; name: string }
811
+ mode: 'workspace-restricted'
812
+ }> {
813
+ // Filtrar databases del workspace configurado
814
+ const allDatabases = await this.databases.findMany()
815
+ const databases = allDatabases.filter(db => db.workspace?.id === this.defaultWorkspace.id)
816
+
817
+ // Usar las tablas que ya vienen en el objeto Database (más eficiente)
818
+ const totalTables = databases.reduce((sum, db) => sum + (db.tables?.length || 0), 0)
819
+
820
+ return {
821
+ totalDatabases: databases.length,
822
+ totalTables,
823
+ isAuthenticated: this.isAuthenticated(),
824
+ tokenValid: !this.auth.isTokenExpired(),
825
+ mode: 'workspace-restricted',
826
+ workspace: {
827
+ id: this.defaultWorkspace.id,
828
+ name: this.defaultWorkspace.name
829
+ }
830
+ }
831
+ }
832
+
833
+ /**
834
+ * Cerrar conexiones HTTP y limpiar recursos
835
+ *
836
+ * Detiene el keep-alive, libera recursos del cliente HTTP y cierra conexiones pendientes.
837
+ * Debe llamarse cuando el cliente ya no se necesite para evitar memory leaks.
838
+ *
839
+ * @example
840
+ * ```typescript
841
+ * // Al final de la aplicación o cuando ya no se necesite el cliente
842
+ * admin.destroy()
843
+ * ```
844
+ *
845
+ * @since 1.0.0
846
+ */
847
+ destroy(): void {
848
+ this.logger?.debug?.('Destroying BaserowWithCredentialsWorkspace')
849
+ this.auth.stopKeepAlive()
850
+ this.http.destroy()
851
+ }
852
+ }