@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,1543 @@
|
|
|
1
|
+
import { HttpService } from './core/HttpService'
|
|
2
|
+
import { ValidationService } from './core/ValidationService'
|
|
3
|
+
import {
|
|
4
|
+
Field,
|
|
5
|
+
CreateFieldRequest,
|
|
6
|
+
UpdateFieldRequest,
|
|
7
|
+
SelectOption,
|
|
8
|
+
NumberSeparator,
|
|
9
|
+
RatingStyle,
|
|
10
|
+
RatingColor,
|
|
11
|
+
FieldConstraint,
|
|
12
|
+
FieldAdvancedOptions,
|
|
13
|
+
ConstraintType,
|
|
14
|
+
BaserowResponse,
|
|
15
|
+
BaserowNotFoundError,
|
|
16
|
+
BaserowValidationError,
|
|
17
|
+
Logger
|
|
18
|
+
} from '../types/index'
|
|
19
|
+
import { sanitizeFieldName, validateFieldType } from '../utils/validation'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Servicio para operaciones CRUD de campos de Baserow
|
|
23
|
+
*
|
|
24
|
+
* Proporciona operaciones completas de campos incluyendo CRUD básico,
|
|
25
|
+
* métodos de creación fluidos para todos los tipos de campo soportados,
|
|
26
|
+
* y operaciones de gestión avanzadas como duplicación y reordenamiento.
|
|
27
|
+
*
|
|
28
|
+
* **Cobertura de Tipos de Campo (21/22 - 95%):**
|
|
29
|
+
* - **Texto (5)**: text, long_text, url, email, phone_number
|
|
30
|
+
* - **Numéricos (3)**: number, boolean, rating
|
|
31
|
+
* - **Fecha/Auditoría (5)**: date, last_modified, last_modified_by, created_on, created_by
|
|
32
|
+
* - **Selección (2)**: single_select, multiple_select
|
|
33
|
+
* - **Relacionales (2)**: link_row, formula
|
|
34
|
+
* - **Avanzados (5)**: file, autonumber, count, rollup, lookup
|
|
35
|
+
*
|
|
36
|
+
* **Características:**
|
|
37
|
+
* - API fluida para cada tipo de campo con parámetros específicos
|
|
38
|
+
* - Validación automática de tipos y configuraciones
|
|
39
|
+
* - Sanitización de nombres de campo
|
|
40
|
+
* - Operaciones avanzadas: duplicar, reordenar
|
|
41
|
+
* - Búsqueda de campos por nombre
|
|
42
|
+
* - **Soporte v1.35+**: Índices para performance y constraints para integridad
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Operaciones básicas
|
|
47
|
+
* const fields = await fieldService.findMany(tableId)
|
|
48
|
+
* const field = await fieldService.get(fieldId)
|
|
49
|
+
* const found = await fieldService.findUnique(tableId, 'email')
|
|
50
|
+
*
|
|
51
|
+
* // API fluida por tipo de campo (MANTENER - Sin cambios)
|
|
52
|
+
* const textField = await fieldService.createTextField(tableId, 'nombre', 'default')
|
|
53
|
+
* const numberField = await fieldService.createNumberField(tableId, 'precio', 2, true)
|
|
54
|
+
* const selectField = await fieldService.createSelectField(tableId, 'estado', [
|
|
55
|
+
* { value: 'active', color: 'blue' },
|
|
56
|
+
* { value: 'inactive', color: 'red' }
|
|
57
|
+
* ])
|
|
58
|
+
*
|
|
59
|
+
* // Campos avanzados (MANTENER - Sin cambios)
|
|
60
|
+
* const linkField = await fieldService.createLinkField(tableId, 'relacionado', targetTableId)
|
|
61
|
+
* const formulaField = await fieldService.createFormulaField(tableId, 'total', 'field("precio") * field("cantidad")')
|
|
62
|
+
*
|
|
63
|
+
* // Campos con índices y constraints (v1.35+)
|
|
64
|
+
* const emailField = await fieldService.createEmailField(tableId, 'email', undefined, {
|
|
65
|
+
* index: true,
|
|
66
|
+
* constraints: [{ type: 'unique_with_empty', active: true }]
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* // Gestión de índices y constraints
|
|
70
|
+
* await fieldService.setFieldIndex(fieldId, true)
|
|
71
|
+
* await fieldService.addFieldConstraint(fieldId, { type: 'unique', active: true })
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @since 1.0.0
|
|
75
|
+
*/
|
|
76
|
+
export class FieldService extends HttpService {
|
|
77
|
+
private validationService: ValidationService
|
|
78
|
+
|
|
79
|
+
constructor(http: any, logger?: Logger) {
|
|
80
|
+
super(http, logger)
|
|
81
|
+
this.validationService = new ValidationService(http, logger)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ===== PUBLIC API (Prisma-style) =====
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Listar todos los campos de una tabla
|
|
88
|
+
*
|
|
89
|
+
* Obtiene todos los campos de una tabla específica con sus definiciones
|
|
90
|
+
* completas incluyendo tipo, configuración y metadatos.
|
|
91
|
+
*
|
|
92
|
+
* @param tableId - ID numérico de la tabla
|
|
93
|
+
* @returns Promise con array de campos de la tabla
|
|
94
|
+
*
|
|
95
|
+
* @throws {BaserowValidationError} Si el tableId es inválido
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const fields = await fieldService.findMany(123)
|
|
100
|
+
* fields.forEach(field => {
|
|
101
|
+
* console.log(`${field.name} (${field.type}): ${field.id}`)
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @since 1.0.0
|
|
106
|
+
*/
|
|
107
|
+
async findMany(tableId: number): Promise<Field[]> {
|
|
108
|
+
this.validationService.validateId(tableId, 'table ID')
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await this.http.get<BaserowResponse<Field> | Field[]>(`/database/fields/table/${tableId}/`)
|
|
112
|
+
|
|
113
|
+
// La API puede devolver array directo o {results: []}
|
|
114
|
+
const fields = Array.isArray(response) ? response : response.results || []
|
|
115
|
+
this.logSuccess('find many fields', tableId, { count: fields.length })
|
|
116
|
+
return fields
|
|
117
|
+
} catch (error) {
|
|
118
|
+
this.handleHttpError(error, 'find many fields', tableId)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Obtener campo por ID
|
|
124
|
+
*
|
|
125
|
+
* Recupera un campo específico por su ID con toda su configuración
|
|
126
|
+
* y metadatos. Útil para inspeccionar configuraciones detalladas.
|
|
127
|
+
*
|
|
128
|
+
* @param fieldId - ID numérico del campo
|
|
129
|
+
* @returns Promise con datos completos del campo
|
|
130
|
+
*
|
|
131
|
+
* @throws {BaserowNotFoundError} Si el campo no existe
|
|
132
|
+
* @throws {BaserowValidationError} Si el fieldId es inválido
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const field = await fieldService.get(456)
|
|
137
|
+
* console.log(`Campo: ${field.name} - Tipo: ${field.type}`)
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @since 1.0.0
|
|
141
|
+
*/
|
|
142
|
+
async get(fieldId: number): Promise<Field> {
|
|
143
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
144
|
+
return this.getById<Field>('/database/fields', fieldId)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Buscar campo por ID o nombre en una tabla
|
|
149
|
+
*
|
|
150
|
+
* Busca un campo específico por su ID o nombre dentro de una tabla.
|
|
151
|
+
* Útil para operaciones dinámicas donde se conoce el identificador pero no se sabe si es ID o nombre.
|
|
152
|
+
*
|
|
153
|
+
* @param tableId - ID numérico de la tabla
|
|
154
|
+
* @param identifier - ID numérico o nombre del campo a buscar
|
|
155
|
+
* @returns Promise con el campo encontrado o null si no existe
|
|
156
|
+
*
|
|
157
|
+
* @throws {BaserowValidationError} Si los parámetros son inválidos
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const field = await fieldService.findUnique(123, 'email')
|
|
162
|
+
* if (field) {
|
|
163
|
+
* console.log(`Campo encontrado: ${field.id}`)
|
|
164
|
+
* } else {
|
|
165
|
+
* console.log('Campo no encontrado')
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* @since 1.0.0
|
|
170
|
+
*/
|
|
171
|
+
async findUnique(tableId: number, identifier: string | number): Promise<Field | null> {
|
|
172
|
+
this.validationService.validateId(tableId, 'table ID')
|
|
173
|
+
|
|
174
|
+
if (typeof identifier === 'number') {
|
|
175
|
+
// Buscar por ID
|
|
176
|
+
try {
|
|
177
|
+
return await this.get(identifier)
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof BaserowNotFoundError) {
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
throw error
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// Buscar por nombre
|
|
186
|
+
this.validationService.validateResourceName(identifier, 'field')
|
|
187
|
+
const fields = await this.findMany(tableId)
|
|
188
|
+
const found = fields.find(field => field.name === identifier) || null
|
|
189
|
+
|
|
190
|
+
if (found) {
|
|
191
|
+
this.logSuccess(`find field by name "${identifier}"`, found.id)
|
|
192
|
+
} else {
|
|
193
|
+
this.logDebug(`No field found with name "${identifier}" in table ${tableId}`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return found
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Crear nuevo campo - API pública
|
|
202
|
+
*
|
|
203
|
+
* Crea un nuevo campo en la tabla especificada. Este es el método base
|
|
204
|
+
* que usan todos los métodos de creación específicos por tipo.
|
|
205
|
+
*
|
|
206
|
+
* @param tableId - ID numérico de la tabla
|
|
207
|
+
* @param data - Configuración completa del campo
|
|
208
|
+
* @returns Promise con el campo creado
|
|
209
|
+
*
|
|
210
|
+
* @throws {BaserowValidationError} Si los datos son inválidos
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const field = await fieldService.create(123, {
|
|
215
|
+
* name: 'Mi Campo',
|
|
216
|
+
* type: 'text',
|
|
217
|
+
* text_default: 'Valor por defecto',
|
|
218
|
+
* description: 'Descripción del campo'
|
|
219
|
+
* })
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @since 1.0.0
|
|
223
|
+
*/
|
|
224
|
+
async create(tableId: number, data: CreateFieldRequest): Promise<Field> {
|
|
225
|
+
return this.createFieldInternal(tableId, data)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ===== PRIVATE METHODS =====
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Crear nuevo campo (método interno)
|
|
232
|
+
* @private - Solo para uso interno del servicio
|
|
233
|
+
*/
|
|
234
|
+
private async createFieldInternal(tableId: number, data: CreateFieldRequest): Promise<Field> {
|
|
235
|
+
this.validationService.validateId(tableId, 'table ID')
|
|
236
|
+
data.name = sanitizeFieldName(data.name)
|
|
237
|
+
this.validationService.validateResourceName(data.name, 'field')
|
|
238
|
+
validateFieldType(data.type, 'type')
|
|
239
|
+
|
|
240
|
+
const payload = {
|
|
241
|
+
table_id: tableId,
|
|
242
|
+
...data
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
this.logDebug(`Creating field "${data.name}" of type ${data.type} in table ${tableId}`, payload)
|
|
247
|
+
const response = await this.http.post<Field>(`/database/fields/table/${tableId}/`, payload)
|
|
248
|
+
this.logSuccess('create field', response.id, { name: data.name, type: data.type })
|
|
249
|
+
return response
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.handleHttpError(error, 'create field', tableId)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Actualizar campo (método interno)
|
|
257
|
+
* @private - Solo para uso por FieldContext
|
|
258
|
+
*/
|
|
259
|
+
private async updateFieldInternal(fieldId: number, data: UpdateFieldRequest): Promise<Field> {
|
|
260
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
261
|
+
|
|
262
|
+
if (data.name !== undefined) {
|
|
263
|
+
data.name = sanitizeFieldName(data.name)
|
|
264
|
+
this.validationService.validateResourceName(data.name, 'field')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
this.logDebug(`Updating field ${fieldId}`, data)
|
|
269
|
+
const response = await this.http.patch<Field>(`/database/fields/${fieldId}/`, data)
|
|
270
|
+
this.logSuccess('update field', fieldId)
|
|
271
|
+
return response
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if ((error as any).status === 404) {
|
|
274
|
+
throw new BaserowNotFoundError('Field', fieldId)
|
|
275
|
+
}
|
|
276
|
+
this.handleHttpError(error, 'update field', fieldId)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Eliminar campo (método interno)
|
|
282
|
+
* @private - Solo para uso por FieldContext
|
|
283
|
+
*/
|
|
284
|
+
private async deleteFieldInternal(fieldId: number): Promise<void> {
|
|
285
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
286
|
+
return this.deleteById('/database/fields', fieldId)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Duplicar campo
|
|
291
|
+
*
|
|
292
|
+
* Crea una copia exacta de un campo existente con un nuevo nombre.
|
|
293
|
+
* Mantiene toda la configuración del campo original.
|
|
294
|
+
*
|
|
295
|
+
* @param fieldId - ID numérico del campo a duplicar
|
|
296
|
+
* @param newName - Nombre para el campo duplicado (opcional)
|
|
297
|
+
* @returns Promise con el campo duplicado
|
|
298
|
+
*
|
|
299
|
+
* @throws {BaserowValidationError} Si los parámetros son inválidos
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const duplicated = await fieldService.duplicate(456, 'Copia del campo')
|
|
304
|
+
* console.log(`Campo duplicado: ${duplicated.id}`)
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* @since 1.0.0
|
|
308
|
+
*/
|
|
309
|
+
async duplicate(fieldId: number, newName?: string): Promise<Field> {
|
|
310
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
311
|
+
|
|
312
|
+
const originalField = await this.get(fieldId)
|
|
313
|
+
const name = newName ? sanitizeFieldName(newName) : `${originalField.name} (copy)`
|
|
314
|
+
|
|
315
|
+
if (newName) {
|
|
316
|
+
this.validationService.validateResourceName(name, 'field')
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
this.logDebug(`Duplicating field ${fieldId} with name "${name}"`, { originalName: originalField.name })
|
|
321
|
+
const response = await this.http.post<Field>(`/database/fields/${fieldId}/duplicate/`, { name })
|
|
322
|
+
this.logSuccess('duplicate field', response.id, { originalId: fieldId, newName: name })
|
|
323
|
+
return response
|
|
324
|
+
} catch (error) {
|
|
325
|
+
this.handleHttpError(error, 'duplicate field', fieldId)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Reordenar campos de una tabla
|
|
331
|
+
*
|
|
332
|
+
* Cambia el orden de visualización de los campos en una tabla.
|
|
333
|
+
* El orden se define por la secuencia de IDs proporcionada.
|
|
334
|
+
*
|
|
335
|
+
* @param tableId - ID numérico de la tabla
|
|
336
|
+
* @param fieldIds - Array de IDs de campos en el nuevo orden deseado
|
|
337
|
+
* @returns Promise que resuelve cuando el reordenamiento se completa
|
|
338
|
+
*
|
|
339
|
+
* @throws {BaserowValidationError} Si los parámetros son inválidos
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* // Reordenar campos: primero ID 3, luego 1, luego 2
|
|
344
|
+
* await fieldService.reorder(123, [3, 1, 2])
|
|
345
|
+
* console.log('Campos reordenados exitosamente')
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* @since 1.0.0
|
|
349
|
+
*/
|
|
350
|
+
async reorder(tableId: number, fieldIds: number[]): Promise<void> {
|
|
351
|
+
this.validationService.validateId(tableId, 'table ID')
|
|
352
|
+
|
|
353
|
+
if (!Array.isArray(fieldIds) || fieldIds.length === 0) {
|
|
354
|
+
throw new Error('fieldIds must be a non-empty array')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fieldIds.forEach(id => this.validationService.validateId(id, 'field ID'))
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
this.logDebug(`Reordering ${fieldIds.length} fields in table ${tableId}`, { fieldIds })
|
|
361
|
+
await this.http.patch(`/database/tables/${tableId}/order-fields/`, {
|
|
362
|
+
field_ids: fieldIds
|
|
363
|
+
})
|
|
364
|
+
this.logSuccess('reorder fields', tableId, { fieldCount: fieldIds.length })
|
|
365
|
+
} catch (error) {
|
|
366
|
+
this.handleHttpError(error, 'reorder fields', tableId)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ===== HELPERS PARA TIPOS ESPECÍFICOS =====
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Crear campo de texto
|
|
374
|
+
*
|
|
375
|
+
* Crea un campo de texto simple con valor por defecto opcional.
|
|
376
|
+
* El tipo de campo más básico para almacenar cadenas de texto cortas.
|
|
377
|
+
*
|
|
378
|
+
* @param tableId - ID numérico de la tabla
|
|
379
|
+
* @param name - Nombre del campo
|
|
380
|
+
* @param defaultValue - Valor por defecto (opcional)
|
|
381
|
+
* @param description - Descripción del campo (opcional)
|
|
382
|
+
* @param advancedOptions - Opciones avanzadas: índice y constraints (v1.35+)
|
|
383
|
+
* @returns Promise con el campo de texto creado
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* const nameField = await fieldService.createTextField(123, 'nombre', 'Sin nombre')
|
|
388
|
+
* const titleField = await fieldService.createTextField(123, 'titulo')
|
|
389
|
+
*
|
|
390
|
+
* // Con índice y constraint (v1.35+)
|
|
391
|
+
* const codeField = await fieldService.createTextField(123, 'codigo', undefined, undefined, {
|
|
392
|
+
* index: true,
|
|
393
|
+
* constraints: [{ type: 'unique_with_empty', active: true }]
|
|
394
|
+
* })
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* @since 1.0.0
|
|
398
|
+
*/
|
|
399
|
+
async createTextField(
|
|
400
|
+
tableId: number,
|
|
401
|
+
name: string,
|
|
402
|
+
defaultValue?: string,
|
|
403
|
+
description?: string,
|
|
404
|
+
advancedOptions?: FieldAdvancedOptions
|
|
405
|
+
): Promise<Field> {
|
|
406
|
+
const baseRequest = {
|
|
407
|
+
name,
|
|
408
|
+
type: 'text' as const,
|
|
409
|
+
text_default: defaultValue || '',
|
|
410
|
+
description
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return this.create(tableId, this.applyAdvancedOptions(baseRequest, advancedOptions))
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Crear campo de texto largo
|
|
418
|
+
*
|
|
419
|
+
* Crea un campo de texto largo (multilinea) para almacenar texto extenso.
|
|
420
|
+
* Ideal para descripciones, comentarios o contenido largo.
|
|
421
|
+
*
|
|
422
|
+
* @param tableId - ID numérico de la tabla
|
|
423
|
+
* @param name - Nombre del campo
|
|
424
|
+
* @param defaultValue - Valor por defecto (opcional)
|
|
425
|
+
* @param description - Descripción del campo (opcional)
|
|
426
|
+
* @returns Promise con el campo de texto largo creado
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* const descField = await fieldService.createLongTextField(123, 'descripcion')
|
|
431
|
+
* const notesField = await fieldService.createLongTextField(123, 'notas', 'Sin notas')
|
|
432
|
+
* ```
|
|
433
|
+
*
|
|
434
|
+
* @since 1.0.0
|
|
435
|
+
*/
|
|
436
|
+
async createLongTextField(
|
|
437
|
+
tableId: number,
|
|
438
|
+
name: string,
|
|
439
|
+
defaultValue?: string,
|
|
440
|
+
description?: string
|
|
441
|
+
): Promise<Field> {
|
|
442
|
+
return this.create(tableId, {
|
|
443
|
+
name,
|
|
444
|
+
type: 'long_text',
|
|
445
|
+
text_default: defaultValue || '',
|
|
446
|
+
description
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Crear campo numérico
|
|
452
|
+
*
|
|
453
|
+
* Crea un campo numérico con configuración completa de formato y validación.
|
|
454
|
+
* Soporta decimales, prefijos/sufijos, separadores y valores por defecto.
|
|
455
|
+
*
|
|
456
|
+
* @param tableId - ID numérico de la tabla
|
|
457
|
+
* @param name - Nombre del campo
|
|
458
|
+
* @param decimalPlaces - Número de decimales (0-10, default: 0)
|
|
459
|
+
* @param allowNegative - Permitir números negativos (default: true)
|
|
460
|
+
* @param defaultValue - Valor numérico por defecto (opcional)
|
|
461
|
+
* @param prefix - Prefijo para mostrar (ej: '$', default: undefined)
|
|
462
|
+
* @param suffix - Sufijo para mostrar (ej: '%', default: undefined)
|
|
463
|
+
* @param separator - Separador de miles (default: 'COMMA_PERIOD')
|
|
464
|
+
* @param description - Descripción del campo (opcional)
|
|
465
|
+
* @returns Promise con el campo numérico creado
|
|
466
|
+
*
|
|
467
|
+
* @throws {BaserowValidationError} Si decimalPlaces no está entre 0-10
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* // Campo de precio con 2 decimales y símbolo $
|
|
472
|
+
* const priceField = await fieldService.createNumberField(123, 'precio', 2, true, 0, '$')
|
|
473
|
+
*
|
|
474
|
+
* // Campo de porcentaje
|
|
475
|
+
* const percentField = await fieldService.createNumberField(123, 'descuento', 1, false, 0, undefined, '%')
|
|
476
|
+
*
|
|
477
|
+
* // Campo entero simple
|
|
478
|
+
* const ageField = await fieldService.createNumberField(123, 'edad', 0)
|
|
479
|
+
* ```
|
|
480
|
+
*
|
|
481
|
+
* @since 1.0.0
|
|
482
|
+
*/
|
|
483
|
+
async createNumberField(
|
|
484
|
+
tableId: number,
|
|
485
|
+
name: string,
|
|
486
|
+
decimalPlaces = 0,
|
|
487
|
+
allowNegative = true,
|
|
488
|
+
defaultValue?: number,
|
|
489
|
+
prefix?: string,
|
|
490
|
+
suffix?: string,
|
|
491
|
+
separator: NumberSeparator = 'COMMA_PERIOD',
|
|
492
|
+
description?: string
|
|
493
|
+
): Promise<Field> {
|
|
494
|
+
// Validar number_decimal_places (debe estar entre 0-10)
|
|
495
|
+
if (!Number.isInteger(decimalPlaces) || decimalPlaces < 0 || decimalPlaces > 10) {
|
|
496
|
+
throw new BaserowValidationError(`Decimal places must be an integer between 0 and 10, got: ${decimalPlaces}`, {
|
|
497
|
+
number_decimal_places: [`Value must be between 0 and 10, got: ${decimalPlaces}`]
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return this.create(tableId, {
|
|
502
|
+
name,
|
|
503
|
+
type: 'number',
|
|
504
|
+
number_decimal_places: decimalPlaces,
|
|
505
|
+
number_negative: allowNegative,
|
|
506
|
+
number_default: defaultValue !== undefined ? defaultValue.toFixed(decimalPlaces) : undefined,
|
|
507
|
+
number_prefix: prefix,
|
|
508
|
+
number_suffix: suffix,
|
|
509
|
+
number_separator: separator,
|
|
510
|
+
description
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Crear campo booleano
|
|
516
|
+
*
|
|
517
|
+
* Crea un campo booleano (checkbox) para valores verdadero/falso.
|
|
518
|
+
* Ideal para estados, flags o configuraciones binarias.
|
|
519
|
+
*
|
|
520
|
+
* @param tableId - ID numérico de la tabla
|
|
521
|
+
* @param name - Nombre del campo
|
|
522
|
+
* @param defaultValue - Valor por defecto (default: false)
|
|
523
|
+
* @param description - Descripción del campo (opcional)
|
|
524
|
+
* @returns Promise con el campo booleano creado
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```typescript
|
|
528
|
+
* const activeField = await fieldService.createBooleanField(123, 'activo', true)
|
|
529
|
+
* const verifiedField = await fieldService.createBooleanField(123, 'verificado')
|
|
530
|
+
* ```
|
|
531
|
+
*
|
|
532
|
+
* @since 1.0.0
|
|
533
|
+
*/
|
|
534
|
+
async createBooleanField(tableId: number, name: string, defaultValue = false, description?: string): Promise<Field> {
|
|
535
|
+
return this.create(tableId, {
|
|
536
|
+
name,
|
|
537
|
+
type: 'boolean',
|
|
538
|
+
boolean_default: defaultValue,
|
|
539
|
+
description
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Crear campo de selección simple
|
|
545
|
+
*
|
|
546
|
+
* Crea un campo de selección simple donde solo se puede elegir una opción.
|
|
547
|
+
* Las opciones incluyen valor y color para visualización.
|
|
548
|
+
*
|
|
549
|
+
* @param tableId - ID numérico de la tabla
|
|
550
|
+
* @param name - Nombre del campo
|
|
551
|
+
* @param options - Array de opciones con value y color
|
|
552
|
+
* @param description - Descripción del campo (opcional)
|
|
553
|
+
* @returns Promise con el campo de selección simple creado
|
|
554
|
+
*
|
|
555
|
+
* @throws {Error} Si no se proporcionan opciones
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```typescript
|
|
559
|
+
* const statusField = await fieldService.createSelectField(123, 'estado', [
|
|
560
|
+
* { value: 'pendiente', color: 'yellow' },
|
|
561
|
+
* { value: 'completado', color: 'green' },
|
|
562
|
+
* { value: 'cancelado', color: 'red' }
|
|
563
|
+
* ])
|
|
564
|
+
* ```
|
|
565
|
+
*
|
|
566
|
+
* @since 1.0.0
|
|
567
|
+
*/
|
|
568
|
+
async createSelectField(
|
|
569
|
+
tableId: number,
|
|
570
|
+
name: string,
|
|
571
|
+
options: SelectOption[],
|
|
572
|
+
description?: string
|
|
573
|
+
): Promise<Field> {
|
|
574
|
+
if (!options || !Array.isArray(options) || options.length === 0) {
|
|
575
|
+
throw new Error('Select field must have at least one option')
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
579
|
+
throw new Error('Select field must have at least one option')
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return this.create(tableId, {
|
|
583
|
+
name,
|
|
584
|
+
type: 'single_select',
|
|
585
|
+
select_options: options,
|
|
586
|
+
description
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Crear campo de selección múltiple
|
|
592
|
+
*
|
|
593
|
+
* Crea un campo de selección múltiple donde se pueden elegir varias opciones.
|
|
594
|
+
* Útil para tags, categorías o cualquier clasificación múltiple.
|
|
595
|
+
*
|
|
596
|
+
* @param tableId - ID numérico de la tabla
|
|
597
|
+
* @param name - Nombre del campo
|
|
598
|
+
* @param options - Array de opciones con value y color
|
|
599
|
+
* @param description - Descripción del campo (opcional)
|
|
600
|
+
* @returns Promise con el campo de selección múltiple creado
|
|
601
|
+
*
|
|
602
|
+
* @throws {Error} Si no se proporcionan opciones
|
|
603
|
+
*
|
|
604
|
+
* @example
|
|
605
|
+
* ```typescript
|
|
606
|
+
* const tagsField = await fieldService.createMultiSelectField(123, 'tags', [
|
|
607
|
+
* { value: 'importante', color: 'red' },
|
|
608
|
+
* { value: 'urgente', color: 'orange' },
|
|
609
|
+
* { value: 'revision', color: 'blue' }
|
|
610
|
+
* ])
|
|
611
|
+
* ```
|
|
612
|
+
*
|
|
613
|
+
* @since 1.0.0
|
|
614
|
+
*/
|
|
615
|
+
async createMultiSelectField(
|
|
616
|
+
tableId: number,
|
|
617
|
+
name: string,
|
|
618
|
+
options: SelectOption[],
|
|
619
|
+
description?: string
|
|
620
|
+
): Promise<Field> {
|
|
621
|
+
if (!options || !Array.isArray(options) || options.length === 0) {
|
|
622
|
+
throw new Error('Multi-select field must have at least one option')
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
626
|
+
throw new Error('Multi-select field must have at least one option')
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return this.create(tableId, {
|
|
630
|
+
name,
|
|
631
|
+
type: 'multiple_select',
|
|
632
|
+
select_options: options,
|
|
633
|
+
description
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Crear campo de fecha
|
|
639
|
+
*
|
|
640
|
+
* Crea un campo de fecha con configuración completa de formato y zona horaria.
|
|
641
|
+
* Soporta fechas simples o fechas con hora, y configuración de timezone.
|
|
642
|
+
*
|
|
643
|
+
* @param tableId - ID numérico de la tabla
|
|
644
|
+
* @param name - Nombre del campo
|
|
645
|
+
* @param includeTime - Incluir hora además de fecha (default: false)
|
|
646
|
+
* @param dateFormat - Formato de fecha (default: 'ISO')
|
|
647
|
+
* @param timeFormat - Formato de hora (default: '24')
|
|
648
|
+
* @param showTzInfo - Mostrar información de timezone (default: false)
|
|
649
|
+
* @param forceTimezone - Timezone forzado (opcional)
|
|
650
|
+
* @param forceTimezoneOffset - Offset de timezone forzado (opcional)
|
|
651
|
+
* @param description - Descripción del campo (opcional)
|
|
652
|
+
* @returns Promise con el campo de fecha creado
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* ```typescript
|
|
656
|
+
* // Fecha simple
|
|
657
|
+
* const birthField = await fieldService.createDateField(123, 'nacimiento')
|
|
658
|
+
*
|
|
659
|
+
* // Fecha con hora
|
|
660
|
+
* const createdField = await fieldService.createDateField(123, 'creado', true, 'ISO', '24')
|
|
661
|
+
* ```
|
|
662
|
+
*
|
|
663
|
+
* @since 1.0.0
|
|
664
|
+
*/
|
|
665
|
+
async createDateField(
|
|
666
|
+
tableId: number,
|
|
667
|
+
name: string,
|
|
668
|
+
includeTime = false,
|
|
669
|
+
dateFormat = 'ISO',
|
|
670
|
+
timeFormat = '24',
|
|
671
|
+
showTzInfo = false,
|
|
672
|
+
forceTimezone?: string,
|
|
673
|
+
forceTimezoneOffset?: number,
|
|
674
|
+
description?: string
|
|
675
|
+
): Promise<Field> {
|
|
676
|
+
return this.create(tableId, {
|
|
677
|
+
name,
|
|
678
|
+
type: 'date',
|
|
679
|
+
date_include_time: includeTime,
|
|
680
|
+
date_format: dateFormat,
|
|
681
|
+
date_time_format: timeFormat,
|
|
682
|
+
date_show_tzinfo: showTzInfo,
|
|
683
|
+
date_force_timezone: forceTimezone,
|
|
684
|
+
date_force_timezone_offset: forceTimezoneOffset,
|
|
685
|
+
description
|
|
686
|
+
})
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Crear campo de enlace a otra tabla
|
|
691
|
+
*
|
|
692
|
+
* Crea un campo de relación que enlaza con otra tabla (foreign key).
|
|
693
|
+
* Permite crear relaciones entre tablas para modelar datos relacionales.
|
|
694
|
+
*
|
|
695
|
+
* @param tableId - ID numérico de la tabla origen
|
|
696
|
+
* @param name - Nombre del campo
|
|
697
|
+
* @param targetTableId - ID numérico de la tabla destino
|
|
698
|
+
* @param description - Descripción del campo (opcional)
|
|
699
|
+
* @returns Promise con el campo de enlace creado
|
|
700
|
+
*
|
|
701
|
+
* @throws {BaserowValidationError} Si targetTableId es inválido
|
|
702
|
+
*
|
|
703
|
+
* @example
|
|
704
|
+
* ```typescript
|
|
705
|
+
* // Relacionar tabla de pedidos con tabla de clientes
|
|
706
|
+
* const customerField = await fieldService.createLinkField(ordersTableId, 'cliente', customersTableId)
|
|
707
|
+
* ```
|
|
708
|
+
*
|
|
709
|
+
* @since 1.0.0
|
|
710
|
+
*/
|
|
711
|
+
async createLinkField(tableId: number, name: string, targetTableId: number, description?: string): Promise<Field> {
|
|
712
|
+
this.validationService.validateId(targetTableId, 'target table ID')
|
|
713
|
+
|
|
714
|
+
return this.create(tableId, {
|
|
715
|
+
name,
|
|
716
|
+
type: 'link_row',
|
|
717
|
+
link_row_table_id: targetTableId,
|
|
718
|
+
description
|
|
719
|
+
})
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Crear campo de fórmula
|
|
724
|
+
*
|
|
725
|
+
* Crea un campo calculado que usa una fórmula para derivar su valor
|
|
726
|
+
* de otros campos de la misma fila. El valor se actualiza automáticamente.
|
|
727
|
+
*
|
|
728
|
+
* @param tableId - ID numérico de la tabla
|
|
729
|
+
* @param name - Nombre del campo
|
|
730
|
+
* @param formula - Fórmula de cálculo (sintaxis de Baserow)
|
|
731
|
+
* @param description - Descripción del campo (opcional)
|
|
732
|
+
* @returns Promise con el campo de fórmula creado
|
|
733
|
+
*
|
|
734
|
+
* @throws {Error} Si la fórmula está vacía
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* // Campo que calcula el total como precio * cantidad
|
|
739
|
+
* const totalField = await fieldService.createFormulaField(
|
|
740
|
+
* 123,
|
|
741
|
+
* 'total',
|
|
742
|
+
* 'field("precio") * field("cantidad")'
|
|
743
|
+
* )
|
|
744
|
+
*
|
|
745
|
+
* // Campo que concatena nombre y apellido
|
|
746
|
+
* const fullNameField = await fieldService.createFormulaField(
|
|
747
|
+
* 123,
|
|
748
|
+
* 'nombre_completo',
|
|
749
|
+
* 'concat(field("nombre"), " ", field("apellido"))'
|
|
750
|
+
* )
|
|
751
|
+
* ```
|
|
752
|
+
*
|
|
753
|
+
* @since 1.0.0
|
|
754
|
+
*/
|
|
755
|
+
async createFormulaField(tableId: number, name: string, formula: string, description?: string): Promise<Field> {
|
|
756
|
+
if (!formula || typeof formula !== 'string' || formula.trim() === '') {
|
|
757
|
+
throw new Error('Formula is required and must be a non-empty string')
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return this.create(tableId, {
|
|
761
|
+
name,
|
|
762
|
+
type: 'formula',
|
|
763
|
+
formula,
|
|
764
|
+
description
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Crear campo de URL
|
|
770
|
+
*
|
|
771
|
+
* Crea un campo especializado para almacenar URLs con validación automática.
|
|
772
|
+
* Los valores se validan como URLs válidas y se muestran como enlaces.
|
|
773
|
+
*
|
|
774
|
+
* @param tableId - ID numérico de la tabla
|
|
775
|
+
* @param name - Nombre del campo
|
|
776
|
+
* @param description - Descripción del campo (opcional)
|
|
777
|
+
* @returns Promise con el campo de URL creado
|
|
778
|
+
*
|
|
779
|
+
* @example
|
|
780
|
+
* ```typescript
|
|
781
|
+
* const websiteField = await fieldService.createUrlField(123, 'sitio_web')
|
|
782
|
+
* const linkedinField = await fieldService.createUrlField(123, 'perfil_linkedin')
|
|
783
|
+
* ```
|
|
784
|
+
*
|
|
785
|
+
* @since 1.0.0
|
|
786
|
+
*/
|
|
787
|
+
async createUrlField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
788
|
+
return this.create(tableId, {
|
|
789
|
+
name,
|
|
790
|
+
type: 'url',
|
|
791
|
+
description
|
|
792
|
+
})
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Crear campo de email
|
|
797
|
+
*
|
|
798
|
+
* Crea un campo especializado para almacenar direcciones de email
|
|
799
|
+
* con validación automática de formato.
|
|
800
|
+
*
|
|
801
|
+
* @param tableId - ID numérico de la tabla
|
|
802
|
+
* @param name - Nombre del campo
|
|
803
|
+
* @param description - Descripción del campo (opcional)
|
|
804
|
+
* @param advancedOptions - Opciones avanzadas: índice y constraints (v1.35+)
|
|
805
|
+
* @returns Promise con el campo de email creado
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```typescript
|
|
809
|
+
* const emailField = await fieldService.createEmailField(123, 'email')
|
|
810
|
+
* const contactField = await fieldService.createEmailField(123, 'email_contacto')
|
|
811
|
+
*
|
|
812
|
+
* // Con índice y restricción de unicidad (v1.35+)
|
|
813
|
+
* const uniqueEmailField = await fieldService.createEmailField(123, 'email', undefined, {
|
|
814
|
+
* index: true,
|
|
815
|
+
* constraints: [{ type: 'unique', active: true }]
|
|
816
|
+
* })
|
|
817
|
+
* ```
|
|
818
|
+
*
|
|
819
|
+
* @since 1.0.0
|
|
820
|
+
*/
|
|
821
|
+
async createEmailField(
|
|
822
|
+
tableId: number,
|
|
823
|
+
name: string,
|
|
824
|
+
description?: string,
|
|
825
|
+
advancedOptions?: FieldAdvancedOptions
|
|
826
|
+
): Promise<Field> {
|
|
827
|
+
const baseRequest = {
|
|
828
|
+
name,
|
|
829
|
+
type: 'email' as const,
|
|
830
|
+
description
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return this.create(tableId, this.applyAdvancedOptions(baseRequest, advancedOptions))
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Crear campo de teléfono
|
|
838
|
+
*
|
|
839
|
+
* Crea un campo especializado para almacenar números de teléfono
|
|
840
|
+
* con formateo automático y validación.
|
|
841
|
+
*
|
|
842
|
+
* @param tableId - ID numérico de la tabla
|
|
843
|
+
* @param name - Nombre del campo
|
|
844
|
+
* @param description - Descripción del campo (opcional)
|
|
845
|
+
* @returns Promise con el campo de teléfono creado
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```typescript
|
|
849
|
+
* const phoneField = await fieldService.createPhoneField(123, 'telefono')
|
|
850
|
+
* const mobileField = await fieldService.createPhoneField(123, 'movil')
|
|
851
|
+
* ```
|
|
852
|
+
*
|
|
853
|
+
* @since 1.0.0
|
|
854
|
+
*/
|
|
855
|
+
async createPhoneField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
856
|
+
return this.create(tableId, {
|
|
857
|
+
name,
|
|
858
|
+
type: 'phone_number',
|
|
859
|
+
description
|
|
860
|
+
})
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Crear campo de rating
|
|
865
|
+
*
|
|
866
|
+
* Crea un campo de calificación visual con estrellas, corazones u otros íconos.
|
|
867
|
+
* Ideal para ratings, puntuaciones o evaluaciones.
|
|
868
|
+
*
|
|
869
|
+
* @param tableId - ID numérico de la tabla
|
|
870
|
+
* @param name - Nombre del campo
|
|
871
|
+
* @param maxValue - Valor máximo (1-10, default: 5)
|
|
872
|
+
* @param color - Color del rating (default: 'yellow')
|
|
873
|
+
* @param style - Estilo del ícono (default: 'star')
|
|
874
|
+
* @param description - Descripción del campo (opcional)
|
|
875
|
+
* @returns Promise con el campo de rating creado
|
|
876
|
+
*
|
|
877
|
+
* @throws {Error} Si maxValue no está entre 1-10
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* ```typescript
|
|
881
|
+
* // Rating de 5 estrellas amarillas
|
|
882
|
+
* const ratingField = await fieldService.createRatingField(123, 'calificacion')
|
|
883
|
+
*
|
|
884
|
+
* // Rating personalizado de 10 corazones rojos
|
|
885
|
+
* const loveField = await fieldService.createRatingField(123, 'amor', 10, 'red', 'heart')
|
|
886
|
+
* ```
|
|
887
|
+
*
|
|
888
|
+
* @since 1.0.0
|
|
889
|
+
*/
|
|
890
|
+
async createRatingField(
|
|
891
|
+
tableId: number,
|
|
892
|
+
name: string,
|
|
893
|
+
maxValue: number = 5,
|
|
894
|
+
color: RatingColor = 'yellow',
|
|
895
|
+
style: RatingStyle = 'star',
|
|
896
|
+
description?: string
|
|
897
|
+
): Promise<Field> {
|
|
898
|
+
this.validationService.validateId(tableId, 'table ID')
|
|
899
|
+
this.validationService.validateResourceName(name, 'field')
|
|
900
|
+
|
|
901
|
+
if (maxValue < 1 || maxValue > 10) {
|
|
902
|
+
throw new Error('maxValue must be between 1 and 10')
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Validación removida - ahora usamos RatingColor enum que tiene valores válidos
|
|
906
|
+
|
|
907
|
+
return this.create(tableId, {
|
|
908
|
+
name,
|
|
909
|
+
type: 'rating',
|
|
910
|
+
max_value: maxValue,
|
|
911
|
+
color: color,
|
|
912
|
+
style: style,
|
|
913
|
+
description
|
|
914
|
+
})
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Crear campo de última modificación (fecha)
|
|
919
|
+
*
|
|
920
|
+
* Crea un campo de auditoría que se actualiza automáticamente
|
|
921
|
+
* con la fecha de la última modificación de la fila.
|
|
922
|
+
*
|
|
923
|
+
* @param tableId - ID numérico de la tabla
|
|
924
|
+
* @param name - Nombre del campo
|
|
925
|
+
* @param description - Descripción del campo (opcional)
|
|
926
|
+
* @returns Promise con el campo de última modificación creado
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* ```typescript
|
|
930
|
+
* const updatedField = await fieldService.createLastModifiedField(123, 'actualizado')
|
|
931
|
+
* ```
|
|
932
|
+
*
|
|
933
|
+
* @since 1.0.0
|
|
934
|
+
*/
|
|
935
|
+
async createLastModifiedField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
936
|
+
return this.create(tableId, {
|
|
937
|
+
name,
|
|
938
|
+
type: 'last_modified',
|
|
939
|
+
description
|
|
940
|
+
})
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Crear campo de última modificación por usuario
|
|
945
|
+
*
|
|
946
|
+
* Crea un campo de auditoría que se actualiza automáticamente
|
|
947
|
+
* con el usuario que realizó la última modificación de la fila.
|
|
948
|
+
*
|
|
949
|
+
* @param tableId - ID numérico de la tabla
|
|
950
|
+
* @param name - Nombre del campo
|
|
951
|
+
* @param description - Descripción del campo (opcional)
|
|
952
|
+
* @returns Promise con el campo de última modificación por usuario creado
|
|
953
|
+
*
|
|
954
|
+
* @example
|
|
955
|
+
* ```typescript
|
|
956
|
+
* const modifiedByField = await fieldService.createLastModifiedByField(123, 'modificado_por')
|
|
957
|
+
* ```
|
|
958
|
+
*
|
|
959
|
+
* @since 1.0.0
|
|
960
|
+
*/
|
|
961
|
+
async createLastModifiedByField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
962
|
+
return this.create(tableId, {
|
|
963
|
+
name,
|
|
964
|
+
type: 'last_modified_by',
|
|
965
|
+
description
|
|
966
|
+
})
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Crear campo de fecha de creación
|
|
971
|
+
*
|
|
972
|
+
* Crea un campo de auditoría que se establece automáticamente
|
|
973
|
+
* con la fecha de creación de la fila (no se modifica después).
|
|
974
|
+
*
|
|
975
|
+
* @param tableId - ID numérico de la tabla
|
|
976
|
+
* @param name - Nombre del campo
|
|
977
|
+
* @param description - Descripción del campo (opcional)
|
|
978
|
+
* @returns Promise con el campo de fecha de creación creado
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* ```typescript
|
|
982
|
+
* const createdField = await fieldService.createCreatedOnField(123, 'creado')
|
|
983
|
+
* ```
|
|
984
|
+
*
|
|
985
|
+
* @since 1.0.0
|
|
986
|
+
*/
|
|
987
|
+
async createCreatedOnField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
988
|
+
return this.create(tableId, {
|
|
989
|
+
name,
|
|
990
|
+
type: 'created_on',
|
|
991
|
+
description
|
|
992
|
+
})
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Crear campo de usuario creador
|
|
997
|
+
*
|
|
998
|
+
* Crea un campo de auditoría que se establece automáticamente
|
|
999
|
+
* con el usuario que creó la fila (no se modifica después).
|
|
1000
|
+
*
|
|
1001
|
+
* @param tableId - ID numérico de la tabla
|
|
1002
|
+
* @param name - Nombre del campo
|
|
1003
|
+
* @param description - Descripción del campo (opcional)
|
|
1004
|
+
* @returns Promise con el campo de usuario creador creado
|
|
1005
|
+
*
|
|
1006
|
+
* @example
|
|
1007
|
+
* ```typescript
|
|
1008
|
+
* const createdByField = await fieldService.createCreatedByField(123, 'creado_por')
|
|
1009
|
+
* ```
|
|
1010
|
+
*
|
|
1011
|
+
* @since 1.0.0
|
|
1012
|
+
*/
|
|
1013
|
+
async createCreatedByField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
1014
|
+
return this.create(tableId, {
|
|
1015
|
+
name,
|
|
1016
|
+
type: 'created_by',
|
|
1017
|
+
description
|
|
1018
|
+
})
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Crear campo de archivo
|
|
1023
|
+
*
|
|
1024
|
+
* Crea un campo para subir y almacenar archivos adjuntos.
|
|
1025
|
+
* Soporta múltiples archivos por fila con previsualización automática.
|
|
1026
|
+
*
|
|
1027
|
+
* @param tableId - ID numérico de la tabla
|
|
1028
|
+
* @param name - Nombre del campo
|
|
1029
|
+
* @param description - Descripción del campo (opcional)
|
|
1030
|
+
* @returns Promise con el campo de archivo creado
|
|
1031
|
+
*
|
|
1032
|
+
* @example
|
|
1033
|
+
* ```typescript
|
|
1034
|
+
* const attachmentField = await fieldService.createFileField(123, 'documentos')
|
|
1035
|
+
* const photoField = await fieldService.createFileField(123, 'fotos')
|
|
1036
|
+
* ```
|
|
1037
|
+
*
|
|
1038
|
+
* @since 1.0.0
|
|
1039
|
+
*/
|
|
1040
|
+
async createFileField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
1041
|
+
return this.create(tableId, {
|
|
1042
|
+
name,
|
|
1043
|
+
type: 'file',
|
|
1044
|
+
description
|
|
1045
|
+
})
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Crear campo de numeración automática
|
|
1050
|
+
*
|
|
1051
|
+
* Crea un campo que asigna automáticamente números secuenciales únicos
|
|
1052
|
+
* a cada nueva fila. Útil para IDs de tickets, números de orden, etc.
|
|
1053
|
+
*
|
|
1054
|
+
* @param tableId - ID numérico de la tabla
|
|
1055
|
+
* @param name - Nombre del campo
|
|
1056
|
+
* @param description - Descripción del campo (opcional)
|
|
1057
|
+
* @returns Promise con el campo de numeración automática creado
|
|
1058
|
+
*
|
|
1059
|
+
* @example
|
|
1060
|
+
* ```typescript
|
|
1061
|
+
* const ticketField = await fieldService.createAutonumberField(123, 'ticket_id')
|
|
1062
|
+
* const orderField = await fieldService.createAutonumberField(123, 'numero_orden')
|
|
1063
|
+
* ```
|
|
1064
|
+
*
|
|
1065
|
+
* @since 1.0.0
|
|
1066
|
+
*/
|
|
1067
|
+
async createAutonumberField(tableId: number, name: string, description?: string): Promise<Field> {
|
|
1068
|
+
return this.create(tableId, {
|
|
1069
|
+
name,
|
|
1070
|
+
type: 'autonumber',
|
|
1071
|
+
description
|
|
1072
|
+
})
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Crear campo de conteo
|
|
1077
|
+
*
|
|
1078
|
+
* Crea un campo que cuenta automáticamente el número de registros
|
|
1079
|
+
* relacionados a través de un campo de enlace. Se actualiza automáticamente.
|
|
1080
|
+
*
|
|
1081
|
+
* @param tableId - ID numérico de la tabla
|
|
1082
|
+
* @param name - Nombre del campo
|
|
1083
|
+
* @param throughFieldId - ID del campo de enlace a través del cual contar
|
|
1084
|
+
* @param description - Descripción del campo (opcional)
|
|
1085
|
+
* @returns Promise con el campo de conteo creado
|
|
1086
|
+
*
|
|
1087
|
+
* @throws {BaserowValidationError} Si throughFieldId es inválido
|
|
1088
|
+
*
|
|
1089
|
+
* @example
|
|
1090
|
+
* ```typescript
|
|
1091
|
+
* // Contar número de pedidos por cliente
|
|
1092
|
+
* const orderCountField = await fieldService.createCountField(
|
|
1093
|
+
* customersTableId,
|
|
1094
|
+
* 'total_pedidos',
|
|
1095
|
+
* customerLinkFieldId
|
|
1096
|
+
* )
|
|
1097
|
+
* ```
|
|
1098
|
+
*
|
|
1099
|
+
* @since 1.0.0
|
|
1100
|
+
*/
|
|
1101
|
+
async createCountField(tableId: number, name: string, throughFieldId: number, description?: string): Promise<Field> {
|
|
1102
|
+
this.validationService.validateId(throughFieldId, 'through field ID')
|
|
1103
|
+
|
|
1104
|
+
return this.create(tableId, {
|
|
1105
|
+
name,
|
|
1106
|
+
type: 'count',
|
|
1107
|
+
through_field_id: throughFieldId,
|
|
1108
|
+
description
|
|
1109
|
+
})
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Crear campo de rollup (agregación)
|
|
1114
|
+
*
|
|
1115
|
+
* Crea un campo que agrega valores de registros relacionados usando
|
|
1116
|
+
* funciones como sum, avg, min, max, etc. Se actualiza automáticamente.
|
|
1117
|
+
*
|
|
1118
|
+
* @param tableId - ID numérico de la tabla
|
|
1119
|
+
* @param name - Nombre del campo
|
|
1120
|
+
* @param throughFieldId - ID del campo de enlace para la relación
|
|
1121
|
+
* @param targetFieldId - ID del campo a agregar en la tabla relacionada
|
|
1122
|
+
* @param rollupFunction - Función de agregación (default: 'sum')
|
|
1123
|
+
* @param description - Descripción del campo (opcional)
|
|
1124
|
+
* @returns Promise con el campo de rollup creado
|
|
1125
|
+
*
|
|
1126
|
+
* @throws {BaserowValidationError} Si los IDs son inválidos
|
|
1127
|
+
*
|
|
1128
|
+
* @example
|
|
1129
|
+
* ```typescript
|
|
1130
|
+
* // Sumar total de ventas por cliente
|
|
1131
|
+
* const totalSalesField = await fieldService.createRollupField(
|
|
1132
|
+
* customersTableId,
|
|
1133
|
+
* 'ventas_totales',
|
|
1134
|
+
* customerLinkFieldId,
|
|
1135
|
+
* orderAmountFieldId,
|
|
1136
|
+
* 'sum'
|
|
1137
|
+
* )
|
|
1138
|
+
*
|
|
1139
|
+
* // Calcular promedio de calificaciones
|
|
1140
|
+
* const avgRatingField = await fieldService.createRollupField(
|
|
1141
|
+
* productsTableId,
|
|
1142
|
+
* 'rating_promedio',
|
|
1143
|
+
* productLinkFieldId,
|
|
1144
|
+
* ratingFieldId,
|
|
1145
|
+
* 'avg'
|
|
1146
|
+
* )
|
|
1147
|
+
* ```
|
|
1148
|
+
*
|
|
1149
|
+
* @since 1.0.0
|
|
1150
|
+
*/
|
|
1151
|
+
async createRollupField(
|
|
1152
|
+
tableId: number,
|
|
1153
|
+
name: string,
|
|
1154
|
+
throughFieldId: number,
|
|
1155
|
+
targetFieldId: number,
|
|
1156
|
+
rollupFunction: string = 'sum',
|
|
1157
|
+
description?: string
|
|
1158
|
+
): Promise<Field> {
|
|
1159
|
+
this.validationService.validateId(throughFieldId, 'through field ID')
|
|
1160
|
+
this.validationService.validateId(targetFieldId, 'target field ID')
|
|
1161
|
+
|
|
1162
|
+
return this.create(tableId, {
|
|
1163
|
+
name,
|
|
1164
|
+
type: 'rollup',
|
|
1165
|
+
through_field_id: throughFieldId,
|
|
1166
|
+
target_field_id: targetFieldId,
|
|
1167
|
+
rollup_function: rollupFunction,
|
|
1168
|
+
description
|
|
1169
|
+
})
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Crear campo de lookup (búsqueda)
|
|
1174
|
+
*
|
|
1175
|
+
* Crea un campo que muestra valores de un campo en registros relacionados.
|
|
1176
|
+
* Similar a un VLOOKUP en spreadsheets. Se actualiza automáticamente.
|
|
1177
|
+
*
|
|
1178
|
+
* @param tableId - ID numérico de la tabla
|
|
1179
|
+
* @param name - Nombre del campo
|
|
1180
|
+
* @param throughFieldId - ID del campo de enlace para la relación
|
|
1181
|
+
* @param targetFieldId - ID del campo a mostrar en la tabla relacionada
|
|
1182
|
+
* @param description - Descripción del campo (opcional)
|
|
1183
|
+
* @returns Promise con el campo de lookup creado
|
|
1184
|
+
*
|
|
1185
|
+
* @throws {BaserowValidationError} Si los IDs son inválidos
|
|
1186
|
+
*
|
|
1187
|
+
* @example
|
|
1188
|
+
* ```typescript
|
|
1189
|
+
* // Mostrar nombre del cliente en tabla de pedidos
|
|
1190
|
+
* const customerNameField = await fieldService.createLookupField(
|
|
1191
|
+
* ordersTableId,
|
|
1192
|
+
* 'nombre_cliente',
|
|
1193
|
+
* customerLinkFieldId,
|
|
1194
|
+
* customerNameFieldId
|
|
1195
|
+
* )
|
|
1196
|
+
*
|
|
1197
|
+
* // Mostrar categoría del producto en pedidos
|
|
1198
|
+
* const productCategoryField = await fieldService.createLookupField(
|
|
1199
|
+
* ordersTableId,
|
|
1200
|
+
* 'categoria_producto',
|
|
1201
|
+
* productLinkFieldId,
|
|
1202
|
+
* categoryFieldId
|
|
1203
|
+
* )
|
|
1204
|
+
* ```
|
|
1205
|
+
*
|
|
1206
|
+
* @since 1.0.0
|
|
1207
|
+
*/
|
|
1208
|
+
async createLookupField(
|
|
1209
|
+
tableId: number,
|
|
1210
|
+
name: string,
|
|
1211
|
+
throughFieldId: number,
|
|
1212
|
+
targetFieldId: number,
|
|
1213
|
+
description?: string
|
|
1214
|
+
): Promise<Field> {
|
|
1215
|
+
this.validationService.validateId(throughFieldId, 'through field ID')
|
|
1216
|
+
this.validationService.validateId(targetFieldId, 'target field ID')
|
|
1217
|
+
|
|
1218
|
+
return this.create(tableId, {
|
|
1219
|
+
name,
|
|
1220
|
+
type: 'lookup',
|
|
1221
|
+
through_field_id: throughFieldId,
|
|
1222
|
+
target_field_id: targetFieldId,
|
|
1223
|
+
description
|
|
1224
|
+
})
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Verificar si un campo existe
|
|
1229
|
+
*
|
|
1230
|
+
* Verifica la existencia de un campo sin cargar todos sus metadatos.
|
|
1231
|
+
* Útil para validaciones antes de operaciones.
|
|
1232
|
+
*
|
|
1233
|
+
* @param fieldId - ID numérico del campo
|
|
1234
|
+
* @returns Promise que resuelve a true si existe, false si no
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```typescript
|
|
1238
|
+
* const exists = await fieldService.exists(456)
|
|
1239
|
+
* if (exists) {
|
|
1240
|
+
* console.log('El campo existe')
|
|
1241
|
+
* } else {
|
|
1242
|
+
* console.log('El campo no existe')
|
|
1243
|
+
* }
|
|
1244
|
+
* ```
|
|
1245
|
+
*
|
|
1246
|
+
* @since 1.0.0
|
|
1247
|
+
*/
|
|
1248
|
+
async exists(fieldId: number): Promise<boolean> {
|
|
1249
|
+
try {
|
|
1250
|
+
await this.get(fieldId)
|
|
1251
|
+
return true
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
if (error instanceof BaserowNotFoundError) {
|
|
1254
|
+
return false
|
|
1255
|
+
}
|
|
1256
|
+
throw error
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// ===== GESTIÓN DE ÍNDICES Y CONSTRAINTS (v1.35+) =====
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Configurar índice en campo para mejorar performance
|
|
1264
|
+
*
|
|
1265
|
+
* Habilita o deshabilita el índice en un campo para acelerar
|
|
1266
|
+
* las operaciones de filtrado hasta 10x más rápido.
|
|
1267
|
+
*
|
|
1268
|
+
* @param fieldId - ID numérico del campo
|
|
1269
|
+
* @param enabled - true para habilitar índice, false para deshabilitar
|
|
1270
|
+
* @returns Promise con el campo actualizado
|
|
1271
|
+
*
|
|
1272
|
+
* @throws {BaserowNotFoundError} Si el campo no existe
|
|
1273
|
+
* @throws {BaserowValidationError} Si el fieldId es inválido
|
|
1274
|
+
*
|
|
1275
|
+
* @example
|
|
1276
|
+
* ```typescript
|
|
1277
|
+
* // Habilitar índice para mejorar filtros en campo email
|
|
1278
|
+
* const field = await fieldService.setFieldIndex(emailFieldId, true)
|
|
1279
|
+
* console.log(`Índice ${field.index ? 'habilitado' : 'deshabilitado'}`)
|
|
1280
|
+
*
|
|
1281
|
+
* // Deshabilitar índice
|
|
1282
|
+
* await fieldService.setFieldIndex(fieldId, false)
|
|
1283
|
+
* ```
|
|
1284
|
+
*
|
|
1285
|
+
* @since 1.1.0
|
|
1286
|
+
*/
|
|
1287
|
+
async setFieldIndex(fieldId: number, enabled: boolean): Promise<Field> {
|
|
1288
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
1289
|
+
|
|
1290
|
+
try {
|
|
1291
|
+
this.logDebug(`${enabled ? 'Enabling' : 'Disabling'} index for field ${fieldId}`)
|
|
1292
|
+
const response = await this.http.patch<Field>(`/database/fields/${fieldId}/`, {
|
|
1293
|
+
index: enabled
|
|
1294
|
+
})
|
|
1295
|
+
this.logSuccess(`set field index`, fieldId, { enabled })
|
|
1296
|
+
return response
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
if ((error as any).status === 404) {
|
|
1299
|
+
throw new BaserowNotFoundError('Field', fieldId)
|
|
1300
|
+
}
|
|
1301
|
+
this.handleHttpError(error, 'set field index', fieldId)
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Agregar restricción de valor a campo
|
|
1307
|
+
*
|
|
1308
|
+
* Agrega una nueva restricción de integridad al campo para
|
|
1309
|
+
* garantizar calidad de datos (ej: valores únicos).
|
|
1310
|
+
*
|
|
1311
|
+
* @param fieldId - ID numérico del campo
|
|
1312
|
+
* @param constraint - Configuración de la restricción
|
|
1313
|
+
* @returns Promise con el campo actualizado
|
|
1314
|
+
*
|
|
1315
|
+
* @throws {BaserowNotFoundError} Si el campo no existe
|
|
1316
|
+
* @throws {BaserowValidationError} Si los parámetros son inválidos
|
|
1317
|
+
*
|
|
1318
|
+
* @example
|
|
1319
|
+
* ```typescript
|
|
1320
|
+
* // Agregar restricción de unicidad (excluyendo vacíos)
|
|
1321
|
+
* const field = await fieldService.addFieldConstraint(emailFieldId, {
|
|
1322
|
+
* type: 'unique',
|
|
1323
|
+
* active: true
|
|
1324
|
+
* })
|
|
1325
|
+
*
|
|
1326
|
+
* // Agregar restricción de unicidad (incluyendo vacíos)
|
|
1327
|
+
* await fieldService.addFieldConstraint(codeFieldId, {
|
|
1328
|
+
* type: 'unique_with_empty',
|
|
1329
|
+
* active: true
|
|
1330
|
+
* })
|
|
1331
|
+
* ```
|
|
1332
|
+
*
|
|
1333
|
+
* @since 1.1.0
|
|
1334
|
+
*/
|
|
1335
|
+
async addFieldConstraint(fieldId: number, constraint: FieldConstraint): Promise<Field> {
|
|
1336
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
1337
|
+
|
|
1338
|
+
if (!constraint.type || typeof constraint.active !== 'boolean') {
|
|
1339
|
+
throw new BaserowValidationError('Invalid constraint configuration', {
|
|
1340
|
+
constraint: ['type and active are required']
|
|
1341
|
+
})
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Obtener constraints existentes
|
|
1345
|
+
const currentField = await this.get(fieldId)
|
|
1346
|
+
const existingConstraints = currentField.constraints || []
|
|
1347
|
+
|
|
1348
|
+
// Verificar si ya existe una constraint del mismo tipo
|
|
1349
|
+
const existingIndex = existingConstraints.findIndex(c => c.type === constraint.type)
|
|
1350
|
+
let updatedConstraints: FieldConstraint[]
|
|
1351
|
+
|
|
1352
|
+
if (existingIndex >= 0) {
|
|
1353
|
+
// Actualizar constraint existente
|
|
1354
|
+
updatedConstraints = [...existingConstraints]
|
|
1355
|
+
updatedConstraints[existingIndex] = constraint
|
|
1356
|
+
} else {
|
|
1357
|
+
// Agregar nueva constraint
|
|
1358
|
+
updatedConstraints = [...existingConstraints, constraint]
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
try {
|
|
1362
|
+
this.logDebug(`Adding constraint ${constraint.type} to field ${fieldId}`, { constraint })
|
|
1363
|
+
const response = await this.http.patch<Field>(`/database/fields/${fieldId}/`, {
|
|
1364
|
+
constraints: updatedConstraints
|
|
1365
|
+
})
|
|
1366
|
+
this.logSuccess('add field constraint', fieldId, { type: constraint.type })
|
|
1367
|
+
return response
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
if ((error as any).status === 404) {
|
|
1370
|
+
throw new BaserowNotFoundError('Field', fieldId)
|
|
1371
|
+
}
|
|
1372
|
+
this.handleHttpError(error, 'add field constraint', fieldId)
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Eliminar restricción de valor de campo
|
|
1378
|
+
*
|
|
1379
|
+
* Elimina una restricción específica del campo por tipo.
|
|
1380
|
+
*
|
|
1381
|
+
* @param fieldId - ID numérico del campo
|
|
1382
|
+
* @param constraintType - Tipo de restricción a eliminar
|
|
1383
|
+
* @returns Promise con el campo actualizado
|
|
1384
|
+
*
|
|
1385
|
+
* @throws {BaserowNotFoundError} Si el campo no existe
|
|
1386
|
+
* @throws {BaserowValidationError} Si el fieldId es inválido
|
|
1387
|
+
*
|
|
1388
|
+
* @example
|
|
1389
|
+
* ```typescript
|
|
1390
|
+
* // Eliminar restricción de unicidad
|
|
1391
|
+
* const field = await fieldService.removeFieldConstraint(emailFieldId, 'unique')
|
|
1392
|
+
* console.log(`Constraints restantes: ${field.constraints?.length || 0}`)
|
|
1393
|
+
* ```
|
|
1394
|
+
*
|
|
1395
|
+
* @since 1.1.0
|
|
1396
|
+
*/
|
|
1397
|
+
async removeFieldConstraint(fieldId: number, constraintType: ConstraintType): Promise<Field> {
|
|
1398
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
1399
|
+
|
|
1400
|
+
// Obtener constraints existentes
|
|
1401
|
+
const currentField = await this.get(fieldId)
|
|
1402
|
+
const existingConstraints = currentField.constraints || []
|
|
1403
|
+
|
|
1404
|
+
// Filtrar constraint a eliminar
|
|
1405
|
+
const updatedConstraints = existingConstraints.filter(c => c.type !== constraintType)
|
|
1406
|
+
|
|
1407
|
+
try {
|
|
1408
|
+
this.logDebug(`Removing constraint ${constraintType} from field ${fieldId}`)
|
|
1409
|
+
const response = await this.http.patch<Field>(`/database/fields/${fieldId}/`, {
|
|
1410
|
+
constraints: updatedConstraints
|
|
1411
|
+
})
|
|
1412
|
+
this.logSuccess('remove field constraint', fieldId, { type: constraintType })
|
|
1413
|
+
return response
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
if ((error as any).status === 404) {
|
|
1416
|
+
throw new BaserowNotFoundError('Field', fieldId)
|
|
1417
|
+
}
|
|
1418
|
+
this.handleHttpError(error, 'remove field constraint', fieldId)
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Obtener restricciones de campo
|
|
1424
|
+
*
|
|
1425
|
+
* Recupera todas las restricciones activas de un campo específico.
|
|
1426
|
+
*
|
|
1427
|
+
* @param fieldId - ID numérico del campo
|
|
1428
|
+
* @returns Promise con array de restricciones del campo
|
|
1429
|
+
*
|
|
1430
|
+
* @throws {BaserowNotFoundError} Si el campo no existe
|
|
1431
|
+
* @throws {BaserowValidationError} Si el fieldId es inválido
|
|
1432
|
+
*
|
|
1433
|
+
* @example
|
|
1434
|
+
* ```typescript
|
|
1435
|
+
* const constraints = await fieldService.getFieldConstraints(emailFieldId)
|
|
1436
|
+
* constraints.forEach(constraint => {
|
|
1437
|
+
* console.log(`Constraint ${constraint.type}: ${constraint.active ? 'activa' : 'inactiva'}`)
|
|
1438
|
+
* })
|
|
1439
|
+
* ```
|
|
1440
|
+
*
|
|
1441
|
+
* @since 1.1.0
|
|
1442
|
+
*/
|
|
1443
|
+
async getFieldConstraints(fieldId: number): Promise<FieldConstraint[]> {
|
|
1444
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
1445
|
+
|
|
1446
|
+
const field = await this.get(fieldId)
|
|
1447
|
+
return field.constraints || []
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* Eliminar todas las restricciones de un campo
|
|
1452
|
+
*
|
|
1453
|
+
* Remueve completamente todas las constraints configuradas en un campo específico,
|
|
1454
|
+
* dejándolo sin ninguna restricción de integridad. Esta operación es útil para
|
|
1455
|
+
* limpiar campos que han acumulado múltiples constraints o para resetear
|
|
1456
|
+
* la configuración de restricciones.
|
|
1457
|
+
*
|
|
1458
|
+
* @param fieldId - ID del campo del cual eliminar todas las constraints
|
|
1459
|
+
* @returns Promise con el campo actualizado sin constraints
|
|
1460
|
+
*
|
|
1461
|
+
* @throws {BaserowValidationError} Si fieldId es inválido
|
|
1462
|
+
* @throws {BaserowNotFoundError} Si el campo no existe
|
|
1463
|
+
* @throws {BaserowError} Si hay error en la comunicación con la API
|
|
1464
|
+
*
|
|
1465
|
+
* @example
|
|
1466
|
+
* ```typescript
|
|
1467
|
+
* // Eliminar todas las constraints de un campo
|
|
1468
|
+
* const field = await fieldService.removeAllFieldConstraints(emailFieldId)
|
|
1469
|
+
* console.log(`Campo "${field.name}" ahora sin constraints`)
|
|
1470
|
+
*
|
|
1471
|
+
* // Verificar que se eliminaron todas
|
|
1472
|
+
* const remainingConstraints = await fieldService.getFieldConstraints(emailFieldId)
|
|
1473
|
+
* console.log(`Constraints restantes: ${remainingConstraints.length}`) // 0
|
|
1474
|
+
* ```
|
|
1475
|
+
*
|
|
1476
|
+
* @since 1.1.0
|
|
1477
|
+
*/
|
|
1478
|
+
async removeAllFieldConstraints(fieldId: number): Promise<Field> {
|
|
1479
|
+
this.validationService.validateId(fieldId, 'field ID')
|
|
1480
|
+
|
|
1481
|
+
try {
|
|
1482
|
+
this.logDebug(`Removing all constraints from field ${fieldId}`)
|
|
1483
|
+
|
|
1484
|
+
const updatedField = await this.updateFieldInternal(fieldId, {
|
|
1485
|
+
constraints: []
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1488
|
+
this.logSuccess('remove all field constraints', fieldId, { removedAll: true })
|
|
1489
|
+
return updatedField
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
this.handleHttpError(error, 'remove all field constraints', fieldId)
|
|
1492
|
+
throw error
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Aplicar opciones avanzadas a solicitud de creación de campo
|
|
1498
|
+
*
|
|
1499
|
+
* Método helper interno que aplica índices y constraints a la configuración
|
|
1500
|
+
* de creación de campo.
|
|
1501
|
+
*
|
|
1502
|
+
* @param baseRequest - Solicitud base de creación
|
|
1503
|
+
* @param advancedOptions - Opciones avanzadas (índice y constraints)
|
|
1504
|
+
* @returns Solicitud enriquecida con opciones avanzadas
|
|
1505
|
+
*
|
|
1506
|
+
* @private
|
|
1507
|
+
* @since 1.1.0
|
|
1508
|
+
*/
|
|
1509
|
+
private applyAdvancedOptions(
|
|
1510
|
+
baseRequest: CreateFieldRequest,
|
|
1511
|
+
advancedOptions?: FieldAdvancedOptions
|
|
1512
|
+
): CreateFieldRequest {
|
|
1513
|
+
if (!advancedOptions) return baseRequest
|
|
1514
|
+
|
|
1515
|
+
const enrichedRequest = { ...baseRequest }
|
|
1516
|
+
|
|
1517
|
+
if (advancedOptions.index !== undefined) {
|
|
1518
|
+
enrichedRequest.index = advancedOptions.index
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
if (advancedOptions.constraints && advancedOptions.constraints.length > 0) {
|
|
1522
|
+
enrichedRequest.constraints = advancedOptions.constraints
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
return enrichedRequest
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// ===== FRIEND ACCESS PATTERN FOR FIELD CONTEXT =====
|
|
1529
|
+
// Symbol-based access que no aparece en la API pública pero permite acceso interno
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Friend access para FieldContext
|
|
1533
|
+
* No aparece en intellisense normal ni en la API pública
|
|
1534
|
+
* @internal
|
|
1535
|
+
*/
|
|
1536
|
+
get [Symbol.for('fieldContext')]() {
|
|
1537
|
+
return {
|
|
1538
|
+
createField: this.createFieldInternal.bind(this),
|
|
1539
|
+
updateField: this.updateFieldInternal.bind(this),
|
|
1540
|
+
deleteField: this.deleteFieldInternal.bind(this)
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|