@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,399 @@
1
+ import { Logger, Field, BaserowNotFoundError, FieldConstraint, ConstraintType } from '../types'
2
+ import { FieldService } from '../services/FieldService'
3
+ import { validateRequired } from '../utils/validation'
4
+
5
+ /**
6
+ * Context para operaciones en un campo específico
7
+ *
8
+ * Proporciona una API fluida para operaciones específicas en un campo individual
9
+ * identificado por nombre o ID. Permite operaciones contextuales como update, delete
10
+ * y gestión avanzada de índices y restricciones con API intuitiva.
11
+ *
12
+ * **Características principales:**
13
+ * - Resolución automática de campo por nombre o ID (lazy loading)
14
+ * - API intuitiva para operaciones avanzadas: index(), constraint()
15
+ * - Operaciones CRUD específicas: update(), delete()
16
+ * - Cache interno para evitar resoluciones repetidas
17
+ * - Logging opcional de todas las operaciones
18
+ * - Validación automática de parámetros
19
+ *
20
+ * **Patrón de API Contextual:**
21
+ * ```
22
+ * field('nombre').update(data) // Actualizar campo específico
23
+ * field('nombre').delete() // Eliminar campo específico
24
+ * field('email').index(true) // Habilitar índice
25
+ * field('codigo').constraint({ type: 'unique', active: true }) // Agregar restricción
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Operaciones CRUD específicas
31
+ * await table.field('nombre').update({ description: 'Nombre completo' })
32
+ * await table.field('obsoleto').delete()
33
+ *
34
+ * // Gestión de índices (v1.35+)
35
+ * await table.field('email').index(true) // Habilitar índice
36
+ * await table.field('email').index(false) // Deshabilitar índice
37
+ *
38
+ * // Gestión de restricciones (v1.35+)
39
+ * await table.field('email').constraint({ type: 'unique', active: true }) // Agregar
40
+ * await table.field('email').constraint('unique') // Eliminar
41
+ * const constraints = await table.field('codigo').getConstraints()
42
+ *
43
+ * // Funciona con ID también
44
+ * await table.field(123).update({ name: 'Nuevo Nombre' })
45
+ * await table.field(456).index(true)
46
+ * ```
47
+ *
48
+ * @since 1.1.0
49
+ */
50
+ export class FieldContext {
51
+ private fieldIdentifier: string | number
52
+ private resolvedField?: Field
53
+ private logger?: Logger
54
+
55
+ /**
56
+ * Crea un nuevo context de campo
57
+ *
58
+ * @param fieldService - Servicio para operaciones de campos
59
+ * @param getTableId - Función para obtener el ID de la tabla padre
60
+ * @param fieldIdentifier - Nombre o ID del campo
61
+ * @param logger - Logger opcional para debug y trazabilidad
62
+ *
63
+ * @since 1.1.0
64
+ */
65
+ constructor(
66
+ private fieldService: FieldService,
67
+ private getTableId: () => Promise<number>,
68
+ fieldIdentifier: string | number,
69
+ logger?: Logger
70
+ ) {
71
+ this.fieldIdentifier = fieldIdentifier
72
+ this.logger = logger
73
+ }
74
+
75
+ /**
76
+ * Actualizar este campo
77
+ *
78
+ * Actualiza los datos del campo. Solo actualiza los campos
79
+ * proporcionados (actualización parcial). Actualiza el cache interno
80
+ * si el nombre cambia.
81
+ *
82
+ * @param data - Datos a actualizar
83
+ * @param data.name - Nuevo nombre del campo (opcional)
84
+ * @param data.description - Nueva descripción (opcional)
85
+ * @returns Promise con el campo actualizado
86
+ *
87
+ * @throws {BaserowNotFoundError} Si el campo no existe
88
+ * @throws {BaserowValidationError} Si los datos son inválidos
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const updated = await table.field('email').update({
93
+ * name: 'Email Corporativo',
94
+ * description: 'Email principal de contacto'
95
+ * })
96
+ * console.log(`Campo actualizado: ${updated.name}`)
97
+ * ```
98
+ *
99
+ * @since 1.1.0
100
+ */
101
+ async update(data: any): Promise<Field> {
102
+ const field = await this.get()
103
+ const contextAccess = (this.fieldService as any)[Symbol.for('fieldContext')]
104
+ const updatedField = await contextAccess.updateField(field.id, data)
105
+
106
+ // Actualizar cache si el nombre cambió
107
+ if (data.name && data.name !== field.name) {
108
+ this.resolvedField = updatedField
109
+ if (this.logger) {
110
+ this.logger.info(`Field updated: "${data.name}" (ID: ${updatedField.id})`)
111
+ }
112
+ }
113
+
114
+ return updatedField
115
+ }
116
+
117
+ /**
118
+ * Eliminar este campo
119
+ *
120
+ * Elimina permanentemente el campo y todos sus datos.
121
+ * Esta operación no se puede deshacer. Limpia el cache interno.
122
+ *
123
+ * @returns Promise que resuelve cuando el campo es eliminado
124
+ *
125
+ * @throws {BaserowNotFoundError} Si el campo no existe
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * await table.field('campo_obsoleto').delete()
130
+ * console.log('Campo eliminado exitosamente')
131
+ * ```
132
+ *
133
+ * @since 1.1.0
134
+ */
135
+ async delete(): Promise<void> {
136
+ const field = await this.get()
137
+ const contextAccess = (this.fieldService as any)[Symbol.for('fieldContext')]
138
+ await contextAccess.deleteField(field.id)
139
+
140
+ if (this.logger) {
141
+ this.logger.info(`Field deleted: "${field.name}" (ID: ${field.id})`)
142
+ }
143
+
144
+ // Limpiar cache
145
+ this.resolvedField = undefined
146
+ }
147
+
148
+ /**
149
+ * Configurar índice en este campo
150
+ *
151
+ * Habilita o deshabilita el índice en el campo para mejorar performance
152
+ * de filtros y búsquedas. API más intuitiva que setFieldIndex().
153
+ *
154
+ * @param enabled - true para habilitar índice, false para deshabilitar
155
+ * @returns Promise con el campo actualizado
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * // Habilitar índice para mejor performance en filtros
160
+ * await table.field('email').index(true)
161
+ *
162
+ * // Deshabilitar índice
163
+ * await table.field('email').index(false)
164
+ * ```
165
+ *
166
+ * @since 1.1.0
167
+ */
168
+ async index(enabled: boolean): Promise<Field> {
169
+ const field = await this.get()
170
+ const updatedField = await this.fieldService.setFieldIndex(field.id, enabled)
171
+
172
+ // Actualizar cache
173
+ this.resolvedField = updatedField
174
+
175
+ if (this.logger) {
176
+ this.logger.info(`Field index ${enabled ? 'enabled' : 'disabled'}: "${field.name}" (ID: ${field.id})`)
177
+ }
178
+
179
+ return updatedField
180
+ }
181
+
182
+ /**
183
+ * Gestionar restricción de valor en este campo
184
+ *
185
+ * API unificada para agregar o eliminar restricciones de valor.
186
+ * Sobrecarga de función para APIs intuitivas:
187
+ * - constraint(object) = agregar restricción
188
+ * - constraint(string) = eliminar restricción por tipo
189
+ *
190
+ * @param constraintOrType - Objeto de restricción para agregar, o string del tipo para eliminar
191
+ * @returns Promise con el campo actualizado
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * // Agregar restricción unique
196
+ * await table.field('email').constraint({ type: 'unique', active: true })
197
+ *
198
+ * // Agregar restricción unique_with_empty
199
+ * await table.field('codigo').constraint({ type: 'unique_with_empty', active: true })
200
+ *
201
+ * // Eliminar restricción por tipo
202
+ * await table.field('email').constraint('unique')
203
+ * await table.field('codigo').constraint('unique_with_empty')
204
+ * ```
205
+ *
206
+ * @since 1.1.0
207
+ */
208
+ async constraint(constraintOrType: FieldConstraint | ConstraintType): Promise<Field> {
209
+ const field = await this.get()
210
+
211
+ let updatedField: Field
212
+
213
+ if (typeof constraintOrType === 'string') {
214
+ // Eliminar restricción por tipo
215
+ updatedField = await this.fieldService.removeFieldConstraint(field.id, constraintOrType)
216
+
217
+ if (this.logger) {
218
+ this.logger.info(`Field constraint removed: "${constraintOrType}" from "${field.name}" (ID: ${field.id})`)
219
+ }
220
+ } else {
221
+ // Agregar restricción
222
+ updatedField = await this.fieldService.addFieldConstraint(field.id, constraintOrType)
223
+
224
+ if (this.logger) {
225
+ this.logger.info(`Field constraint added: "${constraintOrType.type}" to "${field.name}" (ID: ${field.id})`)
226
+ }
227
+ }
228
+
229
+ // Actualizar cache
230
+ this.resolvedField = updatedField
231
+
232
+ return updatedField
233
+ }
234
+
235
+ /**
236
+ * Obtener todas las restricciones de este campo
237
+ *
238
+ * @returns Promise con array de restricciones activas en el campo
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const constraints = await table.field('email').getConstraints()
243
+ * constraints.forEach(constraint => {
244
+ * console.log(`${constraint.type}: ${constraint.active ? 'activa' : 'inactiva'}`)
245
+ * })
246
+ * ```
247
+ *
248
+ * @since 1.1.0
249
+ */
250
+ async getConstraints(): Promise<FieldConstraint[]> {
251
+ const field = await this.get()
252
+ return await this.fieldService.getFieldConstraints(field.id)
253
+ }
254
+
255
+ /**
256
+ * Eliminar todas las restricciones del campo
257
+ *
258
+ * Remueve completamente todas las constraints configuradas en este campo,
259
+ * dejándolo sin ninguna restricción de integridad. Esta operación es útil
260
+ * para limpiar campos que han acumulado múltiples constraints o para resetear
261
+ * completamente la configuración de restricciones.
262
+ *
263
+ * @returns Promise con el campo actualizado sin constraints
264
+ *
265
+ * @throws {BaserowNotFoundError} Si el campo no existe
266
+ * @throws {BaserowError} Si hay error en la comunicación con la API
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * // Eliminar todas las constraints de un campo
271
+ * const field = await table.field('email').deleteConstraints()
272
+ * console.log(`Campo "${field.name}" ahora sin constraints`)
273
+ *
274
+ * // Verificar que se eliminaron todas
275
+ * const constraints = await table.field('email').getConstraints()
276
+ * console.log(`Constraints restantes: ${constraints.length}`) // 0
277
+ * ```
278
+ *
279
+ * @since 1.1.0
280
+ */
281
+ async deleteConstraints(): Promise<Field> {
282
+ const field = await this.get()
283
+ const updatedField = await this.fieldService.removeAllFieldConstraints(field.id)
284
+
285
+ if (this.logger) {
286
+ this.logger.info(`All constraints removed from field "${field.name}" (ID: ${field.id})`)
287
+ }
288
+
289
+ // Actualizar cache
290
+ this.resolvedField = updatedField
291
+
292
+ return updatedField
293
+ }
294
+
295
+ /**
296
+ * Obtener este campo completo
297
+ *
298
+ * Recupera el campo con todos sus datos y metadatos.
299
+ * Utiliza lazy loading con cache para evitar resoluciones repetidas.
300
+ * Soporta identificación por nombre o ID del campo.
301
+ *
302
+ * @returns Promise con el campo completo
303
+ *
304
+ * @throws {BaserowNotFoundError} Si el campo no existe
305
+ * @throws {BaserowValidationError} Si el identificador es inválido
306
+ *
307
+ * @example Obtener por nombre
308
+ * ```typescript
309
+ * const field = await table.field('email').get()
310
+ * console.log(`Campo: ${field.name} (ID: ${field.id})`)
311
+ * console.log(`Tipo: ${field.type}`)
312
+ * ```
313
+ *
314
+ * @example Obtener por ID
315
+ * ```typescript
316
+ * const field = await table.field(123).get()
317
+ * console.log(`Nombre: ${field.name}`)
318
+ * console.log(`Descripción: ${field.description || 'Sin descripción'}`)
319
+ * ```
320
+ *
321
+ * @since 1.1.0
322
+ */
323
+ async get(): Promise<Field> {
324
+ if (this.resolvedField) {
325
+ return this.resolvedField
326
+ }
327
+
328
+ if (typeof this.fieldIdentifier === 'number') {
329
+ // Es un ID numérico
330
+ const field = await this.fieldService.get(this.fieldIdentifier)
331
+ this.resolvedField = field
332
+ } else {
333
+ // Es un nombre string - necesitamos buscar en la tabla
334
+ validateRequired(this.fieldIdentifier, 'field name')
335
+
336
+ // Obtener tableId desde TableContext
337
+ const tableId = await this.getTableId()
338
+
339
+ // Buscar campo por nombre en la tabla
340
+ const field = await this.fieldService.findUnique(tableId, this.fieldIdentifier)
341
+ if (!field) {
342
+ throw new BaserowNotFoundError('Field', this.fieldIdentifier)
343
+ }
344
+
345
+ this.resolvedField = field
346
+ }
347
+
348
+ if (this.logger) {
349
+ this.logger.info(`Retrieved field: "${this.resolvedField.name}" (ID: ${this.resolvedField.id})`)
350
+ }
351
+
352
+ return this.resolvedField
353
+ }
354
+
355
+ /**
356
+ * Verificar si este campo existe
357
+ *
358
+ * Método de utilidad para verificar la existencia del campo
359
+ * sin cargar todos sus datos. Útil para validaciones previas.
360
+ *
361
+ * @returns Promise con true si existe, false si no
362
+ *
363
+ * @example Verificar existencia por nombre
364
+ * ```typescript
365
+ * const exists = await table.field('email').exists()
366
+ * if (exists) {
367
+ * console.log('El campo existe')
368
+ * } else {
369
+ * console.log('El campo no fue encontrado')
370
+ * }
371
+ * ```
372
+ *
373
+ * @example Verificar antes de crear
374
+ * ```typescript
375
+ * const fieldName = 'nuevo_campo'
376
+ * const exists = await table.field(fieldName).exists()
377
+ *
378
+ * if (!exists) {
379
+ * await table.fields.createText(fieldName, undefined, 'Campo creado dinámicamente')
380
+ * console.log('Campo creado exitosamente')
381
+ * } else {
382
+ * console.log('El campo ya existe')
383
+ * }
384
+ * ```
385
+ *
386
+ * @since 1.1.0
387
+ */
388
+ async exists(): Promise<boolean> {
389
+ try {
390
+ await this.get()
391
+ return true
392
+ } catch (error) {
393
+ if ((error as any).name === 'BaserowNotFoundError') {
394
+ return false
395
+ }
396
+ throw error
397
+ }
398
+ }
399
+ }
@@ -0,0 +1,99 @@
1
+ import { Logger, Row } from '../types'
2
+ import { RowService } from '../services/RowService'
3
+
4
+ /**
5
+ * Context para operaciones en una fila específica con TableOnlyContext
6
+ * Proporciona API simplificada para operaciones CRUD de una sola fila
7
+ */
8
+ export class RowOnlyContext {
9
+ constructor(
10
+ private tableId: number | (() => Promise<number>),
11
+ private rowId: number,
12
+ private rowService: RowService,
13
+ private logger?: Logger
14
+ ) {}
15
+
16
+ private async getTableId(): Promise<number> {
17
+ if (typeof this.tableId === 'function') {
18
+ return await this.tableId()
19
+ }
20
+ return this.tableId
21
+ }
22
+
23
+ /**
24
+ * Obtener los datos de la fila
25
+ *
26
+ * @returns Promise con los datos de la fila
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const rowData = await client.table(123).row(456).get()
31
+ * console.log(rowData.name)
32
+ * ```
33
+ */
34
+ async get(): Promise<Row> {
35
+ const tableId = await this.getTableId()
36
+ this.logger?.info(`[RowOnlyContext] Getting row ${this.rowId} from table ${tableId}`)
37
+ return await this.rowService.get(tableId, this.rowId, true)
38
+ }
39
+
40
+ /**
41
+ * Actualizar los datos de la fila
42
+ *
43
+ * @param data - Datos parciales para actualizar
44
+ * @returns Promise con los datos actualizados de la fila
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const updated = await client.table(123).row(456).update({
49
+ * name: 'Nuevo nombre',
50
+ * active: false
51
+ * })
52
+ * ```
53
+ */
54
+ async update(data: Partial<Row>): Promise<Row> {
55
+ const tableId = await this.getTableId()
56
+ this.logger?.info(`[RowOnlyContext] Updating row ${this.rowId} in table ${tableId}`)
57
+ return await this.rowService.updateRow(tableId, this.rowId, data)
58
+ }
59
+
60
+ /**
61
+ * Eliminar la fila
62
+ *
63
+ * @returns Promise que se resuelve cuando la fila es eliminada
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * await client.table(123).row(456).delete()
68
+ * console.log('Fila eliminada')
69
+ * ```
70
+ */
71
+ async delete(): Promise<void> {
72
+ const tableId = await this.getTableId()
73
+ this.logger?.info(`[RowOnlyContext] Deleting row ${this.rowId} from table ${tableId}`)
74
+ await this.rowService.delete(tableId, this.rowId)
75
+ }
76
+
77
+ /**
78
+ * Verificar si la fila existe
79
+ *
80
+ * @returns Promise con true si existe, false si no
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const exists = await client.table(123).row(456).exists()
85
+ * if (exists) {
86
+ * console.log('La fila existe')
87
+ * }
88
+ * ```
89
+ */
90
+ async exists(): Promise<boolean> {
91
+ try {
92
+ const tableId = await this.getTableId()
93
+ await this.rowService.get(tableId, this.rowId)
94
+ return true
95
+ } catch {
96
+ return false
97
+ }
98
+ }
99
+ }