@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,1247 @@
1
+ import {
2
+ Logger,
3
+ Database,
4
+ Table,
5
+ Field,
6
+ Row,
7
+ BaserowNotFoundError,
8
+ CreateTableRequest,
9
+ SelectOption,
10
+ NumberSeparator,
11
+ RatingStyle,
12
+ RatingColor,
13
+ QueryOptions,
14
+ PrismaLikeQueryOptions,
15
+ WhereClause,
16
+ SelectClause,
17
+ FieldAdvancedOptions,
18
+ FieldType,
19
+ CreateFieldByType
20
+ } from '../types'
21
+ import { WorkspaceService } from '../services/WorkspaceService'
22
+ import { DatabaseService } from '../services/DatabaseService'
23
+ import { TableService } from '../services/TableService'
24
+ import { FieldService } from '../services/FieldService'
25
+ import { RowService } from '../services/RowService'
26
+ import { FieldContext } from './FieldContext'
27
+ import { RowOnlyContext } from './RowContext'
28
+ import { PrismaBaserowMapper } from '../utils/prisma-mapper'
29
+ import { FieldCache } from '../utils/field-cache'
30
+ import { validateRequired } from '../utils/validation'
31
+
32
+ /**
33
+ * Context para operaciones en una tabla específica
34
+ *
35
+ * Proporciona una API jerárquica fluida para operaciones administrativas
36
+ * dentro de una tabla específica. Es el nivel más granular de la API
37
+ * jerárquica y permite operaciones completas de campos y filas.
38
+ *
39
+ * **Características principales:**
40
+ * - Resolución automática de tabla por nombre o ID (lazy loading)
41
+ * - API fluida para 21 tipos de campos diferentes
42
+ * - Operaciones CRUD completas de filas con soporte bulk
43
+ * - Operaciones avanzadas: search, count, listAll
44
+ * - Cache interno para evitar resoluciones repetidas
45
+ * - Logging opcional de todas las operaciones
46
+ * - Validación automática de parámetros y tipos
47
+ *
48
+ * **Tipos de Campos Soportados (21/22):**
49
+ * - **Texto**: text, long_text, url, email, phone_number
50
+ * - **Numéricos**: number, boolean, rating
51
+ * - **Fecha/Auditoría**: date, last_modified, last_modified_by, created_on, created_by
52
+ * - **Selección**: single_select, multiple_select
53
+ * - **Relacionales**: link_row, formula
54
+ * - **Avanzados**: file, autonumber, count, rollup, lookup
55
+ *
56
+ * **Patrón de API Jerárquica:**
57
+ * ```
58
+ * table.field.createText('nombre') // Crear campo
59
+ * table.field.list() // Listar campos
60
+ * table.rows.list() // Listar filas
61
+ * table.rows.createBulk([...]) // Operaciones bulk
62
+ * ```
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * // Operaciones de campos - todos los tipos soportados
67
+ * const textField = await table.field.createText('nombre')
68
+ * const numberField = await table.field.createNumber('precio', 2)
69
+ * const selectField = await table.field.createSelect('estado', [
70
+ * { value: 'activo', color: 'blue' },
71
+ * { value: 'inactivo', color: 'red' }
72
+ * ])
73
+ * const linkField = await table.field.createLink('productos', targetTableId)
74
+ *
75
+ * // Campos avanzados
76
+ * const fileField = await table.field.createFile('documentos')
77
+ * const rollupField = await table.field.createRollup('total', linkFieldId, priceFieldId, 'sum')
78
+ *
79
+ * // Operaciones de filas
80
+ * const rows = await table.rows.list({ page: 1, size: 100 })
81
+ * const newRow = await table.rows.create({ nombre: 'Juan', precio: 100 })
82
+ *
83
+ * // Operaciones bulk optimizadas
84
+ * const newRows = await table.rows.createBulk([
85
+ * { nombre: 'Item 1', precio: 50 },
86
+ * { nombre: 'Item 2', precio: 75 }
87
+ * ], { batchSize: 50 })
88
+ *
89
+ * // Búsqueda y agregaciones
90
+ * const found = await table.rows.search('texto a buscar')
91
+ * const count = await table.rows.count({ estado: 'activo' })
92
+ * const allRows = await table.rows.listAll() // Sin paginación
93
+ *
94
+ * // Gestión de la tabla
95
+ * await table.update({ name: 'Nuevo Nombre' })
96
+ * await table.delete()
97
+ * ```
98
+ *
99
+ * @since 1.0.0
100
+ */
101
+ export class TableContext {
102
+ private tableIdentifier: string | number
103
+ private resolvedTable?: Table
104
+ private logger?: Logger
105
+ private fieldCache: FieldCache
106
+
107
+ /**
108
+ * Crea un nuevo context de tabla
109
+ *
110
+ * @param workspaceService - Servicio para operaciones de workspace
111
+ * @param workspaceIdentifier - Identificador del workspace padre (opcional)
112
+ * @param databaseIdentifier - Identificador de la database padre
113
+ * @param tableIdentifier - Nombre o ID de la tabla
114
+ * @param databaseService - Servicio para operaciones de database
115
+ * @param tableService - Servicio para operaciones de tabla
116
+ * @param fieldService - Servicio para operaciones de campos
117
+ * @param rowService - Servicio para operaciones de filas
118
+ * @param logger - Logger opcional para debug y trazabilidad
119
+ *
120
+ * @since 1.0.0
121
+ */
122
+ constructor(
123
+ private workspaceService: WorkspaceService,
124
+ private workspaceIdentifier: string | number | undefined,
125
+ private databaseIdentifier: string | number,
126
+ tableIdentifier: string | number,
127
+ private databaseService: DatabaseService,
128
+ private tableService: TableService,
129
+ private fieldService: FieldService,
130
+ private rowService: RowService,
131
+ logger?: Logger
132
+ ) {
133
+ this.tableIdentifier = tableIdentifier
134
+ this.logger = logger
135
+ this.fieldCache = new FieldCache(logger)
136
+ }
137
+
138
+ /**
139
+ * Obtener metadata completa de campos con cache
140
+ *
141
+ * @private
142
+ * @returns FieldMetadata con tipos y opciones select
143
+ */
144
+ private async getFieldMetadata() {
145
+ const table = await this.get()
146
+ return await this.fieldCache.getFieldMetadata(this.fieldService, table.id)
147
+ }
148
+
149
+ /**
150
+ * Acceder a un campo específico por nombre o ID
151
+ *
152
+ * Crea un context para operaciones específicas en un campo individual.
153
+ * Permite operaciones contextuales como update, delete y gestión avanzada
154
+ * de índices y restricciones con API intuitiva.
155
+ *
156
+ * **Operaciones Disponibles:**
157
+ * - **CRUD Específico**: update(), delete()
158
+ * - **Gestión Avanzada**: index(), constraint(), getConstraints()
159
+ *
160
+ * **Para Operaciones Masivas**: Usa `table.fields.*` para crear, listar, etc.
161
+ *
162
+ * @param fieldIdentifier - Nombre o ID del campo
163
+ * @returns Context de campo para operaciones específicas
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * // Operaciones específicas por campo
168
+ * await table.field('email').update({ description: 'Email corporativo' })
169
+ * await table.field('campo_obsoleto').delete()
170
+ * await table.field(123).update({ name: 'Nuevo Nombre' })
171
+ *
172
+ * // Gestión de índices (API intuitiva)
173
+ * await table.field('email').index(true) // Habilitar índice
174
+ * await table.field('email').index(false) // Deshabilitar índice
175
+ *
176
+ * // Gestión de restricciones (API intuitiva)
177
+ * await table.field('email').constraint({ type: 'unique', active: true }) // Agregar
178
+ * await table.field('email').constraint('unique') // Eliminar
179
+ * const constraints = await table.field('codigo').getConstraints()
180
+ *
181
+ * // Para crear campos usar table.fields.*
182
+ * const textField = await table.fields.createText('nombre')
183
+ * const numberField = await table.fields.createNumber('precio', 2)
184
+ * ```
185
+ *
186
+ * @since 1.1.0
187
+ */
188
+ field(fieldIdentifier: string | number): FieldContext {
189
+ return new FieldContext(
190
+ this.fieldService,
191
+ async () => {
192
+ const table = await this.get()
193
+ return table.id
194
+ },
195
+ fieldIdentifier,
196
+ this.logger
197
+ )
198
+ }
199
+
200
+ /**
201
+ * Acceder a una fila específica por ID
202
+ *
203
+ * Crea un context para operaciones específicas en una fila individual.
204
+ * Permite operaciones contextuales como get, update y delete sin necesidad
205
+ * de pasar el rowId en cada llamada.
206
+ *
207
+ * **Operaciones Disponibles:**
208
+ * - **CRUD Específico**: get(), update(), delete()
209
+ * - **Utilidades**: exists()
210
+ *
211
+ * **Para Operaciones Masivas**: Usa `table.rows.*` para crear, listar, bulk, etc.
212
+ *
213
+ * @param rowId - ID de la fila
214
+ * @returns Context de fila para operaciones específicas
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * // Operaciones específicas por fila
219
+ * const row = await table.row(123).get()
220
+ * await table.row(123).update({ name: 'Juan Carlos', active: true })
221
+ * await table.row(456).delete()
222
+ * const exists = await table.row(789).exists()
223
+ *
224
+ * // Para operaciones masivas usar table.rows.*
225
+ * const allRows = await table.rows.findMany()
226
+ * const newRow = await table.rows.create({ name: 'Ana' })
227
+ * await table.rows.createBulk([...])
228
+ * ```
229
+ *
230
+ * @since 1.1.0
231
+ */
232
+ row(rowId: number): RowOnlyContext {
233
+ return new RowOnlyContext(
234
+ async () => {
235
+ const table = await this.get()
236
+ return table.id
237
+ },
238
+ rowId,
239
+ this.rowService,
240
+ this.logger
241
+ )
242
+ }
243
+
244
+ /**
245
+ * API unificada completa de campos (fields operations) - Prisma-style + Métodos Especializados
246
+ *
247
+ * Proporciona la API completa para gestionar campos combinando:
248
+ * - **API Prisma-style**: findMany, findUnique, create (genérico con tipos dinámicos)
249
+ * - **API Especializada**: Todos los métodos createText, createNumber, etc. (20+ métodos)
250
+ *
251
+ * Esta es la **API principal recomendada** para trabajar con campos.
252
+ * Para operaciones CRUD básicas simples, usar `table.field.*`.
253
+ *
254
+ * **Características:**
255
+ * - **Prisma-style**: Consistencia con otros servicios (findMany, findUnique)
256
+ * - **Métodos Especializados**: API fluida por tipo de campo con parámetros tipados
257
+ * - **Tipos Dinámicos**: create() genérico con IntelliSense contextual
258
+ * - **21 Tipos de Campos**: Cobertura completa (95% de tipos Baserow)
259
+ *
260
+ * @returns Objeto con API unificada completa para operaciones de campos
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * // === API Prisma-style ===
265
+ * const allFields = await table.fields.findMany()
266
+ * const fieldByName = await table.fields.findUnique('precio')
267
+ * const fieldById = await table.fields.findUnique(123)
268
+ *
269
+ * // create() dinámico con IntelliSense contextual
270
+ * const dynamicField = await table.fields.create({
271
+ * type: 'text',
272
+ * name: 'titulo',
273
+ * text_default: 'Sin título' // ✅ Solo opciones de texto
274
+ * })
275
+ *
276
+ * // === API Especializada ===
277
+ * const textField = await table.fields.createText('nombre', 'Default')
278
+ * const numberField = await table.fields.createNumber('precio', 2, true, 0, '$')
279
+ * const selectField = await table.fields.createSelect('estado', [
280
+ * { value: 'activo', color: 'blue' },
281
+ * { value: 'inactivo', color: 'red' }
282
+ * ])
283
+ * const ratingField = await table.fields.createRating('calificacion', 5, 'yellow', 'star')
284
+ * const linkField = await table.fields.createLink('productos', targetTableId)
285
+ * const rollupField = await table.fields.createRollup('total', linkId, fieldId, 'sum')
286
+ *
287
+ * // === Campos Avanzados ===
288
+ * const fileField = await table.fields.createFile('documentos')
289
+ * const autoField = await table.fields.createAutonumber('ticket_id')
290
+ * const countField = await table.fields.createCount('items_count', linkFieldId)
291
+ * ```
292
+ *
293
+ * @since 1.1.0
294
+ */
295
+ get fields() {
296
+ return {
297
+ /**
298
+ * Listar todos los campos de la tabla
299
+ *
300
+ * Obtiene todos los campos que pertenecen a esta tabla.
301
+ * Incluye metadatos completos de cada campo.
302
+ * Equivalente a `field.list()` pero con naming Prisma-style.
303
+ *
304
+ * @returns Promise con array de campos de la tabla
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * const fields = await table.fields.findMany()
309
+ * console.log(`Encontrados ${fields.length} campos`)
310
+ * fields.forEach(field => {
311
+ * console.log(`${field.name} (ID: ${field.id}, Type: ${field.type})`)
312
+ * })
313
+ * ```
314
+ *
315
+ * @since 1.1.0
316
+ */
317
+ findMany: async (): Promise<Field[]> => {
318
+ const table = await this.get()
319
+ return await this.fieldService.findMany(table.id)
320
+ },
321
+
322
+ /**
323
+ * Buscar campo por ID o nombre en esta tabla
324
+ *
325
+ * Busca un campo específico por su ID o nombre dentro de la tabla.
326
+ * Útil para operaciones donde se conoce el identificador pero no se sabe si es ID o nombre.
327
+ * Retorna null si no se encuentra (no lanza error).
328
+ *
329
+ * @param identifier - ID numérico o nombre del campo a buscar
330
+ * @returns Promise con el campo encontrado o null si no existe
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * const fieldByName = await table.fields.findUnique('precio')
335
+ * const fieldById = await table.fields.findUnique(123)
336
+ *
337
+ * if (fieldByName) {
338
+ * console.log(`Campo encontrado: ${fieldByName.id}`)
339
+ * } else {
340
+ * console.log('Campo no encontrado')
341
+ * }
342
+ * ```
343
+ *
344
+ * @since 1.1.0
345
+ */
346
+ findUnique: async (identifier: string | number): Promise<Field | null> => {
347
+ const table = await this.get()
348
+ return await this.fieldService.findUnique(table.id, identifier)
349
+ },
350
+
351
+ /**
352
+ * Crear campo con API genérica y tipos dinámicos
353
+ *
354
+ * Crea un nuevo campo usando una API genérica que proporciona
355
+ * IntelliSense contextual y type safety basado en el tipo seleccionado.
356
+ * Cada tipo de campo expone solo las opciones relevantes.
357
+ *
358
+ * **Tipos Soportados con IntelliSense:**
359
+ * - `text`, `long_text`, `url`, `email`, `phone_number`
360
+ * - `number`, `boolean`, `rating`
361
+ * - `date`, `last_modified`, `created_on`, etc.
362
+ * - `single_select`, `multiple_select`
363
+ * - `link_row`, `formula`
364
+ * - `file`, `autonumber`, `count`, `rollup`, `lookup`
365
+ *
366
+ * @param data - Configuración del campo con tipos dinámicos
367
+ * @returns Promise con el campo creado
368
+ *
369
+ * @throws {BaserowValidationError} Si los datos son inválidos
370
+ * @throws {BaserowNotFoundError} Si la tabla no existe
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * // Campo de texto con valor por defecto
375
+ * const textField = await table.fields.create({
376
+ * type: 'text',
377
+ * name: 'titulo',
378
+ * text_default: 'Sin título',
379
+ * description: 'Título del artículo'
380
+ * })
381
+ *
382
+ * // Campo numérico con formato de moneda
383
+ * const priceField = await table.fields.create({
384
+ * type: 'number',
385
+ * name: 'precio',
386
+ * number_decimal_places: 2,
387
+ * number_prefix: '$',
388
+ * number_negative: false
389
+ * })
390
+ *
391
+ * // Campo de selección con opciones
392
+ * const statusField = await table.fields.create({
393
+ * type: 'single_select',
394
+ * name: 'estado',
395
+ * select_options: [
396
+ * { value: 'activo', color: 'blue' },
397
+ * { value: 'pendiente', color: 'yellow' },
398
+ * { value: 'cancelado', color: 'red' }
399
+ * ]
400
+ * })
401
+ *
402
+ * // Campo de relación a otra tabla
403
+ * const linkField = await table.fields.create({
404
+ * type: 'link_row',
405
+ * name: 'productos',
406
+ * link_row_table_id: 456
407
+ * })
408
+ *
409
+ * // Campo con índice y restricciones (v1.35+)
410
+ * const codeField = await table.fields.create({
411
+ * type: 'text',
412
+ * name: 'codigo',
413
+ * index: true,
414
+ * constraints: [
415
+ * { type: 'unique', active: true }
416
+ * ]
417
+ * })
418
+ * ```
419
+ *
420
+ * @since 1.1.0
421
+ */
422
+ create: async <T extends FieldType>(data: CreateFieldByType<T>): Promise<Field> => {
423
+ const table = await this.get()
424
+ return await this.fieldService.create(table.id, data as any)
425
+ },
426
+
427
+ // === MÉTODOS ESPECIALIZADOS DE CREACIÓN ===
428
+
429
+ // Campos básicos
430
+ createText: async (
431
+ name: string,
432
+ defaultValue?: string,
433
+ description?: string,
434
+ advancedOptions?: FieldAdvancedOptions
435
+ ) => {
436
+ const table = await this.get()
437
+ return await this.fieldService.createTextField(table.id, name, defaultValue, description, advancedOptions)
438
+ },
439
+
440
+ createLongText: async (name: string, defaultValue?: string, description?: string) => {
441
+ const table = await this.get()
442
+ if (description !== undefined) {
443
+ return await this.fieldService.createLongTextField(table.id, name, defaultValue, description)
444
+ } else if (defaultValue !== undefined) {
445
+ return await this.fieldService.createLongTextField(table.id, name, defaultValue)
446
+ } else {
447
+ return await this.fieldService.createLongTextField(table.id, name)
448
+ }
449
+ },
450
+
451
+ createUrl: async (name: string, description?: string) => {
452
+ const table = await this.get()
453
+ if (description !== undefined) {
454
+ return await this.fieldService.createUrlField(table.id, name, description)
455
+ } else {
456
+ return await this.fieldService.createUrlField(table.id, name)
457
+ }
458
+ },
459
+
460
+ createEmail: async (name: string, description?: string, advancedOptions?: FieldAdvancedOptions) => {
461
+ const table = await this.get()
462
+ return await this.fieldService.createEmailField(table.id, name, description, advancedOptions)
463
+ },
464
+
465
+ createPhoneNumber: async (name: string, defaultValue?: string, description?: string) => {
466
+ const table = await this.get()
467
+ return await this.fieldService.create(table.id, {
468
+ name,
469
+ type: 'phone_number',
470
+ text_default: defaultValue || '',
471
+ description
472
+ })
473
+ },
474
+
475
+ // Campos numéricos
476
+ createNumber: async (
477
+ name: string,
478
+ decimalPlaces = 0,
479
+ allowNegative = true,
480
+ defaultValue?: number,
481
+ prefix?: string,
482
+ suffix?: string,
483
+ separator: NumberSeparator = 'COMMA_PERIOD',
484
+ description?: string
485
+ ) => {
486
+ const table = await this.get()
487
+ return await this.fieldService.createNumberField(
488
+ table.id,
489
+ name,
490
+ decimalPlaces,
491
+ allowNegative,
492
+ defaultValue,
493
+ prefix,
494
+ suffix,
495
+ separator,
496
+ description
497
+ )
498
+ },
499
+
500
+ createBoolean: async (name: string, defaultValue = false, description?: string) => {
501
+ const table = await this.get()
502
+ return await this.fieldService.createBooleanField(table.id, name, defaultValue, description)
503
+ },
504
+
505
+ createRating: async (
506
+ name: string,
507
+ maxValue = 5,
508
+ color: RatingColor = 'yellow',
509
+ style: RatingStyle = 'star',
510
+ description?: string
511
+ ) => {
512
+ const table = await this.get()
513
+ return await this.fieldService.createRatingField(table.id, name, maxValue, color, style, description)
514
+ },
515
+
516
+ // Campos de fecha
517
+ createDate: async (
518
+ name: string,
519
+ includeTime = false,
520
+ dateFormat = 'ISO',
521
+ timeFormat = '24',
522
+ showTzInfo = false,
523
+ forceTimezone?: string,
524
+ forceTimezoneOffset?: number,
525
+ description?: string
526
+ ) => {
527
+ const table = await this.get()
528
+ return await this.fieldService.createDateField(
529
+ table.id,
530
+ name,
531
+ includeTime,
532
+ dateFormat,
533
+ timeFormat,
534
+ showTzInfo,
535
+ forceTimezone,
536
+ forceTimezoneOffset,
537
+ description
538
+ )
539
+ },
540
+
541
+ // Campos de selección
542
+ createSelect: async (name: string, options: SelectOption[], description?: string) => {
543
+ const table = await this.get()
544
+ return await this.fieldService.createSelectField(table.id, name, options, description)
545
+ },
546
+
547
+ createMultiSelect: async (name: string, options: SelectOption[], description?: string) => {
548
+ const table = await this.get()
549
+ return await this.fieldService.createMultiSelectField(table.id, name, options, description)
550
+ },
551
+
552
+ // Campos de relación
553
+ createLink: async (name: string, targetTableId: number, description?: string) => {
554
+ const table = await this.get()
555
+ return await this.fieldService.createLinkField(table.id, name, targetTableId, description)
556
+ },
557
+
558
+ createFormula: async (name: string, formula: string, description?: string) => {
559
+ const table = await this.get()
560
+ return await this.fieldService.createFormulaField(table.id, name, formula, description)
561
+ },
562
+
563
+ // Campos de auditoría
564
+ createLastModified: async (name: string, description?: string) => {
565
+ const table = await this.get()
566
+ return await this.fieldService.createLastModifiedField(table.id, name, description)
567
+ },
568
+
569
+ createLastModifiedBy: async (name: string, description?: string) => {
570
+ const table = await this.get()
571
+ return await this.fieldService.createLastModifiedByField(table.id, name, description)
572
+ },
573
+
574
+ createCreatedOn: async (name: string, description?: string) => {
575
+ const table = await this.get()
576
+ return await this.fieldService.createCreatedOnField(table.id, name, description)
577
+ },
578
+
579
+ createCreatedBy: async (name: string, description?: string) => {
580
+ const table = await this.get()
581
+ return await this.fieldService.createCreatedByField(table.id, name, description)
582
+ },
583
+
584
+ // Campos avanzados
585
+ createFile: async (name: string, description?: string) => {
586
+ const table = await this.get()
587
+ return await this.fieldService.createFileField(table.id, name, description)
588
+ },
589
+
590
+ createAutonumber: async (name: string, description?: string) => {
591
+ const table = await this.get()
592
+ return await this.fieldService.createAutonumberField(table.id, name, description)
593
+ },
594
+
595
+ createCount: async (name: string, throughFieldId: number, description?: string) => {
596
+ const table = await this.get()
597
+ return await this.fieldService.createCountField(table.id, name, throughFieldId, description)
598
+ },
599
+
600
+ createRollup: async (
601
+ name: string,
602
+ throughFieldId: number,
603
+ targetFieldId: number,
604
+ rollupFunction: string = 'sum',
605
+ description?: string
606
+ ) => {
607
+ const table = await this.get()
608
+ return await this.fieldService.createRollupField(
609
+ table.id,
610
+ name,
611
+ throughFieldId,
612
+ targetFieldId,
613
+ rollupFunction,
614
+ description
615
+ )
616
+ },
617
+
618
+ createLookup: async (name: string, throughFieldId: number, targetFieldId: number, description?: string) => {
619
+ const table = await this.get()
620
+ return await this.fieldService.createLookupField(table.id, name, throughFieldId, targetFieldId, description)
621
+ }
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Rows - API para operaciones de masa y consultas de filas
627
+ *
628
+ * API para operaciones de masa en filas de esta tabla.
629
+ * Incluye operaciones estilo Prisma, búsqueda, filtrado y operaciones bulk.
630
+ * Para operaciones en filas específicas usar: table.row(id).operation()
631
+ *
632
+ * **Características principales:**
633
+ * - Operaciones estilo Prisma: findMany(), findUnique(), create()
634
+ * - Operaciones de masa: createMany(), updateMany(), deleteMany()
635
+ * - Búsqueda y filtrado: search(), count(), findAll()
636
+ * - Compatible con user field names
637
+ * - Control granular de webhook events
638
+ * - Paginación automática con findAll()
639
+ *
640
+ * @example
641
+ * ```typescript
642
+ * // Operaciones estilo Prisma
643
+ * const rows = await table.rows.findMany({ page: 1, size: 10 })
644
+ * const row = await table.rows.findUnique({ id: 123 })
645
+ * const newRow = await table.rows.create({
646
+ * nombre: 'Juan Pérez',
647
+ * email: 'juan@email.com',
648
+ * activo: true
649
+ * })
650
+ *
651
+ * // Operaciones de masa optimizadas
652
+ * const newRows = await table.rows.createMany([
653
+ * { nombre: 'Ana', email: 'ana@email.com' },
654
+ * { nombre: 'Luis', email: 'luis@email.com' }
655
+ * ], { batchSize: 100, sendWebhookEvents: false })
656
+ *
657
+ * const updates = await table.rows.updateMany([
658
+ * { id: 1, nombre: 'Ana Modificada' },
659
+ * { id: 2, nombre: 'Luis Modificado' }
660
+ * ], { batchSize: 50 })
661
+ *
662
+ * await table.rows.deleteMany([1, 2, 3], { batchSize: 25 })
663
+ *
664
+ * // Búsqueda y agregaciones
665
+ * const found = await table.rows.search('juan', { order_by: 'nombre' })
666
+ * const count = await table.rows.count({ activo: true })
667
+ * const allRows = await table.rows.findAll({ filters: { estado: 'activo' } })
668
+ *
669
+ * // Para operaciones específicas usar API contextual:
670
+ * await table.row(123).update({ nombre: 'Juan Carlos' })
671
+ * await table.row(123).delete()
672
+ * const exists = await table.row(123).exists()
673
+ * ```
674
+ *
675
+ * @since 1.0.0
676
+ */
677
+ get rows() {
678
+ return {
679
+ // === MÉTODOS ESTILO PRISMA ===
680
+
681
+ /**
682
+ * Buscar múltiples filas con opciones avanzadas estilo Prisma
683
+ *
684
+ * Equivalente a Prisma findMany(), proporciona filtrado avanzado,
685
+ * ordenamiento, paginación y selección de campos con sintaxis familiar.
686
+ *
687
+ * @param options - Opciones de consulta estilo Prisma
688
+ * @returns Promise con array de filas que cumplen los criterios
689
+ *
690
+ * @example
691
+ * ```typescript
692
+ * // Consulta compleja estilo Prisma
693
+ * const users = await table.rows.findMany({
694
+ * where: {
695
+ * AND: [
696
+ * { status: 'active' },
697
+ * { age: { gte: 18, lt: 65 } }
698
+ * ],
699
+ * email: { contains: '@company.com' }
700
+ * },
701
+ * select: {
702
+ * id: true,
703
+ * name: true,
704
+ * email: true
705
+ * },
706
+ * orderBy: [
707
+ * { created_at: 'desc' },
708
+ * { name: 'asc' }
709
+ * ],
710
+ * take: 20,
711
+ * skip: 0
712
+ * })
713
+ *
714
+ * // Sintaxis legacy (compatible)
715
+ * const legacy = await table.rows.findMany({
716
+ * filters: { status: 'active' },
717
+ * size: 10,
718
+ * order_by: '-created_at'
719
+ * })
720
+ * ```
721
+ *
722
+ * @since 1.2.0
723
+ */
724
+ findMany: async <T = Row>(options?: PrismaLikeQueryOptions<T>): Promise<T[]> => {
725
+ const table = await this.get()
726
+ const fieldMetadata = await this.getFieldMetadata()
727
+ const baserowOptions = PrismaBaserowMapper.transformPrismaToBaserow(options, fieldMetadata)
728
+ const response = await this.rowService.list(table.id, baserowOptions)
729
+ return response.rows as T[]
730
+ },
731
+
732
+ /**
733
+ * Buscar la primera fila que cumpla los criterios
734
+ *
735
+ * Equivalente a Prisma findFirst(), retorna la primera fila
736
+ * que coincida con los filtros especificados.
737
+ *
738
+ * @param options - Opciones de consulta estilo Prisma
739
+ * @returns Promise con la primera fila encontrada o null
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * const admin = await table.rows.findFirst({
744
+ * where: {
745
+ * role: 'admin',
746
+ * status: 'active'
747
+ * },
748
+ * select: {
749
+ * id: true,
750
+ * name: true,
751
+ * email: true
752
+ * }
753
+ * })
754
+ * ```
755
+ *
756
+ * @since 1.2.0
757
+ */
758
+ findFirst: async <T = Row>(options?: PrismaLikeQueryOptions<T>): Promise<T | null> => {
759
+ const result = await this.rows.findMany({ ...options, take: 1 })
760
+ return (result[0] as T) || null
761
+ },
762
+
763
+ /**
764
+ * Buscar fila por ID único
765
+ *
766
+ * Equivalente a Prisma findUnique(), busca una fila específica
767
+ * por su ID con opciones de selección de campos.
768
+ *
769
+ * @param idOrWhere - ID de la fila o objeto con ID
770
+ * @param options - Opciones de selección y configuración
771
+ * @returns Promise con la fila encontrada o null
772
+ *
773
+ * @example
774
+ * ```typescript
775
+ * // Por ID directo:
776
+ * const user = await table.rows.findUnique(123)
777
+ *
778
+ * // Con objeto where:
779
+ * const user = await table.rows.findUnique({ id: 123 })
780
+ *
781
+ * // Con opciones:
782
+ * const user = await table.rows.findUnique(123, {
783
+ * select: { id: true, name: true, email: true },
784
+ * userFieldNames: true
785
+ * })
786
+ * ```
787
+ *
788
+ * @since 1.2.0
789
+ */
790
+ findUnique: async <T = Row>(
791
+ idOrWhere: number | { id: number },
792
+ options?: {
793
+ select?: SelectClause<T>
794
+ userFieldNames?: boolean
795
+ }
796
+ ): Promise<T | null> => {
797
+ try {
798
+ const table = await this.get()
799
+ const id = typeof idOrWhere === 'number' ? idOrWhere : idOrWhere.id
800
+ return (await this.rowService.get(table.id, id, options?.userFieldNames ?? true)) as T
801
+ } catch (error) {
802
+ if ((error as any).name === 'BaserowNotFoundError') {
803
+ return null
804
+ }
805
+ throw error
806
+ }
807
+ },
808
+
809
+ /**
810
+ * Contar filas que cumplen criterios específicos
811
+ *
812
+ * Equivalente a Prisma count(), cuenta filas sin cargar los datos.
813
+ * Más eficiente que findMany().length para obtener totales.
814
+ *
815
+ * @param options - Opciones con filtros WHERE
816
+ * @returns Promise con el número de filas que cumplen los criterios
817
+ *
818
+ * @example
819
+ * ```typescript
820
+ * const activeUsers = await table.rows.count({
821
+ * where: {
822
+ * status: 'active',
823
+ * age: { gte: 18 }
824
+ * }
825
+ * })
826
+ * ```
827
+ *
828
+ * @since 1.2.0
829
+ */
830
+ count: async <T = Row>(options?: { where?: WhereClause<T> | Record<string, any> }): Promise<number> => {
831
+ const table = await this.get()
832
+ const fieldMetadata = await this.getFieldMetadata()
833
+ const filters = PrismaBaserowMapper.transformWhereToFilters(options?.where, fieldMetadata)
834
+ return await this.rowService.count(table.id, filters)
835
+ },
836
+
837
+ // === MÉTODOS DE ESCRITURA ESTILO PRISMA ===
838
+
839
+ /**
840
+ * Crear nueva fila
841
+ *
842
+ * Equivalente a Prisma create(), crea una nueva fila con los datos
843
+ * proporcionados y retorna la fila creada.
844
+ *
845
+ * @param data - Datos de la nueva fila
846
+ * @returns Promise con la fila creada
847
+ *
848
+ * @example
849
+ * ```typescript
850
+ * const newUser = await table.rows.create({
851
+ * name: 'John Doe',
852
+ * email: 'john@company.com',
853
+ * status: 'active'
854
+ * })
855
+ * ```
856
+ *
857
+ * @since 1.2.0
858
+ */
859
+ create: async <T = Row>(data: Partial<T>): Promise<T> => {
860
+ const table = await this.get()
861
+ return (await this.rowService.createRow(table.id, data)) as T
862
+ },
863
+
864
+ /**
865
+ * Crear múltiples filas en lote
866
+ *
867
+ * Equivalente a Prisma createMany(), crea múltiples filas
868
+ * de forma optimizada usando procesamiento en lotes.
869
+ *
870
+ * @param data - Array de datos para las nuevas filas
871
+ * @param options - Opciones de procesamiento en lotes
872
+ * @returns Promise con array de filas creadas
873
+ *
874
+ * @example
875
+ * ```typescript
876
+ * const newUsers = await table.rows.createMany([
877
+ * { name: 'Alice', email: 'alice@company.com' },
878
+ * { name: 'Bob', email: 'bob@company.com' }
879
+ * ], {
880
+ * batchSize: 50,
881
+ * skipDuplicates: true
882
+ * })
883
+ * ```
884
+ *
885
+ * @since 1.2.0
886
+ */
887
+ createMany: async <T = Row>(
888
+ data: Partial<T>[],
889
+ options?: {
890
+ skipDuplicates?: boolean
891
+ batchSize?: number
892
+ sendWebhookEvents?: boolean
893
+ }
894
+ ): Promise<T[]> => {
895
+ const table = await this.get()
896
+ return (await this.rowService.createBulk(table.id, data, {
897
+ batchSize: options?.batchSize,
898
+ send_webhook_events: options?.sendWebhookEvents
899
+ })) as T[]
900
+ },
901
+
902
+ /**
903
+ * Actualizar múltiples filas por ID
904
+ *
905
+ * Actualiza múltiples filas especificadas por ID de forma
906
+ * optimizada usando procesamiento en lotes.
907
+ *
908
+ * @param updates - Array con ID y datos a actualizar para cada fila
909
+ * @param options - Opciones de procesamiento en lotes
910
+ * @returns Promise con array de filas actualizadas
911
+ *
912
+ * @example
913
+ * ```typescript
914
+ * const updated = await table.rows.updateMany([
915
+ * { id: 1, status: 'active' },
916
+ * { id: 2, status: 'inactive' },
917
+ * { id: 3, name: 'Updated Name' }
918
+ * ])
919
+ * ```
920
+ *
921
+ * @since 1.2.0
922
+ */
923
+ updateMany: async <T = Row>(
924
+ updates: Array<{ id: number } & Partial<T>>,
925
+ options?: {
926
+ batchSize?: number
927
+ sendWebhookEvents?: boolean
928
+ }
929
+ ): Promise<T[]> => {
930
+ const table = await this.get()
931
+ return (await this.rowService.updateBulk(table.id, updates, {
932
+ batchSize: options?.batchSize,
933
+ send_webhook_events: options?.sendWebhookEvents
934
+ })) as T[]
935
+ },
936
+
937
+ /**
938
+ * Eliminar múltiples filas por ID o criterios
939
+ *
940
+ * Elimina múltiples filas especificadas por ID o que cumplan
941
+ * criterios específicos de forma optimizada usando procesamiento en lotes.
942
+ *
943
+ * @param idsOrWhere - Array de IDs o objeto where con criterios
944
+ * @param options - Opciones de procesamiento en lotes
945
+ * @returns Promise que resuelve cuando se completa la eliminación
946
+ *
947
+ * @example
948
+ * ```typescript
949
+ * // Por IDs:
950
+ * await table.rows.deleteMany([1, 2, 3, 4, 5])
951
+ *
952
+ * // Por criterios (estilo Prisma):
953
+ * await table.rows.deleteMany({
954
+ * where: { status: 'inactive', lastLogin: { lt: '2023-01-01' } }
955
+ * })
956
+ * ```
957
+ *
958
+ * @since 1.2.0
959
+ */
960
+ deleteMany: async <T = Row>(
961
+ idsOrWhere: number[] | { where: WhereClause<T> },
962
+ options?: { batchSize?: number }
963
+ ): Promise<void> => {
964
+ const table = await this.get()
965
+
966
+ if (Array.isArray(idsOrWhere)) {
967
+ // Eliminar por IDs
968
+ return await this.rowService.deleteBulk(table.id, idsOrWhere, options)
969
+ } else {
970
+ // Eliminar por criterios where - buscar IDs primero
971
+ const rowsToDelete = await this.rows.findMany({
972
+ where: idsOrWhere.where,
973
+ select: { id: true } as any
974
+ })
975
+ const ids = rowsToDelete.map((row: any) => row.id)
976
+ if (ids.length > 0) {
977
+ return await this.rowService.deleteBulk(table.id, ids, options)
978
+ }
979
+ }
980
+ },
981
+
982
+ // === MÉTODOS AVANZADOS ===
983
+
984
+ /**
985
+ * Buscar filas con texto libre (búsqueda global)
986
+ *
987
+ * Busca texto en todos los campos de la tabla.
988
+ * Útil para funcionalidades de búsqueda general.
989
+ *
990
+ * @param query - Texto a buscar
991
+ * @param options - Opciones adicionales (sin search)
992
+ * @returns Promise con filas que contienen el texto
993
+ *
994
+ * @example
995
+ * ```typescript
996
+ * const results = await table.rows.search('john@company.com', {
997
+ * orderBy: { created_at: 'desc' },
998
+ * take: 10
999
+ * })
1000
+ * ```
1001
+ *
1002
+ * @since 1.2.0
1003
+ */
1004
+ search: async <T = Row>(query: string, options?: Omit<PrismaLikeQueryOptions<T>, 'where'>): Promise<T[]> => {
1005
+ const table = await this.get()
1006
+ const fieldMetadata = await this.getFieldMetadata()
1007
+ const baserowOptions = PrismaBaserowMapper.transformPrismaToBaserow(options, fieldMetadata)
1008
+ return (await this.rowService.search(table.id, query, baserowOptions)) as T[]
1009
+ },
1010
+
1011
+ /**
1012
+ * Obtener todas las filas sin paginación
1013
+ *
1014
+ * Descarga toda la tabla usando paginación automática.
1015
+ * Útil para exportaciones o procesamiento completo.
1016
+ *
1017
+ * @param options - Opciones de filtrado y ordenamiento
1018
+ * @returns Promise con todas las filas de la tabla
1019
+ *
1020
+ * @example
1021
+ * ```typescript
1022
+ * const allActiveUsers = await table.rows.findAll({
1023
+ * where: { status: 'active' },
1024
+ * orderBy: { created_at: 'asc' }
1025
+ * })
1026
+ * ```
1027
+ *
1028
+ * @since 1.2.0
1029
+ */
1030
+ findAll: async <T = Row>(options?: Omit<PrismaLikeQueryOptions<T>, 'take' | 'skip'>): Promise<T[]> => {
1031
+ const table = await this.get()
1032
+ const fieldMetadata = await this.getFieldMetadata()
1033
+ const baserowOptions = PrismaBaserowMapper.transformPrismaToBaserow(options, fieldMetadata)
1034
+ return (await this.rowService.listAll(table.id, baserowOptions)) as T[]
1035
+ },
1036
+
1037
+ // === COMPATIBILIDAD LEGACY ===
1038
+
1039
+ /**
1040
+ * Listar filas (sintaxis legacy)
1041
+ *
1042
+ * Método de compatibilidad para código existente.
1043
+ * Mantiene la API original intacta.
1044
+ *
1045
+ * @param options - Opciones en formato legacy
1046
+ * @returns Promise con respuesta paginada legacy
1047
+ *
1048
+ * @since 1.0.0
1049
+ * @deprecated Usar findMany() para nueva funcionalidad
1050
+ */
1051
+ list: async (options?: QueryOptions) => {
1052
+ const table = await this.get()
1053
+ return await this.rowService.list(table.id, options)
1054
+ }
1055
+ }
1056
+ }
1057
+
1058
+ /**
1059
+ * Actualizar esta tabla
1060
+ *
1061
+ * Actualiza los datos de la tabla. Solo actualiza los campos
1062
+ * proporcionados (actualización parcial). Actualiza el cache interno
1063
+ * si el nombre cambia.
1064
+ *
1065
+ * @param data - Datos a actualizar
1066
+ * @param data.name - Nuevo nombre de la tabla (opcional)
1067
+ * @returns Promise con la tabla actualizada
1068
+ *
1069
+ * @throws {BaserowNotFoundError} Si la tabla no existe
1070
+ * @throws {BaserowValidationError} Si los datos son inválidos
1071
+ *
1072
+ * @example
1073
+ * ```typescript
1074
+ * const updated = await table.update({
1075
+ * name: 'Nuevo Nombre Tabla'
1076
+ * })
1077
+ * console.log(`Tabla actualizada: ${updated.name}`)
1078
+ * ```
1079
+ *
1080
+ * @since 1.0.0
1081
+ */
1082
+ async update(data: Partial<CreateTableRequest>): Promise<Table> {
1083
+ const table = await this.get()
1084
+ const contextAccess = (this.tableService as any)[Symbol.for('tableContext')]
1085
+ const updatedTable = await contextAccess.updateTable(table.id, data)
1086
+
1087
+ // Actualizar cache si el nombre cambió
1088
+ if (data.name && data.name !== table.name) {
1089
+ this.resolvedTable = updatedTable
1090
+ if (this.logger) {
1091
+ this.logger.info(`Table updated: "${data.name}" (ID: ${updatedTable.id})`)
1092
+ }
1093
+ }
1094
+
1095
+ return updatedTable
1096
+ }
1097
+
1098
+ /**
1099
+ * Eliminar esta tabla
1100
+ *
1101
+ * Elimina permanentemente la tabla y todos sus campos, filas y datos.
1102
+ * Esta operación no se puede deshacer. Limpia el cache interno.
1103
+ *
1104
+ * @returns Promise que resuelve cuando la tabla es eliminada
1105
+ *
1106
+ * @throws {BaserowNotFoundError} Si la tabla no existe
1107
+ *
1108
+ * @example
1109
+ * ```typescript
1110
+ * await table.delete()
1111
+ * console.log('Tabla eliminada exitosamente')
1112
+ * ```
1113
+ *
1114
+ * @since 1.0.0
1115
+ */
1116
+ async delete(): Promise<void> {
1117
+ const table = await this.get()
1118
+ const contextAccess = (this.tableService as any)[Symbol.for('tableContext')]
1119
+ await contextAccess.deleteTable(table.id)
1120
+
1121
+ if (this.logger) {
1122
+ this.logger.info(`Table deleted: "${table.name}" (ID: ${table.id})`)
1123
+ }
1124
+
1125
+ // Limpiar cache
1126
+ this.resolvedTable = undefined
1127
+ }
1128
+
1129
+ /**
1130
+ * Obtener esta tabla completa
1131
+ *
1132
+ * Recupera la tabla con todos sus datos y metadatos.
1133
+ * Utiliza lazy loading con cache para evitar resoluciones repetidas.
1134
+ * Soporta identificación por nombre o ID de la tabla.
1135
+ *
1136
+ * @returns Promise con la tabla completa
1137
+ *
1138
+ * @throws {BaserowNotFoundError} Si la tabla no existe
1139
+ * @throws {BaserowValidationError} Si el identificador es inválido
1140
+ *
1141
+ * @example Obtener por nombre
1142
+ * ```typescript
1143
+ * const table = await admin.workspace('WS').database('DB').table('Mi Tabla').get()
1144
+ * console.log(`Tabla: ${table.name} (ID: ${table.id})`)
1145
+ * console.log(`Filas: ${table.rows_count || 0}`)
1146
+ * ```
1147
+ *
1148
+ * @example Obtener por ID
1149
+ * ```typescript
1150
+ * const table = await admin.workspace('WS').database('DB').table(123).get()
1151
+ * console.log(`Nombre: ${table.name}`)
1152
+ * console.log(`Database ID: ${table.database_id}`)
1153
+ * ```
1154
+ *
1155
+ * @since 1.1.0
1156
+ */
1157
+ async get(): Promise<Table> {
1158
+ if (this.resolvedTable) {
1159
+ return this.resolvedTable
1160
+ }
1161
+
1162
+ if (typeof this.tableIdentifier === 'number') {
1163
+ // Es un ID numérico
1164
+ const table = await this.tableService.get(this.tableIdentifier)
1165
+ this.resolvedTable = table
1166
+ } else {
1167
+ // Es un nombre string - necesitamos resolver database primero
1168
+ validateRequired(this.tableIdentifier, 'table name')
1169
+
1170
+ // Resolver database
1171
+ let database: Database
1172
+ if (typeof this.databaseIdentifier === 'number') {
1173
+ const foundDatabase = await this.databaseService.findUnique(this.databaseIdentifier)
1174
+ if (!foundDatabase) {
1175
+ throw new BaserowNotFoundError('Database', this.databaseIdentifier)
1176
+ }
1177
+ database = foundDatabase
1178
+ } else {
1179
+ const foundDatabase = await this.databaseService.findUnique(this.databaseIdentifier)
1180
+ if (!foundDatabase) {
1181
+ throw new BaserowNotFoundError('Database', this.databaseIdentifier)
1182
+ }
1183
+ database = foundDatabase
1184
+ }
1185
+
1186
+ // Buscar tabla por nombre en la database
1187
+ const table = await this.tableService.findUnique(database.id, this.tableIdentifier)
1188
+ if (!table) {
1189
+ throw new BaserowNotFoundError('Table', this.tableIdentifier)
1190
+ }
1191
+
1192
+ this.resolvedTable = table
1193
+ }
1194
+
1195
+ if (this.logger) {
1196
+ this.logger.info(`Retrieved table: "${this.resolvedTable.name}" (ID: ${this.resolvedTable.id})`)
1197
+ }
1198
+
1199
+ return this.resolvedTable
1200
+ }
1201
+
1202
+ /**
1203
+ * Verificar si esta tabla existe
1204
+ *
1205
+ * Método de utilidad para verificar la existencia de la tabla
1206
+ * sin cargar todos sus datos. Útil para validaciones previas.
1207
+ *
1208
+ * @returns Promise con true si existe, false si no
1209
+ *
1210
+ * @example Verificar existencia por nombre
1211
+ * ```typescript
1212
+ * const exists = await admin.workspace('WS').database('DB').table('Mi Tabla').exists()
1213
+ * if (exists) {
1214
+ * console.log('La tabla existe')
1215
+ * } else {
1216
+ * console.log('La tabla no fue encontrada')
1217
+ * }
1218
+ * ```
1219
+ *
1220
+ * @example Verificar antes de crear
1221
+ * ```typescript
1222
+ * const tableName = 'Nueva Tabla'
1223
+ * const tableContext = admin.workspace('WS').database('DB').table(tableName)
1224
+ * const exists = await tableContext.exists()
1225
+ *
1226
+ * if (!exists) {
1227
+ * await tableContext.create()
1228
+ * console.log('Tabla creada exitosamente')
1229
+ * } else {
1230
+ * console.log('La tabla ya existe')
1231
+ * }
1232
+ * ```
1233
+ *
1234
+ * @since 1.1.0
1235
+ */
1236
+ async exists(): Promise<boolean> {
1237
+ try {
1238
+ await this.get()
1239
+ return true
1240
+ } catch (error) {
1241
+ if ((error as any).name === 'BaserowNotFoundError') {
1242
+ return false
1243
+ }
1244
+ throw error
1245
+ }
1246
+ }
1247
+ }