@starbemtech/star-db-query-builder 1.0.38 → 1.2.1

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 (85) hide show
  1. package/.github/workflows/publish.yml +118 -0
  2. package/ARCHITECTURE.md +313 -0
  3. package/CHANGELOG.md +65 -0
  4. package/README.md +1309 -129
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +131 -0
  9. package/coverage/lcov-report/block-navigation.js +1 -1
  10. package/coverage/lcov-report/index.html +41 -11
  11. package/coverage/lcov-report/mysqlClient.ts.html +685 -0
  12. package/coverage/lcov-report/pgClient.ts.html +823 -0
  13. package/coverage/lcov-report/sorter.js +21 -7
  14. package/coverage/lcov.info +533 -0
  15. package/coverage/mysqlClient.ts.html +685 -0
  16. package/coverage/pgClient.ts.html +823 -0
  17. package/coverage/prettify.css +1 -0
  18. package/coverage/prettify.js +2 -0
  19. package/coverage/sort-arrow-sprite.png +0 -0
  20. package/coverage/sorter.js +210 -0
  21. package/dist/index.d.ts +5 -4
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/src/core/repository.d.ts +374 -0
  25. package/dist/src/core/repository.js +677 -0
  26. package/dist/src/core/repository.js.map +1 -0
  27. package/dist/src/{default → core}/types.d.ts +9 -1
  28. package/dist/src/{default → core}/types.js.map +1 -1
  29. package/dist/src/core/utils.d.ts +133 -0
  30. package/dist/src/{default → core}/utils.js +167 -0
  31. package/dist/src/core/utils.js.map +1 -0
  32. package/dist/src/db/IDatabaseClient.d.ts +10 -1
  33. package/dist/src/db/initDb.d.ts +120 -1
  34. package/dist/src/db/initDb.js +119 -0
  35. package/dist/src/db/initDb.js.map +1 -1
  36. package/dist/src/db/mysqlClient.d.ts +23 -1
  37. package/dist/src/db/mysqlClient.js +115 -0
  38. package/dist/src/db/mysqlClient.js.map +1 -1
  39. package/dist/src/db/pgClient.d.ts +22 -1
  40. package/dist/src/db/pgClient.js +127 -0
  41. package/dist/src/db/pgClient.js.map +1 -1
  42. package/dist/src/monitor/monitor.d.ts +6 -1
  43. package/dist/src/monitor/monitor.js +5 -0
  44. package/dist/src/monitor/monitor.js.map +1 -1
  45. package/dist/src/setupTests.d.ts +26 -0
  46. package/dist/src/setupTests.js +43 -0
  47. package/dist/src/setupTests.js.map +1 -0
  48. package/docs/INDEX.md +145 -0
  49. package/docs/methods/findFirst.md +394 -0
  50. package/docs/methods/findMany.md +587 -0
  51. package/docs/methods/insert.md +536 -0
  52. package/docs/methods/insertMany.md +627 -0
  53. package/docs/methods/joins.md +781 -0
  54. package/docs/methods/rawQuery.md +284 -0
  55. package/docs/methods/transactions.md +737 -0
  56. package/eslint.config.mjs +77 -0
  57. package/index.ts +5 -4
  58. package/jest.config.ts +23 -28
  59. package/package.json +52 -29
  60. package/scripts/release.sh +123 -0
  61. package/src/core/repository.ts +865 -0
  62. package/src/{default → core}/types.ts +11 -2
  63. package/src/{default → core}/utils.ts +168 -0
  64. package/src/db/IDatabaseClient.ts +11 -1
  65. package/src/db/__tests__/mysqlClient.test.ts +262 -0
  66. package/src/db/__tests__/pgClient.test.ts +260 -0
  67. package/src/db/initDb.ts +120 -1
  68. package/src/db/mysqlClient.ts +119 -3
  69. package/src/db/pgClient.ts +131 -3
  70. package/src/monitor/monitor.ts +5 -0
  71. package/src/setupTests.ts +45 -0
  72. package/tsconfig.test.json +21 -0
  73. package/.eslintignore +0 -4
  74. package/.eslintrc.json +0 -32
  75. package/coverage/clover.xml +0 -6
  76. package/coverage/coverage-final.json +0 -1
  77. package/dist/.eslintrc.json +0 -32
  78. package/dist/src/default/genericRepository.d.ts +0 -20
  79. package/dist/src/default/genericRepository.js +0 -192
  80. package/dist/src/default/genericRepository.js.map +0 -1
  81. package/dist/src/default/utils.d.ts +0 -9
  82. package/dist/src/default/utils.js.map +0 -1
  83. package/index.d.ts +0 -60
  84. package/src/default/genericRepository.ts +0 -320
  85. /package/dist/src/{default → core}/types.js +0 -0
