@starbemtech/star-db-query-builder 1.3.1 → 1.4.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 (76) hide show
  1. package/.claude/skills/star-db-query-builder/SKILL.md +104 -0
  2. package/CHANGELOG.md +107 -55
  3. package/LICENSE +21 -0
  4. package/README.md +92 -1391
  5. package/bin/install-skill.js +53 -0
  6. package/dist/src/core/repository.d.ts +125 -10
  7. package/dist/src/core/repository.js +336 -43
  8. package/dist/src/core/repository.js.map +1 -1
  9. package/dist/src/core/types.d.ts +29 -6
  10. package/dist/src/core/utils.d.ts +119 -0
  11. package/dist/src/core/utils.js +353 -82
  12. package/dist/src/core/utils.js.map +1 -1
  13. package/dist/src/db/IDatabaseClient.d.ts +14 -4
  14. package/dist/src/db/initDb.d.ts +60 -50
  15. package/dist/src/db/initDb.js +96 -64
  16. package/dist/src/db/initDb.js.map +1 -1
  17. package/dist/src/db/mysqlClient.d.ts +3 -8
  18. package/dist/src/db/mysqlClient.js +10 -11
  19. package/dist/src/db/mysqlClient.js.map +1 -1
  20. package/dist/src/db/pgClient.js +1 -2
  21. package/dist/src/db/pgClient.js.map +1 -1
  22. package/dist/src/monitor/monitor.js +7 -0
  23. package/dist/src/monitor/monitor.js.map +1 -1
  24. package/package.json +32 -24
  25. package/.github/workflows/publish.yml +0 -118
  26. package/.prettierignore +0 -3
  27. package/.prettierrc +0 -5
  28. package/ARCHITECTURE.md +0 -313
  29. package/coverage/base.css +0 -224
  30. package/coverage/block-navigation.js +0 -87
  31. package/coverage/favicon.png +0 -0
  32. package/coverage/index.html +0 -131
  33. package/coverage/lcov-report/base.css +0 -224
  34. package/coverage/lcov-report/block-navigation.js +0 -87
  35. package/coverage/lcov-report/favicon.png +0 -0
  36. package/coverage/lcov-report/index.html +0 -131
  37. package/coverage/lcov-report/mysqlClient.ts.html +0 -685
  38. package/coverage/lcov-report/pgClient.ts.html +0 -823
  39. package/coverage/lcov-report/prettify.css +0 -1
  40. package/coverage/lcov-report/prettify.js +0 -2
  41. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  42. package/coverage/lcov-report/sorter.js +0 -210
  43. package/coverage/lcov.info +0 -533
  44. package/coverage/mysqlClient.ts.html +0 -685
  45. package/coverage/pgClient.ts.html +0 -823
  46. package/coverage/prettify.css +0 -1
  47. package/coverage/prettify.js +0 -2
  48. package/coverage/sort-arrow-sprite.png +0 -0
  49. package/coverage/sorter.js +0 -210
  50. package/dist/src/setupTests.d.ts +0 -26
  51. package/dist/src/setupTests.js +0 -43
  52. package/dist/src/setupTests.js.map +0 -1
  53. package/docs/INDEX.md +0 -145
  54. package/docs/methods/findFirst.md +0 -394
  55. package/docs/methods/findMany.md +0 -587
  56. package/docs/methods/insert.md +0 -536
  57. package/docs/methods/insertMany.md +0 -627
  58. package/docs/methods/joins.md +0 -781
  59. package/docs/methods/rawQuery.md +0 -284
  60. package/docs/methods/transactions.md +0 -737
  61. package/eslint.config.mjs +0 -77
  62. package/index.ts +0 -16
  63. package/jest.config.ts +0 -194
  64. package/scripts/release.sh +0 -123
  65. package/src/core/repository.ts +0 -865
  66. package/src/core/types.ts +0 -97
  67. package/src/core/utils.ts +0 -357
  68. package/src/db/IDatabaseClient.ts +0 -16
  69. package/src/db/__tests__/mysqlClient.test.ts +0 -262
  70. package/src/db/__tests__/pgClient.test.ts +0 -260
  71. package/src/db/initDb.ts +0 -181
  72. package/src/db/mysqlClient.ts +0 -200
  73. package/src/db/pgClient.ts +0 -246
  74. package/src/monitor/monitor.ts +0 -16
  75. package/src/setupTests.ts +0 -45
  76. package/tsconfig.test.json +0 -21
