@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
package/README.md CHANGED
@@ -1,270 +1,1450 @@
1
- # NodeJS Star DB Query Builder
1
+ # Star DB Query Builder
2
+
3
+ A powerful and flexible database query builder library for Node.js applications, supporting PostgreSQL and MySQL databases with TypeScript support.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#-features)
8
+ - [Installation](#-installation)
9
+ - [Quick Start](#-quick-start)
10
+ - [Database Initialization](#database-initialization)
11
+ - [Query Methods](#query-methods)
12
+ - [findFirst](#findfirst)
13
+ - [findMany](#findmany)
14
+ - [insert](#insert)
15
+ - [insertMany](#insertmany)
16
+ - [update](#update)
17
+ - [updateMany](#updatemany)
18
+ - [deleteOne](#deleteone)
19
+ - [deleteMany](#deletemany)
20
+ - [joins](#joins)
21
+ - [rawQuery](#rawquery)
22
+ - [Transactions](#transactions)
23
+ - [withTransaction](#withtransaction)
24
+ - [beginTransaction](#begintransaction)
25
+ - [Types and Interfaces](#types-and-interfaces)
26
+ - [Advanced Usage](#advanced-usage)
27
+ - [Monitoring](#monitoring)
28
+ - [Best Practices](#best-practices)
29
+ - [Error Handling](#error-handling)
30
+ - [Contributing](#contributing)
31
+ - [License](#license)
32
+
33
+ ## ✨ Features
34
+
35
+ ### 🔧 **Core Functionality**
36
+
37
+ - **🔄 Multi-Connection Support**: Connect simultaneously to multiple PostgreSQL and MySQL databases
38
+ - **🛡️ Type Safety**: Complete TypeScript support with strong typing
39
+ - **⚡ Auto Retry**: Automatic retry for transient errors (timeouts, lost connections)
40
+ - **📊 Monitoring**: Event system for monitoring and logging
41
+ - **🔍 Query Builder**: Fluent interface for building complex queries
42
+ - **📦 Batch Operations**: Optimized batch operations (insertMany, updateMany)
43
+
44
+ ### 🗄️ **Database Support**
45
+
46
+ - **PostgreSQL**: Complete support with extensions (unaccent)
47
+ - **MySQL**: Full compatibility with MySQL 5.7+
48
+ - **Connection Pooling**: Efficient connection management
49
+ - **Transaction Support**: Full ACID transaction support with automatic rollback
50
+ - **Raw SQL**: Execute custom SQL queries when needed
51
+
52
+ ### 🛠️ **Development Tools**
53
+
54
+ - **ESLint + Prettier**: Clean and consistent code
55
+ - **Jest**: Unit and integration tests
56
+ - **Husky**: Git hooks for code quality
57
+ - **TypeScript**: Compilation and typing
58
+
59
+ ## 📦 Installation
2
60
 
3
- 🎉 Welcome to the NodeJS Database Library! This library provides a set of robust methods to interact with your database seamlessly. With TypeScript support, it ensures type safety and great developer experience.
4
-
5
- ## Features
61
+ ```bash
62
+ npm install @starbemtech/star-db-query-builder
63
+ # or
64
+ pnpm add @starbemtech/star-db-query-builder
65
+ # or
66
+ yarn add @starbemtech/star-db-query-builder
67
+ ```
6
68
 
7
- - **Multi-Connection Support:** Simultaneously connect to multiple MySQL and PostgreSQL databases.
8
- - **Automatic Retry:** Automatically retries queries in case of transient errors (e.g. connection loss or timeouts).
9
- - **External Configuration:** Customize connection pool settings and retry parameters through external configuration.
10
- - **Monitoring and Logging:** Emits events during the connection and query lifecycle, making it easier to integrate with your logging and monitoring systems.
69
+ ## Quick Start
11
70
 
12
- ## Installation
71
+ ```typescript
72
+ import {
73
+ initDb,
74
+ getDbClient,
75
+ findFirst,
76
+ insert,
77
+ } from '@starbemtech/star-db-query-builder'
78
+
79
+ // Initialize database connection
80
+ await initDb({
81
+ type: 'pg', // or 'mysql'
82
+ options: {
83
+ host: 'localhost',
84
+ port: 5432,
85
+ database: 'myapp',
86
+ user: 'username',
87
+ password: 'password',
88
+ },
89
+ })
13
90
 
14
- ```bash
15
- // Use npm
16
- $ npm install star-db-query-builder
91
+ // Get database client
92
+ const dbClient = getDbClient()
17
93
 
18
- // Use yarn
19
- $ yarn add star-db-query-builder
94
+ // Find a user
95
+ const user = await findFirst({
96
+ tableName: 'users',
97
+ dbClient,
98
+ where: { email: { operator: '=', value: 'user@example.com' } },
99
+ })
20
100
 
21
- // Use pnpm
22
- $ pnpm install star-db-query-builder
101
+ // Insert a new user
102
+ const newUser = await insert({
103
+ tableName: 'users',
104
+ dbClient,
105
+ data: { name: 'John Doe', email: 'john@example.com' },
106
+ })
23
107
  ```
24
108
 
25
- ## Usage
109
+ ## Database Initialization
26
110
 
27
- ### Initialization
111
+ ### initDb
28
112
 
29
- First, initialize the database with the appropriate configuration.
113
+ Initializes a database connection with the specified configuration.
30
114
 
31
115
  ```typescript
32
- import { initDb, getDbClient, PoolConfig } from 'star-db-query-builder';
116
+ await initDb({
117
+ name?: string, // Optional client name (default: 'default')
118
+ type: 'pg' | 'mysql', // Database type
119
+ options: PoolConfig | MySqlPoolOptions, // Connection options
120
+ retryOptions?: RetryOptions, // Optional retry configuration
121
+ installUnaccentExtension?: boolean // PostgreSQL unaccent extension
122
+ })
123
+ ```
33
124
 
34
- // Use PostgresSQL
35
- const pgPoolOptions: PoolConfig = {
36
- host: process.env.PG_HOST,
37
- user: process.env.PG_USER,
38
- password: process.env.PG_PASS,
39
- database: process.env.PG_DB,
40
- // OR
41
- connectionURL: 'YOUR POSTGRES CONNECTION URL'
42
- max: Number(process.env.PG_POOL_MAX) || 10,
43
- connectionTimeoutMillis: Number(process.env.PG_CONN_TIMEOUT) || 0,
44
- // Other pool options as needed
45
- }
125
+ #### PostgreSQL Example
46
126
 
47
- initDb({
48
- name: 'pg-prod',
127
+ ```typescript
128
+ import { initDb } from '@starbemtech/star-db-query-builder'
129
+
130
+ await initDb({
131
+ name: 'main',
49
132
  type: 'pg',
50
- options: pgPoolOptions,
133
+ options: {
134
+ host: 'localhost',
135
+ port: 5432,
136
+ database: 'myapp',
137
+ user: 'postgres',
138
+ password: 'password',
139
+ max: 20,
140
+ idleTimeoutMillis: 30000,
141
+ connectionTimeoutMillis: 2000,
142
+ },
51
143
  retryOptions: {
52
144
  retries: 3,
53
145
  factor: 2,
54
146
  minTimeout: 1000,
55
- // Other retry parameters if needed
56
- }
57
- });
147
+ maxTimeout: 5000,
148
+ },
149
+ installUnaccentExtension: true,
150
+ })
151
+ ```
152
+
153
+ #### MySQL Example
154
+
155
+ ```typescript
156
+ import { initDb } from '@starbemtech/star-db-query-builder'
58
157
 
59
- // User MySQL
60
- initDb({
61
- name: 'mysql-prod',
158
+ await initDb({
159
+ name: 'analytics',
62
160
  type: 'mysql',
63
161
  options: {
64
- host: process.env.MYSQL_HOST,
65
- user: process.env.MYSQL_USER,
66
- password: process.env.MYSQL_PASS,
67
- database: process.env.MYSQL_DB,
68
- connectionLimit: Number(process.env.MYSQL_CONN_LIMIT) || 10,
69
- // OR
70
- url: 'YOUR MYSQL CONNECTION URL'
71
- // Other pool options as needed
162
+ host: 'localhost',
163
+ port: 3306,
164
+ database: 'analytics',
165
+ user: 'root',
166
+ password: 'password',
167
+ connectionLimit: 10,
168
+ acquireTimeout: 60000,
169
+ timeout: 60000,
72
170
  },
73
- retryOptions: {
74
- retries: 3,
75
- factor: 2,
76
- minTimeout: 1000,
77
- // Other retry parameters if needed
78
- }
79
- });
171
+ })
172
+ ```
173
+
174
+ ### getDbClient
175
+
176
+ Retrieves a database client by name.
177
+
178
+ ```typescript
179
+ const dbClient = getDbClient(name?: string)
180
+ ```
181
+
182
+ ```typescript
183
+ // Get default client
184
+ const defaultClient = getDbClient()
80
185
 
81
- // In your service, create an instance of getDbClient
82
- const dbClient = getDbClient('pg-prod');
186
+ // Get named client
187
+ const analyticsClient = getDbClient('analytics')
83
188
  ```
84
189
 
85
- ### Methods
190
+ ## Query Methods
86
191
 
87
- #### findFirst
192
+ ### findFirst
88
193
 
89
- Retrieve the first matching record from a table.
194
+ Finds the first record that matches the specified conditions.
90
195
 
91
196
  ```typescript
92
- import { findFirst } from 'star-db-query-builder'
197
+ const result = await findFirst<T>({
198
+ tableName: string,
199
+ dbClient: IDatabaseClient,
200
+ select?: string[],
201
+ where?: Conditions<T>,
202
+ groupBy?: string[],
203
+ orderBy?: OrderBy
204
+ })
205
+ ```
93
206
 
94
- const result = await findFirst({
207
+ #### Examples
208
+
209
+ ```typescript
210
+ // Find user by email
211
+ const user = await findFirst({
212
+ tableName: 'users',
213
+ dbClient,
214
+ where: {
215
+ email: { operator: '=', value: 'user@example.com' },
216
+ },
217
+ })
218
+
219
+ // Find with specific fields
220
+ const user = await findFirst({
95
221
  tableName: 'users',
96
222
  dbClient,
97
223
  select: ['id', 'name', 'email'],
98
224
  where: {
99
- id: { operator: '=', value: 1 },
225
+ status: { operator: '=', value: 'active' },
100
226
  },
101
227
  })
102
228
 
103
- console.log(result)
229
+ // Find with complex conditions
230
+ const user = await findFirst({
231
+ tableName: 'users',
232
+ dbClient,
233
+ where: {
234
+ AND: [
235
+ { email: { operator: '=', value: 'user@example.com' } },
236
+ { status: { operator: '=', value: 'active' } },
237
+ ],
238
+ },
239
+ })
240
+
241
+ // Find with ordering
242
+ const latestUser = await findFirst({
243
+ tableName: 'users',
244
+ dbClient,
245
+ orderBy: [{ field: 'created_at', direction: 'DESC' }],
246
+ })
104
247
  ```
105
248
 
106
- #### findMany
249
+ ### findMany
107
250
 
108
- Retrieve multiple records from a table.
251
+ Finds multiple records that match the specified conditions.
109
252
 
110
253
  ```typescript
111
- import { findMany } from 'star-db-query-builder'
254
+ const results = await findMany<T>({
255
+ tableName: string,
256
+ dbClient: IDatabaseClient,
257
+ select?: string[],
258
+ where?: Conditions<T>,
259
+ groupBy?: string[],
260
+ orderBy?: OrderBy,
261
+ limit?: number,
262
+ offset?: number,
263
+ unaccent?: boolean
264
+ })
265
+ ```
112
266
 
113
- const results = await findMany({
267
+ #### Examples
268
+
269
+ ```typescript
270
+ // Find all active users
271
+ const users = await findMany({
114
272
  tableName: 'users',
115
273
  dbClient,
116
- select: ['id', 'name', 'email'],
117
274
  where: {
118
- status: { operator: '= ', value: 'active' },
275
+ status: { operator: '=', value: 'active' },
119
276
  },
277
+ })
278
+
279
+ // Find with pagination
280
+ const users = await findMany({
281
+ tableName: 'users',
282
+ dbClient,
120
283
  limit: 10,
121
- offset: 0,
284
+ offset: 20,
285
+ orderBy: [{ field: 'created_at', direction: 'DESC' }],
122
286
  })
123
287
 
124
- console.log(results)
288
+ // Find with complex conditions
289
+ const users = await findMany({
290
+ tableName: 'users',
291
+ dbClient,
292
+ where: {
293
+ OR: [
294
+ { status: { operator: '=', value: 'active' } },
295
+ { status: { operator: '=', value: 'pending' } },
296
+ ],
297
+ created_at: {
298
+ operator: '>=',
299
+ value: new Date('2023-01-01'),
300
+ },
301
+ },
302
+ })
303
+
304
+ // Find with grouping
305
+ const userStats = await findMany({
306
+ tableName: 'users',
307
+ dbClient,
308
+ select: ['status', 'COUNT(*) as count'],
309
+ groupBy: ['status'],
310
+ })
125
311
  ```
126
312
 
127
- #### insert
313
+ ### insert
128
314
 
129
- Insert a new record into a table.
315
+ Inserts a single record into the database.
130
316
 
131
317
  ```typescript
132
- import { insert } from 'star-db-query-builder'
318
+ const result = await insert<P, R>({
319
+ tableName: string,
320
+ dbClient: IDatabaseClient,
321
+ data: P,
322
+ returning?: string[]
323
+ })
324
+ ```
133
325
 
134
- const newUser = { name: 'John Doe', email: 'john@example.com' }
326
+ #### Examples
135
327
 
136
- const insertedUser = await insert({
328
+ ```typescript
329
+ // Simple insert
330
+ const user = await insert({
137
331
  tableName: 'users',
138
332
  dbClient,
139
- data: newUser,
140
- returning: ['id', 'name', 'email'],
333
+ data: {
334
+ name: 'John Doe',
335
+ email: 'john@example.com',
336
+ age: 30,
337
+ },
338
+ })
339
+
340
+ // Insert with specific returning fields
341
+ const user = await insert({
342
+ tableName: 'users',
343
+ dbClient,
344
+ data: {
345
+ name: 'Jane Doe',
346
+ email: 'jane@example.com',
347
+ },
348
+ returning: ['id', 'name', 'email', 'created_at'],
141
349
  })
142
350
 
143
- console.log(insertedUser)
351
+ // Insert with TypeScript typing
352
+ interface UserData {
353
+ name: string
354
+ email: string
355
+ age: number
356
+ }
357
+
358
+ interface User {
359
+ id: string
360
+ name: string
361
+ email: string
362
+ age: number
363
+ created_at: Date
364
+ updated_at: Date
365
+ }
366
+
367
+ const user: User = await insert<UserData, User>({
368
+ tableName: 'users',
369
+ dbClient,
370
+ data: {
371
+ name: 'John Doe',
372
+ email: 'john@example.com',
373
+ age: 30,
374
+ },
375
+ })
144
376
  ```
145
377
 
146
- #### update
378
+ ### insertMany
147
379
 
148
- Update an existing record in a table.
380
+ Inserts multiple records into the database in a single operation.
149
381
 
150
382
  ```typescript
151
- import { update } from 'star-db-query-builder'
383
+ const results = await insertMany<P, R>({
384
+ tableName: string,
385
+ dbClient: IDatabaseClient,
386
+ data: P[],
387
+ returning?: string[]
388
+ })
389
+ ```
152
390
 
153
- const updatedUser = { name: 'John Smith' }
391
+ #### Examples
392
+
393
+ ```typescript
394
+ // Insert multiple users
395
+ const users = await insertMany({
396
+ tableName: 'users',
397
+ dbClient,
398
+ data: [
399
+ { name: 'John Doe', email: 'john@example.com' },
400
+ { name: 'Jane Doe', email: 'jane@example.com' },
401
+ { name: 'Bob Smith', email: 'bob@example.com' },
402
+ ],
403
+ })
154
404
 
155
- const result = await update({
405
+ // Insert with returning fields
406
+ const users = await insertMany({
156
407
  tableName: 'users',
157
408
  dbClient,
158
- id: 1,
159
- data: updatedUser,
409
+ data: [
410
+ { name: 'John Doe', email: 'john@example.com' },
411
+ { name: 'Jane Doe', email: 'jane@example.com' },
412
+ ],
160
413
  returning: ['id', 'name', 'email'],
161
414
  })
415
+ ```
416
+
417
+ ### update
418
+
419
+ Updates a single record by ID.
420
+
421
+ ```typescript
422
+ const result = await update<P, R>({
423
+ tableName: string,
424
+ dbClient: IDatabaseClient,
425
+ id: string,
426
+ data: P,
427
+ returning?: string[]
428
+ })
429
+ ```
430
+
431
+ #### Examples
432
+
433
+ ```typescript
434
+ // Simple update
435
+ const updatedUser = await update({
436
+ tableName: 'users',
437
+ dbClient,
438
+ id: 'user-123',
439
+ data: {
440
+ name: 'John Updated',
441
+ age: 31,
442
+ },
443
+ })
444
+
445
+ // Update with returning fields
446
+ const updatedUser = await update({
447
+ tableName: 'users',
448
+ dbClient,
449
+ id: 'user-123',
450
+ data: {
451
+ status: 'active',
452
+ last_login: new Date(),
453
+ },
454
+ returning: ['id', 'status', 'last_login', 'updated_at'],
455
+ })
456
+ ```
457
+
458
+ ### updateMany
459
+
460
+ Updates multiple records based on specified conditions.
461
+
462
+ ```typescript
463
+ const results = await updateMany<P, R>({
464
+ tableName: string,
465
+ dbClient: IDatabaseClient,
466
+ data: P,
467
+ where: Conditions<T>,
468
+ returning?: string[]
469
+ })
470
+ ```
471
+
472
+ #### Examples
473
+
474
+ ```typescript
475
+ // Update all inactive users
476
+ const updatedUsers = await updateMany({
477
+ tableName: 'users',
478
+ dbClient,
479
+ data: {
480
+ status: 'active',
481
+ updated_at: new Date(),
482
+ },
483
+ where: {
484
+ status: { operator: '=', value: 'inactive' },
485
+ },
486
+ })
162
487
 
163
- console.log(result)
488
+ // Update with complex conditions
489
+ const updatedUsers = await updateMany({
490
+ tableName: 'users',
491
+ dbClient,
492
+ data: {
493
+ last_login: new Date(),
494
+ login_count: { operator: '+', value: 1 },
495
+ },
496
+ where: {
497
+ AND: [
498
+ { status: { operator: '=', value: 'active' } },
499
+ { last_login: { operator: '<', value: new Date('2023-01-01') } },
500
+ ],
501
+ },
502
+ returning: ['id', 'name', 'last_login', 'login_count'],
503
+ })
164
504
  ```
165
505
 
166
- #### deleteOne
506
+ ### deleteOne
167
507
 
168
- Delete a record from a table.
508
+ Deletes a single record by ID (soft delete by default).
169
509
 
170
510
  ```typescript
171
- import { deleteOne } from 'star-db-query-builder'
511
+ await deleteOne<T>({
512
+ tableName: string,
513
+ dbClient: IDatabaseClient,
514
+ id: string,
515
+ permanently?: boolean
516
+ })
517
+ ```
172
518
 
519
+ #### Examples
520
+
521
+ ```typescript
522
+ // Soft delete (sets status to 'deleted')
173
523
  await deleteOne({
174
524
  tableName: 'users',
175
525
  dbClient,
176
- id: 1,
177
- permanently: true,
526
+ id: 'user-123',
178
527
  })
179
528
 
180
- console.log('User deleted')
529
+ // Permanent delete
530
+ await deleteOne({
531
+ tableName: 'users',
532
+ dbClient,
533
+ id: 'user-123',
534
+ permanently: true,
535
+ })
181
536
  ```
182
537
 
183
- #### joins
538
+ ### deleteMany
539
+
540
+ Deletes multiple records by IDs (soft delete by default).
184
541
 
185
- Execute a join query.
542
+ ```typescript
543
+ await deleteMany<T>({
544
+ tableName: string,
545
+ dbClient: IDatabaseClient,
546
+ ids: string[] | number[],
547
+ field?: string,
548
+ permanently?: boolean
549
+ })
550
+ ```
551
+
552
+ #### Examples
186
553
 
187
554
  ```typescript
188
- import { joins } from 'star-db-query-builder'
555
+ // Soft delete multiple users
556
+ await deleteMany({
557
+ tableName: 'users',
558
+ dbClient,
559
+ ids: ['user-1', 'user-2', 'user-3'],
560
+ })
189
561
 
190
- const joinResults = await joins({
562
+ // Permanent delete with custom field
563
+ await deleteMany({
191
564
  tableName: 'orders',
192
565
  dbClient,
193
- select: ['orders.id', 'users.name'],
566
+ ids: [1, 2, 3],
567
+ field: 'order_id',
568
+ permanently: true,
569
+ })
570
+ ```
571
+
572
+ ### joins
573
+
574
+ Executes queries with JOIN operations.
575
+
576
+ ```typescript
577
+ const results = await joins<T>({
578
+ tableName: string,
579
+ dbClient: IDatabaseClient,
580
+ select: string[],
581
+ joins: JoinClause[],
582
+ where?: Conditions<T>,
583
+ groupBy?: string[],
584
+ orderBy?: OrderBy,
585
+ limit?: number,
586
+ offset?: number,
587
+ unaccent?: boolean
588
+ })
589
+ ```
590
+
591
+ #### Examples
592
+
593
+ ```typescript
594
+ // Simple JOIN
595
+ const usersWithOrders = await joins({
596
+ tableName: 'users',
597
+ dbClient,
598
+ select: ['users.id', 'users.name', 'orders.total'],
599
+ joins: [
600
+ {
601
+ type: 'LEFT',
602
+ table: 'orders',
603
+ on: 'users.id = orders.user_id',
604
+ },
605
+ ],
606
+ where: {
607
+ 'users.status': { operator: '=', value: 'active' },
608
+ },
609
+ })
610
+
611
+ // Multiple JOINs
612
+ const report = await joins({
613
+ tableName: 'users',
614
+ dbClient,
615
+ select: [
616
+ 'users.name',
617
+ 'users.email',
618
+ 'COUNT(orders.id) as order_count',
619
+ 'SUM(orders.total) as total_spent',
620
+ 'plans.name as plan_name',
621
+ ],
194
622
  joins: [
195
623
  {
196
- table: 'users',
197
- on: { 'orders.userId': 'users.id' },
624
+ type: 'LEFT',
625
+ table: 'orders',
626
+ on: 'users.id = orders.user_id',
627
+ },
628
+ {
629
+ type: 'LEFT',
630
+ table: 'user_plans',
631
+ on: 'users.id = user_plans.user_id',
632
+ },
633
+ {
634
+ type: 'LEFT',
635
+ table: 'plans',
636
+ on: 'user_plans.plan_id = plans.id',
198
637
  },
199
638
  ],
639
+ groupBy: ['users.id', 'users.name', 'users.email', 'plans.name'],
640
+ having: {
641
+ 'COUNT(orders.id)': { operator: '>', value: 0 },
642
+ },
643
+ orderBy: [{ field: 'total_spent', direction: 'DESC' }],
644
+ })
645
+ ```
646
+
647
+ ### rawQuery
648
+
649
+ Executes raw SQL queries directly on the database.
650
+
651
+ ```typescript
652
+ const result = await rawQuery<T>({
653
+ dbClient: IDatabaseClient,
654
+ sql: string,
655
+ params?: any[]
656
+ })
657
+ ```
658
+
659
+ #### Examples
660
+
661
+ ```typescript
662
+ // Simple raw query
663
+ const users = await rawQuery({
664
+ dbClient,
665
+ sql: 'SELECT * FROM users WHERE active = true',
666
+ })
667
+
668
+ // Raw query with parameters
669
+ const user = await rawQuery({
670
+ dbClient,
671
+ sql: 'SELECT * FROM users WHERE id = ? AND email = ?',
672
+ params: ['user-123', 'user@example.com'],
673
+ })
674
+
675
+ // Complex aggregation
676
+ const stats = await rawQuery({
677
+ dbClient,
678
+ sql: `
679
+ SELECT
680
+ COUNT(*) as total_users,
681
+ AVG(age) as avg_age,
682
+ MAX(created_at) as last_created
683
+ FROM users
684
+ WHERE created_at >= ?
685
+ `,
686
+ params: [new Date('2023-01-01')],
687
+ })
688
+ ```
689
+
690
+ ## Transactions
691
+
692
+ Execute multiple database operations within a single transaction to ensure data consistency and atomicity.
693
+
694
+ ### withTransaction
695
+
696
+ Executes a function within a database transaction with automatic commit/rollback handling.
697
+
698
+ ```typescript
699
+ const result = await withTransaction<T>(
700
+ dbClient: IDatabaseClient,
701
+ transactionFn: (tx: ITransactionClient) => Promise<T>
702
+ ): Promise<T>
703
+ ```
704
+
705
+ #### Examples
706
+
707
+ ```typescript
708
+ import {
709
+ withTransaction,
710
+ insert,
711
+ update,
712
+ } from '@starbemtech/star-db-query-builder'
713
+
714
+ // Create user with profile in a single transaction
715
+ const createUserWithProfile = async (userData: any, profileData: any) => {
716
+ return withTransaction(dbClient, async (tx) => {
717
+ // Create user
718
+ const user = await insert({
719
+ tableName: 'users',
720
+ dbClient: tx,
721
+ data: userData,
722
+ })
723
+
724
+ // Create user profile
725
+ const profile = await insert({
726
+ tableName: 'user_profiles',
727
+ dbClient: tx,
728
+ data: {
729
+ ...profileData,
730
+ user_id: user.id,
731
+ },
732
+ })
733
+
734
+ return { user, profile }
735
+ })
736
+ }
737
+
738
+ // E-commerce order processing
739
+ const processOrder = async (orderData: any, orderItems: any[]) => {
740
+ return withTransaction(dbClient, async (tx) => {
741
+ // Create order
742
+ const order = await insert({
743
+ tableName: 'orders',
744
+ dbClient: tx,
745
+ data: {
746
+ ...orderData,
747
+ status: 'pending',
748
+ total: 0,
749
+ },
750
+ })
751
+
752
+ let totalAmount = 0
753
+
754
+ // Create order items and calculate total
755
+ for (const item of orderItems) {
756
+ await insert({
757
+ tableName: 'order_items',
758
+ dbClient: tx,
759
+ data: {
760
+ ...item,
761
+ order_id: order.id,
762
+ },
763
+ })
764
+
765
+ totalAmount += item.price * item.quantity
766
+
767
+ // Update product stock
768
+ await update({
769
+ tableName: 'products',
770
+ dbClient: tx,
771
+ id: item.product_id,
772
+ data: {
773
+ stock: { operator: '-', value: item.quantity },
774
+ },
775
+ })
776
+ }
777
+
778
+ // Update order total
779
+ await update({
780
+ tableName: 'orders',
781
+ dbClient: tx,
782
+ id: order.id,
783
+ data: {
784
+ total: totalAmount,
785
+ status: 'confirmed',
786
+ },
787
+ })
788
+
789
+ return { order, totalAmount }
790
+ })
791
+ }
792
+ ```
793
+
794
+ ### beginTransaction
795
+
796
+ Creates a transaction client for manual transaction management.
797
+
798
+ ```typescript
799
+ const transaction = await beginTransaction(dbClient: IDatabaseClient): Promise<ITransactionClient>
800
+ ```
801
+
802
+ #### Examples
803
+
804
+ ```typescript
805
+ import {
806
+ beginTransaction,
807
+ insert,
808
+ update,
809
+ } from '@starbemtech/star-db-query-builder'
810
+
811
+ // Manual transaction management
812
+ const complexOperation = async () => {
813
+ const transaction = await beginTransaction(dbClient)
814
+
815
+ try {
816
+ // First operation
817
+ const user = await insert({
818
+ tableName: 'users',
819
+ dbClient: transaction,
820
+ data: { name: 'John Doe', email: 'john@example.com' },
821
+ })
822
+
823
+ // Second operation
824
+ const profile = await insert({
825
+ tableName: 'user_profiles',
826
+ dbClient: transaction,
827
+ data: { user_id: user.id, bio: 'Hello world' },
828
+ })
829
+
830
+ // Third operation
831
+ await update({
832
+ tableName: 'users',
833
+ dbClient: transaction,
834
+ id: user.id,
835
+ data: { profile_created: true },
836
+ })
837
+
838
+ // Commit all changes
839
+ await transaction.commit()
840
+ return { user, profile }
841
+ } catch (error) {
842
+ // Rollback on any error
843
+ await transaction.rollback()
844
+ throw error
845
+ }
846
+ }
847
+ ```
848
+
849
+ ### ITransactionClient Interface
850
+
851
+ ```typescript
852
+ interface ITransactionClient {
853
+ query: <T>(sql: string, params?: any[]) => Promise<T>
854
+ commit: () => Promise<void>
855
+ rollback: () => Promise<void>
856
+ }
857
+ ```
858
+
859
+ ## Types and Interfaces
860
+
861
+ ### Conditions
862
+
863
+ Used for building WHERE clauses with type safety.
864
+
865
+ ```typescript
866
+ type Conditions<T> = {
867
+ [P in keyof T]?: Condition<T[P]>
868
+ } & LogicalCondition<T>
869
+
870
+ type Condition<T> = OperatorCondition | LogicalCondition<T>
871
+
872
+ interface OperatorCondition {
873
+ operator:
874
+ | '='
875
+ | '!='
876
+ | '>'
877
+ | '<'
878
+ | '>='
879
+ | '<='
880
+ | 'LIKE'
881
+ | 'NOT LIKE'
882
+ | 'ILIKE'
883
+ | 'IN'
884
+ | 'NOT IN'
885
+ | 'BETWEEN'
886
+ | 'IS NULL'
887
+ | 'IS NOT NULL'
888
+ | 'NOT EXISTS'
889
+ value: SimpleValue | SimpleValue[]
890
+ }
891
+
892
+ interface LogicalCondition<T> {
893
+ OR?: Conditions<T>[]
894
+ AND?: Conditions<T>[]
895
+ JOINS?: Conditions<object>
896
+ notExists?: OperatorCondition
897
+ }
898
+ ```
899
+
900
+ ### OrderBy
901
+
902
+ Used for specifying sort order.
903
+
904
+ ```typescript
905
+ type OrderBy = { field: string; direction: 'ASC' | 'DESC' }[]
906
+ ```
907
+
908
+ ### JoinClause
909
+
910
+ Used for JOIN operations.
911
+
912
+ ```typescript
913
+ interface JoinClause {
914
+ type: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'
915
+ table: string
916
+ on: string
917
+ }
918
+ ```
919
+
920
+ ## Advanced Usage
921
+
922
+ ### Complex WHERE Conditions
923
+
924
+ ```typescript
925
+ const users = await findMany({
926
+ tableName: 'users',
927
+ dbClient,
200
928
  where: {
201
- JOINS: [
929
+ AND: [
930
+ { status: { operator: '=', value: 'active' } },
202
931
  {
203
- 'users.id': { operator: '=', value: exist.user_id },
932
+ OR: [
933
+ { age: { operator: '>=', value: 18 } },
934
+ { verified: { operator: '=', value: true } },
935
+ ],
204
936
  },
937
+ { created_at: { operator: '>=', value: new Date('2023-01-01') } },
205
938
  ],
206
939
  },
207
940
  })
941
+ ```
942
+
943
+ ### Using Unaccent for PostgreSQL
208
944
 
209
- console.log(joinResults)
945
+ ```typescript
946
+ const users = await findMany({
947
+ tableName: 'users',
948
+ dbClient,
949
+ where: {
950
+ name: { operator: 'ILIKE', value: '%joão%' },
951
+ },
952
+ unaccent: true, // Enables unaccent search
953
+ })
210
954
  ```
211
955
 
212
- ### Monitoring and Logging
956
+ ### Using Unaccent for PostgreSQL
213
957
 
214
- The library provides a monitoring module that emits key events during the lifecycle of connections and queries. You can subscribe to these events to integrate with your logging or monitoring system.
958
+ ```typescript
959
+ const users = await findMany({
960
+ tableName: 'users',
961
+ dbClient,
962
+ where: {
963
+ name: { operator: 'ILIKE', value: '%joão%' },
964
+ },
965
+ unaccent: true, // Enables unaccent search
966
+ })
967
+ ```
215
968
 
216
- #### Available Events
969
+ ## Monitoring
217
970
 
218
- CONNECTION_CREATED: Emitted when a new connection (pool) is established.
219
- QUERY_START: Emitted just before a query starts executing.
220
- QUERY_END: Emitted after a query completes, including its execution time.
221
- QUERY_ERROR: Emitted when an error occurs during query execution.
222
- RETRY_ATTEMPT: Emitted when a query is retried due to a transient error.
971
+ The library provides a comprehensive monitoring system to track database operations and performance.
223
972
 
224
- ```ts
973
+ ### Monitor Events
974
+
975
+ ```typescript
225
976
  import { monitor, MonitorEvents } from '@starbemtech/star-db-query-builder'
226
977
 
978
+ // Monitor connection events
227
979
  monitor.on(MonitorEvents.CONNECTION_CREATED, (data) => {
228
- console.log('Connection created:', data)
980
+ console.log('Database connection created:', data)
229
981
  })
230
982
 
983
+ // Monitor query events
231
984
  monitor.on(MonitorEvents.QUERY_START, (data) => {
232
- console.log('Query started:', data)
985
+ console.log('Query started:', {
986
+ sql: data.sql,
987
+ params: data.params,
988
+ clientType: data.clientType,
989
+ attempt: data.attempt,
990
+ })
233
991
  })
234
992
 
235
993
  monitor.on(MonitorEvents.QUERY_END, (data) => {
236
- console.log('Query finished:', data)
994
+ console.log('Query completed:', {
995
+ elapsedTime: data.elapsedTime,
996
+ clientType: data.clientType,
997
+ })
237
998
  })
238
999
 
239
1000
  monitor.on(MonitorEvents.QUERY_ERROR, (data) => {
240
- console.error('Query error:', data)
1001
+ console.error('Query failed:', {
1002
+ error: data.error,
1003
+ sql: data.sql,
1004
+ elapsedTime: data.elapsedTime,
1005
+ })
1006
+ })
1007
+
1008
+ // Monitor transaction events
1009
+ monitor.on(MonitorEvents.TRANSACTION_COMMIT, (data) => {
1010
+ console.log('Transaction committed:', data)
1011
+ })
1012
+
1013
+ monitor.on(MonitorEvents.TRANSACTION_ROLLBACK, (data) => {
1014
+ console.log('Transaction rolled back:', data)
241
1015
  })
242
1016
 
1017
+ // Monitor retry attempts
243
1018
  monitor.on(MonitorEvents.RETRY_ATTEMPT, (data) => {
244
- console.warn('Retrying query:', data)
1019
+ console.warn('Retry attempt:', {
1020
+ attempt: data.attempt,
1021
+ error: data.error,
1022
+ sql: data.sql,
1023
+ })
245
1024
  })
246
1025
  ```
247
1026
 
248
- ## Customizing the Retry Strategy
1027
+ ### Custom Monitoring Implementation
1028
+
1029
+ ```typescript
1030
+ // Example: Log all database operations to a file
1031
+ import fs from 'fs'
1032
+ import path from 'path'
1033
+
1034
+ const logFile = path.join(__dirname, 'database.log')
1035
+
1036
+ monitor.on(MonitorEvents.QUERY_START, (data) => {
1037
+ const logEntry = {
1038
+ timestamp: new Date().toISOString(),
1039
+ event: 'QUERY_START',
1040
+ sql: data.sql,
1041
+ params: data.params,
1042
+ clientType: data.clientType,
1043
+ }
249
1044
 
250
- The automatic retry mechanism leverages the promise-retry library. You can customize the following parameters:
1045
+ fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n')
1046
+ })
251
1047
 
252
- - retries: Number of retry attempts.
253
- - factor: Exponential backoff factor.
254
- - minTimeout: Minimum time (in milliseconds) to wait between retry attempts.
1048
+ monitor.on(MonitorEvents.QUERY_END, (data) => {
1049
+ const logEntry = {
1050
+ timestamp: new Date().toISOString(),
1051
+ event: 'QUERY_END',
1052
+ elapsedTime: data.elapsedTime,
1053
+ clientType: data.clientType,
1054
+ }
255
1055
 
256
- These parameters are passed via the retryOptions property when initializing the connection.
1056
+ fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n')
1057
+ })
1058
+ ```
1059
+
1060
+ ### Performance Monitoring
1061
+
1062
+ ```typescript
1063
+ // Track slow queries
1064
+ monitor.on(MonitorEvents.QUERY_END, (data) => {
1065
+ if (data.elapsedTime > 1000) {
1066
+ // Queries taking more than 1 second
1067
+ console.warn('Slow query detected:', {
1068
+ sql: data.sql,
1069
+ elapsedTime: data.elapsedTime,
1070
+ clientType: data.clientType,
1071
+ })
1072
+ }
1073
+ })
1074
+
1075
+ // Track connection pool usage
1076
+ monitor.on(MonitorEvents.CONNECTION_CREATED, (data) => {
1077
+ console.log('Connection pool status:', {
1078
+ clientType: data.clientType,
1079
+ poolOptions: data.poolOptions,
1080
+ })
1081
+ })
1082
+ ```
1083
+
1084
+ ## Best Practices
1085
+
1086
+ ### 1. Use TypeScript Types
1087
+
1088
+ ```typescript
1089
+ interface User {
1090
+ id: string
1091
+ name: string
1092
+ email: string
1093
+ created_at: Date
1094
+ }
1095
+
1096
+ const users: User[] = await findMany<User>({
1097
+ tableName: 'users',
1098
+ dbClient,
1099
+ where: { status: { operator: '=', value: 'active' } },
1100
+ })
1101
+ ```
1102
+
1103
+ ### 2. Use Specific Field Selection
1104
+
1105
+ ```typescript
1106
+ // Good: Select only needed fields
1107
+ const users = await findMany({
1108
+ tableName: 'users',
1109
+ dbClient,
1110
+ select: ['id', 'name', 'email'],
1111
+ where: { status: { operator: '=', value: 'active' } },
1112
+ })
1113
+
1114
+ // Avoid: Selecting all fields when not needed
1115
+ const users = await findMany({
1116
+ tableName: 'users',
1117
+ dbClient,
1118
+ where: { status: { operator: '=', value: 'active' } },
1119
+ })
1120
+ ```
1121
+
1122
+ ### 3. Use Pagination for Large Datasets
1123
+
1124
+ ```typescript
1125
+ const users = await findMany({
1126
+ tableName: 'users',
1127
+ dbClient,
1128
+ limit: 50,
1129
+ offset: 0,
1130
+ orderBy: [{ field: 'created_at', direction: 'DESC' }],
1131
+ })
1132
+ ```
1133
+
1134
+ ### 4. Use Batch Operations When Possible
1135
+
1136
+ ```typescript
1137
+ // Good: Batch insert
1138
+ const users = await insertMany({
1139
+ tableName: 'users',
1140
+ dbClient,
1141
+ data: userArray,
1142
+ })
1143
+
1144
+ // Avoid: Multiple individual inserts
1145
+ for (const user of userArray) {
1146
+ await insert({ tableName: 'users', dbClient, data: user })
1147
+ }
1148
+ ```
1149
+
1150
+ ### 5. Handle Errors Properly
1151
+
1152
+ ```typescript
1153
+ try {
1154
+ const user = await findFirst({
1155
+ tableName: 'users',
1156
+ dbClient,
1157
+ where: { email: { operator: '=', value: 'user@example.com' } },
1158
+ })
1159
+ } catch (error) {
1160
+ console.error('Database error:', error.message)
1161
+ // Handle error appropriately
1162
+ }
1163
+ ```
1164
+
1165
+ ### 6. Use Raw Queries Sparingly
1166
+
1167
+ ```typescript
1168
+ // Use built-in methods when possible
1169
+ const users = await findMany({
1170
+ tableName: 'users',
1171
+ dbClient,
1172
+ where: { status: { operator: '=', value: 'active' } },
1173
+ })
1174
+
1175
+ // Use rawQuery only for complex operations
1176
+ const complexStats = await rawQuery({
1177
+ dbClient,
1178
+ sql: 'SELECT ... complex aggregation ...',
1179
+ })
1180
+ ```
1181
+
1182
+ ### 7. Use Transactions for Data Consistency
1183
+
1184
+ ```typescript
1185
+ // Good: Use transactions for related operations
1186
+ const createUserWithProfile = async (userData: any, profileData: any) => {
1187
+ return withTransaction(dbClient, async (tx) => {
1188
+ const user = await insert({
1189
+ tableName: 'users',
1190
+ dbClient: tx,
1191
+ data: userData,
1192
+ })
1193
+
1194
+ await insert({
1195
+ tableName: 'user_profiles',
1196
+ dbClient: tx,
1197
+ data: { ...profileData, user_id: user.id },
1198
+ })
1199
+
1200
+ return user
1201
+ })
1202
+ }
1203
+
1204
+ // Avoid: Multiple separate operations without transactions
1205
+ const badUserCreation = async (userData: any, profileData: any) => {
1206
+ const user = await insert({
1207
+ tableName: 'users',
1208
+ dbClient,
1209
+ data: userData,
1210
+ })
1211
+
1212
+ // If this fails, the user will be created but profile won't
1213
+ await insert({
1214
+ tableName: 'user_profiles',
1215
+ dbClient,
1216
+ data: { ...profileData, user_id: user.id },
1217
+ })
1218
+
1219
+ return user
1220
+ }
1221
+ ```
1222
+
1223
+ ### 8. Keep Transactions Short
1224
+
1225
+ ```typescript
1226
+ // Good: Short, focused transaction
1227
+ const updateUserStatus = async (userId: string, status: string) => {
1228
+ return withTransaction(dbClient, async (tx) => {
1229
+ await update({
1230
+ tableName: 'users',
1231
+ dbClient: tx,
1232
+ id: userId,
1233
+ data: { status },
1234
+ })
1235
+
1236
+ await insert({
1237
+ tableName: 'user_status_history',
1238
+ dbClient: tx,
1239
+ data: { user_id: userId, status, changed_at: new Date() },
1240
+ })
1241
+ })
1242
+ }
1243
+
1244
+ // Avoid: Long-running transactions
1245
+ const badTransaction = async () => {
1246
+ return withTransaction(dbClient, async (tx) => {
1247
+ // ... many operations
1248
+ await someSlowOperation() // This could timeout
1249
+ // ... more operations
1250
+ })
1251
+ }
1252
+ ```
1253
+
1254
+ ## Error Handling
1255
+
1256
+ The library throws descriptive errors for common issues:
1257
+
1258
+ ### Common Errors
1259
+
1260
+ - `Table name is required`
1261
+ - `DB client is required`
1262
+ - `Data object is required`
1263
+ - `ID is required`
1264
+ - `Where condition is required`
1265
+ - `Raw query execution failed: [database message]`
1266
+ - `Transaction execution failed: [database message]`
1267
+
1268
+ ### Transaction Error Handling
1269
+
1270
+ ```typescript
1271
+ import {
1272
+ withTransaction,
1273
+ insert,
1274
+ update,
1275
+ } from '@starbemtech/star-db-query-builder'
1276
+
1277
+ const safeTransaction = async () => {
1278
+ try {
1279
+ return await withTransaction(dbClient, async (tx) => {
1280
+ // Transaction operations
1281
+ const result = await someOperation(tx)
1282
+ return result
1283
+ })
1284
+ } catch (error) {
1285
+ // Transaction was automatically rolled back
1286
+ console.error('Transaction failed:', error.message)
1287
+
1288
+ // Handle specific error types
1289
+ if (error.message.includes('deadlock detected')) {
1290
+ // Handle deadlock - you might want to retry
1291
+ console.warn('Deadlock detected, retrying...')
1292
+ // Implement retry logic
1293
+ } else if (error.message.includes('serialization failure')) {
1294
+ // Handle serialization failure
1295
+ console.warn('Serialization failure, retrying...')
1296
+ // Implement retry logic
1297
+ } else if (error.message.includes('connection lost')) {
1298
+ // Handle connection issues
1299
+ console.error('Database connection lost')
1300
+ // Implement reconnection logic
1301
+ } else {
1302
+ // Handle other errors
1303
+ console.error('Transaction error:', error.message)
1304
+ }
1305
+
1306
+ throw error
1307
+ }
1308
+ }
1309
+ ```
1310
+
1311
+ ### Retry Logic for Transient Errors
1312
+
1313
+ ```typescript
1314
+ const retryTransaction = async <T>(
1315
+ transactionFn: (tx: ITransactionClient) => Promise<T>,
1316
+ maxRetries: number = 3
1317
+ ): Promise<T> => {
1318
+ let lastError: Error
1319
+
1320
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1321
+ try {
1322
+ return await withTransaction(dbClient, transactionFn)
1323
+ } catch (error) {
1324
+ lastError = error as Error
1325
+
1326
+ // Check if error is retryable
1327
+ if (isRetryableError(error) && attempt < maxRetries) {
1328
+ const delay = Math.pow(2, attempt) * 1000 // Exponential backoff
1329
+ console.warn(
1330
+ `Transaction attempt ${attempt} failed, retrying in ${delay}ms...`
1331
+ )
1332
+ await new Promise((resolve) => setTimeout(resolve, delay))
1333
+ continue
1334
+ }
1335
+
1336
+ throw error
1337
+ }
1338
+ }
1339
+
1340
+ throw lastError!
1341
+ }
1342
+
1343
+ const isRetryableError = (error: any): boolean => {
1344
+ const retryableErrors = [
1345
+ 'deadlock detected',
1346
+ 'serialization failure',
1347
+ 'connection lost',
1348
+ 'timeout',
1349
+ ]
1350
+
1351
+ return retryableErrors.some((msg) =>
1352
+ error.message?.toLowerCase().includes(msg)
1353
+ )
1354
+ }
1355
+ ```
1356
+
1357
+ Always wrap database operations in try-catch blocks and handle errors appropriately in your application.
257
1358
 
258
1359
  ## Contributing
259
1360
 
260
- Feel free to contribute by opening pull requests or issues with improvements and bug fixes.
1361
+ We welcome contributions to the Star DB Query Builder! Here's how you can help:
1362
+
1363
+ ### Development Setup
1364
+
1365
+ 1. **Clone the repository**
1366
+
1367
+ ```bash
1368
+ git clone https://github.com/starbem/star-db-query-builder.git
1369
+ cd star-db-query-builder
1370
+ ```
1371
+
1372
+ 2. **Install dependencies**
1373
+
1374
+ ```bash
1375
+ pnpm install
1376
+ ```
1377
+
1378
+ 3. **Run tests**
1379
+
1380
+ ```bash
1381
+ pnpm test
1382
+ ```
1383
+
1384
+ 4. **Run linting**
1385
+
1386
+ ```bash
1387
+ pnpm lint
1388
+ ```
1389
+
1390
+ 5. **Build the project**
1391
+ ```bash
1392
+ pnpm build
1393
+ ```
1394
+
1395
+ ### Contributing Guidelines
1396
+
1397
+ - **Code Style**: Follow the existing code style and use Prettier for formatting
1398
+ - **TypeScript**: Maintain strict TypeScript typing
1399
+ - **Tests**: Add tests for new features and bug fixes
1400
+ - **Documentation**: Update documentation for any API changes
1401
+ - **Commit Messages**: Use conventional commit messages
1402
+
1403
+ ### Pull Request Process
1404
+
1405
+ 1. Fork the repository
1406
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1407
+ 3. Make your changes
1408
+ 4. Add tests for your changes
1409
+ 5. Ensure all tests pass (`pnpm test`)
1410
+ 6. Run linting (`pnpm lint`)
1411
+ 7. Commit your changes (`git commit -m 'feat: add amazing feature'`)
1412
+ 8. Push to your branch (`git push origin feature/amazing-feature`)
1413
+ 9. Open a Pull Request
1414
+
1415
+ ### Reporting Issues
1416
+
1417
+ When reporting issues, please include:
1418
+
1419
+ - **Environment**: Node.js version, database type and version
1420
+ - **Steps to Reproduce**: Clear steps to reproduce the issue
1421
+ - **Expected Behavior**: What you expected to happen
1422
+ - **Actual Behavior**: What actually happened
1423
+ - **Code Sample**: Minimal code sample that demonstrates the issue
1424
+
1425
+ ### Feature Requests
1426
+
1427
+ For feature requests, please:
1428
+
1429
+ - **Describe the feature**: Clear description of what you want
1430
+ - **Use Case**: Explain why this feature would be useful
1431
+ - **Proposed API**: If you have ideas for the API design
1432
+ - **Alternatives**: Any alternative solutions you've considered
261
1433
 
262
1434
  ## License
263
1435
 
264
- This project is licensed under the MIT License.
1436
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1437
+
1438
+ ## Support
1439
+
1440
+ - **Documentation**: [GitHub Wiki](https://github.com/starbem/star-db-query-builder/wiki)
1441
+ - **Issues**: [GitHub Issues](https://github.com/starbem/star-db-query-builder/issues)
1442
+ - **Discussions**: [GitHub Discussions](https://github.com/starbem/star-db-query-builder/discussions)
1443
+
1444
+ ## Changelog
265
1445
 
266
- This documentation explains how to set up connections with external configuration for both connection pool and retry options, retrieve clients to execute queries, and monitor events for logging and diagnostics.
1446
+ See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.
267
1447
 
268
1448
  ---
269
1449
 
270
- 👨‍💻 Happy Coding!
1450
+ Made with ❤️ by the Starbem team