@@ -0,0 +1,865 @@
1
+ import { v4 as uuid } from 'uuid'
2
+ import { QueryParams, QueryBuilder, RawQueryParams } from './types'
3
+ import { ITransactionClient } from '../db/IDatabaseClient'
4
+ import {
5
+ createGroupByClause,
6
+ createLimitClause,
7
+ createOrderByClause,
8
+ generatePlaceholders,
9
+ createSelectFields,
10
+ createWhereClause,
11
+ generateSetClause,
12
+ createOffsetClause,
13
+ } from './utils'
14
+
15
+ /**
16
+ * Finds the first record in the specified table
17
+ *
18
+ * This function retrieves the first record from the specified table based on the provided
19
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
20
+ *
21
+ * @template T - The type of the record to be returned
22
+ * @param params - Query parameters including table name, database client, select fields, where conditions, group by, order by, and limit
23
+ * @returns Promise<T | null> - The first record or null if no record is found
24
+ *
25
+ * @throws {Error} When table name is not provided
26
+ * @throws {Error} When database client is not provided
27
+ *
28
+ * @example
29
+ * const firstRecord = await findFirst({
30
+ * tableName: 'users',
31
+ * dbClient: dbClient,
32
+ * select: ['id', 'name', 'email'],
33
+ * where: { status: 'active' },
34
+ * groupBy: ['status'],
35
+ * orderBy: [{ field: 'created_at', direction: 'DESC' }],
36
+ * })
37
+ *
38
+ * @example
39
+ * const firstRecord = await findFirst({
40
+ * tableName: 'users',
41
+ * dbClient: dbClient,
42
+ * select: ['id', 'name', 'email'],
43
+ * where: { status: 'active' },
44
+ * groupBy: ['status'],
45
+ * orderBy: [{ field: 'created_at', direction: 'DESC' }],
46
+ * })
47
+ */
48
+ export const findFirst = async <T>({
49
+ tableName,
50
+ dbClient,
51
+ select,
52
+ where,
53
+ groupBy,
54
+ orderBy,
55
+ }: QueryParams<T>): Promise<T | null> => {
56
+ if (!tableName) throw new Error('Table name is required')
57
+ if (!dbClient) throw new Error('DB client is required')
58
+
59
+ const fields = createSelectFields(select, dbClient.clientType)
60
+ const [whereClause, params] = createWhereClause<T>(
61
+ where,
62
+ 1,
63
+ dbClient.clientType
64
+ )
65
+
66
+ const orderByClause = createOrderByClause(orderBy)
67
+ const groupByClause = createGroupByClause(groupBy)
68
+
69
+ const rows = await dbClient.query<T[]>(
70
+ `SELECT ${fields} FROM ${tableName}
71
+ ${whereClause.length > 7 ? whereClause : ''}
72
+ ${groupByClause}
73
+ ${orderByClause}
74
+ `,
75
+ params
76
+ )
77
+
78
+ return rows[0] || null
79
+ }
80
+
81
+ /**
82
+ * Finds multiple records in the specified table
83
+ *
84
+ * This function retrieves multiple records from the specified table based on the provided
85
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
86
+ *
87
+ * @template T - The type of the records to be returned
88
+ * @param params - Query parameters including table name, database client, select fields, where conditions, group by, order by, limit, and offset
89
+ * @returns Promise<T[]> - The records found in the table
90
+ *
91
+ * @throws {Error} When table name is not provided
92
+ * @throws {Error} When database client is not provided
93
+ *
94
+ * @example
95
+ * const records = await findMany({
96
+ * tableName: 'users',
97
+ * dbClient: dbClient,
98
+ * select: ['id', 'name', 'email'],
99
+ * where: { status: 'active' },
100
+ * groupBy: ['status'],
101
+ * orderBy: [{ field: 'created_at', direction: 'DESC' }],
102
+ * limit: 10,
103
+ * offset: 0,
104
+ * unaccent: true,
105
+ * })
106
+ */
107
+ export const findMany = async <T>({
108
+ tableName,
109
+ dbClient,
110
+ select,
111
+ where,
112
+ groupBy,
113
+ orderBy,
114
+ limit,
115
+ offset,
116
+ unaccent,
117
+ }: QueryParams<T>): Promise<T[]> => {
118
+ if (!tableName) throw new Error('Table name is required')
119
+ if (!dbClient) throw new Error('DB client is required')
120
+
121
+ const fields = createSelectFields(select, dbClient.clientType)
122
+ const [whereClause, params] = createWhereClause(
123
+ where,
124
+ 1,
125
+ dbClient.clientType,
126
+ unaccent
127
+ )
128
+
129
+ const orderByClause = createOrderByClause(orderBy)
130
+ const groupByClause = createGroupByClause(groupBy)
131
+ const limitClause = createLimitClause(limit)
132
+ const offsetClause = createOffsetClause(offset)
133
+
134
+ const rows = await dbClient.query<T[]>(
135
+ `SELECT ${fields} FROM ${tableName}
136
+ ${whereClause.length > 7 ? whereClause : ''}
137
+ ${groupByClause}
138
+ ${orderByClause}
139
+ ${limitClause}
140
+ ${offsetClause}
141
+ `,
142
+ params
143
+ )
144
+
145
+ return rows || []
146
+ }
147
+
148
+ /**
149
+ * Inserts a new record into the specified table
150
+ *
151
+ * This function inserts a new record into the specified table based on the provided
152
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
153
+ *
154
+ * @template P - The type of the data to be inserted
155
+ * @template R - The type of the record to be returned
156
+ * @param params - Query parameters including table name, database client, data to be inserted, and optional returning fields
157
+ * @returns Promise<R> - The inserted record
158
+ *
159
+ * @throws {Error} When table name is not provided
160
+ * @throws {Error} When database client is not provided
161
+ * @throws {Error} When data object is not provided
162
+ *
163
+ * @example
164
+ * const insertedRecord = await insert({
165
+ * tableName: 'users',
166
+ * dbClient: dbClient,
167
+ * data: { name: 'John Doe', email: 'john.doe@example.com' },
168
+ * returning: ['id', 'name', 'email'],
169
+ * })
170
+ */
171
+ export const insert = async <P, R>({
172
+ tableName,
173
+ dbClient,
174
+ data,
175
+ returning,
176
+ }: QueryParams<R> & { data: P; returning?: string[] }): Promise<R> => {
177
+ if (!tableName) throw new Error('Table name is required')
178
+ if (!dbClient) throw new Error('DB client is required')
179
+ if (!data) throw new Error('Data object is required')
180
+
181
+ const keys =
182
+ dbClient.clientType === 'pg'
183
+ ? Object.keys(data).map((key) =>
184
+ key === 'authorization' ? `"${key}"` : key
185
+ )
186
+ : Object.keys(data)
187
+ const values = Object.values(data)
188
+
189
+ keys.unshift('id')
190
+ const generatedUUID: string = uuid()
191
+ values.unshift(generatedUUID)
192
+
193
+ keys.push('updated_at')
194
+ values.push(new Date())
195
+
196
+ const placeholders = generatePlaceholders(keys, dbClient.clientType)
197
+ let query = `INSERT INTO ${tableName} (${keys.join(
198
+ ', '
199
+ )}) VALUES (${placeholders})`
200
+
201
+ if (dbClient.clientType === 'pg') {
202
+ if (returning && returning.length > 0) {
203
+ query += ` RETURNING ${createSelectFields(
204
+ returning,
205
+ dbClient.clientType
206
+ )}`
207
+ }
208
+ }
209
+
210
+ const inserted = await dbClient.query<R[]>(query, values)
211
+
212
+ if (dbClient.clientType === 'mysql') {
213
+ const rows = await dbClient.query<R[]>(
214
+ `SELECT ${
215
+ returning && returning.length > 0
216
+ ? createSelectFields(returning, dbClient.clientType)
217
+ : '*'
218
+ } FROM ${tableName}
219
+ WHERE
220
+ id = ?
221
+ `,
222
+ [generatedUUID]
223
+ )
224
+
225
+ return rows[0]
226
+ }
227
+
228
+ return inserted[0]
229
+ }
230
+
231
+ /**
232
+ * Inserts multiple records into the specified table
233
+ *
234
+ * This function inserts multiple records into the specified table based on the provided
235
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
236
+ *
237
+ * @template P - The type of the data to be inserted
238
+ * @template R - The type of the record to be returned
239
+ * @param params - Query parameters including table name, database client, data to be inserted, and optional returning fields
240
+ * @returns Promise<R[]> - The inserted records
241
+ *
242
+ * @throws {Error} When table name is not provided
243
+ * @throws {Error} When database client is not provided
244
+ * @throws {Error} When data array is not provided or empty
245
+ *
246
+ * @example
247
+ * const insertedRecords = await insertMany({
248
+ * tableName: 'users',
249
+ * dbClient: dbClient,
250
+ * data: [{ name: 'John Doe', email: 'john.doe@example.com' }, { name: 'Jane Doe', email: 'jane.doe@example.com' }],
251
+ * returning: ['id', 'name', 'email'],
252
+ * })
253
+ */
254
+ export const insertMany = async <P, R>({
255
+ tableName,
256
+ dbClient,
257
+ data,
258
+ returning,
259
+ }: QueryParams<R> & { data: P[]; returning?: string[] }): Promise<R[]> => {
260
+ if (!tableName) throw new Error('Table name is required')
261
+ if (!dbClient) throw new Error('DB client is required')
262
+ if (!data || data.length === 0)
263
+ throw new Error('Data array is required and cannot be empty')
264
+
265
+ const firstItem = data[0] as Record<string, any>
266
+ const keys =
267
+ dbClient.clientType === 'pg'
268
+ ? Object.keys(firstItem).map((key) =>
269
+ key === 'authorization' ? `"${key}"` : key
270
+ )
271
+ : Object.keys(firstItem)
272
+
273
+ const allKeys = ['id', ...keys, 'updated_at']
274
+
275
+ let query = `INSERT INTO ${tableName} (${allKeys.join(', ')}) VALUES `
276
+
277
+ const allValues: any[] = []
278
+ const valueRows: string[] = []
279
+ const generatedIds: string[] = []
280
+
281
+ data.forEach((item, rowIndex) => {
282
+ const values = Object.values(item as Record<string, any>)
283
+ const generatedUUID: string = uuid()
284
+ generatedIds.push(generatedUUID)
285
+ const currentValues = [generatedUUID, ...values, new Date()]
286
+
287
+ allValues.push(...currentValues)
288
+
289
+ // Generate unique placeholders for each row
290
+ if (dbClient.clientType === 'pg') {
291
+ const startIndex = rowIndex * allKeys.length + 1
292
+ const placeholders = allKeys
293
+ .map((_, index) => `$${startIndex + index}`)
294
+ .join(', ')
295
+ valueRows.push(`(${placeholders})`)
296
+ } else {
297
+ const placeholders = allKeys.map(() => '?').join(', ')
298
+ valueRows.push(`(${placeholders})`)
299
+ }
300
+ })
301
+
302
+ query += valueRows.join(', ')
303
+
304
+ if (dbClient.clientType === 'pg') {
305
+ if (returning && returning.length > 0) {
306
+ query += ` RETURNING ${createSelectFields(
307
+ returning,
308
+ dbClient.clientType
309
+ )}`
310
+ }
311
+ }
312
+
313
+ const inserted = await dbClient.query<R[]>(query, allValues)
314
+
315
+ if (dbClient.clientType === 'mysql') {
316
+ const placeholders = generatedIds.map(() => '?').join(', ')
317
+
318
+ const rows = await dbClient.query<R[]>(
319
+ `SELECT ${
320
+ returning && returning.length > 0
321
+ ? createSelectFields(returning, dbClient.clientType)
322
+ : '*'
323
+ } FROM ${tableName}
324
+ WHERE id IN (${placeholders})
325
+ ORDER BY FIELD(id, ${placeholders})
326
+ `,
327
+ [...generatedIds, ...generatedIds]
328
+ )
329
+
330
+ return rows
331
+ }
332
+
333
+ return inserted
334
+ }
335
+
336
+ /**
337
+ * Updates a record in the specified table
338
+ *
339
+ * This function updates a record in the specified table based on the provided
340
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
341
+ *
342
+ * @template P - The type of the data to be updated
343
+ * @template R - The type of the record to be returned
344
+ * @param params - Query parameters including table name, database client, ID of the record to be updated, data to be updated, and optional returning fields
345
+ * @returns Promise<R | void> - The updated record or void if no record is found
346
+ *
347
+ * @throws {Error} When table name is not provided
348
+ * @throws {Error} When database client is not provided
349
+ * @throws {Error} When ID is not provided
350
+ *
351
+ * @example
352
+ * const updatedRecord = await update({
353
+ * tableName: 'users',
354
+ * dbClient: dbClient,
355
+ * id: '123',
356
+ * data: { name: 'John Doe', email: 'john.doe@example.com' },
357
+ * returning: ['id', 'name', 'email'],
358
+ * })
359
+ */
360
+ export const update = async <P, R>({
361
+ tableName,
362
+ dbClient,
363
+ id,
364
+ data,
365
+ returning,
366
+ }: QueryParams<R> & { data: P; returning?: string[] }): Promise<R | void> => {
367
+ if (!tableName) throw new Error('Table name is required')
368
+ if (!dbClient) throw new Error('DB client is required')
369
+ if (!id) throw new Error('ID is required')
370
+ if (!data) throw new Error('Data object is required')
371
+
372
+ const keys = Object.keys(data)
373
+ const values: any[] = Object.values(data)
374
+
375
+ const setClause = generateSetClause(keys, dbClient.clientType)
376
+ let query = `UPDATE ${tableName} SET ${setClause} WHERE id = '${id}'`
377
+
378
+ if (dbClient.clientType === 'pg') {
379
+ if (returning && returning.length > 0) {
380
+ query += ` RETURNING ${createSelectFields(returning, dbClient.clientType)}`
381
+ }
382
+ }
383
+
384
+ const updated = await dbClient.query<R[]>(query, values)
385
+
386
+ if (dbClient.clientType === 'mysql') {
387
+ const rows = await dbClient.query<R[]>(
388
+ `SELECT ${
389
+ returning && returning.length > 0
390
+ ? createSelectFields(returning, dbClient.clientType)
391
+ : '*'
392
+ } FROM ${tableName}
393
+ WHERE
394
+ id = ?
395
+ `,
396
+ [id]
397
+ )
398
+
399
+ return rows[0]
400
+ }
401
+
402
+ return updated[0]
403
+ }
404
+
405
+ /**
406
+ * Updates multiple records in the specified table
407
+ *
408
+ * This function updates multiple records in the specified table based on the provided
409
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
410
+ *
411
+ * @template P - The type of the data to be updated
412
+ * @template R - The type of the record to be returned
413
+ * @param params - Query parameters including table name, database client, data to be updated, where conditions, and optional returning fields
414
+ * @returns Promise<R[]> - The updated records
415
+ *
416
+ * @throws {Error} When table name is not provided
417
+ * @throws {Error} When database client is not provided
418
+ * @throws {Error} When data object is not provided
419
+ * @throws {Error} When where condition is not provided
420
+ *
421
+ * @example
422
+ * const updatedRecords = await updateMany({
423
+ * tableName: 'users',
424
+ * dbClient: dbClient,
425
+ * data: { name: 'John Doe', email: 'john.doe@example.com' },
426
+ * where: { status: 'active' },
427
+ * returning: ['id', 'name', 'email'],
428
+ * })
429
+ */
430
+ export const updateMany = async <P, R>({
431
+ tableName,
432
+ dbClient,
433
+ data,
434
+ where,
435
+ returning,
436
+ }: QueryParams<R> & { data: P; returning?: string[] }): Promise<R[]> => {
437
+ if (!tableName) throw new Error('Table name is required')
438
+ if (!dbClient) throw new Error('DB client is required')
439
+ if (!data) throw new Error('Data object is required')
440
+ if (!where) throw new Error('Where condition is required')
441
+
442
+ const keys = Object.keys(data)
443
+ const values: any[] = Object.values(data)
444
+
445
+ // Generate SET clause with correct placeholders
446
+ const setClause = keys
447
+ .map((key, index) =>
448
+ dbClient.clientType === 'pg' ? `${key} = $${index + 1}` : `${key} = ?`
449
+ )
450
+ .join(', ')
451
+
452
+ // Generate WHERE clause with placeholders starting after SET values
453
+ const [whereClause, whereParams] = createWhereClause(
454
+ where,
455
+ values.length + 1,
456
+ dbClient.clientType
457
+ )
458
+
459
+ let query = `UPDATE ${tableName} SET ${setClause} ${whereClause}`
460
+
461
+ if (dbClient.clientType === 'pg') {
462
+ if (returning && returning.length > 0) {
463
+ query += ` RETURNING ${createSelectFields(returning, dbClient.clientType)}`
464
+ }
465
+ }
466
+
467
+ const updated = await dbClient.query<R[]>(query, [...values, ...whereParams])
468
+
469
+ if (dbClient.clientType === 'mysql') {
470
+ // For MySQL, we need to fetch the updated records separately
471
+ // since MySQL doesn't support RETURNING clause
472
+ const rows = await dbClient.query<R[]>(
473
+ `SELECT ${
474
+ returning && returning.length > 0
475
+ ? createSelectFields(returning, dbClient.clientType)
476
+ : '*'
477
+ } FROM ${tableName}
478
+ ${whereClause}
479
+ `,
480
+ whereParams
481
+ )
482
+
483
+ return rows
484
+ }
485
+
486
+ return updated
487
+ }
488
+
489
+ /**
490
+ * Deletes a record from the specified table
491
+ *
492
+ * This function deletes a record from the specified table based on the provided
493
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
494
+ *
495
+ * @template T - The type of the record to be deleted
496
+ * @param params - Query parameters including table name, database client, ID of the record to be deleted, and optional permanently flag
497
+ * @returns Promise<void> - Resolves when the record is deleted
498
+ *
499
+ * @throws {Error} When table name is not provided
500
+ * @throws {Error} When database client is not provided
501
+ * @throws {Error} When ID is not provided
502
+ *
503
+ * @example
504
+ * await deleteOne({
505
+ * tableName: 'users',
506
+ * dbClient: dbClient,
507
+ * id: '123',
508
+ * permanently: true,
509
+ * })
510
+ */
511
+ export const deleteOne = async <T>({
512
+ tableName,
513
+ dbClient,
514
+ id,
515
+ permanently = false,
516
+ }: QueryParams<T> & { permanently?: boolean }): Promise<void> => {
517
+ if (!tableName) throw new Error('Table name is required')
518
+ if (!dbClient) throw new Error('DB client is required')
519
+ if (!id) throw new Error('ID is required')
520
+
521
+ await dbClient.query(
522
+ permanently
523
+ ? `DELETE FROM ${tableName} WHERE id = ${
524
+ dbClient.clientType === 'pg' ? '$1' : '?'
525
+ }`
526
+ : `UPDATE ${tableName} SET status = 'deleted' WHERE id = ${
527
+ dbClient.clientType === 'pg' ? '$1' : '?'
528
+ }`,
529
+ [id]
530
+ )
531
+ }
532
+
533
+ /**
534
+ * Deletes multiple records from the specified table
535
+ *
536
+ * This function deletes multiple records from the specified table based on the provided
537
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
538
+ *
539
+ * @template T - The type of the record to be deleted
540
+ * @param params - Query parameters including table name, database client, IDs of the records to be deleted, field to be used for deletion, and optional permanently flag
541
+ * @returns Promise<void> - Resolves when the records are deleted
542
+ *
543
+ * @throws {Error} When table name is not provided
544
+ * @throws {Error} When database client is not provided
545
+ * @throws {Error} When IDs are not provided or empty
546
+ * @throws {Error} When field is not provided
547
+ *
548
+ * @example
549
+ * await deleteMany({
550
+ * tableName: 'users',
551
+ * dbClient: dbClient,
552
+ * ids: ['123', '456', '789'],
553
+ * field: 'id',
554
+ * permanently: true,
555
+ * })
556
+ */
557
+ export const deleteMany = async <T>({
558
+ tableName,
559
+ dbClient,
560
+ ids,
561
+ field = 'id',
562
+ permanently = false,
563
+ }: QueryParams<T> & {
564
+ ids: string[] | number[]
565
+ field?: string
566
+ permanently?: boolean
567
+ }): Promise<void> => {
568
+ if (!tableName) throw new Error('Table name is required')
569
+ if (!dbClient) throw new Error('DB client is required')
570
+ if (!ids || ids.length === 0)
571
+ throw new Error('IDs are required and cannot be empty')
572
+ if (!field) throw new Error('Field is required')
573
+
574
+ const placeholders =
575
+ dbClient.clientType === 'pg'
576
+ ? ids.map((_, index) => `$${index + 1}`).join(', ')
577
+ : ids.map(() => '?').join(', ')
578
+
579
+ const query = permanently
580
+ ? `DELETE FROM ${tableName} WHERE ${field} IN (${placeholders})`
581
+ : `UPDATE ${tableName} SET status = 'deleted' WHERE ${field} IN (${placeholders})`
582
+
583
+ await dbClient.query(query, ids)
584
+ }
585
+
586
+ /**
587
+ * Joins multiple tables in the specified table
588
+ *
589
+ * This function joins multiple tables in the specified table based on the provided
590
+ * query parameters. It constructs the SQL query, executes it, and returns the result.
591
+ *
592
+ * @template T - The type of the record to be joined
593
+ * @param params - Query parameters including table name, database client, select fields, joins, where conditions, group by, order by, limit, and offset
594
+ * @returns Promise<T[]> - The records found in the joined tables
595
+ *
596
+ * @throws {Error} When table name is not provided
597
+ * @throws {Error} When database client is not provided
598
+ * @throws {Error} When select fields are not provided
599
+ * @throws {Error} When joins are not provided
600
+ *
601
+ * @example
602
+ * const records = await joins({
603
+ * tableName: 'users',
604
+ * dbClient: dbClient,
605
+ * select: ['id', 'name', 'email'],
606
+ * joins: [{ type: 'INNER', table: 'orders', on: 'users.id = orders.user_id' }],
607
+ * where: { status: 'active' },
608
+ * groupBy: ['status'],
609
+ * orderBy: [{ field: 'created_at', direction: 'DESC' }],
610
+ * limit: 10,
611
+ * offset: 0,
612
+ * unaccent: true,
613
+ * })
614
+ */
615
+ export const joins = async <T>({
616
+ tableName,
617
+ dbClient,
618
+ select,
619
+ joins,
620
+ where,
621
+ groupBy,
622
+ orderBy,
623
+ limit,
624
+ offset,
625
+ unaccent,
626
+ }: QueryParams<T>): Promise<T[]> => {
627
+ if (!tableName) throw new Error('Table name is required')
628
+ if (!dbClient) throw new Error('DB client is required')
629
+
630
+ const fields = Array.isArray(select) ? select : []
631
+ const selectFields = createSelectFields(fields, dbClient.clientType)
632
+ const [whereClause, params] = createWhereClause(
633
+ where,
634
+ 1,
635
+ dbClient.clientType,
636
+ unaccent
637
+ )
638
+ const groupByClause = createGroupByClause(groupBy)
639
+ const orderByClause = createOrderByClause(orderBy)
640
+ const limitClause = createLimitClause(limit)
641
+ const offsetClause = createOffsetClause(offset)
642
+
643
+ const queryBuilder: QueryBuilder = {
644
+ select: [selectFields],
645
+ from: tableName,
646
+ joins: joins,
647
+ where: whereClause,
648
+ groupBy: [groupByClause],
649
+ orderBy: orderByClause,
650
+ limit: limitClause,
651
+ offset: offsetClause,
652
+ }
653
+
654
+ const queryString = await buildQuery(queryBuilder)
655
+
656
+ const rows = await dbClient.query<T[]>(queryString, params)
657
+
658
+ return rows
659
+ }
660
+
661
+ /**
662
+ * Executa uma query SQL raw diretamente no banco de dados
663
+ * @param params - Parâmetros da query raw
664
+ * @returns Promise com o resultado da query
665
+ *
666
+ * @example
667
+ * // Query simples sem parâmetros
668
+ * const users = await rawQuery({
669
+ * dbClient,
670
+ * sql: 'SELECT * FROM users WHERE active = true'
671
+ * })
672
+ *
673
+ * @example
674
+ * // Query com parâmetros
675
+ * const user = await rawQuery({
676
+ * dbClient,
677
+ * sql: 'SELECT * FROM users WHERE id = ? AND email = ?',
678
+ * params: ['user-id', 'user@example.com']
679
+ * })
680
+ *
681
+ * @example
682
+ * // Query de agregação
683
+ * const stats = await rawQuery({
684
+ * dbClient,
685
+ * sql: `
686
+ * SELECT
687
+ * COUNT(*) as total_users,
688
+ * AVG(age) as avg_age,
689
+ * MAX(created_at) as last_created
690
+ * FROM users
691
+ * WHERE created_at >= ?
692
+ * `,
693
+ * params: [new Date('2023-01-01')]
694
+ * })
695
+ */
696
+ export const rawQuery = async <T = any>({
697
+ dbClient,
698
+ sql,
699
+ params = [],
700
+ }: RawQueryParams): Promise<T> => {
701
+ if (!dbClient) throw new Error('DB client is required')
702
+ if (!sql || typeof sql !== 'string')
703
+ throw new Error('SQL query is required and must be a string')
704
+
705
+ try {
706
+ const result = await dbClient.query<T>(sql, params)
707
+ return result
708
+ } catch (error) {
709
+ throw new Error(
710
+ `Raw query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`
711
+ )
712
+ }
713
+ }
714
+
715
+ /**
716
+ * Builds a query string from the provided query parameters
717
+ *
718
+ * This function builds a query string from the provided query parameters. It constructs the SQL query, executes it, and returns the result.
719
+ *
720
+ * @param params - Query parameters including select fields, from table, joins, where conditions, group by, order by, limit, and offset
721
+ * @returns Promise<string> - The query string
722
+ *
723
+ * @example
724
+ * const queryString = await buildQuery({
725
+ * select: ['id', 'name', 'email'],
726
+ * from: 'users',
727
+ * joins: [{ type: 'INNER', table: 'orders', on: 'users.id = orders.user_id' }],
728
+ * where: { status: 'active' },
729
+ * groupBy: ['status'],
730
+ * orderBy: [{ field: 'created_at', direction: 'DESC' }],
731
+ * limit: 10,
732
+ * offset: 0,
733
+ * })
734
+ */
735
+ async function buildQuery(params: QueryBuilder): Promise<string> {
736
+ let queryString = `SELECT ${params.select.join(', ')} FROM ${params.from}`
737
+
738
+ if (params.joins) {
739
+ for (const join of params.joins) {
740
+ queryString += ` ${join.type} JOIN ${join.table} ON ${join.on}`
741
+ }
742
+ }
743
+
744
+ if (params.where) {
745
+ queryString += `${params.where}`
746
+ }
747
+
748
+ if (params.groupBy) {
749
+ queryString += `${params.groupBy}`
750
+ }
751
+
752
+ if (params.orderBy) {
753
+ queryString += `${params.orderBy}`
754
+ }
755
+
756
+ if (params.limit) {
757
+ queryString += `${params.limit}`
758
+ }
759
+
760
+ if (params.offset) {
761
+ queryString += `${params.offset}`
762
+ }
763
+
764
+ return queryString
765
+ }
766
+
767
+ /**
768
+ * Executes a function within a database transaction
769
+ * @param dbClient - Database client instance
770
+ * @param transactionFn - Function to execute within the transaction
771
+ * @returns Promise with the result of the transaction function
772
+ *
773
+ * @example
774
+ * // Simple transaction
775
+ * const result = await withTransaction(dbClient, async (tx) => {
776
+ * const user = await insert({
777
+ * tableName: 'users',
778
+ * dbClient: tx,
779
+ * data: { name: 'John', email: 'john@example.com' }
780
+ * })
781
+ *
782
+ * await insert({
783
+ * tableName: 'user_profiles',
784
+ * dbClient: tx,
785
+ * data: { user_id: user.id, bio: 'Hello world' }
786
+ * })
787
+ *
788
+ * return user
789
+ * })
790
+ *
791
+ * @example
792
+ * // Transaction with error handling
793
+ * try {
794
+ * const result = await withTransaction(dbClient, async (tx) => {
795
+ * // Multiple operations that must succeed or fail together
796
+ * const order = await insert({
797
+ * tableName: 'orders',
798
+ * dbClient: tx,
799
+ * data: { user_id: 'user-123', total: 100 }
800
+ * })
801
+ *
802
+ * await update({
803
+ * tableName: 'users',
804
+ * dbClient: tx,
805
+ * id: 'user-123',
806
+ * data: { last_order_id: order.id }
807
+ * })
808
+ *
809
+ * return order
810
+ * })
811
+ * } catch (error) {
812
+ * // Transaction was automatically rolled back
813
+ * console.error('Transaction failed:', error)
814
+ * }
815
+ */
816
+ export const withTransaction = async <T>(
817
+ dbClient: any,
818
+ transactionFn: (tx: ITransactionClient) => Promise<T>
819
+ ): Promise<T> => {
820
+ const transaction = await dbClient.beginTransaction()
821
+
822
+ try {
823
+ const result = await transactionFn(transaction)
824
+ await transaction.commit()
825
+ return result
826
+ } catch (error) {
827
+ await transaction.rollback()
828
+ throw error
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Creates a transaction client for manual transaction management
834
+ * @param dbClient - Database client instance
835
+ * @returns Promise with the transaction client
836
+ *
837
+ * @example
838
+ * // Manual transaction management
839
+ * const transaction = await beginTransaction(dbClient)
840
+ *
841
+ * try {
842
+ * const user = await insert({
843
+ * tableName: 'users',
844
+ * dbClient: transaction,
845
+ * data: { name: 'John', email: 'john@example.com' }
846
+ * })
847
+ *
848
+ * await insert({
849
+ * tableName: 'user_profiles',
850
+ * dbClient: transaction,
851
+ * data: { user_id: user.id, bio: 'Hello world' }
852
+ * })
853
+ *
854
+ * await transaction.commit()
855
+ * return user
856
+ * } catch (error) {
857
+ * await transaction.rollback()
858
+ * throw error
859
+ * }
860
+ */
861
+ export const beginTransaction = async (
862
+ dbClient: any
863
+ ): Promise<ITransactionClient> => {
864
+ return dbClient.beginTransaction()
865
+ }