@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,246 @@
1
+ import { HttpService } from './core/HttpService'
2
+ import { ValidationService } from './core/ValidationService'
3
+ import { Database, BaserowResponse, BaserowNotFoundError, Logger } from '../types/index'
4
+
5
+ /**
6
+ * Servicio para operaciones CRUD de databases de Baserow
7
+ *
8
+ * Proporciona operaciones completas de databases (aplicaciones) incluyendo
9
+ * CRUD básico, búsqueda por nombre y gestión dentro de workspaces.
10
+ * Las databases son contenedores de tablas en la jerarquía de Baserow.
11
+ *
12
+ * **Características:**
13
+ * - CRUD completo: create, read, update, delete
14
+ * - Búsqueda de databases por nombre
15
+ * - Gestión por workspace
16
+ * - Validación de nombres y IDs
17
+ * - Manejo robusto de errores
18
+ *
19
+ * **Jerarquía de Baserow:**
20
+ * ```
21
+ * Workspace → Database → Table → Field/Row
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // Operaciones básicas
27
+ * const databases = await databaseService.list()
28
+ * const database = await databaseService.findUnique(123)
29
+ *
30
+ * // Búsqueda y gestión
31
+ * const found = await databaseService.findDatabaseByName('Mi Database')
32
+ * const newDb = await databaseService.createDatabase(workspaceId, 'Nueva DB')
33
+ * await databaseService.updateDatabase(dbId, { name: 'Nuevo Nombre' })
34
+ * await databaseService.delete(dbId)
35
+ * ```
36
+ *
37
+ * @since 1.0.0
38
+ */
39
+ export class DatabaseService extends HttpService {
40
+ private validationService: ValidationService
41
+ private defaultWorkspaceId?: number
42
+
43
+ constructor(http: any, logger?: Logger, defaultWorkspaceId?: number) {
44
+ super(http, logger)
45
+ this.validationService = new ValidationService(http, logger)
46
+ this.defaultWorkspaceId = defaultWorkspaceId
47
+ }
48
+
49
+ // ===== PUBLIC API (Prisma-style) =====
50
+
51
+ /**
52
+ * Listar todas las databases accesibles
53
+ *
54
+ * Obtiene todas las databases a las que el token tiene acceso.
55
+ * Con JWT devuelve todas las databases del usuario, con Database Token
56
+ * solo las databases permitidas por el token.
57
+ *
58
+ * @returns Promise con array de databases
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const databases = await databaseService.findMany()
63
+ * databases.forEach(db => {
64
+ * console.log(`${db.name} (ID: ${db.id}) - ${db.tables?.length || 0} tablas`)
65
+ * })
66
+ * ```
67
+ *
68
+ * @since 1.0.0
69
+ */
70
+ async findMany(): Promise<Database[]> {
71
+ try {
72
+ // JWT API devuelve array directo, Database Token API devuelve {results: [...]}
73
+ const response = await this.http.get<BaserowResponse<Database> | Database[]>('/applications/')
74
+ const databases = Array.isArray(response) ? response : response.results
75
+
76
+ this.logSuccess('list databases', undefined, { count: databases.length })
77
+ return databases || []
78
+ } catch (error) {
79
+ this.handleHttpError(error, 'list databases')
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Buscar database por ID o nombre
85
+ *
86
+ * Busca una database específica por su ID o nombre dentro de todas las
87
+ * databases accesibles. Útil para operaciones dinámicas donde se
88
+ * conoce el identificador pero no se sabe si es ID o nombre.
89
+ *
90
+ * @param identifier - ID numérico o nombre de la database a buscar
91
+ * @returns Promise con la database encontrada o null si no existe
92
+ *
93
+ * @throws {BaserowValidationError} Si el identificador es inválido
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const database = await databaseService.findUnique('CRM')
98
+ * if (database) {
99
+ * console.log(`Database encontrada: ${database.id}`)
100
+ * } else {
101
+ * console.log('Database no encontrada')
102
+ * }
103
+ * ```
104
+ *
105
+ * @since 1.0.0
106
+ */
107
+ async findUnique(identifier: string | number): Promise<Database | null> {
108
+ if (typeof identifier === 'number') {
109
+ // Buscar por ID
110
+ try {
111
+ this.validationService.validateId(identifier, 'database ID')
112
+ return await this.getById<Database>('/applications', identifier)
113
+ } catch (error) {
114
+ if (error instanceof BaserowNotFoundError) {
115
+ return null
116
+ }
117
+ throw error
118
+ }
119
+ } else {
120
+ // Buscar por nombre
121
+ this.validationService.validateResourceName(identifier, 'database')
122
+ const databases = await this.findMany()
123
+ const found = databases.find(db => db.name === identifier) || null
124
+
125
+ if (found) {
126
+ this.logSuccess(`find database by name "${identifier}"`, found.id)
127
+ } else {
128
+ this.logDebug(`No database found with name "${identifier}"`)
129
+ }
130
+
131
+ return found
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Crear nueva database - API pública
137
+ *
138
+ * Crea una nueva database dentro del workspace especificado o usa el workspace por defecto.
139
+ * La database se crea vacía sin tablas iniciales.
140
+ *
141
+ * @param workspaceIdOrName - ID numérico del workspace donde crear la database, o nombre si hay workspace por defecto
142
+ * @param name - Nombre de la nueva database (opcional si el primer parámetro es nombre)
143
+ * @returns Promise con la database creada
144
+ *
145
+ * @throws {BaserowValidationError} Si los parámetros son inválidos
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * // Con workspace específico
150
+ * const newDatabase = await databaseService.create(456, 'Nueva Database')
151
+ *
152
+ * // Con workspace por defecto (ClientWithCredsWs)
153
+ * const newDatabase = await databaseService.create('Nueva Database')
154
+ * ```
155
+ *
156
+ * @since 1.0.0
157
+ */
158
+ async create(workspaceIdOrName: number | string, name?: string): Promise<Database> {
159
+ if (typeof workspaceIdOrName === 'number') {
160
+ // Caso tradicional: create(workspaceId, name)
161
+ if (!name) {
162
+ throw new Error('Database name is required when providing workspace ID')
163
+ }
164
+ return this.createDatabaseInternal(workspaceIdOrName, name)
165
+ } else {
166
+ // Caso nuevo: create(name) usando workspace por defecto
167
+ if (!this.defaultWorkspaceId) {
168
+ throw new Error('No default workspace configured. Use create(workspaceId, name) instead')
169
+ }
170
+ return this.createDatabaseInternal(this.defaultWorkspaceId, workspaceIdOrName)
171
+ }
172
+ }
173
+
174
+ // ===== PRIVATE METHODS =====
175
+
176
+ /**
177
+ * Crear nueva database (método interno)
178
+ * @private - Solo para uso interno del servicio
179
+ */
180
+ private async createDatabaseInternal(workspaceId: number, name: string): Promise<Database> {
181
+ this.validationService.validateId(workspaceId, 'workspace ID')
182
+ this.validationService.validateResourceName(name, 'database')
183
+
184
+ const requestData = {
185
+ type: 'database',
186
+ name
187
+ }
188
+
189
+ try {
190
+ this.logDebug(`Creating database "${name}" in workspace ${workspaceId}`, requestData)
191
+ const response = await this.http.post<Database>(`/applications/workspace/${workspaceId}/`, requestData)
192
+ this.logSuccess('create database', response.id, { name: name })
193
+ return response
194
+ } catch (error) {
195
+ this.handleHttpError(error, 'create database')
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Actualizar database existente (método interno)
201
+ * @private - Solo para uso por DatabaseContext
202
+ */
203
+ private async updateDatabaseInternal(id: number, data: { name?: string; workspace_id?: number }): Promise<Database> {
204
+ this.validationService.validateId(id, 'database ID')
205
+
206
+ if (data.name) {
207
+ this.validationService.validateResourceName(data.name, 'database')
208
+ }
209
+
210
+ try {
211
+ this.logDebug(`Updating database ${id}`, data)
212
+ const response = await this.http.patch<Database>(`/applications/${id}/`, data)
213
+ this.logSuccess('update database', id)
214
+ return response
215
+ } catch (error) {
216
+ if ((error as any).status === 404) {
217
+ throw new BaserowNotFoundError('Database', id)
218
+ }
219
+ this.handleHttpError(error, 'update database', id)
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Eliminar database (método interno)
225
+ * @private - Solo para uso por DatabaseContext
226
+ */
227
+ private async deleteDatabaseInternal(id: number): Promise<void> {
228
+ this.validationService.validateId(id, 'database ID')
229
+ return this.deleteById('/applications', id)
230
+ }
231
+
232
+ // ===== FRIEND ACCESS PATTERN FOR DATABASE CONTEXT =====
233
+ // Symbol-based access que no aparece en la API pública pero permite acceso interno
234
+
235
+ /**
236
+ * Friend access para DatabaseContext
237
+ * No aparece en intellisense normal ni en la API pública
238
+ * @internal
239
+ */
240
+ get [Symbol.for('databaseContext')]() {
241
+ return {
242
+ updateDatabase: this.updateDatabaseInternal.bind(this),
243
+ deleteDatabase: this.deleteDatabaseInternal.bind(this)
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,186 @@
1
+ import { HttpService } from './core/HttpService'
2
+ import { ValidationService } from './core/ValidationService'
3
+ import {
4
+ DatabaseToken,
5
+ DatabaseTokenPermissions,
6
+ CreateDatabaseTokenRequest,
7
+ UpdateDatabaseTokenRequest,
8
+ BaserowResponse,
9
+ BaserowNotFoundError,
10
+ Logger
11
+ } from '../types/index'
12
+ import { validateString } from '../utils/validation'
13
+
14
+ export class DatabaseTokenService extends HttpService {
15
+ private validationService: ValidationService
16
+
17
+ constructor(http: any, logger?: Logger) {
18
+ super(http, logger)
19
+ this.validationService = new ValidationService(http, logger)
20
+ }
21
+
22
+ /**
23
+ * Listar database tokens de un workspace
24
+ */
25
+ async list(workspaceId: number): Promise<DatabaseToken[]> {
26
+ this.validationService.validateId(workspaceId, 'workspace ID')
27
+
28
+ try {
29
+ const response = await this.http.get<BaserowResponse<any> | any[]>(`/database/tokens/`, {
30
+ params: { workspace_id: workspaceId }
31
+ })
32
+
33
+ // La API puede devolver array directo o {results: []}
34
+ const rawTokens = Array.isArray(response) ? response : response.results || []
35
+ // Mapear key → token para consistencia en la interfaz
36
+ const tokens = rawTokens.map((token: any) => ({ ...token, token: token.key || '' }))
37
+ this.logSuccess('list database tokens', workspaceId, { count: tokens.length })
38
+ return tokens
39
+ } catch (error) {
40
+ this.handleHttpError(error, 'list database tokens', workspaceId)
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Obtener database token por ID
46
+ */
47
+ async get(tokenId: number): Promise<DatabaseToken> {
48
+ this.validationService.validateId(tokenId, 'token ID')
49
+ const response = await this.getById<any>('/database/tokens', tokenId)
50
+ return { ...response, token: response.key || '' }
51
+ }
52
+
53
+ /**
54
+ * Obtener database token por su valor (key)
55
+ * Busca en la lista de tokens del workspace correspondiente
56
+ */
57
+ async getToken(tokenValue: string): Promise<DatabaseToken> {
58
+ validateString(tokenValue, 'token value')
59
+
60
+ try {
61
+ this.logDebug(`Getting database token by value`, { tokenPreview: tokenValue.substring(0, 8) + '...' })
62
+
63
+ // Como no hay endpoint directo, buscamos en todas las listas de workspaces
64
+ // Este es un método menos eficiente pero funcional
65
+ throw new Error('getToken requires workspace ID - use context.getToken() instead')
66
+ } catch (error) {
67
+ this.handleHttpError(error, 'get database token by value', tokenValue)
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Crear nuevo database token
73
+ *
74
+ * NOTA: La API de Baserow tiene un comportamiento específico:
75
+ * 1. Al crear el token, siempre establece TODOS los permisos en `true`
76
+ * 2. Para aplicar permisos específicos, se requiere una actualización posterior
77
+ *
78
+ * Esta implementación maneja automáticamente este comportamiento:
79
+ * - POST /database/tokens/ → crea token con permisos completos
80
+ * - PATCH /database/tokens/{id}/ → ajusta permisos al valor deseado
81
+ */
82
+ async createToken(workspaceId: number, data: CreateDatabaseTokenRequest): Promise<DatabaseToken> {
83
+ this.validationService.validateId(workspaceId, 'workspace ID')
84
+ this.validationService.validateResourceName(data.name, 'token')
85
+
86
+ if (!data.permissions) {
87
+ throw new Error('Database token permissions are required')
88
+ }
89
+
90
+ const payload = {
91
+ workspace: workspaceId,
92
+ ...data
93
+ }
94
+
95
+ try {
96
+ this.logDebug(`Creating database token "${data.name}" for workspace ${workspaceId}`, payload)
97
+ // 1. Crear token (la API establece automáticamente permisos completos)
98
+ const response = await this.http.post<any>(`/database/tokens/`, payload)
99
+ if (!response.key) throw new Error('Database token key is missing')
100
+ response.token = response.key
101
+ // 2. Actualizar permisos al valor deseado (si no son permisos completos)
102
+ const needsPermissionUpdate = !this.hasFullPermissions(data.permissions)
103
+ if (needsPermissionUpdate) {
104
+ const updatedToken = await this.updateToken(response.id, { permissions: data.permissions })
105
+ this.logSuccess('create database token', response.id, { name: data.name, workspace: workspaceId })
106
+ return updatedToken
107
+ }
108
+ this.logSuccess('create database token', response.id, { name: data.name, workspace: workspaceId })
109
+ return { ...response, token: response.key, permissions: data.permissions }
110
+ } catch (error) {
111
+ this.handleHttpError(error, 'create database token', workspaceId)
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Verificar si los permisos dados son permisos completos (todos true)
117
+ */
118
+ private hasFullPermissions(permissions: DatabaseTokenPermissions): boolean {
119
+ return permissions.create && permissions.read && permissions.update && permissions.delete
120
+ }
121
+
122
+ /**
123
+ * Actualizar database token
124
+ */
125
+ async updateToken(tokenId: number, data: UpdateDatabaseTokenRequest): Promise<DatabaseToken> {
126
+ this.validationService.validateId(tokenId, 'token ID')
127
+
128
+ if (data.name !== undefined) {
129
+ this.validationService.validateResourceName(data.name, 'token')
130
+ }
131
+
132
+ try {
133
+ this.logDebug(`Updating database token ${tokenId}`, data)
134
+ const response = await this.http.patch<any>(`/database/tokens/${tokenId}/`, data)
135
+ this.logSuccess('update database token', tokenId)
136
+ return { ...response, token: response.key || '' }
137
+ } catch (error) {
138
+ if ((error as any).status === 404) {
139
+ throw new BaserowNotFoundError('Database Token', tokenId)
140
+ }
141
+ this.handleHttpError(error, 'update database token', tokenId)
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Eliminar database token
147
+ */
148
+ async delete(tokenId: number): Promise<void> {
149
+ this.validationService.validateId(tokenId, 'token ID')
150
+ return this.deleteById('/database/tokens', tokenId)
151
+ }
152
+
153
+ /**
154
+ * Verificar si un database token existe
155
+ */
156
+ async exists(tokenId: number): Promise<boolean> {
157
+ try {
158
+ await this.get(tokenId)
159
+ return true
160
+ } catch (error) {
161
+ if (error instanceof BaserowNotFoundError) {
162
+ return false
163
+ }
164
+ throw error
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Buscar database token por nombre en un workspace
170
+ */
171
+ async findByName(workspaceId: number, name: string): Promise<DatabaseToken | null> {
172
+ this.validationService.validateId(workspaceId, 'workspace ID')
173
+ this.validationService.validateResourceName(name, 'token')
174
+
175
+ const tokens = await this.list(workspaceId)
176
+ const found = tokens.find(token => token.name === name) || null
177
+
178
+ if (found) {
179
+ this.logSuccess(`find database token by name "${name}"`, found.id)
180
+ } else {
181
+ this.logDebug(`No database token found with name "${name}" in workspace ${workspaceId}`)
182
+ }
183
+
184
+ return found
185
+ }
186
+ }