@starbemtech/star-db-query-builder 1.1.0 → 1.3.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 (88) hide show
  1. package/.github/workflows/publish.yml +118 -0
  2. package/ARCHITECTURE.md +313 -0
  3. package/CHANGELOG.md +67 -0
  4. package/README.md +1275 -210
  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/base.css +224 -0
  10. package/coverage/lcov-report/block-navigation.js +87 -0
  11. package/coverage/lcov-report/favicon.png +0 -0
  12. package/coverage/lcov-report/index.html +131 -0
  13. package/coverage/lcov-report/mysqlClient.ts.html +685 -0
  14. package/coverage/lcov-report/pgClient.ts.html +823 -0
  15. package/coverage/lcov-report/prettify.css +1 -0
  16. package/coverage/lcov-report/prettify.js +2 -0
  17. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  18. package/coverage/lcov-report/sorter.js +210 -0
  19. package/coverage/lcov.info +533 -0
  20. package/coverage/mysqlClient.ts.html +685 -0
  21. package/coverage/pgClient.ts.html +823 -0
  22. package/coverage/prettify.css +1 -0
  23. package/coverage/prettify.js +2 -0
  24. package/coverage/sort-arrow-sprite.png +0 -0
  25. package/coverage/sorter.js +210 -0
  26. package/dist/index.d.ts +5 -4
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/src/core/repository.d.ts +374 -0
  30. package/dist/src/core/repository.js +677 -0
  31. package/dist/src/core/repository.js.map +1 -0
  32. package/dist/src/{default → core}/types.d.ts +8 -0
  33. package/dist/src/{default → core}/types.js.map +1 -1
  34. package/dist/src/core/utils.d.ts +133 -0
  35. package/dist/src/{default → core}/utils.js +167 -0
  36. package/dist/src/core/utils.js.map +1 -0
  37. package/dist/src/db/IDatabaseClient.d.ts +10 -1
  38. package/dist/src/db/initDb.d.ts +120 -1
  39. package/dist/src/db/initDb.js +119 -0
  40. package/dist/src/db/initDb.js.map +1 -1
  41. package/dist/src/db/mysqlClient.d.ts +23 -1
  42. package/dist/src/db/mysqlClient.js +115 -0
  43. package/dist/src/db/mysqlClient.js.map +1 -1
  44. package/dist/src/db/pgClient.d.ts +22 -1
  45. package/dist/src/db/pgClient.js +127 -0
  46. package/dist/src/db/pgClient.js.map +1 -1
  47. package/dist/src/monitor/monitor.d.ts +6 -1
  48. package/dist/src/monitor/monitor.js +5 -0
  49. package/dist/src/monitor/monitor.js.map +1 -1
  50. package/dist/src/setupTests.d.ts +26 -0
  51. package/dist/src/setupTests.js +43 -0
  52. package/dist/src/setupTests.js.map +1 -0
  53. package/docs/INDEX.md +145 -0
  54. package/docs/methods/findFirst.md +394 -0
  55. package/docs/methods/findMany.md +587 -0
  56. package/docs/methods/insert.md +536 -0
  57. package/docs/methods/insertMany.md +627 -0
  58. package/docs/methods/joins.md +781 -0
  59. package/docs/methods/rawQuery.md +284 -0
  60. package/docs/methods/transactions.md +737 -0
  61. package/eslint.config.mjs +77 -0
  62. package/index.ts +5 -4
  63. package/jest.config.ts +23 -28
  64. package/package.json +53 -30
  65. package/scripts/release.sh +123 -0
  66. package/src/core/repository.ts +865 -0
  67. package/src/{default → core}/types.ts +10 -1
  68. package/src/{default → core}/utils.ts +168 -0
  69. package/src/db/IDatabaseClient.ts +11 -1
  70. package/src/db/__tests__/mysqlClient.test.ts +262 -0
  71. package/src/db/__tests__/pgClient.test.ts +260 -0
  72. package/src/db/initDb.ts +120 -1
  73. package/src/db/mysqlClient.ts +119 -3
  74. package/src/db/pgClient.ts +131 -3
  75. package/src/monitor/monitor.ts +5 -0
  76. package/src/setupTests.ts +45 -0
  77. package/tsconfig.test.json +21 -0
  78. package/.eslintignore +0 -4
  79. package/.eslintrc.json +0 -32
  80. package/dist/.eslintrc.json +0 -32
  81. package/dist/src/default/genericRepository.d.ts +0 -28
  82. package/dist/src/default/genericRepository.js +0 -284
  83. package/dist/src/default/genericRepository.js.map +0 -1
  84. package/dist/src/default/utils.d.ts +0 -9
  85. package/dist/src/default/utils.js.map +0 -1
  86. package/index.d.ts +0 -60
  87. package/src/default/genericRepository.ts +0 -461
  88. /package/dist/src/{default → core}/types.js +0 -0