package/README.md CHANGED
@@ -1,1450 +1,151 @@
1
- # Star DB Query Builder
1
+ # @starbemtech/star-db-query-builder
2
2
 
3
- A powerful and flexible database query builder library for Node.js applications, supporting PostgreSQL and MySQL databases with TypeScript support.
3
+ ## Overview
4
4
 
5
- ## Table of Contents
5
+ A TypeScript query-builder library shared across Starbem's backend microservices for building and executing PostgreSQL and MySQL queries alongside Prisma. It exposes a generic, type-safe repository API (`findFirst`, `findMany`, `insert`, `update`, `joins`, transactions, etc.) plus connection management for named PostgreSQL and MySQL pools. It is not a deployed service — it's published to npm and imported as a dependency by services such as accounts-ms, doctors-ms, and partner-service.
6
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)
7
+ ## Tech Stack
32
8
 
33
- ## ✨ Features
9
+ - TypeScript, compiled with `tsc` (Node.js >= 18)
10
+ - `pg` (PostgreSQL driver) and `mysql2` (MySQL driver)
11
+ - `promise-retry` for automatic retry on transient query/connection errors
12
+ - `uuid` for generating record IDs on insert/upsert
13
+ - Jest + `ts-jest` for testing
14
+ - ESLint (flat config, `typescript-eslint`) + Prettier for linting/formatting
15
+ - Husky + `lint-staged` for pre-commit checks
16
+ - pnpm (pinned to `8.6.2`) as package manager
34
17
 
35
- ### 🔧 **Core Functionality**
18
+ ## Architecture
36
19
 
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)
20
+ The library is organized into three modules under `src/`:
43
21
 
44
- ### 🗄️ **Database Support**
22
+ - **`src/core`** — the generic repository (`repository.ts`) with all CRUD/query functions, shared TypeScript types (`types.ts`), and SQL-building utilities (`utils.ts`) that assemble `WHERE`/`SET`/`ORDER BY`/`GROUP BY`/`LIMIT`/`OFFSET` clauses and enforce identifier/SQL-fragment safety (`assertValidIdentifier`, `assertSafeSqlFragment`, `assertNoAutoManagedColumns`, `assertWithinBindParamLimit`).
23
+ - **`src/db`** — connection management. `initDb.ts` creates and registers named PostgreSQL/MySQL pools (with optional retry options, query timeout, and the `unaccent` extension for pg), `pgClient.ts`/`mysqlClient.ts` are the concrete client implementations, and `IDatabaseClient.ts` defines the shared client/transaction contract.
24
+ - **`src/monitor`** — an `EventEmitter`-based instance (`monitor`) emitting `CONNECTION_CREATED`, `QUERY_START`, `QUERY_END`, `QUERY_ERROR`, and `RETRY_ATTEMPT` events for observability.
45
25
 
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
26
+ `index.ts` is the sole public entry point, re-exporting `initDb`/`getDbClient`/`closeDb`/etc. from `src/db`, all repository functions from `src/core/repository`, the `monitor` instance, and the public TypeScript types.
51
27
 
52
- ### 🛠️ **Development Tools**
28
+ Note: `ARCHITECTURE.md` in this repo references an older `default/genericRepository.ts` layout — the current source of truth is `src/core/`, `src/db/`, and `src/monitor/` as described above.
53
29
 
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
30
+ ## Security & Operational Notes
58
31
 
