@gzl10/baserow 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +435 -0
  2. package/README.md +847 -0
  3. package/dist/index.d.ts +8749 -0
  4. package/dist/index.js +11167 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +91 -0
  7. package/src/BaserowClient.ts +501 -0
  8. package/src/ClientWithCreds.ts +545 -0
  9. package/src/ClientWithCredsWs.ts +852 -0
  10. package/src/ClientWithToken.ts +171 -0
  11. package/src/contexts/DatabaseClientContext.ts +114 -0
  12. package/src/contexts/DatabaseContext.ts +870 -0
  13. package/src/contexts/DatabaseTokenContext.ts +331 -0
  14. package/src/contexts/FieldContext.ts +399 -0
  15. package/src/contexts/RowContext.ts +99 -0
  16. package/src/contexts/TableClientContext.ts +291 -0
  17. package/src/contexts/TableContext.ts +1247 -0
  18. package/src/contexts/TableOnlyContext.ts +74 -0
  19. package/src/contexts/WorkspaceContext.ts +490 -0
  20. package/src/express/errors.ts +260 -0
  21. package/src/express/index.ts +69 -0
  22. package/src/express/middleware.ts +225 -0
  23. package/src/express/serializers.ts +314 -0
  24. package/src/index.ts +247 -0
  25. package/src/presets/performance.ts +262 -0
  26. package/src/services/AuthService.ts +472 -0
  27. package/src/services/DatabaseService.ts +246 -0
  28. package/src/services/DatabaseTokenService.ts +186 -0
  29. package/src/services/FieldService.ts +1543 -0
  30. package/src/services/RowService.ts +982 -0
  31. package/src/services/SchemaControlService.ts +420 -0
  32. package/src/services/TableService.ts +781 -0
  33. package/src/services/WorkspaceService.ts +113 -0
  34. package/src/services/core/BaseAuthClient.ts +111 -0
  35. package/src/services/core/BaseClient.ts +107 -0
  36. package/src/services/core/BaseService.ts +71 -0
  37. package/src/services/core/HttpService.ts +115 -0
  38. package/src/services/core/ValidationService.ts +149 -0
  39. package/src/types/auth.ts +177 -0
  40. package/src/types/core.ts +91 -0
  41. package/src/types/errors.ts +105 -0
  42. package/src/types/fields.ts +456 -0
  43. package/src/types/index.ts +222 -0
  44. package/src/types/requests.ts +333 -0
  45. package/src/types/responses.ts +50 -0
  46. package/src/types/schema.ts +446 -0
  47. package/src/types/tokens.ts +36 -0
  48. package/src/types.ts +11 -0
  49. package/src/utils/auth.ts +174 -0
  50. package/src/utils/axios.ts +647 -0
  51. package/src/utils/field-cache.ts +164 -0
  52. package/src/utils/httpFactory.ts +66 -0
  53. package/src/utils/jwt-decoder.ts +188 -0
  54. package/src/utils/jwtTokens.ts +50 -0
  55. package/src/utils/performance.ts +105 -0
  56. package/src/utils/prisma-mapper.ts +961 -0
  57. package/src/utils/validation.ts +463 -0
  58. package/src/validators/schema.ts +419 -0
