@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.
- package/CHANGELOG.md +435 -0
- package/README.md +847 -0
- package/dist/index.d.ts +8749 -0
- package/dist/index.js +11167 -0
- package/dist/index.js.map +1 -0
- package/package.json +91 -0
- package/src/BaserowClient.ts +501 -0
- package/src/ClientWithCreds.ts +545 -0
- package/src/ClientWithCredsWs.ts +852 -0
- package/src/ClientWithToken.ts +171 -0
- package/src/contexts/DatabaseClientContext.ts +114 -0
- package/src/contexts/DatabaseContext.ts +870 -0
- package/src/contexts/DatabaseTokenContext.ts +331 -0
- package/src/contexts/FieldContext.ts +399 -0
- package/src/contexts/RowContext.ts +99 -0
- package/src/contexts/TableClientContext.ts +291 -0
- package/src/contexts/TableContext.ts +1247 -0
- package/src/contexts/TableOnlyContext.ts +74 -0
- package/src/contexts/WorkspaceContext.ts +490 -0
- package/src/express/errors.ts +260 -0
- package/src/express/index.ts +69 -0
- package/src/express/middleware.ts +225 -0
- package/src/express/serializers.ts +314 -0
- package/src/index.ts +247 -0
- package/src/presets/performance.ts +262 -0
- package/src/services/AuthService.ts +472 -0
- package/src/services/DatabaseService.ts +246 -0
- package/src/services/DatabaseTokenService.ts +186 -0
- package/src/services/FieldService.ts +1543 -0
- package/src/services/RowService.ts +982 -0
- package/src/services/SchemaControlService.ts +420 -0
- package/src/services/TableService.ts +781 -0
- package/src/services/WorkspaceService.ts +113 -0
- package/src/services/core/BaseAuthClient.ts +111 -0
- package/src/services/core/BaseClient.ts +107 -0
- package/src/services/core/BaseService.ts +71 -0
- package/src/services/core/HttpService.ts +115 -0
- package/src/services/core/ValidationService.ts +149 -0
- package/src/types/auth.ts +177 -0
- package/src/types/core.ts +91 -0
- package/src/types/errors.ts +105 -0
- package/src/types/fields.ts +456 -0
- package/src/types/index.ts +222 -0
- package/src/types/requests.ts +333 -0
- package/src/types/responses.ts +50 -0
- package/src/types/schema.ts +446 -0
- package/src/types/tokens.ts +36 -0
- package/src/types.ts +11 -0
- package/src/utils/auth.ts +174 -0
- package/src/utils/axios.ts +647 -0
- package/src/utils/field-cache.ts +164 -0
- package/src/utils/httpFactory.ts +66 -0
- package/src/utils/jwt-decoder.ts +188 -0
- package/src/utils/jwtTokens.ts +50 -0
- package/src/utils/performance.ts +105 -0
- package/src/utils/prisma-mapper.ts +961 -0
- package/src/utils/validation.ts +463 -0
- 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
|
+
}
|