59
- ## 📦 Installation
32
+ - **`monitor` events carry raw query parameters.** `QUERY_START`/`QUERY_END`/`QUERY_ERROR` include the unredacted `params` array passed to `dbClient.query(...)` — the same values bound into the SQL (user emails, document numbers, health data depending on what a service queries by). If your service attaches a listener that logs or forwards these events (e.g. to Datadog/Sentry), redact or drop `params` before persisting the event anywhere subject to LGPD, rather than logging the monitor payload as-is.
33
+ - **Automatic retry is not aware of query idempotency.** `retryOptions` (on `initDb`/`createPgClient`/`createMysqlClient`) retries any query — including `rawQuery` and non-idempotent statements like `UPDATE ... SET n = n + 1` — on a transient connection error (`ECONNRESET`, `ETIMEDOUT`, etc.). If the original attempt's statement actually reached the database before the connection dropped, a retry can apply it twice. Prefer idempotent statements (keyed upserts, conditional updates) for anything run under `retryOptions`, or disable retries for statements where a double-apply would be unsafe.
34
+
35
+ ## Folder Structure
36
+
37
+ ```
38
+ src/
39
+ ├── core/ # generic repository, types, and SQL-building/validation utilities
40
+ │ └── __tests__/ # unit tests for repository.ts and utils.ts
41
+ ├── db/ # PostgreSQL/MySQL client implementations and pool initialization
42
+ │ └── __tests__/ # unit tests for initDb, pgClient, mysqlClient
43
+ ├── monitor/ # EventEmitter-based query/connection event system
44
+ │ └── __tests__/ # unit tests for monitor.ts
45
+ └── setupTests.ts # Jest test setup
46
+ ```
47
+
48
+ ## Installation
60
49
 
61
50
  ```bash
62
- npm install @starbemtech/star-db-query-builder
63
- # or
64
51
  pnpm add @starbemtech/star-db-query-builder
65
- # or
66
- yarn add @starbemtech/star-db-query-builder
67
52
  ```
68
53
 
69
- ## Quick Start
54
+ ## Usage
70
55
 