@@ -0,0 +1,74 @@
1
+ import { Logger } from '../types'
2
+ import { TableClientContext } from './TableClientContext'
3
+ import { RowOnlyContext } from './RowContext'
4
+ import { TableService } from '../services/TableService'
5
+ import { FieldService } from '../services/FieldService'
6
+ import { RowService } from '../services/RowService'
7
+
8
+ /**
9
+ * Context para acceso directo a una tabla específica con Database Token
10
+ *
11
+ * A diferencia de TableContext que requiere database context, este contexto
12
+ * permite acceso directo a una tabla específica usando solo su ID.
13
+ * Diseñado para Database Tokens que ya conocen los IDs de tabla específicos.
14
+ */
15
+ export class TableOnlyContext {
16
+ private tableId: number
17
+ private logger?: Logger
18
+
19
+ constructor(
20
+ tableId: number,
21
+ private tableService: TableService,
22
+ private fieldService: FieldService,
23
+ private rowService: RowService,
24
+ logger?: Logger
25
+ ) {
26
+ this.tableId = tableId
27
+ this.logger = logger
28
+ }
29
+
30
+ /**
31
+ * Obtener información de la tabla
32
+ */
33
+ async get() {
34
+ return await this.tableService.get(this.tableId)
35
+ }
36
+
37
+ /**
38
+ * Verificar si la tabla existe
39
+ */
40
+ async exists(): Promise<boolean> {
41
+ return await this.tableService.exists(this.tableId)
42
+ }
43
+
44
+ /**
45
+ * Acceso a operaciones de filas de esta tabla
46
+ */
47
+ get rows() {
48
+ return new TableClientContext(
49
+ 0, // databaseId no se usa en operaciones de rows con Database Token
50
+ this.tableId,
51
+ this.tableService,
52
+ this.fieldService,
53
+ this.rowService,
54
+ this.logger
55
+ ).rows
56
+ }
57
+
58
+ /**
59
+ * Acceso a una fila específica por ID
60
+ *
61
+ * @param rowId - ID de la fila
62
+ * @returns Contexto de fila individual para operaciones CRUD
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const row = await client.table(123).row(456).get()
67
+ * await client.table(123).row(456).update({ name: 'Nuevo nombre' })
68
+ * await client.table(123).row(456).delete()
69
+ * ```
70
+ */
71
+ row(rowId: number): RowOnlyContext {
72
+ return new RowOnlyContext(this.tableId, rowId, this.rowService, this.logger)
73
+ }
74
+ }
@@ -0,0 +1,490 @@
1
+ import { Logger, Workspace, BaserowNotFoundError, Database } from '../types'
2
+ import { DatabaseContext } from './DatabaseContext'
3
+ import { DatabaseTokenContext } from './DatabaseTokenContext'
4
+ import { WorkspaceService, UpdateWorkspaceRequest } from '../services/WorkspaceService'
5
+ import { DatabaseService } from '../services/DatabaseService'
6
+ import { DatabaseTokenService } from '../services/DatabaseTokenService'
7
+ import { TableService } from '../services/TableService'
8
+ import { FieldService } from '../services/FieldService'
9
+ import { RowService } from '../services/RowService'
10
+
11
+ /**
12
+ * Context para operaciones en un workspace específico
13
+ *
14
+ * Proporciona una API jerárquica fluida para operaciones administrativas
15
+ * dentro de un workspace específico. Permite el acceso encadenado
16
+ * workspace → database → table → field/rows y operaciones CRUD completas.
17
+ *
18
+ * **Características principales:**
19
+ * - Resolución automática de workspace por nombre o ID (lazy loading)
20
+ * - API fluida para operaciones de databases: list, find, create
21
+ * - Operaciones CRUD del workspace: update, delete
22
+ * - Acceso directo a database contexts específicos
23
+ * - Gestión de database tokens del workspace
24
+ * - Cache interno para evitar resoluciones repetidas
25
+ * - Logging opcional de todas las operaciones
26
+ *
27
+ * **Patrón de API Jerárquica:**
28
+ * ```
29
+ * workspace.databases.list() // Operaciones masivas
30
+ * workspace.database('name') // Context específico
31
+ * workspace.databaseToken // Database tokens
32
+ * workspace.update({ name: 'nuevo' }) // Actualizar workspace
33
+ * workspace.delete() // Eliminar workspace
34
+ * ```
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // Gestión del workspace
39
+ * const updated = await workspace.update({ name: 'Nuevo Nombre' })
40
+ * await workspace.delete() // Elimina workspace y todo su contenido
41
+ *
42
+ * // Operaciones básicas de databases
43
+ * const databases = await workspace.databases.list()
44
+ * const newDb = await workspace.databases.create('Nueva Database')
45
+ * const found = await workspace.databases.find('Existente')
46
+ *
47
+ * // Acceso jerárquico a database
48
+ * const dbContext = workspace.database('Mi Database')
49
+ * const table = await dbContext.tables.create({ name: 'Mi Tabla' })
50
+ *
51
+ * // Gestión de database tokens
52
+ * const tokens = await workspace.databaseToken.list()
53
+ * const token = await workspace.databaseToken.create('read', ['table1'])
54
+ * ```
55
+ *
56
+ * @since 1.0.0
57
+ */
58
+ export class WorkspaceContext {
59
+ private workspaceIdentifier: string | number
60
+ private resolvedWorkspace?: Workspace
61
+ private logger?: Logger
62
+
63
+ /**
64
+ * Crea un nuevo context de workspace
65
+ *
66
+ * El constructor es interno y no debe ser llamado directamente.
67
+ * Usar `admin.workspace(nameOrId)` para crear contextos de workspace.
68
+ *
69
+ * @param workspaceService - Servicio para operaciones de workspace (CRUD completo)
70
+ * @param databaseService - Servicio para operaciones de database
71
+ * @param databaseTokenService - Servicio para gestión de tokens
72
+ * @param tableService - Servicio para operaciones de tabla
73
+ * @param fieldService - Servicio para operaciones de campos
74
+ * @param rowService - Servicio para operaciones de filas
75
+ * @param workspaceIdentifier - Nombre (string) o ID (number) del workspace
76
+ * @param logger - Logger opcional para debug y trazabilidad
77
+ *
78
+ * @example Uso típico del contexto
79
+ * ```typescript
80
+ * // Creación del contexto (interno)
81
+ * const workspaceContext = admin.workspace('Mi Empresa')
82
+ *
83
+ * // Operaciones CRUD del workspace
84
+ * const updated = await workspaceContext.update({ name: 'Nueva Empresa' })
85
+ * await workspaceContext.delete()
86
+ *
87
+ * // Operaciones de databases
88
+ * const databases = await workspaceContext.databases.list()
89
+ * const dbContext = workspaceContext.database('CRM')
90
+ * ```
91
+ *
92
+ * @internal
93
+ * @since 1.0.0
94
+ */
95
+ constructor(
96
+ private workspaceService: WorkspaceService,
97
+ private databaseService: DatabaseService,
98
+ private databaseTokenService: DatabaseTokenService,
99
+ private tableService: TableService,
100
+ private fieldService: FieldService,
101
+ private rowService: RowService,
102
+ workspaceIdentifier: string | number,
103
+ logger?: Logger
104
+ ) {
105
+ this.workspaceIdentifier = workspaceIdentifier
106
+ this.logger = logger
107
+ }
108
+
109
+ /**
110
+ * Operaciones masivas de databases en este workspace
111
+ *
112
+ * Proporciona métodos para gestionar databases de forma masiva:
113
+ * listar todas, buscar por nombre y crear nuevas databases.
114
+ * Todas las operaciones están restringidas al workspace actual.
115
+ *
116
+ * @returns Objeto con métodos para operaciones de databases
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // Listar todas las databases del workspace
121
+ * const databases = await workspace.databases.list()
122
+ *
123
+ * // Buscar database específica
124
+ * const db = await workspace.databases.find('Mi Database')
125
+ *
126
+ * // Crear nueva database
127
+ * const newDb = await workspace.databases.create('Nueva Database')
128
+ * ```
129
+ *
130
+ * @since 1.0.0
131
+ */
132
+ get databases() {
133
+ return {
134
+ /**
135
+ * Listar todas las databases del workspace
136
+ *
137
+ * Obtiene todas las databases que pertenecen a este workspace.
138
+ * Filtra automáticamente las databases globales para mostrar
139
+ * solo las del workspace actual.
140
+ *
141
+ * @returns Promise con array de databases del workspace
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * const databases = await workspace.databases.list()
146
+ * console.log(`Encontradas ${databases.length} databases`)
147
+ * databases.forEach(db => {
148
+ * console.log(`${db.name} (ID: ${db.id})`)
149
+ * })
150
+ * ```
151
+ *
152
+ * @since 1.0.0
153
+ */
154
+ list: async (): Promise<Database[]> => {
155
+ const workspace = await this.get()
156
+ const allDatabases = await this.databaseService.findMany()
157
+ return allDatabases.filter(db => db.workspace?.id === workspace.id)
158
+ },
159
+
160
+ /**
161
+ * Buscar database por nombre en este workspace
162
+ *
163
+ * Busca una database específica por nombre dentro del workspace.
164
+ * Útil para operaciones donde se conoce el nombre pero no el ID.
165
+ *
166
+ * @param name - Nombre exacto de la database a buscar
167
+ * @returns Promise con la database encontrada o null si no existe
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const db = await workspace.databases.find('CRM')
172
+ * if (db) {
173
+ * console.log(`Database encontrada: ${db.id}`)
174
+ * } else {
175
+ * console.log('Database no encontrada')
176
+ * }
177
+ * ```
178
+ *
179
+ * @since 1.0.0
180
+ */
181
+ find: async (name: string): Promise<Database | null> => {
182
+ const databases = await this.databases.list()
183
+ return databases.find(db => db.name === name) || null
184
+ },
185
+
186
+ /**
187
+ * Crear nueva database en este workspace
188
+ *
189
+ * Crea una nueva database vacía en este workspace.
190
+ * La database se crea sin tablas iniciales.
191
+ *
192
+ * @param name - Nombre de la nueva database
193
+ * @returns Promise con la database creada
194
+ *
195
+ * @throws {BaserowValidationError} Si el nombre es inválido
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const newDb = await workspace.databases.create('Nueva Database')
200
+ * console.log(`Database creada: ${newDb.id} - ${newDb.name}`)
201
+ * ```
202
+ *
203
+ * @since 1.0.0
204
+ */
205
+ create: async (name: string): Promise<Database> => {
206
+ const workspace = await this.get()
207
+ return await this.databaseService.create(workspace.id, name)
208
+ }
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Acceder a una base de datos específica en este workspace
214
+ *
215
+ * Crea un context para operaciones específicas en una database.
216
+ * Permite acceso jerárquico a tablas, campos y filas de la database.
217
+ * El identificador puede ser nombre (string) o ID (number).
218
+ *
219
+ * @param databaseIdentifier - Nombre o ID numérico de la database
220
+ * @returns Context de database para operaciones específicas
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * // Acceso por nombre
225
+ * const dbContext = workspace.database('Mi Database')
226
+ *
227
+ * // Acceso por ID
228
+ * const dbContext2 = workspace.database(123)
229
+ *
230
+ * // Operaciones jerárquicas
231
+ * const tables = await dbContext.tables.findMany()
232
+ * const table = await dbContext.tables.create({ name: 'Nueva Tabla' })
233
+ * ```
234
+ *
235
+ * @since 1.0.0
236
+ */
237
+ database(databaseIdentifier: string | number): DatabaseContext {
238
+ return new DatabaseContext(
239
+ this.workspaceService,
240
+ this.workspaceIdentifier,
241
+ databaseIdentifier,
242
+ this.databaseService,
243
+ this.tableService,
244
+ this.fieldService,
245
+ this.rowService,
246
+ this.logger
247
+ )
248
+ }
249
+
250
+ /**
251
+ * Operaciones de database tokens en este workspace
252
+ *
253
+ * Proporciona acceso al context de database tokens para gestionar
254
+ * tokens de acceso específicos del workspace. Los database tokens
255
+ * permiten acceso limitado a databases y tablas específicas.
256
+ *
257
+ * @returns Context de database tokens
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * // Listar tokens existentes
262
+ * const tokens = await workspace.databaseToken.list()
263
+ *
264
+ * // Crear token de solo lectura
265
+ * const readToken = await workspace.databaseToken.create(
266
+ * 'read',
267
+ * ['table1', 'table2']
268
+ * )
269
+ *
270
+ * // Crear token de escritura
271
+ * const writeToken = await workspace.databaseToken.create(
272
+ * 'create',
273
+ * ['table1']
274
+ * )
275
+ * ```
276
+ *
277
+ * @since 1.0.0
278
+ */
279
+ get databaseToken(): DatabaseTokenContext {
280
+ return new DatabaseTokenContext(
281
+ this.workspaceService,
282
+ this.workspaceIdentifier,
283
+ this.databaseTokenService,
284
+ this.logger
285
+ )
286
+ }
287
+
288
+ /**
289
+ * Actualizar este workspace
290
+ *
291
+ * Actualiza las propiedades del workspace actual mediante API jerárquica.
292
+ * El cache interno se actualiza automáticamente con los nuevos datos.
293
+ * Soporta identificación por nombre o ID del workspace.
294
+ *
295
+ * @param data - Datos para actualizar el workspace
296
+ * @param data.name - Nuevo nombre del workspace (opcional)
297
+ * @returns Promise con el workspace actualizado con los nuevos datos
298
+ *
299
+ * @throws {BaserowNotFoundError} Si el workspace no existe o no se puede encontrar
300
+ * @throws {BaserowValidationError} Si los datos proporcionados son inválidos
301
+ *
302
+ * @example Actualización por nombre
303
+ * ```typescript
304
+ * const updated = await admin.workspace('Mi Empresa').update({
305
+ * name: 'Nueva Empresa S.A.'
306
+ * })
307
+ * console.log(`Workspace actualizado: ${updated.name}`)
308
+ * ```
309
+ *
310
+ * @example Actualización por ID
311
+ * ```typescript
312
+ * const updated = await admin.workspace(123).update({
313
+ * name: 'Empresa Actualizada'
314
+ * })
315
+ * console.log(`ID: ${updated.id}, Nombre: ${updated.name}`)
316
+ * ```
317
+ *
318
+ * @since 1.0.0
319
+ */
320
+ async update(data: UpdateWorkspaceRequest): Promise<Workspace> {
321
+ const workspace = await this.get()
322
+ const contextAccess = (this.workspaceService as any)[Symbol.for('workspaceContext')]
323
+ const updatedWorkspace = await contextAccess.updateWorkspace(workspace.id, data)
324
+
325
+ // Actualizar cache interno
326
+ this.resolvedWorkspace = updatedWorkspace
327
+
328
+ if (this.logger) {
329
+ this.logger.info(`Workspace updated: "${updatedWorkspace.name}" (ID: ${updatedWorkspace.id})`)
330
+ }
331
+
332
+ return updatedWorkspace
333
+ }
334
+
335
+ /**
336
+ * Eliminar este workspace
337
+ *
338
+ * Elimina permanentemente el workspace y todas sus databases, tablas y datos asociados.
339
+ * Esta operación es **irreversible** y limpia automáticamente el cache interno.
340
+ * Soporta identificación por nombre o ID del workspace.
341
+ *
342
+ * ⚠️ **ADVERTENCIA**: Esta operación elimina todo el contenido del workspace:
343
+ * - Todas las databases del workspace
344
+ * - Todas las tablas y campos de esas databases
345
+ * - Todos los datos (filas) almacenados
346
+ * - Todos los database tokens asociados
347
+ *
348
+ * @returns Promise que resuelve cuando el workspace es completamente eliminado
349
+ *
350
+ * @throws {BaserowNotFoundError} Si el workspace no existe o no se puede encontrar
351
+ * @throws {BaserowAuthError} Si no tienes permisos para eliminar el workspace
352
+ *
353
+ * @example Eliminación por nombre
354
+ * ```typescript
355
+ * // Eliminar workspace identificado por nombre
356
+ * await admin.workspace('Mi Empresa').delete()
357
+ * console.log('Workspace "Mi Empresa" eliminado exitosamente')
358
+ * ```
359
+ *
360
+ * @example Eliminación por ID
361
+ * ```typescript
362
+ * // Eliminar workspace identificado por ID numérico
363
+ * await admin.workspace(123).delete()
364
+ * console.log('Workspace con ID 123 eliminado exitosamente')
365
+ * ```
366
+ *
367
+ * @example Patrón de confirmación seguro
368
+ * ```typescript
369
+ * const workspaceName = 'Empresa Temporal'
370
+ * const workspace = await admin.workspace(workspaceName).get()
371
+ *
372
+ * console.log(`¿Seguro que quieres eliminar "${workspace.name}"?`)
373
+ * console.log(`Contiene ${workspace.databases?.length || 0} databases`)
374
+ *
375
+ * // Confirmar antes de eliminar
376
+ * if (confirmDeletion) {
377
+ * await admin.workspace(workspaceName).delete()
378
+ * console.log('Workspace eliminado exitosamente')
379
+ * }
380
+ * ```
381
+ *
382
+ * @since 1.0.0
383
+ */
384
+ async delete(): Promise<void> {
385
+ const workspace = await this.get()
386
+ const contextAccess = (this.workspaceService as any)[Symbol.for('workspaceContext')]
387
+ await contextAccess.deleteWorkspace(workspace.id)
388
+
389
+ if (this.logger) {
390
+ this.logger.info(`Workspace deleted: "${workspace.name}" (ID: ${workspace.id})`)
391
+ }
392
+
393
+ // Limpiar cache
394
+ this.resolvedWorkspace = undefined
395
+ }
396
+
397
+ /**
398
+ * Obtener este workspace completo
399
+ *
400
+ * Recupera el workspace con todos sus datos y metadatos.
401
+ * Utiliza lazy loading con cache para evitar resoluciones repetidas.
402
+ * Soporta identificación por nombre o ID del workspace.
403
+ *
404
+ * @returns Promise con el workspace completo
405
+ *
406
+ * @throws {BaserowNotFoundError} Si el workspace no existe
407
+ * @throws {BaserowValidationError} Si el identificador es inválido
408
+ *
409
+ * @example Obtener por nombre
410
+ * ```typescript
411
+ * const workspace = await admin.workspace('Mi Empresa').get()
412
+ * console.log(`Workspace: ${workspace.name} (ID: ${workspace.id})`)
413
+ * console.log(`Creado: ${workspace.created_on}`)
414
+ * ```
415
+ *
416
+ * @example Obtener por ID
417
+ * ```typescript
418
+ * const workspace = await admin.workspace(123).get()
419
+ * console.log(`Nombre: ${workspace.name}`)
420
+ * console.log(`Usuarios: ${workspace.users?.length || 0}`)
421
+ * ```
422
+ *
423
+ * @since 1.1.0
424
+ */
425
+ async get(): Promise<Workspace> {
426
+ if (this.resolvedWorkspace) {
427
+ return this.resolvedWorkspace
428
+ }
429
+
430
+ // Usar findUnique que maneja tanto ID como nombre
431
+ const workspace = await this.workspaceService.findUnique(this.workspaceIdentifier)
432
+
433
+ if (!workspace) {
434
+ throw new BaserowNotFoundError('Workspace', this.workspaceIdentifier)
435
+ }
436
+
437
+ this.resolvedWorkspace = workspace
438
+
439
+ if (this.logger) {
440
+ this.logger.info(`Retrieved workspace: "${workspace.name}" (ID: ${workspace.id})`)
441
+ }
442
+
443
+ return workspace
444
+ }
445
+
446
+ /**
447
+ * Verificar si este workspace existe
448
+ *
449
+ * Método de utilidad para verificar la existencia del workspace
450
+ * sin cargar todos sus datos. Útil para validaciones previas.
451
+ *
452
+ * @returns Promise con true si existe, false si no
453
+ *
454
+ * @example Verificar existencia por nombre
455
+ * ```typescript
456
+ * const exists = await admin.workspace('Mi Empresa').exists()
457
+ * if (exists) {
458
+ * console.log('El workspace existe')
459
+ * } else {
460
+ * console.log('El workspace no fue encontrado')
461
+ * }
462
+ * ```
463
+ *
464
+ * @example Verificar antes de crear
465
+ * ```typescript
466
+ * const workspaceName = 'Nueva Empresa'
467
+ * const exists = await admin.workspace(workspaceName).exists()
468
+ *
469
+ * if (!exists) {
470
+ * await admin.workspaces.create({ name: workspaceName })
471
+ * console.log('Workspace creado exitosamente')
472
+ * } else {
473
+ * console.log('El workspace ya existe')
474
+ * }
475
+ * ```
476
+ *
477
+ * @since 1.1.0
478
+ */
479
+ async exists(): Promise<boolean> {
480
+ try {
481
+ await this.get()
482
+ return true
483
+ } catch (error) {
484
+ if ((error as any).name === 'BaserowNotFoundError') {
485
+ return false
486
+ }
487
+ throw error
488
+ }
489
+ }
490
+ }