wabe-postgres 0.5.3 → 0.5.4

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/src/index.ts DELETED
@@ -1,766 +0,0 @@
1
- import { Pool } from 'pg'
2
- import type {
3
- AdapterOptions,
4
- DatabaseAdapter,
5
- GetObjectOptions,
6
- CreateObjectOptions,
7
- UpdateObjectOptions,
8
- GetObjectsOptions,
9
- CreateObjectsOptions,
10
- UpdateObjectsOptions,
11
- DeleteObjectsOptions,
12
- WhereType,
13
- DeleteObjectOptions,
14
- OutputType,
15
- CountOptions,
16
- OrderType,
17
- WabeTypes,
18
- TypeField,
19
- SchemaInterface,
20
- } from 'wabe'
21
-
22
- const getSQLColumnCreateTableFromType = <T extends WabeTypes>(type: TypeField<T>) => {
23
- switch (type.type) {
24
- case 'String':
25
- return `TEXT${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
26
- case 'Int':
27
- return `INT${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
28
- case 'Float':
29
- return `FLOAT${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
30
- case 'Boolean':
31
- return `BOOLEAN${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
32
- case 'Email':
33
- return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
34
- case 'Phone':
35
- return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
36
- case 'Date':
37
- // Because we store date in iso string in database
38
- return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
39
- case 'File':
40
- return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
41
- case 'Object':
42
- return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
43
- case 'Array':
44
- return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
45
- case 'Pointer':
46
- return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
47
- case 'Relation':
48
- return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
49
- default:
50
- // Enum or scalar
51
- return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
52
- }
53
- }
54
-
55
- /** Resolve where operators (e.g. { string: { equalTo: 'user1' } }) to plain values for JSON. */
56
- const resolveWhereToPlain = (obj: unknown): unknown => {
57
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj
58
- return Object.fromEntries(
59
- Object.entries(obj).map(([key, value]) => [
60
- key,
61
- value && typeof value === 'object' && !Array.isArray(value) && 'equalTo' in value
62
- ? (value as { equalTo: unknown }).equalTo
63
- : resolveWhereToPlain(value),
64
- ]),
65
- )
66
- }
67
-
68
- export const buildPostgresOrderQuery = <
69
- T extends WabeTypes,
70
- K extends keyof T['types'],
71
- U extends keyof T['types'][K],
72
- >(
73
- order?: OrderType<T, K, U>,
74
- ): string => {
75
- if (!order) return ''
76
-
77
- const objectKeys = Object.keys(order) as Array<keyof OrderType<T, K, U>>
78
-
79
- if (objectKeys.length === 0) return ''
80
-
81
- const orderClauses = objectKeys.map(
82
- (key) => `${key === 'id' ? '_id' : String(key)} ${order[key]}`,
83
- )
84
-
85
- return `ORDER BY ${orderClauses.join(', ')}`
86
- }
87
-
88
- export const buildPostgresWhereQueryAndValues = <T extends WabeTypes, K extends keyof T['types']>(
89
- where?: WhereType<T, K>,
90
- startParamIndex = 1,
91
- parentKey?: string,
92
- ): { query: string; values: any[]; paramIndex: number } => {
93
- if (!where) return { query: '', values: [], paramIndex: startParamIndex }
94
-
95
- const objectKeys = Object.keys(where) as Array<keyof WhereType<T, K>>
96
-
97
- if (objectKeys.length === 0) return { query: '', values: [], paramIndex: startParamIndex }
98
-
99
- const acc = objectKeys.reduce(
100
- (acc, key) => {
101
- const value = where[key]
102
- const keyToWrite = key === 'id' ? '_id' : String(key)
103
-
104
- const fullKey = parentKey ? `${parentKey}->>'${keyToWrite}'` : `"${keyToWrite}"`
105
-
106
- const simpleFullKey = parentKey ? `${parentKey}->'${keyToWrite}'` : `"${keyToWrite}"`
107
-
108
- if ('equalTo' in (value || {})) {
109
- if (value?.equalTo === null || value?.equalTo === undefined) {
110
- acc.conditions.push(`${fullKey} IS NULL`)
111
- return acc
112
- }
113
-
114
- acc.conditions.push(`${fullKey} = $${acc.paramIndex}`)
115
- acc.values.push(
116
- Array.isArray(value.equalTo) ? JSON.stringify(value.equalTo) : value.equalTo,
117
- )
118
- acc.paramIndex++
119
- return acc
120
- }
121
-
122
- if ('notEqualTo' in (value || {})) {
123
- if (value?.notEqualTo === null || value?.notEqualTo === undefined) {
124
- acc.conditions.push(`${fullKey} IS NOT NULL`)
125
- return acc
126
- }
127
-
128
- acc.conditions.push(`${fullKey} IS DISTINCT FROM $${acc.paramIndex}`)
129
- acc.values.push(
130
- Array.isArray(value.notEqualTo) ? JSON.stringify(value.notEqualTo) : value.notEqualTo,
131
- )
132
- acc.paramIndex++
133
- return acc
134
- }
135
-
136
- if (value?.exists === true) {
137
- if (parentKey) {
138
- acc.conditions.push(
139
- `${parentKey} IS NOT NULL
140
- AND ${parentKey} ? '${keyToWrite}'
141
- AND ${parentKey}->'${keyToWrite}' IS NOT NULL
142
- AND ${parentKey}->'${keyToWrite}' <> 'null'::jsonb`,
143
- )
144
- } else {
145
- acc.conditions.push(`"${keyToWrite}" IS NOT NULL`)
146
- }
147
- return acc
148
- }
149
-
150
- if (value?.exists === false) {
151
- if (parentKey) {
152
- acc.conditions.push(
153
- `${parentKey} IS NULL
154
- OR NOT (${parentKey} ? '${keyToWrite}')
155
- OR ${parentKey}->'${keyToWrite}' IS NULL
156
- OR ${parentKey}->'${keyToWrite}' = 'null'::jsonb`,
157
- )
158
- } else {
159
- acc.conditions.push(`"${keyToWrite}" IS NULL`)
160
- }
161
- return acc
162
- }
163
-
164
- if (value?.greaterThan || value?.greaterThan === null) {
165
- acc.conditions.push(`${fullKey} > $${acc.paramIndex}`)
166
- acc.values.push(value.greaterThan)
167
- acc.paramIndex++
168
- return acc
169
- }
170
-
171
- if (value?.greaterThanOrEqualTo || value?.greaterThanOrEqualTo === null) {
172
- acc.conditions.push(`${fullKey} >= $${acc.paramIndex}`)
173
- acc.values.push(value.greaterThanOrEqualTo)
174
- acc.paramIndex++
175
- return acc
176
- }
177
-
178
- if (value?.lessThan || value?.lessThan === null) {
179
- acc.conditions.push(`${fullKey} < $${acc.paramIndex}`)
180
- acc.values.push(value.lessThan)
181
- acc.paramIndex++
182
- return acc
183
- }
184
-
185
- if (value?.lessThanOrEqualTo || value?.lessThanOrEqualTo === null) {
186
- acc.conditions.push(`${fullKey} <= $${acc.paramIndex}`)
187
- acc.values.push(value.lessThanOrEqualTo)
188
- acc.paramIndex++
189
- return acc
190
- }
191
-
192
- if (value?.in && Array.isArray(value.in) && value.in.length > 0) {
193
- const placeholders = value.in.map(() => `$${acc.paramIndex++}`).join(', ')
194
-
195
- acc.conditions.push(`${fullKey} IN (${placeholders})`)
196
-
197
- acc.values.push(...value.in)
198
- return acc
199
- }
200
-
201
- if (value?.notIn && Array.isArray(value.notIn) && value.notIn.length > 0) {
202
- const placeholders = value.notIn.map(() => `$${acc.paramIndex++}`).join(', ')
203
- acc.conditions.push(`${fullKey} NOT IN (${placeholders})`)
204
- acc.values.push(...value.notIn)
205
- return acc
206
- }
207
-
208
- if (value?.contains) {
209
- // Simple access on json field because contains is use for array or object column
210
- acc.conditions.push(`${simpleFullKey} @> $${acc.paramIndex}`)
211
- acc.values.push(
212
- Array.isArray(value.contains)
213
- ? JSON.stringify(value.contains)
214
- : JSON.stringify([value.contains]),
215
- )
216
- acc.paramIndex++
217
- return acc
218
- }
219
-
220
- if (value?.notContains) {
221
- // Simple access on json field because contains is use for array or object column
222
- acc.conditions.push(`NOT (${simpleFullKey} @> $${acc.paramIndex})`)
223
- const toJson = (v: unknown) => (Array.isArray(v) ? JSON.stringify(v) : JSON.stringify([v]))
224
- acc.values.push(
225
- typeof value.notContains === 'object' && !Array.isArray(value.notContains)
226
- ? toJson(resolveWhereToPlain(value.notContains))
227
- : toJson(value.notContains),
228
- )
229
- acc.paramIndex++
230
- return acc
231
- }
232
-
233
- if (key === 'OR' && Array.isArray(value) && value.length > 0) {
234
- const orConditions = value.map((orWhere) => {
235
- const {
236
- query,
237
- values: orValues,
238
- paramIndex: newParamIndex,
239
- } = buildPostgresWhereQueryAndValues(orWhere, acc.paramIndex)
240
- acc.paramIndex = newParamIndex
241
- return { query, values: orValues }
242
- })
243
-
244
- const orQueries = orConditions.filter(({ query }) => query).map(({ query }) => `${query}`)
245
-
246
- if (orQueries.length > 0) {
247
- acc.conditions.push(`(${orQueries.join(' OR ')})`)
248
-
249
- for (const orCondition of orConditions) {
250
- acc.values.push(...orCondition.values)
251
- }
252
- }
253
- return acc
254
- }
255
-
256
- if (key === 'AND' && Array.isArray(value) && value.length > 0) {
257
- const andConditions = value.map((andWhere) => {
258
- const {
259
- query,
260
- values: andValues,
261
- paramIndex: newParamIndex,
262
- } = buildPostgresWhereQueryAndValues(andWhere, acc.paramIndex)
263
-
264
- acc.paramIndex = newParamIndex
265
-
266
- return { query, values: andValues }
267
- })
268
-
269
- const andQueries = andConditions.filter(({ query }) => query).map(({ query }) => `${query}`)
270
-
271
- if (andQueries.length > 0) {
272
- acc.conditions.push(`(${andQueries.join(' AND ')})`)
273
-
274
- for (const andCondition of andConditions) {
275
- acc.values.push(...andCondition.values)
276
- }
277
- }
278
- return acc
279
- }
280
-
281
- if (typeof value === 'object') {
282
- const nestedResult = buildPostgresWhereQueryAndValues(
283
- value as any,
284
- acc.paramIndex,
285
- simpleFullKey,
286
- )
287
- if (nestedResult.query) {
288
- acc.conditions.push(`(${nestedResult.query})`)
289
- acc.values.push(...nestedResult.values)
290
- acc.paramIndex = nestedResult.paramIndex
291
- }
292
- return acc
293
- }
294
-
295
- return acc
296
- },
297
- {
298
- conditions: [] as string[],
299
- values: [] as any[],
300
- paramIndex: startParamIndex,
301
- },
302
- )
303
-
304
- return {
305
- query: acc.conditions.length > 0 ? acc.conditions.join(' AND ') : '',
306
- values: acc.values,
307
- paramIndex: acc.paramIndex,
308
- }
309
- }
310
-
311
- const computeValuesFromData = (data: Record<string, any>) => {
312
- return Object.values(data).map((value) => {
313
- if (Array.isArray(value)) return JSON.stringify(value)
314
-
315
- return value
316
- })
317
- }
318
-
319
- export class PostgresAdapter<T extends WabeTypes> implements DatabaseAdapter<T> {
320
- public options: AdapterOptions
321
- public postgresPool: Pool
322
- public pool: Pool
323
-
324
- constructor(options: AdapterOptions) {
325
- this.options = options
326
- this.postgresPool = new Pool({
327
- connectionString: `${options.databaseUrl}/postgres`,
328
- })
329
- this.pool = new Pool({
330
- // TODO: Improve this to support ending with a slash and more.
331
- // Need to parse to detect if user already added the database name
332
- connectionString: `${this.options.databaseUrl}/${this.options.databaseName}`,
333
- })
334
- }
335
-
336
- async initializeDatabase(schema: SchemaInterface<T>) {
337
- // We create the database with the pool on postgres database
338
- const client = await this.postgresPool.connect()
339
-
340
- try {
341
- const res = await client.query('SELECT datname FROM pg_database WHERE datname = $1', [
342
- this.options.databaseName,
343
- ])
344
-
345
- if (res.rowCount === 0) await client.query(`CREATE DATABASE "${this.options.databaseName}"`)
346
-
347
- await Promise.all(
348
- (schema.classes || []).map((classSchema) => {
349
- return this.createClassIfNotExist(classSchema.name, schema)
350
- }),
351
- )
352
- } finally {
353
- client.release()
354
- }
355
- }
356
-
357
- async clearDatabase() {
358
- const client = await this.pool.connect()
359
-
360
- try {
361
- const tablesResult = await client.query(`
362
- SELECT tablename
363
- FROM pg_catalog.pg_tables
364
- WHERE schemaname = 'public' AND tablename != 'Role'
365
- `)
366
-
367
- await Promise.all(
368
- tablesResult.rows.map((table) =>
369
- client.query(`TRUNCATE TABLE "${table.tablename}" CASCADE`),
370
- ),
371
- )
372
- } finally {
373
- client.release()
374
- }
375
- }
376
-
377
- async close() {
378
- if (!this.pool.ended) await this.pool.end()
379
- if (!this.postgresPool.ended) await this.postgresPool.end()
380
- }
381
-
382
- async createClassIfNotExist(className: keyof T['types'], schema: SchemaInterface<T>) {
383
- const schemaClass = schema?.classes?.find((currentClass) => currentClass.name === className)
384
-
385
- if (!schemaClass) throw new Error(`${String(className)} is not defined in schema`)
386
-
387
- const client = await this.pool.connect()
388
-
389
- try {
390
- const columns = Object.entries(schemaClass.fields)
391
-
392
- const createTableParams = columns
393
- .map(([fieldName, field]) => {
394
- const sqlColumnCreateTable = getSQLColumnCreateTableFromType(field)
395
-
396
- return `"${fieldName}" ${sqlColumnCreateTable}`
397
- })
398
- .join(', ')
399
-
400
- // Create the table if it doesn't exist
401
- await client.query(`
402
- CREATE TABLE IF NOT EXISTS "${String(className)}" (
403
- _id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
404
- ${createTableParams}
405
- )
406
- `)
407
-
408
- // Update the table if a column is added after the first launch
409
- const res = await client.query(
410
- `
411
- SELECT column_name
412
- FROM information_schema.columns
413
- WHERE table_name = $1
414
- `,
415
- [String(className)],
416
- )
417
-
418
- const existingColumns = res.rows.map((row) => row.column_name)
419
-
420
- // Add missing columns to the table
421
- await Promise.all(
422
- columns
423
- .filter(([fieldName]) => !existingColumns.includes(fieldName))
424
- .map(([fieldName, field]) => {
425
- const sqlColumnCreateTable = getSQLColumnCreateTableFromType(field)
426
- return client.query(`
427
- ALTER TABLE "${String(className)}"
428
- ADD COLUMN "${fieldName}" ${sqlColumnCreateTable}
429
- `)
430
- }),
431
- )
432
-
433
- // Create indexes if they don't exist
434
- const indexes = schemaClass.indexes || []
435
-
436
- await Promise.all(
437
- indexes.map((index) => {
438
- const indexType = index.unique ? 'UNIQUE' : ''
439
- const indexDirection = index.order === 'ASC' ? 'ASC' : 'DESC'
440
-
441
- return client.query(`
442
- CREATE ${indexType} INDEX IF NOT EXISTS
443
- idx_${String(className)}_${String(index.field)}
444
- ON "${String(className)}" (${String(index.field)}' ${indexDirection})
445
- `)
446
- }),
447
- )
448
-
449
- return className
450
- } finally {
451
- client.release()
452
- }
453
- }
454
-
455
- async count<K extends keyof T['types']>(params: CountOptions<T, K>) {
456
- const { className, where } = params
457
-
458
- const client = await this.pool.connect()
459
-
460
- try {
461
- const { query, values } = buildPostgresWhereQueryAndValues(where)
462
-
463
- const whereClause = query ? `WHERE ${query}` : ''
464
-
465
- const result = await client.query(
466
- `SELECT COUNT(*) FROM "${String(className)}" ${whereClause}`,
467
- values,
468
- )
469
-
470
- return Number(result.rows[0].count)
471
- } finally {
472
- client.release()
473
- }
474
- }
475
-
476
- async getObject<K extends keyof T['types'], U extends keyof T['types'][K]>(
477
- params: GetObjectOptions<T, K, U>,
478
- ): Promise<OutputType<T, K, U>> {
479
- const { className, id, select, where } = params
480
-
481
- const client = await this.pool.connect()
482
-
483
- try {
484
- // 2 because 1 is _id
485
- const { query, values } = buildPostgresWhereQueryAndValues(where, 2)
486
-
487
- const whereClause = query ? `AND ${query}` : ''
488
-
489
- const selectFields = select
490
- ? Object.keys(select)
491
- .filter((key) => select[key as keyof typeof select])
492
- .map((key) => `"${key === 'id' ? '_id' : key}"`)
493
- : []
494
-
495
- const selectExpression = selectFields.length > 0 ? selectFields.join(', ') : '*'
496
-
497
- const result = await client.query(
498
- `SELECT ${selectExpression} FROM "${String(
499
- className,
500
- )}" WHERE _id = $1 ${whereClause} LIMIT 1`,
501
- [id, ...values],
502
- )
503
-
504
- if (result.rows.length === 0) throw new Error('Object not found')
505
-
506
- const row = result.rows[0]
507
-
508
- const { _id, ...data } = row
509
-
510
- return {
511
- ...data,
512
- id: _id,
513
- } as OutputType<T, K, U>
514
- } finally {
515
- client.release()
516
- }
517
- }
518
-
519
- async getObjects<
520
- K extends keyof T['types'],
521
- U extends keyof T['types'][K],
522
- W extends keyof T['types'][K],
523
- >(params: GetObjectsOptions<T, K, U, W>): Promise<OutputType<T, K, W>[]> {
524
- const { className, select, where, offset, first, order } = params
525
-
526
- const client = await this.pool.connect()
527
-
528
- try {
529
- const { query, values } = buildPostgresWhereQueryAndValues(where)
530
-
531
- const whereClause = query ? `WHERE ${query}` : ''
532
- const orderClause = buildPostgresOrderQuery(order)
533
- const limitClause = first ? `LIMIT ${first}` : ''
534
- const offsetClause = offset ? `OFFSET ${offset}` : ''
535
-
536
- const selectFields = select
537
- ? Object.keys(select)
538
- .filter((key) => select[key as keyof typeof select])
539
- .map((key) => `"${key === 'id' ? '_id' : key}"`)
540
- : []
541
-
542
- const selectExpression = selectFields.length > 0 ? selectFields.join(', ') : '*'
543
-
544
- const result = await client.query(
545
- `SELECT ${selectExpression} FROM "${String(
546
- className,
547
- )}" ${whereClause} ${orderClause} ${limitClause} ${offsetClause}`,
548
- values,
549
- )
550
-
551
- const rows = result.rows as Array<Record<string, any>>
552
-
553
- return rows.map((row) => {
554
- const { _id, ...data } = row
555
-
556
- return {
557
- ...data,
558
- id: _id,
559
- } as OutputType<T, K, W>
560
- })
561
- } finally {
562
- client.release()
563
- }
564
- }
565
-
566
- async createObject<
567
- K extends keyof T['types'],
568
- U extends keyof T['types'][K],
569
- W extends keyof T['types'][K],
570
- >(params: CreateObjectOptions<T, K, U, W>): Promise<{ id: string }> {
571
- const { className, data } = params
572
-
573
- const client = await this.pool.connect()
574
-
575
- try {
576
- const columns = Object.keys(data).map((column) => `"${column}"`)
577
- const values = computeValuesFromData(data)
578
- const placeholders = columns.map((_, index) => `$${index + 1}`)
579
-
580
- const result = await client.query(
581
- columns.length === 0
582
- ? `INSERT INTO "${String(className)}" DEFAULT VALUES RETURNING _id`
583
- : `INSERT INTO "${String(className)}" (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING _id`,
584
- values,
585
- )
586
-
587
- return { id: result.rows[0]._id }
588
- } finally {
589
- client.release()
590
- }
591
- }
592
-
593
- async createObjects<
594
- K extends keyof T['types'],
595
- U extends keyof T['types'][K],
596
- W extends keyof T['types'][K],
597
- X extends keyof T['types'][K],
598
- >(params: CreateObjectsOptions<T, K, U, W, X>): Promise<Array<{ id: string }>> {
599
- const { className, data } = params
600
-
601
- const client = await this.pool.connect()
602
-
603
- try {
604
- if (data.length === 0) return []
605
-
606
- // Every line is empty
607
- if (data.every((item) => Object.keys(item).length === 0)) {
608
- const placeholders = data.map(() => 'DEFAULT VALUES').join(', ')
609
-
610
- const result = await client.query(
611
- `INSERT INTO "${String(className)}" ${placeholders} RETURNING _id`,
612
- )
613
-
614
- return result.rows.map((row) => ({ id: row._id }))
615
- }
616
-
617
- const allColumns = Array.from(new Set(data.flatMap((item) => Object.keys(item))))
618
-
619
- const columns = allColumns.map((column) => `"${column}"`)
620
-
621
- const values = data.flatMap((item: any) =>
622
- allColumns.map((col) => {
623
- const value = item[col]
624
- return Array.isArray(value) ? JSON.stringify(value) : (value ?? null)
625
- }),
626
- )
627
-
628
- const placeholders = data.map((_, rowIndex) => {
629
- const offset = rowIndex * allColumns.length
630
- return `(${allColumns.map((_, colIndex) => `$${offset + colIndex + 1}`).join(', ')})`
631
- })
632
-
633
- const result = await client.query(
634
- `INSERT INTO "${String(className)}" (${columns.join(
635
- ', ',
636
- )}) VALUES ${placeholders.join(', ')} RETURNING _id`,
637
- values,
638
- )
639
-
640
- return result.rows.map((row) => ({ id: row._id }))
641
- } finally {
642
- client.release()
643
- }
644
- }
645
-
646
- async updateObject<
647
- K extends keyof T['types'],
648
- U extends keyof T['types'][K],
649
- W extends keyof T['types'][K],
650
- >(params: UpdateObjectOptions<T, K, U, W>): Promise<{ id: string }> {
651
- const { className, id, data, where } = params
652
-
653
- const client = await this.pool.connect()
654
-
655
- try {
656
- const dataKeys = Object.keys(data)
657
- const { query, values } = buildPostgresWhereQueryAndValues(where, dataKeys.length + 2)
658
-
659
- const whereClause = query ? `AND ${query}` : ''
660
-
661
- const setClause = dataKeys.map((key, index) => `"${key}" = $${index + 2}`).join(', ')
662
-
663
- const result = await client.query(
664
- `UPDATE "${String(className)}" SET ${setClause} WHERE _id = $1 ${whereClause} RETURNING _id`,
665
- [id, ...computeValuesFromData(data), ...values],
666
- )
667
-
668
- if (result.rowCount === 0) throw new Error('Object not found')
669
-
670
- return { id }
671
- } finally {
672
- client.release()
673
- }
674
- }
675
-
676
- async updateObjects<
677
- K extends keyof T['types'],
678
- U extends keyof T['types'][K],
679
- W extends keyof T['types'][K],
680
- X extends keyof T['types'][K],
681
- >(params: UpdateObjectsOptions<T, K, U, W, X>): Promise<Array<{ id: string }>> {
682
- const { className, where, data, offset, first, context, order } = params
683
-
684
- const client = await this.pool.connect()
685
-
686
- try {
687
- const objectsBeforeUpdate = await context.wabe.controllers.database.getObjects({
688
- className,
689
- where,
690
- // @ts-expect-error
691
- select: { id: true },
692
- offset,
693
- first,
694
- context: {
695
- ...context,
696
- isRoot: true,
697
- },
698
- order,
699
- })
700
-
701
- if (objectsBeforeUpdate.length === 0) return []
702
-
703
- const objectIds = objectsBeforeUpdate.filter(Boolean).map((obj: any) => obj.id) as string[]
704
-
705
- await Promise.all(
706
- objectIds.map(async (id) => {
707
- const setClause = Object.keys(data)
708
- .map((key, index) => `"${key}" = $${index + 1}`)
709
- .join(', ')
710
-
711
- await client.query(
712
- `UPDATE "${String(className)}" SET ${setClause} WHERE _id = $${Object.keys(data).length + 1}`,
713
- [...computeValuesFromData(data), id],
714
- )
715
- }),
716
- )
717
-
718
- return objectsBeforeUpdate.filter(Boolean).map((obj: any) => ({ id: obj.id }))
719
- } finally {
720
- client.release()
721
- }
722
- }
723
-
724
- async deleteObject<K extends keyof T['types'], U extends keyof T['types'][K]>(
725
- params: DeleteObjectOptions<T, K, U>,
726
- ): Promise<void> {
727
- const { className, id, where } = params
728
-
729
- const client = await this.pool.connect()
730
-
731
- try {
732
- const { query, values } = buildPostgresWhereQueryAndValues(where, 2)
733
-
734
- const whereClause = query ? `AND ${query}` : ''
735
-
736
- const result = await client.query(
737
- `DELETE FROM "${String(className)}" WHERE _id = $1 ${whereClause}`,
738
- [id, ...values],
739
- )
740
-
741
- if (result.rowCount === 0) throw new Error('Object not found')
742
- } finally {
743
- client.release()
744
- }
745
- }
746
-
747
- async deleteObjects<
748
- K extends keyof T['types'],
749
- U extends keyof T['types'][K],
750
- W extends keyof T['types'][K],
751
- >(params: DeleteObjectsOptions<T, K, U, W>): Promise<void> {
752
- const { className, where } = params
753
-
754
- const client = await this.pool.connect()
755
-
756
- try {
757
- const { query, values } = buildPostgresWhereQueryAndValues(where)
758
-
759
- const whereClause = query ? `WHERE ${query}` : ''
760
-
761
- await client.query(`DELETE FROM "${String(className)}" ${whereClause}`, values)
762
- } finally {
763
- client.release()
764
- }
765
- }
766
- }