71
56
  ```typescript
72
57
  import {
73
58
  initDb,
74
59
  getDbClient,
75
60
  findFirst,
61
+ findMany,
76
62
  insert,
63
+ update,
64
+ withTransaction,
77
65
  } from '@starbemtech/star-db-query-builder'
78
66
 
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
- })
90
-
91
- // Get database client
92
- const dbClient = getDbClient()
93
-
94
- // Find a user
95
- const user = await findFirst({
96
- tableName: 'users',
97
- dbClient,
98
- where: { email: { operator: '=', value: 'user@example.com' } },
99
- })
100
-
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
- })
107
- ```
108
-
109
- ## Database Initialization
110
-
111
- ### initDb
112
-
113
- Initializes a database connection with the specified configuration.
114
-
115
- ```typescript
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
- ```
124
-
125
- #### PostgreSQL Example
126
-
127
- ```typescript
128
- import { initDb } from '@starbemtech/star-db-query-builder'
129
-
67
+ // Initialize a named PostgreSQL connection (call once at startup)
130
68
  await initDb({
131
- name: 'main',
69
+ name: 'default',
132
70
  type: 'pg',
133
71
  options: {
134
- host: 'localhost',
72
+ host: process.env.DB_HOST,
135
73
  port: 5432,
136
- database: 'myapp',
137
- user: 'postgres',
138
- password: 'password',
139
- max: 20,
140
- idleTimeoutMillis: 30000,
141
- connectionTimeoutMillis: 2000,
142
- },
143
- retryOptions: {
144
- retries: 3,
145
- factor: 2,
146
- minTimeout: 1000,
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'
157
-
158
- await initDb({
159
- name: 'analytics',
160
- type: 'mysql',
161
- options: {
162
- host: 'localhost',
163
- port: 3306,
164
- database: 'analytics',
165
- user: 'root',
166
- password: 'password',
167
- connectionLimit: 10,
168
- acquireTimeout: 60000,
169
- timeout: 60000,
170
- },
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()
185
-
186
- // Get named client
187
- const analyticsClient = getDbClient('analytics')
188
- ```
189
-
190
- ## Query Methods
191
-
192
- ### findFirst
193
-
194
- Finds the first record that matches the specified conditions.
195
-
196
- ```typescript
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
- ```
206
-
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({
221
- tableName: 'users',
222
- dbClient,
223
- select: ['id', 'name', 'email'],
224
- where: {
225
- status: { operator: '=', value: 'active' },
226
- },
227
- })
228
-
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
- })
247
- ```
248
-
249
- ### findMany
250
-
251
- Finds multiple records that match the specified conditions.
252
-
253
- ```typescript
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
- ```
266
-
267
- #### Examples
268
-
269
- ```typescript
270
- // Find all active users
271
- const users = await findMany({
272
- tableName: 'users',
273
- dbClient,
274
- where: {
275
- status: { operator: '=', value: 'active' },
276
- },
277
- })
278
-
279
- // Find with pagination
280
- const users = await findMany({
281
- tableName: 'users',
282
- dbClient,
283
- limit: 10,
284
- offset: 20,
285
- orderBy: [{ field: 'created_at', direction: 'DESC' }],
286
- })
287
-
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
- })
311
- ```
312
-
313
- ### insert
314
-
315
- Inserts a single record into the database.
316
-
317
- ```typescript
318
- const result = await insert<P, R>({
319
- tableName: string,
320
- dbClient: IDatabaseClient,
321
- data: P,
322
- returning?: string[]
323
- })
324
- ```
325
-
326
- #### Examples
327
-
328
- ```typescript
329
- // Simple insert
330
- const user = await insert({
331
- tableName: 'users',
332
- dbClient,
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'],
349
- })
350
-
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
- })
376
- ```
377
-
378
- ### insertMany
379
-
380
- Inserts multiple records into the database in a single operation.
381
-
382
- ```typescript
383
- const results = await insertMany<P, R>({
384
- tableName: string,
385
- dbClient: IDatabaseClient,
386
- data: P[],
387
- returning?: string[]
388
- })
389
- ```
390
-
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
- })
404
-
405
- // Insert with returning fields
406
- const users = await insertMany({
407
- tableName: 'users',
408
- dbClient,
409
- data: [
410
- { name: 'John Doe', email: 'john@example.com' },
411
- { name: 'Jane Doe', email: 'jane@example.com' },
412
- ],
413
- returning: ['id', 'name', 'email'],
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
- })
487
-
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
- })
504
- ```
505
-
506
- ### deleteOne
507
-
508
- Deletes a single record by ID (soft delete by default).
509
-
510
- ```typescript
511
- await deleteOne<T>({
512
- tableName: string,
513
- dbClient: IDatabaseClient,
514
- id: string,
515
- permanently?: boolean
516
- })
517
- ```
518
-
519
- #### Examples
520
-
521
- ```typescript
522
- // Soft delete (sets status to 'deleted')
523
- await deleteOne({
524
- tableName: 'users',
525
- dbClient,
526
- id: 'user-123',
527
- })
528
-
529
- // Permanent delete
530
- await deleteOne({
531
- tableName: 'users',
532
- dbClient,
533
- id: 'user-123',
534
- permanently: true,
535
- })
536
- ```
537
-
538
- ### deleteMany
539
-
540
- Deletes multiple records by IDs (soft delete by default).
541
-
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
553
-
554
- ```typescript
555
- // Soft delete multiple users
556
- await deleteMany({
557
- tableName: 'users',
558
- dbClient,
559
- ids: ['user-1', 'user-2', 'user-3'],
560
- })
561
-
562
- // Permanent delete with custom field
563
- await deleteMany({
564
- tableName: 'orders',
565
- dbClient,
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
- ],
622
- joins: [
623
- {
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',
637
- },
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,
928
- where: {
929
- AND: [
930
- { status: { operator: '=', value: 'active' } },
931
- {
932
- OR: [
933
- { age: { operator: '>=', value: 18 } },
934
- { verified: { operator: '=', value: true } },
935
- ],
936
- },
937
- { created_at: { operator: '>=', value: new Date('2023-01-01') } },
938
- ],
939
- },
940
- })
941
- ```
942
-
943
- ### Using Unaccent for PostgreSQL
944
-
945
- ```typescript
946
- const users = await findMany({
947
- tableName: 'users',
948
- dbClient,
949
- where: {
950
- name: { operator: 'ILIKE', value: '%joão%' },
74
+ database: process.env.DB_NAME,
75
+ user: process.env.DB_USER,
76
+ password: process.env.DB_PASSWORD,
951
77
  },
952
- unaccent: true, // Enables unaccent search
78
+ retryOptions: { retries: 3 },
953
79
  })
954
- ```
955
80
 