@@ -55,7 +55,6 @@ export type DBClients = 'pg' | 'mysql'
55
55
 
56
56
  export type OrderBy = { field: string; direction: 'ASC' | 'DESC' }[]
57
57
 
58
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
59
58
  export interface QueryParams<T> {
60
59
  tableName: string
61
60
  dbClient: IDatabaseClient
@@ -86,3 +85,13 @@ export interface QueryBuilder {
86
85
  limit?: string
87
86
  offset?: string
88
87
  }
88
+
89
+ export interface RawQueryParams {
90
+ dbClient: IDatabaseClient
91
+ sql: string
92
+ params?: any[]
93
+ }
94
+
95
+ export interface TransactionParams {
96
+ dbClient: IDatabaseClient
97
+ }
@@ -1,5 +1,22 @@
1
1
  import { Conditions, Condition, OrderBy, DBClients } from './types'
2
2
 
3
+ /**
4
+ * Converts an array of strings to a comma-separated string with quotes
5
+ *
6
+ * This function takes an array of strings and converts it to a comma-separated string
7
+ * with quotes. It handles the differences between PostgreSQL and MySQL syntax for
8
+ * string arrays.
9
+ *
10
+ * @param items - The array of strings to convert
11
+ * @param clientType - The type of database client
12
+ * @returns A comma-separated string with quotes
13
+ *
14
+ * @example
15
+ * const items = ['item1', 'item2', 'item3']
16
+ * const clientType = 'pg'
17
+ * const result = arrayToStringWithQuotes(items, clientType)
18
+ * // result will be: 'item1', 'item2', 'item3'
19
+ */
3
20
  const arrayToStringWithQuotes = (
4
21
  items: string[],
5
22
  clientType: DBClients
@@ -10,9 +27,52 @@ const arrayToStringWithQuotes = (
10
27
  return itemsWithQuotes.join(', ')
11
28
  }
12
29
 
30
+ /**
31
+ * Generates a PostgreSQL placeholder for a parameter
32
+ *
33
+ * This function generates a PostgreSQL placeholder for a parameter. It returns
34
+ * a string with a dollar sign and the index of the parameter.
35
+ *
36
+ * @param index - The index of the parameter
37
+ * @returns A PostgreSQL placeholder
38
+ *
39
+ * @example
40
+ * const index = 1
41
+ * const placeholder = pgPlaceholderGenerator(index)
42
+ * // placeholder will be: $1
43
+ */
13
44
  const pgPlaceholderGenerator = (index: number) => `$${index}`
45
+
46
+ /**
47
+ * Generates a MySQL placeholder for a parameter
48
+ *
49
+ * This function generates a MySQL placeholder for a parameter. It returns
50
+ * a string with a question mark.
51
+ *
52
+ * @returns A MySQL placeholder
53
+ *
54
+ * @example
55
+ * const placeholder = mysqlPlaceholderGenerator()
56
+ * // placeholder will be: ?
57
+ */
14
58
  const mysqlPlaceholderGenerator = () => `?`
15
59
 
60
+ /**
61
+ * Creates a SELECT clause for a query
62
+ *
63
+ * This function creates a SELECT clause for a query. It takes an array of fields
64
+ * and a database client type and returns a string with the fields separated by commas.
65
+ *
66
+ * @param fields - The array of fields to select
67
+ * @param clientType - The type of database client
68
+ * @returns A string with the fields separated by commas
69
+ *
70
+ * @example
71
+ * const fields = ['id', 'name', 'email']
72
+ * const clientType = 'pg'
73
+ * const result = createSelectFields(fields, clientType)
74
+ * // result will be: "id, name, email"
75
+ */
16
76
  export const createSelectFields = (
17
77
  fields: string[] = [],
18
78
  clientType: DBClients
@@ -22,6 +82,22 @@ export const createSelectFields = (
22
82
  : '*'
23
83
  }
24
84
 
85
+ /**
86
+ * Generates placeholders for a query
87
+ *
88
+ * This function generates placeholders for a query. It takes an array of keys
89
+ * and a database client type and returns a string with the placeholders separated by commas.
90
+ *
91
+ * @param keys - The array of keys to generate placeholders for
92
+ * @param clientType - The type of database client
93
+ * @returns A string with the placeholders separated by commas
94
+ *
95
+ * @example
96
+ * const keys = ['id', 'name', 'email']
97
+ * const clientType = 'pg'
98
+ * const result = generatePlaceholders(keys, clientType)
99
+ * // result will be: $1, $2, $3
100
+ */
25
101
  export const generatePlaceholders = (
26
102
  keys: any[],
27
103
  clientType: DBClients
@@ -31,6 +107,22 @@ export const generatePlaceholders = (
31
107
  .join(', ')
32
108
  }
33
109
 
110
+ /**
111
+ * Generates a SET clause for a query
112
+ *
113
+ * This function generates a SET clause for a query. It takes an array of keys
114
+ * and a database client type and returns a string with the keys and placeholders separated by commas.
115
+ *
116
+ * @param keys - The array of keys to generate SET clause for
117
+ * @param clientType - The type of database client
118
+ * @returns A string with the keys and placeholders separated by commas
119
+ *
120
+ * @example
121
+ * const keys = ['id', 'name', 'email']
122
+ * const clientType = 'pg'
123
+ * const result = generateSetClause(keys, clientType)
124
+ * // result will be: "id = $1, name = $2, email = $3"
125
+ */
34
126
  export const generateSetClause = (
35
127
  keys: any[],
36
128
  clientType: DBClients
@@ -42,6 +134,26 @@ export const generateSetClause = (
42
134
  .join(', ')
43
135
  }
44
136
 
137
+ /**
138
+ * Creates a WHERE clause for a query
139
+ *
140
+ * This function creates a WHERE clause for a query. It takes an array of conditions
141
+ * and a database client type and returns a string with the conditions separated by AND.
142
+ *
143
+ * @param conditions - The array of conditions to create WHERE clause for
144
+ * @param startIndex - The index of the first parameter
145
+ * @param clientType - The type of database client
146
+ * @param unaccent - Whether to use unaccent function
147
+ * @returns A string with the conditions separated by AND
148
+ *
149
+ * @example
150
+ * const conditions = [{ field: 'name', operator: '=', value: 'John Doe' }]
151
+ * const startIndex = 1
152
+ * const clientType = 'pg'
153
+ * const unaccent = true
154
+ * const result = createWhereClause(conditions, startIndex, clientType, unaccent)
155
+ * // result will be: "name = $1"
156
+ */
45
157
  export const createWhereClause = <T>(
46
158
  conditions: Conditions<T> = {},
47
159
  startIndex = 1,
@@ -167,22 +279,78 @@ export const createWhereClause = <T>(
167
279
  return [whereClause, values, index]
168
280
  }
169
281
 
282
+ /**
283
+ * Creates an ORDER BY clause for a query
284
+ *
285
+ * This function creates an ORDER BY clause for a query. It takes an array of
286
+ * order by fields and returns a string with the fields separated by commas.
287
+ *
288
+ * @param orderBy - The array of order by fields
289
+ * @returns A string with the fields separated by commas
290
+ *
291
+ * @example
292
+ * const orderBy = [{ field: 'created_at', direction: 'DESC' }]
293
+ * const result = createOrderByClause(orderBy)
294
+ * // result will be: "ORDER BY created_at DESC"
295
+ */
170
296
  export const createOrderByClause = (orderBy?: OrderBy) => {
171
297
  if (!orderBy || orderBy.length === 0) return ''
172
298
  const clause = orderBy.map((o) => `${o.field} ${o.direction}`).join(', ')
173
299
  return ` ORDER BY ${clause}`
174
300
  }
175
301
 
302
+ /**
303
+ * Creates a GROUP BY clause for a query
304
+ *
305
+ * This function creates a GROUP BY clause for a query. It takes an array of
306
+ * group by fields and returns a string with the fields separated by commas.
307
+ *
308
+ * @param groupBy - The array of group by fields
309
+ * @returns A string with the fields separated by commas
310
+ *
311
+ * @example
312
+ * const groupBy = ['status']
313
+ * const result = createGroupByClause(groupBy)
314
+ * // result will be: "GROUP BY status"
315
+ */
176
316
  export const createGroupByClause = (groupBy?: string[]) => {
177
317
  if (!groupBy || groupBy.length === 0) return ''
178
318
  return ` GROUP BY ${groupBy.join(', ')}`
179
319
  }
180
320
 
321
+ /**
322
+ * Creates a LIMIT clause for a query
323
+ *
324
+ * This function creates a LIMIT clause for a query. It takes a limit number
325
+ * and returns a string with the limit.
326
+ *
327
+ * @param limit - The limit number
328
+ * @returns A string with the limit
329
+ *
330
+ * @example
331
+ * const limit = 10
332
+ * const result = createLimitClause(limit)
333
+ * // result will be: "LIMIT 10"
334
+ */
181
335
  export const createLimitClause = (limit?: number) => {
182
336
  if (!limit) return ''
183
337
  return ` LIMIT ${limit}`
184
338
  }
185
339
 
340
+ /**
341
+ * Creates an OFFSET clause for a query
342
+ *
343
+ * This function creates an OFFSET clause for a query. It takes an offset number
344
+ * and returns a string with the offset.
345
+ *
346
+ * @param offset - The offset number
347
+ * @returns A string with the offset
348
+ *
349
+ * @example
350
+ * const offset = 10
351
+ * const result = createOffsetClause(offset)
352
+ * // result will be: "OFFSET 10"
353
+ */
186
354
  export const createOffsetClause = (offset?: number) => {
187
355
  if (!offset) return ''
188
356
  return ` OFFSET ${offset}`
@@ -1,6 +1,16 @@
1
- import { DBClients } from '../default/types'
1
+ import { DBClients } from '../core/types'
2
2
 
3
+ export interface ITransactionClient {
4
+ query: <T>(sql: string, params?: any[]) => Promise<T>
5
+ commit: () => Promise<void>
6
+ rollback: () => Promise<void>
7
+ }
8
+
9
+ /**
10
+ * Database client interface
11
+ */
3
12
  export type IDatabaseClient = {
4
13
  clientType: DBClients
5
14
  query: <T>(sql: string, params?: any[]) => Promise<T>
15
+ beginTransaction: () => Promise<ITransactionClient>
6
16
  }
@@ -0,0 +1,262 @@
1
+ import { createMysqlClient } from '../mysqlClient'
2
+ import { createPool, Pool } from 'mysql2/promise'
3
+
4
+ // Mock mysql2 module
5
+ jest.mock('mysql2/promise', () => ({
6
+ createPool: jest.fn(),
7
+ }))
8
+
9
+ // Mock monitor
10
+ jest.mock('../../monitor/monitor', () => ({
11
+ monitor: {
12
+ emit: jest.fn(),
13
+ },
14
+ MonitorEvents: {
15
+ CONNECTION_CREATED: 'CONNECTION_CREATED',
16
+ QUERY_START: 'QUERY_START',
17
+ QUERY_END: 'QUERY_END',
18
+ QUERY_ERROR: 'QUERY_ERROR',
19
+ RETRY_ATTEMPT: 'RETRY_ATTEMPT',
20
+ TRANSACTION_COMMIT: 'TRANSACTION_COMMIT',
21
+ TRANSACTION_ROLLBACK: 'TRANSACTION_ROLLBACK',
22
+ },
23
+ }))
24
+
25
+ describe('MySqlClient', () => {
26
+ let mockPool: jest.Mocked<Pool>
27
+
28
+ beforeEach(() => {
29
+ mockPool = {
30
+ query: jest.fn(),
31
+ connect: jest.fn(),
32
+ end: jest.fn(),
33
+ execute: jest.fn(),
34
+ getConnection: jest.fn(),
35
+ config: {},
36
+ } as any
37
+ ;(createPool as jest.MockedFunction<typeof createPool>).mockReturnValue(
38
+ mockPool
39
+ )
40
+ })
41
+
42
+ afterEach(() => {
43
+ jest.clearAllMocks()
44
+ })
45
+
46
+ describe('createMysqlClient', () => {
47
+ it('should return client with correct clientType', () => {
48
+ const client = createMysqlClient(mockPool)
49
+
50
+ expect(client.clientType).toBe('mysql')
51
+ })
52
+
53
+ it('should execute query and return rows', async () => {
54
+ const mockRows = [{ id: 1, name: 'John Doe' }]
55
+ mockPool.execute.mockResolvedValue([mockRows, []] as any)
56
+
57
+ const client = createMysqlClient(mockPool)
58
+ const result = await client.query('SELECT * FROM users WHERE id = ?', [1])
59
+
60
+ expect(mockPool.execute).toHaveBeenCalledWith(
61
+ 'SELECT * FROM users WHERE id = ?',
62
+ [1]
63
+ )
64
+ expect(result).toEqual(mockRows)
65
+ })
66
+
67
+ it('should handle query without parameters', async () => {
68
+ const mockRows = [{ id: 1, name: 'John Doe' }]
69
+ mockPool.execute.mockResolvedValue([mockRows, []] as any)
70
+
71
+ const client = createMysqlClient(mockPool)
72
+ const result = await client.query('SELECT * FROM users')
73
+
74
+ expect(mockPool.execute).toHaveBeenCalledWith(
75
+ 'SELECT * FROM users',
76
+ undefined
77
+ )
78
+ expect(result).toEqual(mockRows)
79
+ })
80
+
81
+ it('should handle empty result set', async () => {
82
+ mockPool.execute.mockResolvedValue([[], []] as any)
83
+
84
+ const client = createMysqlClient(mockPool)
85
+ const result = await client.query('SELECT * FROM users WHERE id = ?', [
86
+ 999,
87
+ ])
88
+
89
+ expect(result).toEqual([])
90
+ })
91
+
92
+ it('should handle query errors', async () => {
93
+ const error = new Error('Database connection failed')
94
+ mockPool.execute.mockRejectedValue(error)
95
+
96
+ const client = createMysqlClient(mockPool)
97
+
98
+ await expect(client.query('SELECT * FROM users')).rejects.toThrow(
99
+ 'Database connection failed'
100
+ )
101
+ })
102
+
103
+ it('should handle retry options', async () => {
104
+ const mockRows = [{ id: 1, name: 'John Doe' }]
105
+ mockPool.execute.mockResolvedValue([mockRows, []] as any)
106
+
107
+ const retryOptions = {
108
+ retries: 3,
109
+ factor: 2,
110
+ minTimeout: 1000,
111
+ maxTimeout: 5000,
112
+ }
113
+
114
+ const client = createMysqlClient(mockPool, retryOptions)
115
+ const result = await client.query('SELECT * FROM users')
116
+
117
+ expect(result).toEqual(mockRows)
118
+ })
119
+
120
+ it('should handle transient errors with retry', async () => {
121
+ const transientError = new Error('Connection lost')
122
+ ;(transientError as any).code = 'ECONNRESET'
123
+
124
+ const mockRows = [{ id: 1, name: 'John Doe' }]
125
+
126
+ // First call fails with transient error, second succeeds
127
+ mockPool.execute
128
+ .mockRejectedValueOnce(transientError)
129
+ .mockResolvedValueOnce([mockRows, []] as any)
130
+
131
+ const retryOptions = {
132
+ retries: 3,
133
+ factor: 2,
134
+ minTimeout: 100,
135
+ maxTimeout: 500,
136
+ }
137
+
138
+ const client = createMysqlClient(mockPool, retryOptions)
139
+ const result = await client.query('SELECT * FROM users')
140
+
141
+ expect(mockPool.execute).toHaveBeenCalledTimes(2)
142
+ expect(result).toEqual(mockRows)
143
+ })
144
+
145
+ it('should throw non-transient errors immediately', async () => {
146
+ const permanentError = new Error('Table does not exist')
147
+ ;(permanentError as any).code = 'ER_NO_SUCH_TABLE'
148
+
149
+ mockPool.execute.mockRejectedValue(permanentError)
150
+
151
+ const retryOptions = {
152
+ retries: 3,
153
+ factor: 2,
154
+ minTimeout: 100,
155
+ maxTimeout: 500,
156
+ }
157
+
158
+ const client = createMysqlClient(mockPool, retryOptions)
159
+
160
+ await expect(
161
+ client.query('SELECT * FROM nonexistent_table')
162
+ ).rejects.toThrow('Table does not exist')
163
+ expect(mockPool.execute).toHaveBeenCalledTimes(1)
164
+ })
165
+
166
+ describe('beginTransaction', () => {
167
+ let mockConnection: any
168
+
169
+ beforeEach(() => {
170
+ mockConnection = {
171
+ execute: jest.fn(),
172
+ beginTransaction: jest.fn(),
173
+ commit: jest.fn(),
174
+ rollback: jest.fn(),
175
+ release: jest.fn(),
176
+ }
177
+ mockPool.getConnection.mockResolvedValue(mockConnection)
178
+ })
179
+
180
+ it('should create a transaction client', async () => {
181
+ const client = createMysqlClient(mockPool)
182
+ const transaction = await client.beginTransaction()
183
+
184
+ expect(mockPool.getConnection).toHaveBeenCalled()
185
+ expect(mockConnection.beginTransaction).toHaveBeenCalled()
186
+ expect(transaction).toHaveProperty('query')
187
+ expect(transaction).toHaveProperty('commit')
188
+ expect(transaction).toHaveProperty('rollback')
189
+ })
190
+
191
+ it('should execute queries within transaction', async () => {
192
+ const mockRows = [{ id: 1, name: 'John Doe' }]
193
+ mockConnection.execute.mockResolvedValue([mockRows, []])
194
+
195
+ const client = createMysqlClient(mockPool)
196
+ const transaction = await client.beginTransaction()
197
+ const result = await transaction.query(
198
+ 'SELECT * FROM users WHERE id = ?',
199
+ [1]
200
+ )
201
+
202
+ expect(mockConnection.execute).toHaveBeenCalledWith(
203
+ 'SELECT * FROM users WHERE id = ?',
204
+ [1]
205
+ )
206
+ expect(result).toEqual(mockRows)
207
+ })
208
+
209
+ it('should commit transaction successfully', async () => {
210
+ const client = createMysqlClient(mockPool)
211
+ const transaction = await client.beginTransaction()
212
+ await transaction.commit()
213
+
214
+ expect(mockConnection.commit).toHaveBeenCalled()
215
+ expect(mockConnection.release).toHaveBeenCalled()
216
+ })
217
+
218
+ it('should rollback transaction on error', async () => {
219
+ const client = createMysqlClient(mockPool)
220
+ const transaction = await client.beginTransaction()
221
+ await transaction.rollback()
222
+
223
+ expect(mockConnection.rollback).toHaveBeenCalled()
224
+ expect(mockConnection.release).toHaveBeenCalled()
225
+ })
226
+
227
+ it('should release connection even if commit fails', async () => {
228
+ const commitError = new Error('Commit failed')
229
+ mockConnection.commit.mockRejectedValue(commitError)
230
+
231
+ const client = createMysqlClient(mockPool)
232
+ const transaction = await client.beginTransaction()
233
+
234
+ await expect(transaction.commit()).rejects.toThrow('Commit failed')
235
+ expect(mockConnection.release).toHaveBeenCalled()
236
+ })
237
+
238
+ it('should release connection even if rollback fails', async () => {
239
+ const rollbackError = new Error('Rollback failed')
240
+ mockConnection.rollback.mockRejectedValue(rollbackError)
241
+
242
+ const client = createMysqlClient(mockPool)
243
+ const transaction = await client.beginTransaction()
244
+
245
+ await expect(transaction.rollback()).rejects.toThrow('Rollback failed')
246
+ expect(mockConnection.release).toHaveBeenCalled()
247
+ })
248
+
249
+ it('should release connection if beginTransaction fails', async () => {
250
+ const beginError = new Error('Begin transaction failed')
251
+ mockConnection.beginTransaction.mockRejectedValue(beginError)
252
+
253
+ const client = createMysqlClient(mockPool)
254
+
255
+ await expect(client.beginTransaction()).rejects.toThrow(
256
+ 'Begin transaction failed'
257
+ )
258
+ expect(mockConnection.release).toHaveBeenCalled()
259
+ })
260
+ })
261
+ })
262
+ })