@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,545 @@
1
+ import { BaserowAdminConfig, LoginResponse, 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 { WorkspaceService } from './services/WorkspaceService'
8
+ import { DatabaseService } from './services/DatabaseService'
9
+ import { DatabaseTokenService } from './services/DatabaseTokenService'
10
+ import { TableService } from './services/TableService'
11
+ import { FieldService } from './services/FieldService'
12
+ import { RowService } from './services/RowService'
13
+ import { WorkspaceContext } from './contexts/WorkspaceContext'
14
+ import { BaseAuthClient } from './services/core/BaseAuthClient'
15
+
16
+ /**
17
+ * Cliente administrativo con acceso global a todos los workspaces
18
+ *
19
+ * Proporciona acceso completo de administración usando JWT authentication con auto-refresh.
20
+ * API jerárquica que siempre comienza por workspace para mantener contexto claro.
21
+ * Ideal para scripts de administración y operaciones multi-workspace.
22
+ *
23
+ * **Características:**
24
+ * - Autenticación JWT con auto-refresh automático
25
+ * - API jerárquica: siempre `admin.workspace(name).database(name)`
26
+ * - Acceso global a todos los workspaces del usuario
27
+ * - Operaciones administrativas completas: crear/modificar estructura
28
+ * - Resolución automática de nombres a IDs
29
+ * - Validación de permisos y contextos
30
+ *
31
+ * **API Jerárquica:**
32
+ * - `admin.workspaces` → operaciones masivas de workspaces
33
+ * - `admin.workspace(name)` → contexto de workspace específico
34
+ * - `admin.workspace(name).databases` → operaciones masivas de databases
35
+ * - `admin.workspace(name).database(name)` → contexto de database específica
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // Crear cliente admin global usando la API unificada
40
+ * const admin = await BaserowClient.create({
41
+ * url: 'https://baserow.example.com',
42
+ * credentials: {
43
+ * email: 'admin@example.com',
44
+ * password: 'password123'
45
+ * }
46
+ * })
47
+ *
48
+ * // API jerárquica que siempre comienza por workspace
49
+ * const workspaces = await admin.workspaces.findMany()
50
+ * const workspace = await admin.workspaces.create({ name: 'New Workspace' })
51
+ *
52
+ * // Gestión completa de workspaces con API jerárquica
53
+ * const updatedWS = await admin.workspace('Company').update({ name: 'New Company' })
54
+ * await admin.workspace('Old Company').delete()
55
+ *
56
+ * // Operaciones específicas con contexto jerárquico
57
+ * const database = await admin.workspace('Company')
58
+ * .database('CRM')
59
+ * .create()
60
+ *
61
+ * const table = await admin.workspace('Company')
62
+ * .database('CRM')
63
+ * .table('Customers')
64
+ * .create({
65
+ * data: [['Name', 'Email'], ['Alice', 'alice@example.com']],
66
+ * first_row_header: true
67
+ * })
68
+ * ```
69
+ *
70
+ * @since 1.0.0
71
+ */
72
+ export class ClientWithCreds extends BaseAuthClient<BaserowAdminConfig, 'credentials'> {
73
+ // Servicios para operaciones globales
74
+ public readonly workspaces: WorkspaceService
75
+ private readonly databaseService: DatabaseService
76
+ private readonly databaseTokenService: DatabaseTokenService
77
+ private readonly tableService: TableService
78
+ private readonly fieldService: FieldService
79
+ private readonly rowService: RowService
80
+
81
+ private constructor(config: BaserowAdminConfig, loginResponse: LoginResponse) {
82
+ // Procesar performance con defaults antes de guardar
83
+ const processedConfig = {
84
+ ...config,
85
+ performance: PerformanceManager.merge(config.performance)
86
+ }
87
+
88
+ // Inicializar HttpClient con JWT token
89
+ const http = createHttpClient({
90
+ url: processedConfig.url,
91
+ token: loginResponse.access_token,
92
+ logger: processedConfig.logger,
93
+ performance: processedConfig.performance
94
+ })
95
+
96
+ // Inicializar AuthService
97
+ const auth = new AuthService(http, processedConfig.logger)
98
+
99
+ super(processedConfig, http, auth, processedConfig.logger)
100
+
101
+ // Transferir tokens al AuthService ANTES de configurar auto-refresh
102
+ setupJwtTokens(this.auth, loginResponse)
103
+
104
+ // Configurar auto-refresh de tokens (ahora tiene los tokens almacenados)
105
+ this.auth.setupAutoRefresh()
106
+
107
+ // Iniciar keep-alive si está habilitado
108
+ if (processedConfig.keepAlive?.enabled) {
109
+ const intervalMinutes = processedConfig.keepAlive.intervalMinutes ?? 7200
110
+ this.auth.startKeepAlive(intervalMinutes)
111
+ }
112
+
113
+ // Inicializar servicios
114
+ this.workspaces = new WorkspaceService(this.http, this.logger)
115
+ this.databaseService = new DatabaseService(this.http, this.logger)
116
+ this.databaseTokenService = new DatabaseTokenService(this.http, this.logger)
117
+ this.tableService = new TableService(this.http, this.logger)
118
+ this.fieldService = new FieldService(this.http, this.logger)
119
+ this.rowService = new RowService(this.http, this.logger)
120
+
121
+ if (this.logger) {
122
+ this.logger.info('ClientWithCreds initialized with JWT authentication (global mode)')
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Campos que deben excluirse por defecto para cliente con credenciales
128
+ * Excluye credentials por seguridad
129
+ */
130
+ protected getDefaultExcludeKeys(): string[] {
131
+ return ['credentials']
132
+ }
133
+
134
+ /**
135
+ * Crear instancia de ClientWithCreds con auto-login
136
+ *
137
+ * Factory method que maneja la autenticación automáticamente y retorna una instancia
138
+ * lista para usar con JWT token válido y auto-refresh configurado.
139
+ *
140
+ * **Keep-Alive opcional** para backends 24/7:
141
+ * - Habilitar con `keepAlive: { enabled: true }` para evitar expiración de refresh token
142
+ * - Default: re-login cada 5 días (7200 min), margen de seguridad de 2 días
143
+ * - ⚠️ Almacena credenciales en memoria (solo para backends confiables)
144
+ *
145
+ * @param config - Configuración del cliente administrativo
146
+ * @param config.url - URL del servidor Baserow
147
+ * @param config.credentials - Credenciales de usuario (email/password)
148
+ * @param config.keepAlive - Opcional: configuración keep-alive para backends 24/7
149
+ * @param config.logger - Logger opcional para debugging
150
+ * @returns Promise que resuelve a instancia autenticada de ClientWithCreds
151
+ *
152
+ * @throws {BaserowConfigError} Si la configuración es inválida
153
+ * @throws {BaserowAuthError} Si las credenciales son incorrectas
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * // Cliente básico sin keep-alive
158
+ * const admin = await BaserowClient.create({
159
+ * url: 'https://baserow.example.com',
160
+ * credentials: {
161
+ * email: 'admin@example.com',
162
+ * password: 'secure-password'
163
+ * }
164
+ * })
165
+ *
166
+ * // Cliente con keep-alive para backend 24/7 (re-login cada 5 días)
167
+ * const adminBackend = await BaserowClient.create({
168
+ * url: 'https://baserow.example.com',
169
+ * credentials: {
170
+ * email: 'admin@example.com',
171
+ * password: 'secure-password'
172
+ * },
173
+ * keepAlive: { enabled: true }
174
+ * })
175
+ *
176
+ * // Cliente listo para usar
177
+ * const workspaces = await admin.workspaces.findMany()
178
+ * ```
179
+ *
180
+ * @since 1.0.0
181
+ */
182
+ static async create(config: BaserowAdminConfig): Promise<ClientWithCreds> {
183
+ validateRequired(config, 'config')
184
+ validateString(config.url, 'url')
185
+ validateRequired(config.credentials, 'credentials')
186
+ validateString(config.credentials.email, 'email')
187
+ validateString(config.credentials.password, 'password')
188
+
189
+ if (!validateUrl(config.url)) {
190
+ throw new BaserowConfigError('Invalid URL format', 'url')
191
+ }
192
+
193
+ // Crear HttpClient temporal para login
194
+ const tempHttp = createHttpClient({
195
+ url: config.url,
196
+ token: '', // Sin token inicial
197
+ logger: config.logger,
198
+ performance: config.performance
199
+ })
200
+
201
+ // Crear AuthService temporal
202
+ const tempAuth = new AuthService(tempHttp, config.logger)
203
+
204
+ // Realizar login
205
+ const loginResponse = await tempAuth.login(config.credentials)
206
+
207
+ if (config.logger) {
208
+ config.logger.info(`ClientWithCreds: Logged in as ${loginResponse.user.username}`)
209
+ }
210
+
211
+ // Crear instancia con token válido
212
+ // Los tokens son transferidos dentro del constructor
213
+ const admin = new ClientWithCreds(config, loginResponse)
214
+
215
+ return admin
216
+ }
217
+
218
+ /**
219
+ * Verificar estado de salud del servidor Baserow
220
+ *
221
+ * Realiza un health check del servidor sin requerir autenticación.
222
+ * Útil para verificar conectividad antes de realizar operaciones.
223
+ *
224
+ * @returns Promise que resuelve a `true` si el servidor está saludable, `false` en caso contrario
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * const isHealthy = await admin.health()
229
+ * if (!isHealthy) {
230
+ * console.log('Servidor Baserow no disponible')
231
+ * return
232
+ * }
233
+ *
234
+ * // Proceder con operaciones
235
+ * const workspaces = await admin.workspaces.findMany()
236
+ * ```
237
+ *
238
+ * @since 1.0.0
239
+ */
240
+ async health(): Promise<boolean> {
241
+ try {
242
+ // Usar endpoint de salud oficial (no requiere autenticación)
243
+ const healthUrl = `${this.config.url.replace(/\/$/, '')}/api/_health/`
244
+ const response = await fetch(healthUrl, {
245
+ method: 'GET',
246
+ headers: { 'Content-Type': 'application/json' }
247
+ })
248
+
249
+ return response.ok
250
+ } catch (error) {
251
+ if (this.logger) {
252
+ this.logger.error('ClientWithCreds health check failed:', error)
253
+ }
254
+ return false
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Obtener la configuración actual del cliente (sin exponer credenciales)
260
+ *
261
+ * Retorna una copia de solo lectura de la configuración, excluyendo las credenciales
262
+ * por seguridad. Útil para debugging y logging.
263
+ *
264
+ * @returns Configuración actual sin credenciales (solo lectura)
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * const config = admin.getConfig()
269
+ * console.log('URL servidor:', config.url)
270
+ * // Las credenciales no están incluidas por seguridad
271
+ * ```
272
+ *
273
+ * @since 1.0.0
274
+ */
275
+ getConfig(): Readonly<Omit<BaserowAdminConfig, 'credentials'>> {
276
+ return this.getConfigBase()
277
+ }
278
+
279
+ /**
280
+ * Verificar si el cliente está autenticado
281
+ *
282
+ * Comprueba si hay un token JWT válido y no expirado.
283
+ * Si el token está próximo a expirar, el auto-refresh se encargará de renovarlo.
284
+ *
285
+ * @returns `true` si está autenticado con token válido, `false` en caso contrario
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * if (admin.isAuthenticated()) {
290
+ * const workspaces = await admin.workspaces.findMany()
291
+ * } else {
292
+ * console.log('Sesión expirada, necesario re-login')
293
+ * }
294
+ * ```
295
+ *
296
+ * @since 1.0.0
297
+ */
298
+ isAuthenticated(): boolean {
299
+ return this.auth.isAuthenticated()
300
+ }
301
+
302
+ /**
303
+ * Obtener el token JWT actual
304
+ *
305
+ * Retorna el token de acceso actual si está disponible.
306
+ * Útil para debugging o integración con otros sistemas.
307
+ *
308
+ * @returns Token JWT actual o `undefined` si no está autenticado
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * const token = admin.getCurrentToken()
313
+ * if (token) {
314
+ * console.log('Token disponible:', token.substring(0, 20) + '...')
315
+ * }
316
+ * ```
317
+ *
318
+ * @since 1.0.0
319
+ */
320
+ getCurrentToken(): string | undefined {
321
+ return this.auth.getCurrentToken()
322
+ }
323
+
324
+ /**
325
+ * Renovar el token de acceso manualmente
326
+ *
327
+ * Fuerza la renovación del token JWT usando el refresh token.
328
+ * Normalmente no es necesario llamar esto manualmente ya que el auto-refresh
329
+ * se encarga automáticamente.
330
+ *
331
+ * @returns Promise que resuelve al nuevo token de acceso
332
+ *
333
+ * @throws {BaserowAuthError} Si el refresh token es inválido o expirado
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * try {
338
+ * const newToken = await admin.refreshToken()
339
+ * console.log('Token renovado exitosamente')
340
+ * } catch (error) {
341
+ * console.log('Error renovando token, necesario re-login')
342
+ * }
343
+ * ```
344
+ *
345
+ * @since 1.0.0
346
+ */
347
+ async refreshToken(): Promise<string> {
348
+ return await this.auth.refreshAccessToken()
349
+ }
350
+
351
+ /**
352
+ * Verificar si el cliente necesita re-autenticación
353
+ *
354
+ * Útil para detectar si los tokens se han perdido o el refresh token expiró,
355
+ * permitiendo manejar proactivamente la re-autenticación antes de hacer requests.
356
+ *
357
+ * @returns `true` si necesita re-login, `false` si la sesión es válida
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * if (admin.needsReLogin()) {
362
+ * console.log('Sesión expirada, solicitando credenciales...')
363
+ * const newAdmin = await ClientWithCreds.create({
364
+ * url: config.url,
365
+ * credentials: { email, password }
366
+ * })
367
+ * } else {
368
+ * const workspaces = await admin.workspaces.findMany()
369
+ * }
370
+ * ```
371
+ *
372
+ * @since 1.0.4
373
+ */
374
+ needsReLogin(): boolean {
375
+ if (!this.auth.getCurrentToken() || !this.auth['refreshToken']) {
376
+ return true
377
+ }
378
+
379
+ if (this.auth.isTokenExpired() && !this.auth['refreshToken']) {
380
+ return true
381
+ }
382
+
383
+ return false
384
+ }
385
+
386
+ /**
387
+ * Obtener estado detallado de autenticación
388
+ *
389
+ * @returns Objeto con información detallada del estado de autenticación
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * const status = admin.getAuthStatus()
394
+ * console.log('Autenticado:', status.isAuthenticated)
395
+ * console.log('Necesita re-login:', status.needsReLogin)
396
+ * ```
397
+ *
398
+ * @since 1.0.4
399
+ */
400
+ getAuthStatus(): {
401
+ isAuthenticated: boolean
402
+ hasAccessToken: boolean
403
+ hasRefreshToken: boolean
404
+ tokenExpiry?: Date
405
+ isTokenExpired: boolean
406
+ needsReLogin: boolean
407
+ } {
408
+ const hasAccessToken = !!this.auth.getCurrentToken()
409
+ const hasRefreshToken = !!this.auth['refreshToken']
410
+ const tokenExpiry = this.auth['tokenExpiry'] as Date | undefined
411
+ const isTokenExpired = this.auth.isTokenExpired()
412
+
413
+ return {
414
+ isAuthenticated: this.auth.isAuthenticated(),
415
+ hasAccessToken,
416
+ hasRefreshToken,
417
+ tokenExpiry,
418
+ isTokenExpired,
419
+ needsReLogin: this.needsReLogin()
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Cerrar sesión y limpiar tokens
425
+ *
426
+ * Invalida los tokens JWT, detiene el keep-alive y limpia el estado de autenticación.
427
+ * Después de llamar este método, el cliente necesitará volver a autenticarse.
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * // Al final de la aplicación o cambio de usuario
432
+ * admin.logout()
433
+ *
434
+ * // El cliente ya no está autenticado
435
+ * console.log(admin.isAuthenticated()) // false
436
+ * ```
437
+ *
438
+ * @since 1.0.0
439
+ */
440
+ logout(): void {
441
+ this.auth.logout() // Esto internamente llama stopKeepAlive()
442
+ if (this.logger) {
443
+ this.logger.info('ClientWithCreds: Logged out')
444
+ }
445
+ }
446
+
447
+ // ============================================================================
448
+ // API JERÁRQUICA - SIEMPRE COMIENZA POR WORKSPACE
449
+ // ============================================================================
450
+
451
+ /**
452
+ * Acceder a un workspace específico (API jerárquica)
453
+ *
454
+ * Crea un contexto de workspace que permite operaciones en databases y recursos anidados.
455
+ * Acepta tanto nombres de workspace como IDs numéricos.
456
+ *
457
+ * @param workspaceIdentifier - Nombre del workspace o ID numérico
458
+ * @returns Contexto de workspace con acceso a databases y operaciones
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * // Usando nombre del workspace
463
+ * const databases = await admin.workspace('My Workspace').databases.list()
464
+ * const database = await admin.workspace('My Workspace').databases.create({
465
+ * name: 'New Database'
466
+ * })
467
+ *
468
+ * // Usando ID numérico
469
+ * const tables = await admin.workspace(123).database('My DB').tables.findMany()
470
+ *
471
+ * // API jerárquica completa
472
+ * const table = await admin.workspace('Company')
473
+ * .database('CRM')
474
+ * .table('Customers')
475
+ * .create({
476
+ * data: [['Name', 'Email'], ['John', 'john@example.com']],
477
+ * first_row_header: true
478
+ * })
479
+ * ```
480
+ *
481
+ * @since 1.0.0
482
+ */
483
+ workspace(workspaceIdentifier: string | number): WorkspaceContext {
484
+ return new WorkspaceContext(
485
+ this.workspaces,
486
+ this.databaseService,
487
+ this.databaseTokenService,
488
+ this.tableService,
489
+ this.fieldService,
490
+ this.rowService,
491
+ workspaceIdentifier,
492
+ this.logger
493
+ )
494
+ }
495
+
496
+ /**
497
+ * Obtener estadísticas de uso global
498
+ */
499
+ async getUsageStats(): Promise<{
500
+ totalWorkspaces: number
501
+ totalDatabases: number
502
+ totalTables: number
503
+ isAuthenticated: boolean
504
+ tokenValid: boolean
505
+ mode: 'global'
506
+ }> {
507
+ const workspaces = await this.workspaces.findMany()
508
+
509
+ // Contar databases y tables de todos los workspaces
510
+ let totalDatabases = 0
511
+ let totalTables = 0
512
+
513
+ for (const workspace of workspaces) {
514
+ const databases = await this.databaseService.findMany()
515
+ const workspaceDatabases = databases.filter(db => db.workspace?.id === workspace.id)
516
+ totalDatabases += workspaceDatabases.length
517
+
518
+ // Usar las tablas que ya vienen en el objeto Database (más eficiente)
519
+ totalTables += workspaceDatabases.reduce((sum, db) => sum + (db.tables?.length || 0), 0)
520
+ }
521
+
522
+ return {
523
+ totalWorkspaces: workspaces.length,
524
+ totalDatabases,
525
+ totalTables,
526
+ isAuthenticated: this.isAuthenticated(),
527
+ tokenValid: !this.auth.isTokenExpired(),
528
+ mode: 'global'
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Cerrar conexiones HTTP y limpiar recursos
534
+ *
535
+ * Detiene el keep-alive y libera recursos del cliente HTTP.
536
+ * Debe llamarse cuando el cliente ya no se necesite.
537
+ *
538
+ * @since 1.0.0
539
+ */
540
+ destroy(): void {
541
+ this.logger?.debug?.('Destroying ClientWithCreds')
542
+ this.auth.stopKeepAlive()
543
+ this.http.destroy()
544
+ }
545
+ }