956
- ### Using Unaccent for PostgreSQL
81
+ const dbClient = getDbClient('default')
957
82
 
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
- ```
968
-
969
- ## Monitoring
970
-
971
- The library provides a comprehensive monitoring system to track database operations and performance.
972
-
973
- ### Monitor Events
974
-
975
- ```typescript
976
- import { monitor, MonitorEvents } from '@starbemtech/star-db-query-builder'
977
-
978
- // Monitor connection events
979
- monitor.on(MonitorEvents.CONNECTION_CREATED, (data) => {
980
- console.log('Database connection created:', data)
981
- })
982
-
983
- // Monitor query events
984
- monitor.on(MonitorEvents.QUERY_START, (data) => {
985
- console.log('Query started:', {
986
- sql: data.sql,
987
- params: data.params,
988
- clientType: data.clientType,
989
- attempt: data.attempt,
990
- })
991
- })
992
-
993
- monitor.on(MonitorEvents.QUERY_END, (data) => {
994
- console.log('Query completed:', {
995
- elapsedTime: data.elapsedTime,
996
- clientType: data.clientType,
997
- })
998
- })
999
-
1000
- monitor.on(MonitorEvents.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)
1015
- })
1016
-
1017
- // Monitor retry attempts
1018
- monitor.on(MonitorEvents.RETRY_ATTEMPT, (data) => {
1019
- console.warn('Retry attempt:', {
1020
- attempt: data.attempt,
1021
- error: data.error,
1022
- sql: data.sql,
1023
- })
1024
- })
1025
- ```
1026
-
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
- }
1044
-
1045
- fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n')
1046
- })
1047
-
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
- }
1055
-
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({
83
+ // Query
84
+ const user = await findFirst<{ id: string; name: string }>({
1116
85
  tableName: 'users',
1117
86
  dbClient,
87
+ select: ['id', 'name'],
1118
88
  where: { status: { operator: '=', value: 'active' } },
1119
89
  })
1120
- ```
1121
-
1122
- ### 3. Use Pagination for Large Datasets
1123
90
 
1124
- ```typescript
1125
- const users = await findMany({
91
+ // Insert
92
+ const created = await insert({
1126
93
  tableName: 'users',
1127
94
  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' } },
95
+ data: { name: 'John Doe', email: 'john.doe@example.com' },
96
+ returning: ['id', 'name', 'email'],
1173
97
  })
1174
98
 
1175
- // Use rawQuery only for complex operations
1176
- const complexStats = await rawQuery({
1177
- dbClient,
1178
- sql: 'SELECT ... complex aggregation ...',
99
+ // Transaction
100
+ await withTransaction(dbClient, async (tx) => {
101
+ const inserted = await insert({ tableName: 'orders', dbClient: tx, data: { total: 100 } })
102
+ await update({ tableName: 'users', dbClient: tx, id: 'user-id', data: { last_order_id: inserted.id } })
1179
103
  })
1180
104
  ```
