@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.
- package/.github/workflows/publish.yml +118 -0
- package/ARCHITECTURE.md +313 -0
- package/CHANGELOG.md +65 -0
- package/README.md +1309 -129
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/lcov-report/block-navigation.js +1 -1
- package/coverage/lcov-report/index.html +41 -11
- package/coverage/lcov-report/mysqlClient.ts.html +685 -0
- package/coverage/lcov-report/pgClient.ts.html +823 -0
- package/coverage/lcov-report/sorter.js +21 -7
- package/coverage/lcov.info +533 -0
- package/coverage/mysqlClient.ts.html +685 -0
- package/coverage/pgClient.ts.html +823 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/core/repository.d.ts +374 -0
- package/dist/src/core/repository.js +677 -0
- package/dist/src/core/repository.js.map +1 -0
- package/dist/src/{default → core}/types.d.ts +9 -1
- package/dist/src/{default → core}/types.js.map +1 -1
- package/dist/src/core/utils.d.ts +133 -0
- package/dist/src/{default → core}/utils.js +167 -0
- package/dist/src/core/utils.js.map +1 -0
- package/dist/src/db/IDatabaseClient.d.ts +10 -1
- package/dist/src/db/initDb.d.ts +120 -1
- package/dist/src/db/initDb.js +119 -0
- package/dist/src/db/initDb.js.map +1 -1
- package/dist/src/db/mysqlClient.d.ts +23 -1
- package/dist/src/db/mysqlClient.js +115 -0
- package/dist/src/db/mysqlClient.js.map +1 -1
- package/dist/src/db/pgClient.d.ts +22 -1
- package/dist/src/db/pgClient.js +127 -0
- package/dist/src/db/pgClient.js.map +1 -1
- package/dist/src/monitor/monitor.d.ts +6 -1
- package/dist/src/monitor/monitor.js +5 -0
- package/dist/src/monitor/monitor.js.map +1 -1
- package/dist/src/setupTests.d.ts +26 -0
- package/dist/src/setupTests.js +43 -0
- package/dist/src/setupTests.js.map +1 -0
- package/docs/INDEX.md +145 -0
- package/docs/methods/findFirst.md +394 -0
- package/docs/methods/findMany.md +587 -0
- package/docs/methods/insert.md +536 -0
- package/docs/methods/insertMany.md +627 -0
- package/docs/methods/joins.md +781 -0
- package/docs/methods/rawQuery.md +284 -0
- package/docs/methods/transactions.md +737 -0
- package/eslint.config.mjs +77 -0
- package/index.ts +5 -4
- package/jest.config.ts +23 -28
- package/package.json +52 -29
- package/scripts/release.sh +123 -0
- package/src/core/repository.ts +865 -0
- package/src/{default → core}/types.ts +11 -2
- package/src/{default → core}/utils.ts +168 -0
- package/src/db/IDatabaseClient.ts +11 -1
- package/src/db/__tests__/mysqlClient.test.ts +262 -0
- package/src/db/__tests__/pgClient.test.ts +260 -0
- package/src/db/initDb.ts +120 -1
- package/src/db/mysqlClient.ts +119 -3
- package/src/db/pgClient.ts +131 -3
- package/src/monitor/monitor.ts +5 -0
- package/src/setupTests.ts +45 -0
- package/tsconfig.test.json +21 -0
- package/.eslintignore +0 -4
- package/.eslintrc.json +0 -32
- package/coverage/clover.xml +0 -6
- package/coverage/coverage-final.json +0 -1
- package/dist/.eslintrc.json +0 -32
- package/dist/src/default/genericRepository.d.ts +0 -20
- package/dist/src/default/genericRepository.js +0 -192
- package/dist/src/default/genericRepository.js.map +0 -1
- package/dist/src/default/utils.d.ts +0 -9
- package/dist/src/default/utils.js.map +0 -1
- package/index.d.ts +0 -60
- package/src/default/genericRepository.ts +0 -320
- /package/dist/src/{default → core}/types.js +0 -0
package/README.md
CHANGED
|
@@ -1,270 +1,1450 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
$ npm install star-db-query-builder
|
|
91
|
+
// Get database client
|
|
92
|
+
const dbClient = getDbClient()
|
|
17
93
|
|
|
18
|
-
//
|
|
19
|
-
|
|
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
|
-
//
|
|
22
|
-
|
|
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
|
-
##
|
|
109
|
+
## Database Initialization
|
|
26
110
|
|
|
27
|
-
###
|
|
111
|
+
### initDb
|
|
28
112
|
|
|
29
|
-
|
|
113
|
+
Initializes a database connection with the specified configuration.
|
|
30
114
|
|
|
31
115
|
```typescript
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
127
|
+
```typescript
|
|
128
|
+
import { initDb } from '@starbemtech/star-db-query-builder'
|
|
129
|
+
|
|
130
|
+
await initDb({
|
|
131
|
+
name: 'main',
|
|
49
132
|
type: 'pg',
|
|
50
|
-
options:
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
name: 'mysql-prod',
|
|
158
|
+
await initDb({
|
|
159
|
+
name: 'analytics',
|
|
62
160
|
type: 'mysql',
|
|
63
161
|
options: {
|
|
64
|
-
host:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
//
|
|
82
|
-
const
|
|
186
|
+
// Get named client
|
|
187
|
+
const analyticsClient = getDbClient('analytics')
|
|
83
188
|
```
|
|
84
189
|
|
|
85
|
-
|
|
190
|
+
## Query Methods
|
|
86
191
|
|
|
87
|
-
|
|
192
|
+
### findFirst
|
|
88
193
|
|
|
89
|
-
|
|
194
|
+
Finds the first record that matches the specified conditions.
|
|
90
195
|
|
|
91
196
|
```typescript
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
+
status: { operator: '=', value: 'active' },
|
|
100
226
|
},
|
|
101
227
|
})
|
|
102
228
|
|
|
103
|
-
|
|
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
|
-
|
|
249
|
+
### findMany
|
|
107
250
|
|
|
108
|
-
|
|
251
|
+
Finds multiple records that match the specified conditions.
|
|
109
252
|
|
|
110
253
|
```typescript
|
|
111
|
-
|
|
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
|
-
|
|
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: '=
|
|
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:
|
|
284
|
+
offset: 20,
|
|
285
|
+
orderBy: [{ field: 'created_at', direction: 'DESC' }],
|
|
122
286
|
})
|
|
123
287
|
|
|
124
|
-
|
|
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
|
-
|
|
313
|
+
### insert
|
|
128
314
|
|
|
129
|
-
|
|
315
|
+
Inserts a single record into the database.
|
|
130
316
|
|
|
131
317
|
```typescript
|
|
132
|
-
|
|
318
|
+
const result = await insert<P, R>({
|
|
319
|
+
tableName: string,
|
|
320
|
+
dbClient: IDatabaseClient,
|
|
321
|
+
data: P,
|
|
322
|
+
returning?: string[]
|
|
323
|
+
})
|
|
324
|
+
```
|
|
133
325
|
|
|
134
|
-
|
|
326
|
+
#### Examples
|
|
135
327
|
|
|
136
|
-
|
|
328
|
+
```typescript
|
|
329
|
+
// Simple insert
|
|
330
|
+
const user = await insert({
|
|
137
331
|
tableName: 'users',
|
|
138
332
|
dbClient,
|
|
139
|
-
data:
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
378
|
+
### insertMany
|
|
147
379
|
|
|
148
|
-
|
|
380
|
+
Inserts multiple records into the database in a single operation.
|
|
149
381
|
|
|
150
382
|
```typescript
|
|
151
|
-
|
|
383
|
+
const results = await insertMany<P, R>({
|
|
384
|
+
tableName: string,
|
|
385
|
+
dbClient: IDatabaseClient,
|
|
386
|
+
data: P[],
|
|
387
|
+
returning?: string[]
|
|
388
|
+
})
|
|
389
|
+
```
|
|
152
390
|
|
|
153
|
-
|
|
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
|
-
|
|
405
|
+
// Insert with returning fields
|
|
406
|
+
const users = await insertMany({
|
|
156
407
|
tableName: 'users',
|
|
157
408
|
dbClient,
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
506
|
+
### deleteOne
|
|
167
507
|
|
|
168
|
-
|
|
508
|
+
Deletes a single record by ID (soft delete by default).
|
|
169
509
|
|
|
170
510
|
```typescript
|
|
171
|
-
|
|
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:
|
|
177
|
-
permanently: true,
|
|
526
|
+
id: 'user-123',
|
|
178
527
|
})
|
|
179
528
|
|
|
180
|
-
|
|
529
|
+
// Permanent delete
|
|
530
|
+
await deleteOne({
|
|
531
|
+
tableName: 'users',
|
|
532
|
+
dbClient,
|
|
533
|
+
id: 'user-123',
|
|
534
|
+
permanently: true,
|
|
535
|
+
})
|
|
181
536
|
```
|
|
182
537
|
|
|
183
|
-
|
|
538
|
+
### deleteMany
|
|
539
|
+
|
|
540
|
+
Deletes multiple records by IDs (soft delete by default).
|
|
184
541
|
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
562
|
+
// Permanent delete with custom field
|
|
563
|
+
await deleteMany({
|
|
191
564
|
tableName: 'orders',
|
|
192
565
|
dbClient,
|
|
193
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
929
|
+
AND: [
|
|
930
|
+
{ status: { operator: '=', value: 'active' } },
|
|
202
931
|
{
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
956
|
+
### Using Unaccent for PostgreSQL
|
|
213
957
|
|
|
214
|
-
|
|
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
|
-
|
|
969
|
+
## Monitoring
|
|
217
970
|
|
|
218
|
-
|
|
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
|
-
|
|
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('
|
|
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:',
|
|
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
|
|
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
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
1045
|
+
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n')
|
|
1046
|
+
})
|
|
251
1047
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1446
|
+
See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.
|
|
267
1447
|
|
|
268
1448
|
---
|
|
269
1449
|
|
|
270
|
-
|
|
1450
|
+
Made with ❤️ by the Starbem team
|