1181
105
 
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
- ```
106
+ The full set of exported functions (`findFirst`, `findMany`, `findManyCursor`, `insert`, `insertMany`, `upsert`, `update`, `updateMany`, `deleteOne`, `deleteMany`, `joins`, `rawQuery`, `withTransaction`, `beginTransaction`) is documented with JSDoc in `src/core/repository.ts` and in `docs/methods/`.
1253
107
 
1254
- ## Error Handling
108
+ ## Available Scripts
1255
109
 
1256
- The library throws descriptive errors for common issues:
110
+ | Script | Description |
111
+ |---|---|
112
+ | `build` | Compiles TypeScript to `dist/` via `tsc` (runs `clean` first) |
113
+ | `test` | Runs the Jest test suite |
114
+ | `test:watch` | Runs Jest in watch mode |
115
+ | `test:coverage` | Runs Jest with coverage report |
116
+ | `test:ci` | Runs Jest in CI mode (`--ci --coverage --watchAll=false`) |
117
+ | `lint` | Lints `src/**/*.ts` with ESLint |
118
+ | `lint:fix` | Lints and auto-fixes `src/**/*.ts` |
119
+ | `format` | Formats `src/**/*.ts` with Prettier |
120
+ | `format:check` | Checks formatting without writing changes |
121
+ | `type:check` | Type-checks without emitting (`tsc --noEmit`) |
122
+ | `clean` | Removes `dist` and `coverage` |
123
+ | `version:patch` / `version:minor` / `version:major` | Bumps version via `npm version` |
124
+ | `release` | Runs the interactive release helper (`scripts/release.sh`) |
125
+ | `release:patch` / `release:minor` / `release:major` | Bumps version and pushes tags |
126
+ | `prepublishOnly` | Clean, build, and `test:ci`, run automatically before `npm publish` |
1257
127
 
1258
- ### Common Errors
128
+ ## Testing
1259
129
 
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
- }
130
+ ```bash
131
+ pnpm test # run the suite
132
+ pnpm test:watch # watch mode
133
+ pnpm test:coverage
134
+ pnpm test:ci # CI mode, used by ci.yml and release.yml
1355
135
  ```
1356
136
 
1357
- Always wrap database operations in try-catch blocks and handle errors appropriately in your application.
137
+ Tests live alongside each module in `__tests__/` directories under `src/core`, `src/db`, and `src/monitor`.
1358
138
 
1359
139
  ## Contributing
1360
140
 
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
1433
-
1434
- ## License
1435
-
1436
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1437
-
1438
- ## Support
141
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the branch naming, commit, and PR conventions, and [AGENTS.md](./AGENTS.md) for the full development workflow, including the mandatory SQL-identifier-sanitization rule for any function that accepts a caller-supplied table/column name.
1439
142
 
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)
143
+ ## CI/CD
1443
144
 
1444
- ## Changelog
145
+ `.github/workflows/ci.yml` runs on every push and pull request to `main`, across a Node 18/20/22 matrix: `pnpm install --frozen-lockfile`, then `pnpm lint`, `pnpm format:check`, `pnpm type:check`, `pnpm build`, and `pnpm test:ci`, in that order.
1445
146
 
1446
- See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.
147
+ `.github/workflows/release.yml` runs on pushed tags matching `v*.*.*`: installs, builds, runs `test:ci`, generates release notes from git log since the previous tag, creates a GitHub Release, and publishes to npm via OIDC Trusted Publishing (no `NPM_TOKEN`).
1447
148
 
1448
- ---
149
+ ## Release Process
1449
150
 
1450
- Made with ❤️ by the Starbem team
151
+ See [RELEASE.md](./RELEASE.md) for the full step-by-step. In short: merge to `main`, run the local gate (lint/format/type-check/build/test:ci), update `CHANGELOG.md`, bump the version with `pnpm run version:<patch|minor|major>` (or use the already-bumped `package.json` version), then push the `vX.Y.Z` tag — pushing the tag is what triggers `release.yml` and the npm publish. `scripts/release.sh` wraps the routine steps into one interactive prompt (`pnpm run release`).